substrate 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 20af64cb9a41e61ad9af8a5f678f3485947689193ad3f71d7638bd332452c88b
4
- data.tar.gz: 34bc75ca3736801c7f35d968a626e32a597d62c36ef6516b7f727c1cd21e53ff
3
+ metadata.gz: f70ea3ee7d06a49e41215d92a1b04db9173cbf357cbeccbd9bb85adc281b2e45
4
+ data.tar.gz: b86bcb7ff7b2f9eab771a2e3419b1ab0cf42a08bb4ae6d4bd906d2cee0ea0c5f
5
5
  SHA512:
6
- metadata.gz: 46c461e08228dcf87eed7d6e6c791619b3a83fc6ee72f1e2804db5e9d81a606d0299afdd3a69c5ce4ba52dc3a3fff1720db1dee30ad1cb810fb7f1bcbcc855d6
7
- data.tar.gz: 744c7900ef351097160f26a695c440f1197b07ad9aafd38c06d9ce86a5a6714d445d766d048c1cd8ed69cabef792618e4879b19b5383d599441908d3d59c6447
6
+ metadata.gz: 2d2687f91dbe8ca128de93e975de5e0a25359e8e8f1d7399b6092721b553ac26985eeff2d2963a7112f70bf11f98fdbcfff8f30e4462cb9fc9b3375349e6cf37
7
+ data.tar.gz: e303be1550bd6d5f98635689316e9be8e17d88657e196d614133f84aad0674745db45ffb0f1702dfc22ac3f5798e923113f612151fe98a71bfbce8ba602b0542
@@ -5,14 +5,16 @@ require "stringio"
5
5
 
6
6
  module Substrate
7
7
  class ExtractPdf < Command
8
- def initialize(bytes:, source_ref:, limits: Mediums::Pdf::Limits.new)
8
+ def initialize(bytes:, source_ref:, limits: Mediums::Pdf::Limits.new, page_separator: "\n\n")
9
9
  @bytes = bytes
10
10
  @source_ref = source_ref
11
11
  @limits = limits
12
+ @page_separator = page_separator
12
13
  end
13
14
 
14
15
  def call
15
16
  validate_input
17
+ validate_page_separator
16
18
  extract_document
17
19
  end
18
20
 
@@ -23,6 +25,14 @@ module Substrate
23
25
  @limits.check!(:input_bytes, @bytes.bytesize)
24
26
  end
25
27
 
28
+ def validate_page_separator
29
+ raise ArgumentError, "page_separator must be a String" unless @page_separator.is_a?(String)
30
+ @page_separator = @page_separator.encode(Encoding::UTF_8).dup.freeze
31
+ raise ArgumentError, "page_separator must contain valid UTF-8" unless @page_separator.valid_encoding?
32
+ rescue EncodingError => error
33
+ raise ArgumentError, "page_separator must contain valid UTF-8", cause: error
34
+ end
35
+
26
36
  def extract_document
27
37
  StringIO.open(@bytes) do |io|
28
38
  document = HexaPDF::Document.new(io: io, config: {"document.auto_decrypt" => false, "filter.map" => {BrotliDecode: nil}})
@@ -69,14 +79,13 @@ module Substrate
69
79
  end
70
80
 
71
81
  def extract_pages(objects)
72
- text_bytes = 0
82
+ @text = +""
73
83
  objects.map.with_index do |page, index|
74
84
  @limits.check!(:pages, index + 1)
75
85
  text = page.extract_text.encode(Encoding::UTF_8)
76
86
  raise Mediums::Pdf::InvalidText, "PDF text contains invalid UTF-8 or NUL" unless text.valid_encoding? && !text.include?("\0")
77
- text_bytes += text.bytesize + ((index == 0) ? 0 : 2)
78
- @limits.check!(:text_bytes, text_bytes)
79
- Mediums::Pdf::Page.new(number: index + 1, text: text.freeze)
87
+ append_page_text(text, index)
88
+ Mediums::Pdf::Page.new(number: index + 1, text: text.freeze, crop_box: crop_box(page))
80
89
  end.freeze.tap do |pages|
