dptm6 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.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +46 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/dptm6.gemspec +29 -0
- data/exe/dptm6 +6 -0
- data/lib/dptm6.rb +4 -0
- data/lib/dptm6/cli.rb +123 -0
- data/lib/dptm6/path.rb +93 -0
- data/lib/dptm6/pdf.rb +389 -0
- data/lib/dptm6/version.rb +3 -0
- metadata +103 -0
checksums.yaml
ADDED
@@ -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
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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)の条件の下でオープンソースとして利用可能です。
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -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__)
|
data/bin/setup
ADDED
data/dptm6.gemspec
ADDED
@@ -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
|
data/exe/dptm6
ADDED
data/lib/dptm6.rb
ADDED
data/lib/dptm6/cli.rb
ADDED
@@ -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
|
data/lib/dptm6/path.rb
ADDED
@@ -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
|
data/lib/dptm6/pdf.rb
ADDED
@@ -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
|
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: []
|