pdf-core 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/pdf/core.rb +35 -0
- data/lib/pdf/core/annotations.rb +60 -0
- data/lib/pdf/core/byte_string.rb +9 -0
- data/lib/pdf/core/destinations.rb +90 -0
- data/lib/pdf/core/document_state.rb +78 -0
- data/lib/pdf/core/filter_list.rb +51 -0
- data/lib/pdf/core/filters.rb +36 -0
- data/lib/pdf/core/graphics_state.rb +68 -0
- data/lib/pdf/core/literal_string.rb +16 -0
- data/lib/pdf/core/name_tree.rb +177 -0
- data/lib/pdf/core/object_store.rb +308 -0
- data/lib/pdf/core/outline.rb +315 -0
- data/lib/pdf/core/page.rb +212 -0
- data/lib/pdf/core/page_geometry.rb +126 -0
- data/lib/pdf/core/pdf_object.rb +99 -0
- data/lib/pdf/core/reference.rb +103 -0
- data/lib/pdf/core/stream.rb +98 -0
- data/lib/pdf/core/text.rb +275 -0
- data/pdf-core.gemspec +26 -0
- metadata +140 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1e04dbc623a502e35e4ab9879b4e418a22799525
|
4
|
+
data.tar.gz: 8bcd0c9a82f9330fa074af80e43f8f371edba50f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ede1498abbf6a6a93e1410cd019c320beb0b490a64ebbbf2bc1d13c2ba56e257e098db49da253444a7dbc70482a28b8ca1f8dd41bf9fa310a88080856a1445e5
|
7
|
+
data.tar.gz: d872d4b0b03aab9bd55bb3a1c2caa855280bbb6216e1ca89bcd0fd4aa9329f70893a846e0db53fd826a4069c1300e1f100369b88e7ba976d0d9ff49b50c4d9d7
|
data/lib/pdf/core.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require_relative "core/pdf_object"
|
2
|
+
require_relative "core/annotations"
|
3
|
+
require_relative "core/byte_string"
|
4
|
+
require_relative "core/destinations"
|
5
|
+
require_relative "core/filters"
|
6
|
+
require_relative "core/stream"
|
7
|
+
require_relative "core/reference"
|
8
|
+
require_relative "core/literal_string"
|
9
|
+
require_relative "core/filter_list"
|
10
|
+
require_relative "core/page"
|
11
|
+
require_relative "core/object_store"
|
12
|
+
require_relative "core/document_state"
|
13
|
+
require_relative "core/name_tree"
|
14
|
+
require_relative "core/graphics_state"
|
15
|
+
require_relative "core/page_geometry"
|
16
|
+
require_relative "core/outline"
|
17
|
+
|
18
|
+
module PDF
|
19
|
+
module Core
|
20
|
+
module Errors
|
21
|
+
# This error is raised when PdfObject() fails
|
22
|
+
FailedObjectConversion = Class.new(StandardError)
|
23
|
+
|
24
|
+
# This error is raised when object store fails to load a template file
|
25
|
+
TemplateError = Class.new(StandardError)
|
26
|
+
|
27
|
+
# This error is raise when trying to restore a graphic state that
|
28
|
+
EmptyGraphicStateStack = Class.new(StandardError)
|
29
|
+
|
30
|
+
# This error is raised when Document#page_layout is set to anything
|
31
|
+
# other than :portrait or :landscape
|
32
|
+
InvalidPageLayout = Class.new(StandardError)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# annotations.rb : Implements low-level annotation support for PDF
|
4
|
+
#
|
5
|
+
# Copyright November 2008, Jamis Buck. All Rights Reserved.
|
6
|
+
#
|
7
|
+
# This is free software. Please see the LICENSE and COPYING files for details.
|
8
|
+
#
|
9
|
+
module PDF
|
10
|
+
module Core
|
11
|
+
# Provides very low-level support for annotations.
|
12
|
+
#
|
13
|
+
module Annotations #:nodoc:
|
14
|
+
|
15
|
+
# Adds a new annotation (section 8.4 in PDF spec) to the current page.
|
16
|
+
# +options+ must be a Hash describing the annotation.
|
17
|
+
#
|
18
|
+
def annotate(options)
|
19
|
+
state.page.dictionary.data[:Annots] ||= []
|
20
|
+
options = sanitize_annotation_hash(options)
|
21
|
+
state.page.dictionary.data[:Annots] << ref!(options)
|
22
|
+
return options
|
23
|
+
end
|
24
|
+
|
25
|
+
# A convenience method for creating Text annotations. +rect+ must be an array
|
26
|
+
# of four numbers, describing the bounds of the annotation. +contents+ should
|
27
|
+
# be a string, to be shown when the annotation is activated.
|
28
|
+
#
|
29
|
+
def text_annotation(rect, contents, options={})
|
30
|
+
options = options.merge(:Subtype => :Text, :Rect => rect, :Contents => contents)
|
31
|
+
annotate(options)
|
32
|
+
end
|
33
|
+
|
34
|
+
# A convenience method for creating Link annotations. +rect+ must be an array
|
35
|
+
# of four numbers, describing the bounds of the annotation. The +options+ hash
|
36
|
+
# should include either :Dest (describing the target destination, usually as a
|
37
|
+
# string that has been recorded in the document's Dests tree), or :A (describing
|
38
|
+
# an action to perform on clicking the link), or :PA (for describing a URL to
|
39
|
+
# link to).
|
40
|
+
#
|
41
|
+
def link_annotation(rect, options={})
|
42
|
+
options = options.merge(:Subtype => :Link, :Rect => rect)
|
43
|
+
annotate(options)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def sanitize_annotation_hash(options)
|
49
|
+
options = options.merge(:Type => :Annot)
|
50
|
+
|
51
|
+
if options[:Dest].is_a?(String)
|
52
|
+
options[:Dest] = PDF::Core::LiteralString.new(options[:Dest])
|
53
|
+
end
|
54
|
+
|
55
|
+
options
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# Implements destination support for PDF
|
4
|
+
#
|
5
|
+
# Copyright November 2008, Jamis Buck. All Rights Reserved.
|
6
|
+
#
|
7
|
+
# This is free software. Please see the LICENSE and COPYING files for details.
|
8
|
+
|
9
|
+
module PDF
|
10
|
+
module Core
|
11
|
+
module Destinations #:nodoc:
|
12
|
+
|
13
|
+
# The maximum number of children to fit into a single node in the Dests tree.
|
14
|
+
NAME_TREE_CHILDREN_LIMIT = 20 #:nodoc:
|
15
|
+
|
16
|
+
# The Dests name tree in the Name dictionary (see Prawn::Document::Internal#names).
|
17
|
+
# This name tree is used to store named destinations (PDF spec 8.2.1).
|
18
|
+
# (For more on name trees, see section 3.8.4 in the PDF spec.)
|
19
|
+
#
|
20
|
+
def dests
|
21
|
+
names.data[:Dests] ||= ref!(PDF::Core::NameTree::Node.new(self, NAME_TREE_CHILDREN_LIMIT))
|
22
|
+
end
|
23
|
+
|
24
|
+
# Adds a new destination to the dests name tree (see #dests). The
|
25
|
+
# +reference+ parameter will be converted into a PDF::Core::Reference if
|
26
|
+
# it is not already one.
|
27
|
+
#
|
28
|
+
def add_dest(name, reference)
|
29
|
+
reference = ref!(reference) unless reference.is_a?(PDF::Core::Reference)
|
30
|
+
dests.data.add(name, reference)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Return a Dest specification for a specific location (and optional zoom
|
34
|
+
# level).
|
35
|
+
#
|
36
|
+
def dest_xyz(left, top, zoom=nil, dest_page=page)
|
37
|
+
[dest_page.dictionary, :XYZ, left, top, zoom]
|
38
|
+
end
|
39
|
+
|
40
|
+
# Return a Dest specification that will fit the given page into the
|
41
|
+
# viewport.
|
42
|
+
#
|
43
|
+
def dest_fit(dest_page=page)
|
44
|
+
[dest_page.dictionary, :Fit]
|
45
|
+
end
|
46
|
+
|
47
|
+
# Return a Dest specification that will fit the given page horizontally
|
48
|
+
# into the viewport, aligned vertically at the given top coordinate.
|
49
|
+
#
|
50
|
+
def dest_fit_horizontally(top, dest_page=page)
|
51
|
+
[dest_page.dictionary, :FitH, top]
|
52
|
+
end
|
53
|
+
|
54
|
+
# Return a Dest specification that will fit the given page vertically
|
55
|
+
# into the viewport, aligned horizontally at the given left coordinate.
|
56
|
+
#
|
57
|
+
def dest_fit_vertically(left, dest_page=page)
|
58
|
+
[dest_page.dictionary, :FitV, left]
|
59
|
+
end
|
60
|
+
|
61
|
+
# Return a Dest specification that will fit the given rectangle into the
|
62
|
+
# viewport, for the given page.
|
63
|
+
#
|
64
|
+
def dest_fit_rect(left, bottom, right, top, dest_page=page)
|
65
|
+
[dest_page.dictionary, :FitR, left, bottom, right, top]
|
66
|
+
end
|
67
|
+
|
68
|
+
# Return a Dest specfication that will fit the given page's bounding box
|
69
|
+
# into the viewport.
|
70
|
+
#
|
71
|
+
def dest_fit_bounds(dest_page=page)
|
72
|
+
[dest_page.dictionary, :FitB]
|
73
|
+
end
|
74
|
+
|
75
|
+
# Same as #dest_fit_horizontally, but works on the page's bounding box
|
76
|
+
# instead of the entire page.
|
77
|
+
#
|
78
|
+
def dest_fit_bounds_horizontally(top, dest_page=page)
|
79
|
+
[dest_page.dictionary, :FitBH, top]
|
80
|
+
end
|
81
|
+
|
82
|
+
# Same as #dest_fit_vertically, but works on the page's bounding box
|
83
|
+
# instead of the entire page.
|
84
|
+
#
|
85
|
+
def dest_fit_bounds_vertically(left, dest_page=page)
|
86
|
+
[dest_page.dictionary, :FitBV, left]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module PDF
|
2
|
+
module Core
|
3
|
+
class DocumentState #:nodoc:
|
4
|
+
def initialize(options)
|
5
|
+
normalize_metadata(options)
|
6
|
+
|
7
|
+
if options[:template]
|
8
|
+
@store = PDF::Core::ObjectStore.new(:template => options[:template])
|
9
|
+
@store.info.data.merge!(options[:info]) if options[:info]
|
10
|
+
else
|
11
|
+
@store = PDF::Core::ObjectStore.new(:info => options[:info])
|
12
|
+
end
|
13
|
+
|
14
|
+
@version = 1.3
|
15
|
+
@pages = []
|
16
|
+
@page = nil
|
17
|
+
@trailer = {}
|
18
|
+
@compress = options.fetch(:compress, false)
|
19
|
+
@encrypt = options.fetch(:encrypt, false)
|
20
|
+
@encryption_key = options[:encryption_key]
|
21
|
+
@optimize_objects = options.fetch(:optimize_objects, false)
|
22
|
+
@skip_encoding = options.fetch(:skip_encoding, false)
|
23
|
+
@before_render_callbacks = []
|
24
|
+
@on_page_create_callback = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
attr_accessor :store, :version, :pages, :page, :trailer, :compress,
|
28
|
+
:encrypt, :encryption_key, :optimize_objects, :skip_encoding,
|
29
|
+
:before_render_callbacks, :on_page_create_callback
|
30
|
+
|
31
|
+
def populate_pages_from_store(document)
|
32
|
+
return 0 if @store.page_count <= 0 || @pages.size > 0
|
33
|
+
|
34
|
+
count = (1..@store.page_count)
|
35
|
+
@pages = count.map do |index|
|
36
|
+
orig_dict_id = @store.object_id_for_page(index)
|
37
|
+
PDF::Core::Page.new(document, :object_id => orig_dict_id)
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
def normalize_metadata(options)
|
43
|
+
options[:info] ||= {}
|
44
|
+
options[:info][:Creator] ||= "Prawn"
|
45
|
+
options[:info][:Producer] ||= "Prawn"
|
46
|
+
|
47
|
+
options[:info]
|
48
|
+
end
|
49
|
+
|
50
|
+
def insert_page(page, page_number)
|
51
|
+
pages.insert(page_number, page)
|
52
|
+
store.pages.data[:Kids].insert(page_number, page.dictionary)
|
53
|
+
store.pages.data[:Count] += 1
|
54
|
+
end
|
55
|
+
|
56
|
+
def on_page_create_action(doc)
|
57
|
+
on_page_create_callback[doc] if on_page_create_callback
|
58
|
+
end
|
59
|
+
|
60
|
+
def before_render_actions(doc)
|
61
|
+
before_render_callbacks.each{ |c| c.call(self) }
|
62
|
+
end
|
63
|
+
|
64
|
+
def page_count
|
65
|
+
pages.length
|
66
|
+
end
|
67
|
+
|
68
|
+
def render_body(output)
|
69
|
+
store.compact if optimize_objects
|
70
|
+
store.each do |ref|
|
71
|
+
ref.offset = output.size
|
72
|
+
output << (@encrypt ? ref.encrypted_object(@encryption_key) :
|
73
|
+
ref.object)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module PDF
|
2
|
+
module Core
|
3
|
+
class FilterList
|
4
|
+
def initialize
|
5
|
+
@list = []
|
6
|
+
end
|
7
|
+
|
8
|
+
def <<(filter)
|
9
|
+
case filter
|
10
|
+
when Symbol
|
11
|
+
@list << [filter, nil]
|
12
|
+
when ::Hash
|
13
|
+
filter.each do |name, params|
|
14
|
+
@list << [name, params]
|
15
|
+
end
|
16
|
+
else
|
17
|
+
raise "Can not interpret input as filter: #{filter.inspect}"
|
18
|
+
end
|
19
|
+
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
def normalized
|
24
|
+
@list
|
25
|
+
end
|
26
|
+
alias_method :to_a, :normalized
|
27
|
+
|
28
|
+
def names
|
29
|
+
@list.map do |(name, _)|
|
30
|
+
name
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def decode_params
|
35
|
+
@list.map do |(_, params)|
|
36
|
+
params
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def inspect
|
41
|
+
@list.inspect
|
42
|
+
end
|
43
|
+
|
44
|
+
def each(&block)
|
45
|
+
@list.each do |filter|
|
46
|
+
block.call(filter)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# prawn/core/filters.rb : Implements stream filters
|
4
|
+
#
|
5
|
+
# Copyright February 2013, Alexander Mankuta. All Rights Reserved.
|
6
|
+
#
|
7
|
+
# This is free software. Please see the LICENSE and COPYING files for details.
|
8
|
+
|
9
|
+
require 'zlib'
|
10
|
+
|
11
|
+
module PDF
|
12
|
+
module Core
|
13
|
+
module Filters
|
14
|
+
module FlateDecode
|
15
|
+
def self.encode(stream, params = nil)
|
16
|
+
Zlib::Deflate.deflate(stream)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.decode(stream, params = nil)
|
20
|
+
Zlib::Inflate.inflate(stream)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Pass through stub
|
25
|
+
module DCTDecode
|
26
|
+
def self.encode(stream, params = nil)
|
27
|
+
stream
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.decode(stream, params = nil)
|
31
|
+
stream
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
# Implements graphics state saving and restoring
|
4
|
+
#
|
5
|
+
# Copyright January 2010, Michael Witrant. All Rights Reserved.
|
6
|
+
#
|
7
|
+
# This is free software. Please see the LICENSE and COPYING files for details
|
8
|
+
#
|
9
|
+
|
10
|
+
|
11
|
+
module PDF
|
12
|
+
module Core
|
13
|
+
class GraphicStateStack
|
14
|
+
attr_accessor :stack
|
15
|
+
|
16
|
+
def initialize(previous_state = nil)
|
17
|
+
self.stack = [GraphicState.new(previous_state)]
|
18
|
+
end
|
19
|
+
|
20
|
+
def save_graphic_state(graphic_state = nil)
|
21
|
+
stack.push(GraphicState.new(graphic_state || current_state))
|
22
|
+
end
|
23
|
+
|
24
|
+
def restore_graphic_state
|
25
|
+
if stack.empty?
|
26
|
+
raise PDF::Core::Errors::EmptyGraphicStateStack,
|
27
|
+
"\n You have reached the end of the graphic state stack"
|
28
|
+
end
|
29
|
+
stack.pop
|
30
|
+
end
|
31
|
+
|
32
|
+
def current_state
|
33
|
+
stack.last
|
34
|
+
end
|
35
|
+
|
36
|
+
def present?
|
37
|
+
stack.size > 0
|
38
|
+
end
|
39
|
+
|
40
|
+
def empty?
|
41
|
+
stack.empty?
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
class GraphicState
|
47
|
+
attr_accessor :color_space, :dash, :cap_style, :join_style, :line_width, :fill_color, :stroke_color
|
48
|
+
|
49
|
+
def initialize(previous_state = nil)
|
50
|
+
@color_space = previous_state ? previous_state.color_space.dup : {}
|
51
|
+
@fill_color = previous_state ? previous_state.fill_color : "000000"
|
52
|
+
@stroke_color = previous_state ? previous_state.stroke_color : "000000"
|
53
|
+
@dash = previous_state ? previous_state.dash : { :dash => nil, :space => nil, :phase => 0 }
|
54
|
+
@cap_style = previous_state ? previous_state.cap_style : :butt
|
55
|
+
@join_style = previous_state ? previous_state.join_style : :miter
|
56
|
+
@line_width = previous_state ? previous_state.line_width : 1
|
57
|
+
end
|
58
|
+
|
59
|
+
def dash_setting
|
60
|
+
if @dash[:dash].kind_of?(Array)
|
61
|
+
"[#{@dash[:dash].join(' ')}] #{@dash[:phase]} d"
|
62
|
+
else
|
63
|
+
"[#{@dash[:dash]} #{@dash[:space]}] #{@dash[:phase]} d"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|