pdfbeads 1.0.9 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -8,7 +8,7 @@
8
8
 
9
9
  <meta name="Generator" content="Written directly in html">
10
10
 
11
- <meta name="Description" content="Руководство пользователя PDFBEADS версии 1.0">
11
+ <meta name="Description" content="Руководство пользователя pdfbeads версии 1.1">
12
12
 
13
13
  <style type="text/css">
14
14
  body {
@@ -30,22 +30,22 @@
30
30
  h1 {
31
31
  font-size: 36px;
32
32
  font-family: Times New Roman, Times, serif;
33
- text-align: center;
34
- font-style: normal;
33
+ text-align: center;
34
+ font-style: normal;
35
35
  font-weight: bold
36
36
  }
37
37
  h2 {
38
38
  font-size: 20px;
39
39
  font-family: Arial, Helvetica, sans-serif;
40
- text-align: center;
41
- font-style: normal;
40
+ text-align: center;
41
+ font-style: normal;
42
42
  font-weight: bold;
43
43
  }
44
44
  h3 {
45
45
  font-size: 16px;
46
46
  font-family: Arial, Helvetica, sans-serif;
47
- text-align: left;
48
- font-style: italic;
47
+ text-align: left;
48
+ font-style: italic;
49
49
  font-weight: bold;
50
50
  }
51
51
  dt {
@@ -57,9 +57,9 @@
57
57
 
58
58
  <body>
59
59
 
60
- <h1>Руководство пользователя PDFBEADS версии 1.0</h1>
60
+ <h1>Руководство пользователя pdfbeads версии 1.1</h1>
61
61
 
62
- <p>(c) Алексей Крюков, 2010</p>
62
+ <p>(c) Алексей Крюков, 2013</p>
63
63
 
64
64
  <p>Утилита pdfbeads предназначена для создания электронных книг в формате
65
65
  PDF из предварительно обработанных отсканированных страниц. В отличие от
@@ -90,7 +90,8 @@ JPEG2000;</p></li>
90
90
  <li><p>создание PDF-файлов с оглавлением и метаданными;</p></li>
91
91
 
92
92
  <li><p>добавление скрытого текстового слоя из документов в формате hOCR
93
- с корректной обработкой символов кириллицы.</p></li>
93
+ с корректной обработкой символов кириллицы либо перенос текста из другого
94
+ PDF-файла.</p></li>
94
95
 
95
96
  </ul>
96
97
 
@@ -103,23 +104,19 @@ JPEG2000;</p></li>
103
104
  <h2>Требования</h2>
104
105
 
105
106
  <p>Для запуска программы требуется прежде всего интерпретатор языка Ruby
106
- версии 1.8 или 1.9, доступный в дистрибутивах большинства Unix-подобных
107
+ версии 1.8 или выше, доступный в дистрибутивах большинства Unix-подобных
107
108
  систем. Версия для Windows может быть загружена с сайта <a
108
109
  href="http://www.rubyinstaller.org/">RubyInstaller</a>. Для корректной
109
110
  установки pdfbeads необходимо также загрузить пакетный менеджер RubyGems,
110
111
  представляющий собой стандартный интерфейс языка Ruby для работы с
111
112
  расширениями. Кроме того, в дополнение к основному дистрибутиву Ruby
112
- понадобятся расширения RMagick и hpricot (последнее&nbsp;&mdash; для
113
- обработки распознанного текста в формате hOCR).</p>
113
+ понадобятся расширения RMagick, Nokogiri (для обработки распознанного
114
+ текста в формате hOCR) и PDF::Reader (для считывания распознанного
115
+ текста из другого PDF-файла).</p>
114
116
 
115
117
  <p>Если вы хотите создавать PDF-файлы с использованием формата сжатия
116
118
  данных JBIG2, то в системе также должна быть установлена утилита jbig2
117
- из пакета <a href="http://github.com/agl/jbig2enc">jbig2enc</a>. Внимание:
118
- по состоянию на октябрь 2010&nbsp;г., когда пишется этот файл,
119
- настоятельно рекомендуется использовать версию jbig2, самостоятельно
120
- собранную из исходников, доступных в репозитории git, поскольку более
121
- ранние версии не обеспечивают корректного сохранения информации о разрешении
122
- изображений.</p>
119
+ из пакета <a href="http://github.com/agl/jbig2enc">jbig2enc</a>.</p>
123
120
 
124
121
  <h2>Установка</h2>
125
122
 
@@ -132,10 +129,10 @@ gem install pdfbeads
132
129
  </pre>
133
130
 
134
131
  <p>Перед запуском программы необходимо удостовериться, что расширение
135
- RMagick установлено и доступно интерпретатору Ruby. К сожалению, эту
136
- зависимость нельзя отследить автоматически, поскольку в некоторых
132
+ RMagick установлено и доступно интерпретатору Ruby. <strong>К сожалению, эту
133
+ зависимость нельзя отследить автоматически</strong>, поскольку в некоторых
137
134
  дистрибутивах Linux (в частности, Ubuntu) пакет RMagick устанавливается в
138
- обход механизма RubyGems, так что утилите gem о нем ничего не известно.</p>
135
+ обход механизма RubyGems, так что утилите <tt>gem</tt> о нем ничего не известно.</p>
139
136
 
140
137
  <p>Пользователям Ubuntu также следует иметь в виду, что в этом дистрибутиве
141
138
  исполняемые файлы из пакетов gem по умолчанию распаковываются в каталоги
@@ -195,7 +192,7 @@ HTM(L) или HOCR, содержащих распознанный текст в
195
192
  pdfbeads, иногда занимает довольно много времени, эти файлы в дальнейшем
196
193
  не удаляются с диска и могут быть повторно использованы при последующих
197
194
  прогонах в целях экономии времени. Для того, чтобы заставить pdfbeads
198
- заменить такие файлы заново созданными версиями, можно запустить его с опцией
195
+ заменить такие файлы заново созданными версиями, можно запустить его с ключом
199
196
  <tt>-f</tt> или <tt>--force-update</tt>.</p>
200
197
 
201
198
  <p>pdfbeads предназначен для сборки PDF из предварительно обработанных
@@ -239,7 +236,7 @@ pdfbeads [options] [files to process] [&gt; output_file.pdf]
239
236
  специальное назначение.</p>
240
237
 
241
238
  <p>Вместо записи PDF-файла на стандартное устройство вывода можно использовать
242
- опцию <tt>-o</tt> или <tt>--output</tt>, сопроводив ее указанием имени файла.</p>
239
+ ключ <tt>-o</tt> или <tt>--output</tt>, сопроводив ее указанием имени файла.</p>
243
240
 
244
241
  <h2>Обработка бинаризованных изображений</h2>
245
242
 
@@ -276,12 +273,12 @@ ImageMagick, что существенно увеличивает скорост
276
273
  <p>По умолчанию передний план страницы упаковывается с помощью технологии
277
274
  сжатия JBIG2, для чего pdfbeads использует утилиту <a
278
275
  href="http://github.com/agl/jbig2enc">jbig2enc</a> (автор&nbsp;&mdash; Адам
279
- Лэнгли). При этом можно задать опцию <tt>-p</tt> (<tt>--pages-per-dict</tt>),
276
+ Лэнгли). При этом можно задать ключ <tt>-p</tt> (<tt>--pages-per-dict</tt>),
280
277
  чтобы указать желательное количество страниц, использующих общий словарь
281
278
  разделенных символов (по умолчанию&nbsp;&mdash; 15).</p>
282
279
 
283
- <p>Если утилита jbig2enc недоступна, либо при запуске pdfbeads была указана
284
- опция <tt>-m</tt> (<tt>--mask-compression</tt>) с аргументом `G4' (синонимы&nbsp;&mdash;
280
+ <p>Если утилита jbig2enc недоступна, либо при запуске pdfbeads был указан
281
+ ключ <tt>-m</tt> (<tt>--mask-compression</tt>) с аргументом `G4' (синонимы&nbsp;&mdash;
285
282
  `Group4', `CCITTFax'), то вместо JBIG2-сжатия будет использоваться формат
286
283
  CCITT Group 4 fax.</p>
287
284
 
@@ -303,7 +300,7 @@ CCITT Group 4 fax.</p>
303
300
  `JP2' или `JPX'), `JPEG' (с синонимом `JPG'), а также `LOSSLESS'
304
301
  (синонимы&nbsp;&mdash; `DEFLATE', `PNG'). Если используемая сборка библиотеки
305
302
  ImageMagick поддерживает формат JPEG2000, по умолчанию используется именно
306
- он; в противном случае&nbsp;&mdash; JPEG. Если выбрана опция LOSSLESS,
303
+ он; в противном случае&nbsp;&mdash; JPEG. Если выбрано значение LOSSLESS,
307
304
  то pdfbeads будет использовать для сжатия изображений технологию deflate.
308
305
  Следует иметь в виду, что это может привести к значительному возрастанию
309
306
  объема данных по сравнению с форматами JPEG2000 или JPEG.</p></dd>
@@ -314,7 +311,7 @@ ImageMagick поддерживает формат JPEG2000, по умолчан
314
311
 
315
312
  <dt>-g, --grayscale</dt>
316
313
  <dd><p>Заставляет pdfbeads принудительно конвертировать цветные картинки в
317
- оттенки серого. Данная опция может быть полезна в том случае, если исходные
314
+ оттенки серого. Данный ключ может быть полезен в том случае, если исходные
318
315
  сканы были выполнены в цвете, но фактически содержали только черно-белые
319
316
  картинки, причем преобразование в оттенки серого не было выполнено на этапе
320
317
  первичной сканобработки. Такая ситуация часто возникает, в частности, при
@@ -354,7 +351,7 @@ pdfbeads, необходимо подготовить два графическ
354
351
  <tt>*.bg.*</tt>) будет содержать фон, освобожденный от текстовых данных,
355
352
  а на втором (с суффиксом <tt>*.fg.*</tt>) останутся только элементы маски
356
353
  с присущей им текстурой. Данная процедура по смыслу напоминает операцию,
357
- осуществляемую утилитой <tt>djvumake</tt> при указании опции <tt>PPM</tt>,
354
+ осуществляемую утилитой <tt>djvumake</tt> при указании ключа <tt>PPM</tt>,
358
355
  и имеет ту же самую цель: создание трехслойной страницы, где один из
359
356
  полноцветных слоев отвечает за отображение фона, а другой&nbsp;&mdash;
360
357
  за раскраску наложенной на этот фон маски.</p>
@@ -379,9 +376,9 @@ pdfbeads, необходимо подготовить два графическ
379
376
  <a href="http://www.imagemagick.org/discourse-server/viewtopic.php?p=41498#p41498">дискуссии
380
377
  на форуме ImageMagick</a>, где обсуждались возможные способы удаления текста
381
378
  с картинки с последующим заполнением образовавшихся «дырок»
382
- исходя из значений соседних пикселей.
379
+ исходя из значений соседних пикселей.</p>
383
380
 
384
- </p><h2>Дополнительные возможности</h2>
381
+ <h2>Дополнительные возможности</h2>
385
382
 
386
383
  <h3>Добавление метаданных</h3>
387
384
 
@@ -397,7 +394,7 @@ pdfbeads, необходимо подготовить два графическ
397
394
  <tt>Author</tt>, <tt>Subject</tt> и <tt>Keywords</tt>. Строки, начинающиеся
398
395
  с символа `#', считаются комментариями и игнорируются.</p>
399
396
 
400
- <p>Ссылку на созданный файл можно передать pdfbeads с помощью опции
397
+ <p>Ссылку на созданный файл можно передать pdfbeads с помощью ключа
401
398
  <tt>-M</tt> (или <tt>--meta</tt>).</p>
402
399
 
403
400
  <h3>Метки страниц</h3>
@@ -481,11 +478,54 @@ PDF-файлу. Для этого используется параметр <tt>
481
478
  параметр указывает, должен ли данный пункт оглавления отображаться
482
479
  развернутым по умолчанию (символы `+' и `1' означают «да»).</p>
483
480
 
484
- <p>Опцию <tt>--toc</tt> целесообразно использовать в сочетании с опцией
481
+ <p>Ключ <tt>--toc</tt> целесообразно использовать в сочетании с ключом
485
482
  <tt>--labels</tt>. В этом случае в файле оглавления можно использовать
486
483
  те же номера страниц, что и в бумажной книге, не задумываясь о сдвигах
487
484
  нумерации.</p>
488
485
 
486
+ <h3>Добавление текстового слоя</h3>
487
+
488
+ <p>pdfbeads позволяет создавать документы PDF со скрытым текстовым
489
+ слоем. Последний может быть либо получен из файлов в формате
490
+ <a href="http://docs.google.com/View?docid=dfxcv4vc_67g844kf">hOCR</a>
491
+ (расширение языка HTML, позволяющее сохранять в документе информацию
492
+ о положении символов и элементов разметки текста на странице), либо
493
+ импортирован из другого PDF-файла.</p>
494
+
495
+ <p>Для создания файлов в формате hOCR необходимо воспользоваться программой
496
+ оптического распознавания символов, поддерживающей этот формат, например
497
+ <a href="https://launchpad.net/cuneiform-linux/">Cuneiform</a> или
498
+ <a href="http://code.google.com/p/tesseract-ocr/">Tesseract</a>.
499
+ Распознанный текст следует сохранить в той же директории, что и остальные
500
+ файлы, относящиеся к проекту. При этом каждой распознанной странице должен
501
+ соответствовать отдельный файл с тем же базовым именем, что и у исходного
502
+ изображения, при расширении HTM(L) или HOCR. Обработка файлов hOCR
503
+ осуществляется автоматически при условии, что интерпретатору Ruby доступно
504
+ расширение Nokogiri.</p>
505
+
506
+ <p>Иное возможное решение заключается в том, чтобы импортировать текстовый
507
+ слой из другого PDF-файла (естественно, последний должен быть получен путем
508
+ распознавания тех же самых изображений, которые предполагается затем обработать
509
+ с помощью pdfbeads). Имя полученного файла следует передать pdfbeads с помощью
510
+ ключа <tt>-T</tt> (полная форма&nbsp;&mdash; <tt>-text-pdf</tt>). Эта
511
+ возможность особенно важна в тех случаях, когда приходится использовать для
512
+ распознавания текста коммерческое приложение (например,
513
+ <a href="http://www.abbyy.ru/finereader/">ABBYY Finereader</a>), в котором
514
+ не предусмотрена поддержка формата hOCR. <strong>Внимание:</strong> возможно,
515
+ вам придется поэкспериментировать с настройками экспорта PDF в OCR-приложении
516
+ для того, чтобы получить наилучшее соответствие между размещением распознанного
517
+ текста на странице и исходным изображением. В частности, в ABBYY Finereader
518
+ 11-й версии желаемый результат достигается только при сохранении файла в
519
+ режиме «текст под изображением».</p>
520
+
521
+ <h3>Обработка документов с направлением текста справа налево</h3>
522
+
523
+ <p>Ключ <tt>-R</tt> (или <tt>--right-to-left</tt> позволяет сохранить
524
+ в создаваемом файле пометку, указывающую на то, что основной язык
525
+ данного документа предполагает направление чтения справа налево. Данный
526
+ флажок используется Acrobat Reader при выборе порядка следования страниц в
527
+ режиме их попарного отображения.</p>
528
+
489
529
  <h2>Лицензия</h2>
490
530
 
491
531
  <p>Данная программа является свободным программным обеспечением. Вы
@@ -8,7 +8,7 @@
8
8
  # Unlike other PDF creation tools, this utility attempts to implement
9
9
  # the approach typically used for DjVu books. Its key feature is
10
10
  # separating scanned text (typically black, but indexed images with
11
- # a small number of colors are also accepted) from halftone images
11
+ # a small number of colors are also accepted) from halftone images
12
12
  # placed into a background layer.
13
13
  #
14
14
  # Copyright (C) 2010 Alexey Kryukov (amkryukov@gmail.com).
@@ -30,19 +30,25 @@
30
30
  #
31
31
  #######################################################################
32
32
 
33
- require 'iconv'
34
33
  require 'zlib'
35
34
 
36
35
  require 'RMagick'
37
36
  include Magick
38
37
 
39
38
  begin
40
- require 'hpricot'
41
- $has_hpricot = true
39
+ require 'nokogiri'
40
+ $has_nokogiri = true
42
41
  rescue LoadError
43
- $stderr.puts( "Warning: the hpricot extension is not available. I'll not be able" )
42
+ $stderr.puts( "Warning: the nokogiri extension is not available. I'll not be able" )
44
43
  $stderr.puts( "\tto create hidden text layer from hOCR files." )
45
- $has_hpricot = false
44
+ $has_nokogiri = false
45
+ end
46
+
47
+ begin
48
+ require 'pdf/reader'
49
+ $has_pdfreader = true
50
+ rescue LoadError
51
+ $has_pdfreader = false
46
52
  end
47
53
 
48
54
  unless ''.respond_to? :ord
@@ -50,6 +56,11 @@ unless ''.respond_to? :ord
50
56
  require 'jcode'
51
57
  end
52
58
 
59
+ # Require iconv for Ruby version less than 1.9.3
60
+ unless ''.respond_to? :encode
61
+ require 'iconv'
62
+ end
63
+
53
64
  class String
54
65
  # Protect strings which are supposed be treated as a raw sequence of bytes.
55
66
  # This is important for Ruby 1.9. For earlier versions the method just
@@ -9,7 +9,7 @@
9
9
  # Unlike other PDF creation tools, this utility attempts to implement
10
10
  # the approach typically used for DjVu books. Its key feature is
11
11
  # separating scanned text (typically black, but indexed images with
12
- # a small number of colors are also accepted) from halftone images
12
+ # a small number of colors are also accepted) from halftone images
13
13
  # placed into a background layer.
14
14
  #
15
15
  # Copyright (C) 2010 Alexey Kryukov (amkryukov@gmail.com).
@@ -69,6 +69,7 @@ class PDFBeads::PDFBuilder
69
69
  labels = PDFLabels.new( @pdfargs[:labels] ) unless @pdfargs[:labels].nil?
70
70
  toc = PDFTOC.new( @pdfargs[:toc] ) unless @pdfargs[:toc].nil?
71
71
  meta = parseMeta( @pdfargs[:meta] )
72
+ reader = getPDFReader( @pdfargs[:textpdf] )
72
73
 
73
74
  cat = XObj.new(Hash[
74
75
  'Type' => '/Catalog',
@@ -98,12 +99,12 @@ class PDFBeads::PDFBuilder
98
99
  info.addToDict(key, "(\xFE\xFF#{meta[key].to_text})")
99
100
  end
100
101
 
101
- out = XObj.new(Hash[
102
- 'Type' => '/Outlines',
103
- 'Count' => 0
104
- ])
105
- @doc.addObject(out)
106
- cat.addToDict('Outlines', ref(out.getID))
102
+ if ( toc != nil and toc.length > 0 ) or @pdfargs[:rtl]
103
+ vpref = XObj.new(Hash.new())
104
+ vpref.addToDict('Direction', "/R2L") if @pdfargs[:rtl]
105
+ @doc.addObject(vpref)
106
+ cat.addToDict('ViewerPreferences', ref(vpref.getID))
107
+ end
107
108
 
108
109
  pages = XObj.new(Hash[
109
110
  'Type' => '/Pages'
@@ -132,7 +133,7 @@ class PDFBeads::PDFBuilder
132
133
  'Intent' => '[/View/Design]'
133
134
  })
134
135
  @doc.addObject(ocBack)
135
- cat.addToDict('OCProperties',
136
+ cat.addToDict('OCProperties',
136
137
  sprintf("<< /OCGs[%s %s] /D<< /Intent /View /BaseState (ON) /Order[%s %s] >>>>",
137
138
  ref(ocFore.getID), ref(ocBack.getID), ref(ocFore.getID), ref(ocBack.getID)))
138
139
 
@@ -150,9 +151,14 @@ class PDFBeads::PDFBuilder
150
151
  begin
151
152
  # If possible, use iso8859-1 (aka PDFDocEncoding) for page labels:
152
153
  # it is at least guaranteed to be safe
153
- ltitl = Iconv.iconv( "iso8859-1", "utf-8", rng[:prefix] ).first
154
- nTree << "/P (#{ltitl.to_text}) "
155
- rescue Iconv::InvalidCharacter, Iconv::IllegalSequence
154
+ if rng[:prefix].respond_to? :encode
155
+ ltitl = rng[:prefix].encode( "iso8859-1", "utf-8" )
156
+ else
157
+ ltitl = Iconv.iconv( "iso8859-1", "utf-8", rng[:prefix] ).first
158
+ end
159
+ nTree << "/P (#{ltitl.to_text}) "
160
+ # Iconv::InvalidCharacter, Iconv::IllegalSequence, Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError
161
+ rescue
156
162
  ltitl = Iconv.iconv( "utf-16be", "utf-8", rng[:prefix] ).first
157
163
  # If there is no number (just prefix) then put a zero character after the prefix:
158
164
  # this makes acroread happy, but prevents displaying the number in evince
@@ -176,27 +182,31 @@ class PDFBeads::PDFBuilder
176
182
 
177
183
  needs_font = false
178
184
  fonts = encodings = nil
179
- pagefiles.each do |p|
180
- unless p.hocr_path.nil?
181
- needs_font = true
182
- break
185
+ unless reader.nil?
186
+ fdict = importPDFFonts( reader,@pdfargs[:textpdf] )
187
+ else
188
+ pagefiles.each do |p|
189
+ unless p.hocr_path.nil?
190
+ needs_font = true
191
+ break
192
+ end
183
193
  end
184
- end
185
194
 
186
- if needs_font
187
- fonts = Array.new()
188
- encodings = [ [' '] ]
189
- fdict = XObj.new( Hash[] )
190
- @doc.addObject( fdict )
191
-
192
- descr = XObj.new( Hash[
193
- 'Type' => '/FontDescriptor',
194
- 'BaseFont' => '/Times-Roman',
195
- ] )
196
- @fdata.header.each_key do |key|
197
- descr.addToDict( key,@fdata.header[key] )
195
+ if needs_font
196
+ fonts = Array.new()
197
+ encodings = [ [' '] ]
198
+ fdict = XObj.new( Hash[] )
199
+ @doc.addObject( fdict )
200
+
201
+ descr = XObj.new( Hash[
202
+ 'Type' => '/FontDescriptor',
203
+ 'BaseFont' => '/Times-Roman',
204
+ ] )
205
+ @fdata.header.each_key do |key|
206
+ descr.addToDict( key,@fdata.header[key] )
207
+ end
208
+ @doc.addObject( descr )
198
209
  end
199
- @doc.addObject( descr )
200
210
  end
201
211
 
202
212
  pagefiles.each do |p|
@@ -261,17 +271,24 @@ class PDFBeads::PDFBuilder
261
271
  doc_objs.concat( [contents, resobj, resources] )
262
272
 
263
273
  hocr = nil
264
- unless p.hocr_path.nil?
265
- hocr = open( p.hocr_path ) { |f| Hpricot.parse( f ) }
274
+ if not reader.nil?
266
275
  procSet << '/Text'
267
- c_str << getPDFText( hocr,pheight,72.0/xres,72.0/yres,encodings )
276
+ c_str << getPDFText( reader,pidx,@pdfargs[:debug] )
277
+ elsif not p.hocr_path.nil?
278
+ hocr = open( p.hocr_path ) { |f| Nokogiri::HTML( f ) }
279
+ procSet << '/Text'
280
+ c_str << getHOCRText( hocr,pheight,72.0/xres,72.0/yres,encodings )
268
281
  end
269
282
 
270
- contents.reinit( Hash[
271
- 'Filter' => '/FlateDecode'
272
- ], Zlib::Deflate.deflate( c_str,9 ) )
283
+ unless @pdfargs[:debug]
284
+ contents.reinit( Hash[
285
+ 'Filter' => '/FlateDecode'
286
+ ], Zlib::Deflate.deflate( c_str,9 ) )
287
+ else
288
+ contents.reinit( Hash[], c_str )
289
+ end
273
290
  resources.addToDict( 'ProcSet', "[ #{procSet.join(' ')} ]" )
274
- resources.addToDict( 'Font', ref( fdict.getID ) ) unless hocr.nil?
291
+ resources.addToDict( 'Font', ref( fdict.getID ) ) unless hocr.nil? and reader.nil?
275
292
 
276
293
  page = XObj.new(Hash[
277
294
  'Type' => '/Page',
@@ -325,6 +342,7 @@ class PDFBeads::PDFBuilder
325
342
  getOutlineObjs( toc,pages_by_num,page_objs[0].getID )
326
343
  cat.addToDict('Outlines', ref(toc[0][:pdfobj].getID))
327
344
  cat.addToDict('PageMode', "/UseOutlines")
345
+ vpref.addToDict('NonFullScreenPageMode', "/UseOutlines")
328
346
  end
329
347
 
330
348
  if @pdfargs[:delfiles]
@@ -381,7 +399,11 @@ class PDFBeads::PDFBuilder
381
399
  key = $1
382
400
  if keys.include? key
383
401
  begin
384
- ret[key] = Iconv.iconv( "utf-16be", "utf-8", $2 ).first
402
+ if $2.respond_to? :encode
403
+ ret[key] = $2.encode( "utf-16be", "utf-8" )
404
+ else
405
+ ret[key] = Iconv.iconv( "utf-16be", "utf-8", $2 ).first
406
+ end
385
407
  rescue
386
408
  $stderr.puts("Error: metadata should be specified in utf-8")
387
409
  end
@@ -392,6 +414,171 @@ class PDFBeads::PDFBuilder
392
414
  ret
393
415
  end
394
416
 
417
+ def getPDFReader( path )
418
+ return nil if path.nil? or path.eql? ''
419
+ return nil unless File.file? path
420
+
421
+ PDF::Reader.new( path )
422
+ end
423
+
424
+ def encodePDFArray( in_a )
425
+ out_a = Array.new()
426
+ out_a << '['
427
+ in_a.each do |item|
428
+ if item.is_a? String
429
+ out_a << ( '(' << item.to_s << ')' )
430
+ elsif item.is_a? Symbol
431
+ out_a << ( '/' << item.to_s )
432
+ elsif item.is_a? Array
433
+ out_a << encodePDFArray( item )
434
+ else
435
+ out_a << item.to_s
436
+ end
437
+ end
438
+ out_a << ']'
439
+ out_a.join( ' ' )
440
+ end
441
+
442
+ def encodePDFObjEntry( inhash,outobj,label )
443
+ if inhash[label].is_a? String
444
+ outobj.addToDict( label,"(#{inhash[label]})" )
445
+
446
+ elsif inhash[label].is_a? Symbol
447
+ outobj.addToDict( label,"/#{inhash[label]}" )
448
+
449
+ elsif inhash[label].is_a? Integer
450
+ outobj.addToDict( label,"#{inhash[label]}" )
451
+
452
+ elsif inhash[label].is_a? Array
453
+ outobj.addToDict( label,encodePDFArray( inhash[label] ) )
454
+
455
+ elsif inhash[label].is_a? Hash
456
+ newobj = XObj.new( Hash.new() )
457
+ @doc.addObject( newobj )
458
+ outobj.addToDict( label,ref(newobj.getID) )
459
+ inhash[label].keys.each do |newlabel|
460
+ encodePDFObjEntry( inhash[label],newobj,newlabel )
461
+ end
462
+
463
+ elsif inhash[label].is_a? PDF::Reader::Stream
464
+ newobj = XObj.new( Hash.new(),inhash[label].data )
465
+ @doc.addObject( newobj )
466
+ outobj.addToDict( label,ref(newobj.getID) )
467
+ inhash[label].hash.keys.each do |newlabel|
468
+ encodePDFObjEntry( inhash[label].hash,newobj,newlabel ) unless newlabel.eql? :Length
469
+ end
470
+ end
471
+ end
472
+
473
+ def importPDFFont( label,font )
474
+ fontobj = XObj.new( Hash.new() )
475
+ fontobj.addToDict( 'Name',"/#{label}" ) unless label.nil?
476
+ @doc.addObject( fontobj )
477
+
478
+ if font.has_key? :DescendantFonts
479
+ dfonts = Array.new()
480
+ font[:DescendantFonts].each {|dfont| dfonts << importPDFFont( nil,dfont ) }
481
+ fontobj.addToDict( "DescendantFonts",'[ ' << dfonts.map{|dfont| ref(dfont.getID)}.join(' ') << ' ]' )
482
+ end
483
+
484
+ [ :BaseFont, :Type, :Subtype, :FirstChar, :LastChar, :Widths, :FontDescriptor,
485
+ :Encoding, :ToUnicode, :DW, :W, :CIDSystemInfo, :CIDToGIDMap ].each do |fontkey|
486
+ encodePDFObjEntry( font,fontobj,fontkey ) if font.has_key? fontkey
487
+ end
488
+ fontobj
489
+ end
490
+
491
+ def importPDFFonts( reader,path )
492
+ fonts = Hash.new()
493
+ reader.pages.each_index do |i|
494
+ $stderr.puts("Reading font data from #{path}: page #{i}\n")
495
+ page = reader.pages[i]
496
+ page.fonts.each do |label,font|
497
+ fonts[label] = page.objects.deref( font ) unless fonts.has_key? label
498
+ end
499
+ end
500
+
501
+ fdict = XObj.new( Hash[] )
502
+ @doc.addObject( fdict )
503
+ fonts.keys.sort_by {|sym| sym.to_s}.each do |label|
504
+ fontobj = importPDFFont( label,fonts[label] )
505
+ fdict.addToDict( label,ref(fontobj.getID) )
506
+ end
507
+ fdict
508
+ end
509
+
510
+ def getPDFText( reader,pidx,debug )
511
+ return "" unless reader.pages.length > pidx
512
+
513
+ page = reader.pages[pidx]
514
+ pcont = page.raw_content.to_binary()
515
+ cidx = 0
516
+ in_t = false
517
+ pstack = 0
518
+ prevc = "\0"
519
+ ch_start = -1
520
+ ret = ""
521
+ tr_val = debug ? 0 : 3
522
+
523
+ pcont.each_byte do |char|
524
+ if char.chr.eql? '('
525
+ ctx = pcont[0,cidx].match( /\\+$/ )
526
+ pstack += 1 if ( ctx.nil? or ctx[0].length % 2 == 0 )
527
+ elsif char.chr.eql? ')'
528
+ ctx = pcont[0,cidx].match( /\\+$/ )
529
+ pstack -= 1 if ( ctx.nil? or ctx[0].length % 2 == 0 )
530
+ end
531
+
532
+ unless pstack > 0
533
+ # Text state operators may occur outside text objects. We have to take care of this
534
+ if not in_t and prevc.eql? 'T'
535
+ case char.chr
536
+ when 'c'
537
+ if pcont[0,cidx-1] =~ /([-+]?\d*\.?\d+)\s+$/
538
+ ret << " #{$1} Tc"
539
+ end
540
+ when 'w'
541
+ if pcont[0,cidx-1] =~ /([-+]?\d*\.?\d+)\s+$/
542
+ ret << " #{$1} Tw"
543
+ end
544
+ when 'z'
545
+ if pcont[0,cidx-1] =~ /([-+]?\d*\.?\d+)\s+$/
546
+ ret << " #{$1} Tz"
547
+ end
548
+ when 'L'
549
+ if pcont[0,cidx-1] =~ /([-+]?\d*\.?\d+)\s+$/
550
+ ret << " #{$1} TL"
551
+ end
552
+ when 'f'
553
+ if pcont[0,cidx-1] =~ /\/([A-Za-z0-9]+)\s+([-+]?\d*\.?\d+)\s+$/
554
+ ret << " /#{$1} #{$2} Tf"
555
+ end
556
+ # Tr operators are ignored, since we always need either a hidden text (3 Tr)
557
+ # or (for debugging purposes) a visible text without special effects (0 Tr)
558
+ when 's'
559
+ if pcont[0,cidx-1] =~ /([-+]?\d*\.?\d+)\s+$/
560
+ chunks << " #{$1} Ts"
561
+ end
562
+ end
563
+ elsif not in_t and ( prevc + char.chr ).eql? 'BT'
564
+ ch_start = cidx -1
565
+ in_t = true
566
+ elsif in_t and ( prevc + char.chr ).eql? 'ET'
567
+ chunk = pcont.slice( ch_start,cidx - ch_start + 1 )
568
+ chunk.gsub!( /\d{1}\s+Tr/,"#{tr_val} Tr" )
569
+ ret << "\n" << chunk
570
+ ch_start = -1
571
+ in_t = false
572
+ end
573
+ end
574
+
575
+ prevc = char.chr
576
+ cidx += 1
577
+ end
578
+ return "\nq #{tr_val} Tr" << ret << " Q" if ret.length > 0
579
+ return ""
580
+ end
581
+
395
582
  def getOutlineObjs( toc,page_ids,fp_id )
396
583
  root = toc[0]
397
584
  root[:pdfobj] = XObj.new( Hash[
@@ -453,8 +640,8 @@ class PDFBeads::PDFBuilder
453
640
  def elementCoordinates( element,xscale,yscale )
454
641
  out = [0,0,0,0]
455
642
 
456
- if element.attributes.to_hash.has_key? 'title'
457
- if /bbox((\s+\d+){4})/.match(element.attributes.to_hash['title'])
643
+ if element.attributes.has_key? 'title'
644
+ if /bbox((\s+\d+){4})/.match(element.attributes['title'].content)
458
645
  coords = $1.strip.split(/\s+/)
459
646
  out = [ (coords[0].to_i*xscale).to_f,(coords[1].to_i*xscale).to_f,
460
647
  (coords[2].to_i*yscale).to_f,(coords[3].to_i*yscale).to_f ]
@@ -463,23 +650,16 @@ class PDFBeads::PDFBuilder
463
650
  return out
464
651
  end
465
652
 
466
- def elementText( elem,charset )
467
- txt = ''
468
- begin
469
- txt = elem.to_plain_text.strip
470
- txt = Iconv.iconv( 'utf-8',charset,txt ).first unless charset.downcase.eql? 'utf-8'
471
- rescue
472
- end
473
-
474
- txt.force_encoding( 'utf-8' ) if txt.respond_to? :force_encoding
475
- return txt
653
+ def elementText( elem )
654
+ # used to put some Iconv stuff here, but nokogiri makes this conversion itself
655
+ return elem.inner_text.strip
476
656
  end
477
657
 
478
- def getOCRUnits( ocr_line,lbbox,fsize,charset,xscale,yscale )
658
+ def getOCRUnits( ocr_line,lbbox,fsize,xscale,yscale )
479
659
  units = Array.new()
480
- ocr_words = ocr_line.search("//span[@class='ocrx_word']")
660
+ ocr_words = ocr_line.xpath(".//span[@class='ocrx_word']")
481
661
  ocr_chars = nil
482
- ocr_chars = ocr_line.at("//span[@class='ocr_cinfo']") if ocr_words.length == 0
662
+ ocr_chars = ocr_line.at_xpath(".//span[@class='ocr_cinfo']") if ocr_words.length == 0
483
663
 
484
664
  # If 'ocrx_word' elements are available (as in Tesseract owtput), split the line
485
665
  # into individual words
@@ -487,16 +667,16 @@ class PDFBeads::PDFBuilder
487
667
  ocr_words.each do |word|
488
668
  bbox = elementCoordinates( word,xscale,yscale )
489
669
  next if bbox == [0,0,0,0]
490
- txt = elementText( word,charset )
670
+ txt = elementText( word )
491
671
  units << [txt,bbox]
492
672
  end
493
673
 
494
- # If 'ocrx_cinfo' data is available (as in Cuneiform) owtput, then split it
674
+ # If 'ocrx_cinfo' data is available (as in Cuneiform) owtput, then split it
495
675
  # into individual characters and then combine them into words
496
- elsif not ocr_chars.nil? and ocr_chars.attributes.to_hash.has_key? 'title'
497
- if /x_bboxes([-\s\d]+)/.match( ocr_chars.attributes.to_hash['title'] )
676
+ elsif not ocr_chars.nil? and ocr_chars.attributes.has_key? 'title'
677
+ if /x_bboxes([-\s\d]+)/.match( ocr_chars.attributes['title'].content )
498
678
  coords = $1.strip.split(/\s+/)
499
- ltxt = elementText( ocr_line,charset )
679
+ ltxt = elementText( ocr_line )
500
680
  charcnt = 0
501
681
  ltxt.each_char { |uc| charcnt += 1 }
502
682
 
@@ -521,10 +701,11 @@ class PDFBeads::PDFBuilder
521
701
  if /^\s+$/.match( uc )
522
702
  wtxt = ''
523
703
 
524
- # A workaround for probable hpricot bug, which sometimes causes whitespace
525
- # characters from inside a string to be stripped. So if we find
526
- # a bounding box with negative values we assume there was a whitespace
527
- # character here, even if not preserved in the string itself
704
+ # A workaround for probable hpricot bug (TODO: is Nokogiri affected?),
705
+ # which sometimes causes whitespace characters from inside a string
706
+ # to be stripped. So if we find a bounding box with negative values
707
+ # we assume there was a whitespace character here, even if not
708
+ # preserved in the string itself
528
709
  else
529
710
  wtxt = uc
530
711
  i += 1
@@ -541,7 +722,7 @@ class PDFBeads::PDFBuilder
541
722
 
542
723
  # If neither word nor character bounding boxes are available, then store the line as a whole
543
724
  if units.length == 0
544
- ltxt = elementText( ocr_line,charset )
725
+ ltxt = elementText( ocr_line )
545
726
  units << [ltxt,lbbox] unless ltxt.eql? ''
546
727
  end
547
728
 
@@ -549,22 +730,15 @@ class PDFBeads::PDFBuilder
549
730
  return units
550
731
  end
551
732
 
552
- def getPDFText( hocr,pheight,xscale,yscale,encodings )
733
+ def getHOCRText( hocr,pheight,xscale,yscale,encodings )
553
734
  fsize = 10
554
735
  cur_enc = nil
555
736
  ret = " BT 3 Tr "
556
737
 
557
- charset = 'utf-8'
558
- hocr.search("//meta[@http-equiv='Content-Type']").each do |el|
559
- attrs = el.attributes.to_hash
560
- charset = $1 if attrs.has_key? 'content' and
561
- /\Atext\/html;charset=([A-Za-z0-9-]+)\Z/i.match( attrs['content'] )
562
- end
563
-
564
- hocr.search("//span[@class='ocr_line']").each do |line|
738
+ hocr.xpath("//span[@class='ocr_line']").each do |line|
565
739
  lbbox = elementCoordinates( line,xscale,yscale )
566
740
  next if lbbox[2] - lbbox[0] <= 0 or lbbox[3] - lbbox[1] <= 0
567
- units = getOCRUnits( line,lbbox,fsize,charset,xscale,yscale )
741
+ units = getOCRUnits( line,lbbox,fsize,xscale,yscale )
568
742
  next if units.length == 0
569
743
 
570
744
  wwidth = 0
@@ -573,7 +747,9 @@ class PDFBeads::PDFBuilder
573
747
  ltxt << unit[0]
574
748
  wwidth += ( unit[1][2] - unit[1][0] )
575
749
  end
576
- ratio = wwidth / @fdata.getLineWidth( ltxt,fsize )
750
+ lw = @fdata.getLineWidth( ltxt,fsize )
751
+ ratio = 1
752
+ ratio = wwidth / lw unless lw == 0
577
753
  pos = lbbox[0]
578
754
  posdiff = 0
579
755
 
@@ -592,7 +768,11 @@ class PDFBeads::PDFBuilder
592
768
  txt8 = ''
593
769
  wtxt.each_char do |char|
594
770
  begin
595
- Iconv.iconv( "utf-16be","utf-8",char )
771
+ if char.respond_to? :encode
772
+ char.encode!( "utf-16be", "utf-8" )
773
+ else
774
+ Iconv.iconv( "utf-16be","utf-8",char )
775
+ end
596
776
  rescue
597
777
  rawbytes = char.unpack( 'C*' )
598
778
  bs = ''