dptm6 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e63193f44881d5e5a8a61cbd10de60f0a18149f8881f1fbff8d3a4bb3cf26da4
4
+ data.tar.gz: 5ba36893056cc101acd5b25897aa6b596d1df678f98fa7aed07a9c34b362e91c
5
+ SHA512:
6
+ metadata.gz: 98f1a69940881ba2517ad09e12a6b330386c17c88d4e0c490f37017742948aae00d62ad592ce350a93a7fd7fc591ccfb5e8046778b21bf53062223cfa4ad6b83
7
+ data.tar.gz: 88e9d0f8b5e018751fc37939dbd0f45acc62ae24603593f6cf5b282a444fce9aa516d25f7897edc0f706f56fc3dc5c27d9283e910b9c20933b6ccd4c49dbb658
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.5.1
5
+ before_install: gem install bundler -v 1.16.2
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in dptm6.gemspec
6
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Masahiro Nomoto
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,46 @@
1
+ # dptm6 (dclpdftonemerge for DCL 6)
2
+
3
+ このgemは、[地球流体電脳ライブラリ](https://www.gfd-dennou.org/library/dcl/)(DCL)によって描画・出力したPDFファイルのトーン(塗りつぶし)の断片を結合してファイルを軽くするコマンド `dptm6` を提供します。
4
+
5
+ DCLはバージョン5まで独自のPostScriptファイルを出力していましたが、バージョン6から[cairo](https://www.cairographics.org)を用いたPDF出力に変更されました。以前のバージョン用のアプリケーション `dptm2` は[GFD電脳Ruby小物置き場](http://davis.gfd-dennou.org/rubygadgets/ja/?%28Application%29+DCLのPSファイルのトーンを結合する2)で公開しています。
6
+
7
+ ## インストール
8
+
9
+ Rubyがインストールされた環境で以下のコマンドを実行してください。
10
+
11
+ $ gem install dptm6
12
+
13
+ ## 使い方
14
+
15
+ 大前提として、このコマンドはDCLで出力して未編集のPDFに対してのみ正しく動作します。
16
+
17
+ 基本的な使い方は、コマンドの後ろに変換したいPDFファイルを指定するだけです。変換後のファイル名は、元の名前に番号を付け加えたものになります。(番号は既存のファイルを上書きしないように選ばれます)
18
+
19
+ ```
20
+ $ ls *.pdf
21
+ dcl.pdf
22
+
23
+ $ dptm6 dcl.pdf
24
+ $ ls *.pdf
25
+ dcl.pdf dcl_1.pdf
26
+
27
+ $ dptm6 dcl.pdf
28
+ $ ls *.pdf
29
+ dcl.pdf dcl_1.pdf dcl_2.pdf
30
+ ```
31
+
32
+ 出力するファイル名を指定することもできます。入力ファイルの全ページをつなげたPDFができます。
33
+
34
+ $ dptm6 -o dcl-merged.pdf dcl-a.pdf dcl-b.pdf dcl-c.pdf
35
+
36
+ 入力ファイルの一部ページだけを抽出することもできます。(先頭を0ページ目と数えます)
37
+
38
+ $ dptm6 input.pdf[0,5...2,8..-1] #=> [0,5,4,3,8,9,...,n-1]
39
+
40
+ その他の説明はヘルプを参照してください。だたし、現在は開発時のデバッグ用オプションしかありません。
41
+
42
+ $ dptm6 -h
43
+
44
+ ## ライセンス
45
+
46
+ このgemは、[MITライセンス](https://opensource.org/licenses/MIT)の条件の下でオープンソースとして利用可能です。
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "dptm6"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,29 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "dptm6/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "dptm6"
8
+ spec.version = DPTM6::VERSION
9
+ spec.authors = ["Masahiro Nomoto"]
10
+ spec.email = ["hmmnrst@users.noreply.github.com"]
11
+
12
+ spec.summary = %q{dclpdftonemerge for DCL 6}
13
+ spec.description = %q{Merge tones in PDF made by DCL (Dennou Club Library) ver. 6}
14
+ spec.homepage = "https://github.com/hmmnrst/dptm6"
15
+ spec.license = "MIT"
16
+
17
+ # Specify which files should be added to the gem when it is released.
18
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
19
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
20
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
21
+ end
22
+ spec.bindir = "exe"
23
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
+ spec.require_paths = ["lib"]
25
+
26
+ spec.add_development_dependency "bundler", "~> 1.16"
27
+ spec.add_development_dependency "rake", "~> 10.0"
28
+ spec.add_development_dependency "rspec", "~> 3.0"
29
+ end
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'dptm6'
4
+ require 'dptm6/cli'
5
+
6
+ DPTM6::CLI.new(ARGV).exec
@@ -0,0 +1,4 @@
1
+ require "dptm6/version"
2
+
3
+ module DPTM6
4
+ end
@@ -0,0 +1,123 @@
1
+ require 'zlib'
2
+ require 'dptm6/pdf'
3
+
4
+ module DPTM6; class CLI
5
+ def initialize(args)
6
+ @options = { :compression_level => Zlib::DEFAULT_COMPRESSION, :optimization_level => 1 }
7
+ @files = []
8
+
9
+ args = args.dup
10
+ while (arg = args.shift)
11
+ case arg
12
+ when /\A-C(\d++)?/ # compression level
13
+ @options[:compression_level] = ($~[1] || Zlib::DEFAULT_COMPRESSION).to_i
14
+ when /\A-O(\d?+)/ # optimization level
15
+ @options[:optimization_level] = ($~[1] || 1).to_i
16
+ when /\A-o/ # output file name
17
+ @options[:outfile] = args.shift
18
+ when /\A-h/i
19
+ @options[:help] = true
20
+ else
21
+ file = arg.sub(/\[([^\[\]]*+)\]\z/, "")
22
+ str = ($~ ? $~[1] : "0..-1")
23
+ pages = str.gsub("\s", "").split(",").collect do |range|
24
+ md = range.match(/\A(-?+\d++)(?:(-|..|...)(-?+\d++))?+\z/)
25
+ raise "invalid page specification: [#{range}]" unless md
26
+
27
+ p1 = md[1].to_i
28
+ p2 = (md[3] || p1).to_i
29
+ case md[2]
30
+ when "..." then p1...p2
31
+ else p1..p2
32
+ end
33
+ end
34
+ @files << [file, pages]
35
+ end
36
+ end
37
+ end
38
+
39
+ def exec
40
+ if (@options[:help] || @files.empty?)
41
+ put_help
42
+ return
43
+ end
44
+
45
+ clevs = [:NO_COMPRESSION, :BEST_SPEED, :BEST_COMPRESSION, :DEFAULT_COMPRESSION].inject({}) do |hash,name|
46
+ hash[Zlib.const_get(name)] = name
47
+ hash
48
+ end
49
+ clev = @options[:compression_level]
50
+ clev = Zlib::DEFAULT_COMPRESSION unless (0..9).include?(clev)
51
+ STDERR.puts("#compression level : #{clevs[clev] || clev}")
52
+
53
+ optlev = @options[:optimization_level]
54
+ STDERR.puts("#optimization flag : #{optlev != 0}")
55
+
56
+ outfile = @options[:outfile] || find_nextfile(@files[0][0])
57
+ STDERR.puts("#output file name : #{outfile}")
58
+
59
+ pdf2 = PDF::File.create(outfile)
60
+ head = nil
61
+
62
+ @files.each do |file,pages|
63
+ pdf = PDF::File.open(file)
64
+ unless head
65
+ head = pdf.get_object(0)
66
+ head.move_to(pdf2, 0).write
67
+ end
68
+
69
+ n = pdf.pages.size
70
+ pages.each do |range|
71
+ first = range.first % n
72
+ last = range.last % n
73
+ inc = (first <= last ? 1 : -1)
74
+ last -= inc if range.exclude_end?
75
+
76
+ first.step(last, inc) do |i|
77
+ STDERR.puts("processing #{file}[#{i}]")
78
+ image = pdf.get_dclimage(i)
79
+ image.set_deflevel(clev)
80
+ image.parse if (optlev > 0)
81
+ image.move_to(pdf2, nil)
82
+ image.write
83
+ end
84
+ end
85
+ pdf.close
86
+ end
87
+
88
+ pdf2.write_info
89
+ pdf2.write_xref
90
+ pdf2.close
91
+ end
92
+
93
+ def find_nextfile(basefile)
94
+ pos = basefile.rindex(".pdf")
95
+ file = nil
96
+ (1..99).each do |i|
97
+ file = basefile.dup.insert(pos, "_#{i}")
98
+ return file unless File.exist?(file)
99
+ end
100
+ raise "failed to create a new filename. (last candidate: #{file})"
101
+ end
102
+
103
+ def put_help
104
+ STDERR.puts(<<-EOS)
105
+ usage: ruby #{File.basename(__FILE__)} [-C[n]] [-O[n]] [-o output.pdf] input.pdf...
106
+
107
+ options
108
+ -C[n] : compression level
109
+ 0 : NO_COMPRESSION
110
+ 1 - 9 : BEST_SPEED - BEST_COMPRESSION
111
+ others : DEFAULT_COMPRESSION (default)
112
+ -O[n] : optimization flag
113
+ 0 : off
114
+ others : on (default)
115
+ -o output.pdf : output filename
116
+ If not specified, "input_%d.pdf" is used instead.
117
+
118
+ input files
119
+ Filenames can have page specifications.
120
+ example: input.pdf[0,5...2,8..-1] #=> [0,5,4,3,8,9,...,n-1]
121
+ EOS
122
+ end
123
+ end; end
@@ -0,0 +1,93 @@
1
+ class DPTM6::Path
2
+ class Node
3
+ attr_reader :x, :y, :op
4
+ attr_accessor :prev, :next
5
+
6
+ def initialize(x, y, op)
7
+ @x = x.to_f
8
+ @y = y.to_f
9
+ @op = op.to_sym
10
+ end
11
+
12
+ def connect(next_node)
13
+ self.next = next_node
14
+ next_node.prev = self
15
+ self
16
+ end
17
+
18
+ def reconnect(opp_node)
19
+ # assume that self.x == opp_node.x && self.y == opp_node.y
20
+ if (self.next.x == opp_node.prev.x && self.next.y == opp_node.prev.y)
21
+ opp_node.prev.connect(self.next)
22
+ self.connect(opp_node)
23
+ self
24
+ else
25
+ nil
26
+ end
27
+ end
28
+
29
+ def output(op = @op)
30
+ @op = nil
31
+ "%g %g %s " % [@x, @y, op]
32
+ end
33
+ end
34
+
35
+ def initialize(nodes = [])
36
+ @nodes = nodes
37
+ end
38
+
39
+ def add(buf_node)
40
+ (buf_node << buf_node[0]).each_cons(2) { |a,b| @nodes << a.connect(b) } if (buf_node.length >= 2)
41
+ end
42
+
43
+ def optimize
44
+ return self unless @nodes
45
+
46
+ # reconnect nodes (total number of nodes is not changed)
47
+ @nodes.group_by(&:y).each do |y,nodes|
48
+ nodes.group_by(&:x).each do |x,buf|
49
+ while (n1 = buf.pop)
50
+ buf.each { |n2| break if n1.reconnect(n2) }
51
+ end
52
+ end
53
+ end
54
+
55
+ # remove needless nodes
56
+ @nodes.reject! do |node|
57
+ d1x = node.prev.x - node.x
58
+ d1y = node.prev.y - node.y
59
+ d2x = node.next.x - node.x
60
+ d2y = node.next.y - node.y
61
+ if (d1x * d2y == d2x * d1y)
62
+ node.prev.connect(node.next)
63
+ true
64
+ else
65
+ false
66
+ end
67
+ end
68
+
69
+ self
70
+ end
71
+
72
+ def output_fill(buf = '')
73
+ return buf if (@nodes.length < 2)
74
+ buf << "\n" unless (buf[-1] == "\n")
75
+ nodes = @nodes.dup
76
+ while (node0 = node = nodes.shift)
77
+ next unless node.op
78
+ buf << node.output(:m)
79
+ until ((node = node.next) == node0)
80
+ buf << node.output(:l)
81
+ end
82
+ buf << "h\n"
83
+ end
84
+ buf << "B\n"
85
+ end
86
+
87
+ def output_stroke(buf = '', close = false)
88
+ return buf if (@nodes.length < 1)
89
+ @nodes.each { |node| buf << node.output }
90
+ buf << "h " if close
91
+ buf << "S\n"
92
+ end
93
+ end
@@ -0,0 +1,389 @@
1
+ require 'zlib'
2
+ require 'dptm6/path'
3
+
4
+ module DPTM6; module PDF
5
+ REGEXP_DATA = /\A(?:(?'dict'<<(?>\g'dict'|[^<>]++)*+>>\s*+)|(?'num'\s*\d+\s*+))/m
6
+
7
+ class File
8
+ attr_reader :fp, :trailer
9
+ attr_accessor :xref, :pages
10
+ private_class_method :new
11
+
12
+ @@obj0_pages = nil
13
+ @@obj0_info = nil
14
+ @@obj0_root = nil
15
+
16
+ def self.open(file)
17
+ new(file, "rb")
18
+ end
19
+
20
+ def self.create(file)
21
+ new(file, "wb")
22
+ end
23
+
24
+ def initialize(file, mode)
25
+ @fp = Kernel.open(file, mode)
26
+
27
+ if (mode[0] == "r")
28
+ read_xref
29
+ read_info
30
+ else
31
+ @xref = []
32
+ @pages = []
33
+ end
34
+ end
35
+
36
+ def close
37
+ @fp.close
38
+ end
39
+
40
+ def get_dclimage(num)
41
+ PDF::DCLImage.new(self, num)
42
+ end
43
+
44
+ def get_object(num, type = PDF::Object)
45
+ type.new(self, num)
46
+ end
47
+
48
+ def get_object_by_key(dict, key, type = PDF::Object)
49
+ ary = dict.scan(/#{key}\s++(\d++)\s++\d++\s++R/m)
50
+ return nil if ary.empty?
51
+
52
+ num = ary[0][0].to_i
53
+ get_object(num, type)
54
+ end
55
+
56
+ def write_info
57
+ str = @pages.collect { |i| "#{i} 0 R " }.join
58
+ @@obj0_pages.data[/\/Kids\s++\[\s++([\d\sR]++)\]/,1] = str
59
+ @@obj0_pages.data[/\/Count\s++(\d++)/,1] = @pages.size.to_s
60
+ @@obj0_pages.move_to(self, 1).write
61
+
62
+ @@obj0_info.move_to(self, @xref.size).write
63
+
64
+ @@obj0_root.move_to(self, @xref.size).write
65
+ end
66
+
67
+ def write_xref
68
+ pos = @fp.pos
69
+ size = @xref.size
70
+
71
+ @fp.write("xref\n")
72
+ @fp.write("#{0} #{size}\n")
73
+ @xref.sort_by(&:last).each do |i,j,k|
74
+ @fp.write("%010d %05d %c \n" % (k == 0 ? [i, 65535, "f"] : [i, 0, "n"]))
75
+ end
76
+
77
+ @fp.write(<<-EOS)
78
+ trailer
79
+ << /Size #{size}
80
+ /Root #{size-1} 0 R
81
+ /Info #{size-2} 0 R
82
+ >>
83
+ EOS
84
+
85
+ @fp.write(<<-EOS)
86
+ startxref
87
+ #{pos}
88
+ %%EOF
89
+ EOS
90
+ end
91
+
92
+ private
93
+
94
+ def read_xref
95
+ @fp.pos = @fp.size - 40
96
+ str = @fp.read
97
+ pos_xref = str[/startxref\s++(\d++)/m,1].to_i
98
+
99
+ # get beginning positions of objects
100
+ @fp.pos = pos_xref
101
+ str = @fp.gets.chomp!
102
+ if (str != "xref")
103
+ raise "invalid data (expected 'xref')"
104
+ end
105
+ a, b, = @fp.gets.split.collect(&:to_i)
106
+ pos_obj = b.times.collect { @fp.gets.split[0].to_i }
107
+
108
+ # get ending positions of objects
109
+ tmp = pos_obj.each_with_index.sort_by(&:first) << [pos_xref, b]
110
+ tmp = tmp.each_cons(2).collect { |a,b| [a[0], b[0] - a[0], a[1]] }
111
+ @xref = tmp.sort_by(&:last)
112
+
113
+ @fp.gets
114
+ @trailer = @fp.read.slice(PDF::REGEXP_DATA)
115
+ end
116
+
117
+ def read_info
118
+ @obj_root = get_object_by_key(@trailer, '/Root')
119
+ @obj_info = get_object_by_key(@trailer, '/Info')
120
+ @obj_pages = get_object_by_key(@obj_root.data, '/Pages')
121
+ @@obj0_root ||= @obj_root
122
+ @@obj0_info ||= @obj_info
123
+ @@obj0_pages ||= @obj_pages
124
+
125
+ str = @obj_pages.data[/\/Kids\s++\[\s++([\d\sR]++)\]/m,1] #=> (\d+ 0 R )*
126
+ @pages = str.scan(/(\d++)\s++(\d++)\s++(R)/).collect { |a| a[0].to_i }
127
+ end
128
+ end
129
+
130
+ class Object
131
+ attr_reader :pdf, :num
132
+ attr_accessor :data
133
+
134
+ def initialize(pdf, num)
135
+ @pdf = pdf
136
+ @num = num
137
+ read
138
+ end
139
+
140
+ def to_string
141
+ return @data if (@num == 0 && @data =~ /%PDF/)
142
+
143
+ "#{@num} #{0} obj\n" << @data << "endobj\n"
144
+ end
145
+
146
+ def move_to(pdf, num)
147
+ @pdf = pdf
148
+ @num = num
149
+ self
150
+ end
151
+
152
+ def write
153
+ fp = @pdf.fp
154
+ pos = fp.pos
155
+ fp.write(to_string)
156
+ len = fp.pos - pos
157
+ @pdf.xref << [pos, len, @num]
158
+ end
159
+
160
+ def replace_pagenum(hash)
161
+ hash.each do |key,num|
162
+ @data[/#{key}\s++(\d++)\s++\d++\s++R/,1] = num.to_s
163
+ end
164
+ end
165
+
166
+ private
167
+
168
+ def read
169
+ fp = @pdf.fp
170
+ pos, len, = @pdf.xref[@num]
171
+
172
+ fp.pos = pos
173
+ buf = fp.read(len)
174
+
175
+ if (buf =~ /\A%PDF/)
176
+ @data = buf
177
+ return
178
+ end
179
+
180
+ buf.slice!(/\A[^\n]++\n/m)
181
+ @data = buf.slice!(PDF::REGEXP_DATA)
182
+ end
183
+ end
184
+
185
+ class PageObject < Object
186
+ def to_string
187
+ replace_pagenum('/Contents' => @obj_content.num,
188
+ '/Resources' => @obj_resource.num)
189
+ super
190
+ end
191
+
192
+ def write
193
+ @pdf.pages << @num
194
+ super
195
+ end
196
+
197
+ private
198
+
199
+ def read
200
+ super
201
+ @obj_content = @pdf.get_object_by_key(@data, '/Contents' , PDF::StreamObject )
202
+ @obj_resource = @pdf.get_object_by_key(@data, '/Resources', PDF::ResourceObject)
203
+ end
204
+ end
205
+
206
+ class ResourceObject < Object
207
+ attr_reader :x_objects
208
+
209
+ def to_string
210
+ unless @x_objects.empty?
211
+ ary = @x_objects.collect { |key,obj| "#{key} #{obj.num} 0 R" }
212
+ @data[/\/XObject\s++(<<[^<>]*+>>)/,1] = "<< #{ary.join(' ')} >>"
213
+ end
214
+ super
215
+ end
216
+
217
+ private
218
+
219
+ def read
220
+ super
221
+ # /XObject << /x5 5 0 R /x6 6 0 R >>
222
+ @x_objects = {}
223
+ if (@data =~ /\/XObject\s++<<([^<>]++)>>/m)
224
+ $~[1].scan(/(\/\w++)\s++(\d++)\s++(\d++)\s++(R)/) do |a|
225
+ @x_objects[a[0]] = @pdf.get_object(a[1].to_i, PDF::StreamObject)
226
+ end
227
+ end
228
+ end
229
+ end
230
+
231
+ class StreamObject < Object
232
+ attr_reader :obj_length
233
+
234
+ def set_deflevel(level)
235
+ @def_level = level if @stream
236
+ end
237
+
238
+ def stream
239
+ Zlib.inflate(@stream)
240
+ end
241
+
242
+ def stream=(str)
243
+ @stream = Zlib.deflate(str, @def_level)
244
+ end
245
+
246
+ def to_string
247
+ replace_pagenum('/Length' => @obj_length.num)
248
+
249
+ buf = "#{@num} #{0} obj\n"
250
+ buf << @data
251
+ if @stream
252
+ @obj_length.data = " #{@stream.length}\n"
253
+ buf << "stream\n" << @stream << "\nendstream\n"
254
+ end
255
+ buf << "endobj\n"
256
+ end
257
+
258
+ def write
259
+ super
260
+ @obj_length.write
261
+ end
262
+
263
+ private
264
+
265
+ def read
266
+ fp = @pdf.fp
267
+ pos, len, = @pdf.xref[@num]
268
+
269
+ fp.pos = pos
270
+ buf = fp.read(len)
271
+
272
+ buf.slice!(/\A[^\n]++\n/m)
273
+ @data = buf.slice!(PDF::REGEXP_DATA)
274
+
275
+ @obj_length = @pdf.get_object_by_key(@data, '/Length')
276
+ length = @obj_length.data.to_i
277
+
278
+ if @data.include?("/FlateDecode")
279
+ buf.slice!(/\A[^\n]++\n/m) #=> "stream\n"
280
+ @stream = buf[0,length]
281
+ @def_level = Zlib::DEFAULT_COMPRESSION
282
+ end
283
+ end
284
+ end
285
+
286
+ class DCLImage < PageObject
287
+ def initialize(pdf, num)
288
+ super(pdf, pdf.pages[num])
289
+ end
290
+
291
+ def move_to(pdf, num)
292
+ # OBJECT / NUMBER
293
+ # psimage +1
294
+ # length +2
295
+ # resource +0
296
+ # page +3+nx
297
+ # xobj_i +3+i
298
+ # length +3+nx+1+i
299
+ m = pdf.xref.size + 1
300
+ nx = @obj_resource.x_objects.size
301
+ @obj_content. move_to(pdf, m + 1)
302
+ @obj_content.obj_length.move_to(pdf, m + 2)
303
+ @obj_resource. move_to(pdf, m )
304
+ super(pdf, m + 3 + nx)
305
+ @obj_resource.x_objects.each_with_index do |(key,obj),i|
306
+ obj. move_to(pdf, m + 3 + i)
307
+ obj.obj_length.move_to(pdf, m + 4 + nx + i)
308
+ end
309
+ self
310
+ end
311
+
312
+ def set_deflevel(level)
313
+ @obj_content.set_deflevel(level)
314
+ end
315
+
316
+ def write
317
+ @obj_content. write
318
+ @obj_resource.write
319
+ super
320
+ @obj_resource.x_objects.each_value(&:write)
321
+ end
322
+
323
+ REGEXP_PARSE = Regexp.compile('(?:(\[[^\[\]]*+\])\s++)?+' << # array
324
+ '(?:(-?+\d++(?:\.\d++)?+)\s++)?+' * 6 << # numbers
325
+ '(?:(/\w++)\s++)?+' << # set ExtGState parameters ("'/a0 'gs ")
326
+ '(\w++)\s++' << # operator (required)
327
+ '((?:/\w++\s++\w++\s++){2})?+' ) # draw XObject ("... cm '/a0 gs /x5 Do '")
328
+ def parse
329
+ stream_new = "q\n"
330
+
331
+ # previous values
332
+ concat = nil
333
+ color = { :rg => nil, :RG => nil }
334
+ flag_x = false
335
+
336
+ buf_node = []
337
+ path = Path.new
338
+ str_check = @obj_content.stream.gsub(REGEXP_PARSE) do |a|
339
+ md = $~
340
+ op = md[-2].to_sym
341
+ case op
342
+ when :m, :l # moveto, lineto
343
+ buf_node << Path::Node.new(md[2], md[3], op)
344
+ when :h, :B # close, fill and stroke
345
+ if flag_x # frame of XObject
346
+ flag_x = false
347
+ Path.new(buf_node).output_stroke(stream_new, true)
348
+ path = Path.new
349
+ else
350
+ path.add(buf_node)
351
+ end
352
+ buf_node = []
353
+ when :S # stroke
354
+ path.optimize.output_fill(stream_new)
355
+ path = Path.new
356
+ Path.new(buf_node).output_stroke(stream_new)
357
+ buf_node = []
358
+ when :rg, :RG # set RGB for stroking / non-stroking
359
+ rgbstr = md[2..4].join(' ')
360
+ if (rgbstr == color[op])
361
+ else
362
+ path.optimize.output_fill(stream_new)
363
+ path = Path.new
364
+ color[op] = rgbstr
365
+ stream_new << a
366
+ end
367
+ when :cm # concat matrix
368
+ if md[-1] # insert XObject
369
+ flag_x = true
370
+ stream_new << "q\n"
371
+ stream_new << "0 1 1 0 -842 0 cm\n" if concat # cancel concat matrix for paths
372
+ stream_new << a << "Q\n"
373
+ else
374
+ stream_new << "q " << (concat = a) unless concat
375
+ end
376
+ when :q, :Q # gsave, grestore
377
+ else # others : copy
378
+ stream_new << a
379
+ end
380
+ '' # delete parsed string
381
+ end
382
+ STDERR.puts("WARNING: Some script could not be parsed -- #{str_check.inspect}") if (str_check != '')
383
+
384
+ stream_new << "Q\n"
385
+ @obj_content.stream = stream_new
386
+ self
387
+ end
388
+ end
389
+ end; end
@@ -0,0 +1,3 @@
1
+ module DPTM6
2
+ VERSION = "1.0.0"
3
+ end
metadata ADDED
@@ -0,0 +1,103 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dptm6
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Masahiro Nomoto
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-11-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.16'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.16'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ description: Merge tones in PDF made by DCL (Dennou Club Library) ver. 6
56
+ email:
57
+ - hmmnrst@users.noreply.github.com
58
+ executables:
59
+ - dptm6
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - ".gitignore"
64
+ - ".rspec"
65
+ - ".travis.yml"
66
+ - Gemfile
67
+ - LICENSE.txt
68
+ - README.md
69
+ - Rakefile
70
+ - bin/console
71
+ - bin/setup
72
+ - dptm6.gemspec
73
+ - exe/dptm6
74
+ - lib/dptm6.rb
75
+ - lib/dptm6/cli.rb
76
+ - lib/dptm6/path.rb
77
+ - lib/dptm6/pdf.rb
78
+ - lib/dptm6/version.rb
79
+ homepage: https://github.com/hmmnrst/dptm6
80
+ licenses:
81
+ - MIT
82
+ metadata: {}
83
+ post_install_message:
84
+ rdoc_options: []
85
+ require_paths:
86
+ - lib
87
+ required_ruby_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ requirements: []
98
+ rubyforge_project:
99
+ rubygems_version: 2.7.6
100
+ signing_key:
101
+ specification_version: 4
102
+ summary: dclpdftonemerge for DCL 6
103
+ test_files: []