pdfbeads 1.0.9 → 1.1.1

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.
@@ -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 = ''