81
90
  raise Mediums::Pdf::NoSelectableText, "PDF contains no selectable text" if pages.all? { |page| page.text.match?(/\A[[:space:]]*\z/) }
82
91
  end
@@ -84,12 +93,41 @@ module Substrate
84
93
  raise Mediums::Pdf::InvalidText, "PDF text contains invalid UTF-8", cause: error
85
94
  end
86
95
 
96
+ def append_page_text(text, index)
97
+ separator = index.zero? ? "" : @page_separator
98
+ @limits.check!(:text_bytes, @text.bytesize + separator.bytesize + text.bytesize)
99
+ @text << separator << text
100
+ end
101
+
102
+ def crop_box(page)
103
+ media, crop = page_rectangles(page)
104
+ return unless usable_box?(media) && usable_box?(crop)
105
+ box = Mediums::Pdf::Box.new(left: [media.left, crop.left].max,
106
+ bottom: [media.bottom, crop.bottom].max, right: [media.right, crop.right].min,
107
+ top: [media.top, crop.top].min)
108
+ box if box.right > box.left && box.top > box.bottom
109
+ end
110
+
111
+ def page_rectangles(page)
112
+ media = page[:MediaBox]
113
+ crop = page[:CropBox]
114
+ [media, crop.nil? ? media : crop]
115
+ rescue HexaPDF::Error
116
+ [nil, nil]
117
+ end
118
+
119
+ def usable_box?(rectangle)
120
+ rectangle.is_a?(HexaPDF::Rectangle) &&
121
+ rectangle.to_a.all? { |value| value.is_a?(Numeric) && value.finite? } &&
122
+ rectangle.right > rectangle.left && rectangle.top > rectangle.bottom
123
+ end
124
+
87
125
  def publish_content(pages, outline)
88
126
  metadata = {extractor: "HexaPDF", extractor_version: HexaPDF::VERSION.dup.freeze,
89
127
  input_bytes: @bytes.bytesize, page_count: pages.size,
90
- text_bytes: pages.sum { |page| page.text.bytesize } + 2 * (pages.size - 1)}.freeze
128
+ text_bytes: @text.bytesize}.freeze
91
129
  success(payload: Mediums::Pdf::Content.new(pages: pages, source_ref: @source_ref,
92
- metadata: metadata, outline: outline))
130
+ metadata: metadata, outline: outline, text: @text.freeze))
93
131
  end
94
132
  end
95
133
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Substrate
4
+ module Mediums
5
+ module Pdf
6
+ Box = Data.define(:left, :bottom, :right, :top)
7
+ end
8
+ end
9
+ end
@@ -3,11 +3,7 @@
3
3
  module Substrate
4
4
  module Mediums
5
5
  module Pdf
6
- Content = Data.define(:pages, :source_ref, :metadata, :outline) do
7
- def text
8
- pages.map(&:text).join("\n\n").freeze
9
- end
10
- end
6
+ Content = Data.define(:pages, :source_ref, :metadata, :outline, :text)
11
7
  end
12
8
  end
13
9
  end
@@ -3,7 +3,7 @@
3
3
  module Substrate
4
4
  module Mediums
5
5
  module Pdf
6
- Page = Data.define(:number, :text)
6
+ Page = Data.define(:number, :text, :crop_box)
7
7
  end
8
8
  end
9
9
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Substrate
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: substrate
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dan Frenette
@@ -98,6 +98,7 @@ files:
98
98
  - lib/substrate/handlers/raw_text.rb
99
99
  - lib/substrate/handlers/text_file.rb
100
100
  - lib/substrate/handlers/transcript.rb
101
+ - lib/substrate/mediums/pdf/box.rb
101
102
  - lib/substrate/mediums/pdf/chapter.rb
102
103
  - lib/substrate/mediums/pdf/content.rb
103
104
  - lib/substrate/mediums/pdf/destination.rb