hexapdf 1.1.1 → 1.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 +4 -4
- data/CHANGELOG.md +79 -0
- data/README.md +1 -1
- data/lib/hexapdf/cli/command.rb +63 -63
- data/lib/hexapdf/cli/inspect.rb +14 -5
- data/lib/hexapdf/cli/modify.rb +0 -1
- data/lib/hexapdf/cli/optimize.rb +5 -5
- data/lib/hexapdf/composer.rb +14 -0
- data/lib/hexapdf/configuration.rb +26 -0
- data/lib/hexapdf/content/graphics_state.rb +1 -1
- data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +1 -1
- data/lib/hexapdf/document/annotations.rb +173 -0
- data/lib/hexapdf/document/layout.rb +45 -6
- data/lib/hexapdf/document.rb +28 -7
- data/lib/hexapdf/error.rb +11 -3
- data/lib/hexapdf/font/true_type/subsetter.rb +15 -2
- data/lib/hexapdf/font/true_type_wrapper.rb +1 -0
- data/lib/hexapdf/font/type1_wrapper.rb +1 -0
- data/lib/hexapdf/layout/style.rb +101 -7
- data/lib/hexapdf/object.rb +2 -2
- data/lib/hexapdf/pdf_array.rb +25 -3
- data/lib/hexapdf/tokenizer.rb +4 -1
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +57 -8
- data/lib/hexapdf/type/acro_form/field.rb +1 -0
- data/lib/hexapdf/type/acro_form/form.rb +7 -6
- data/lib/hexapdf/type/acro_form/java_script_actions.rb +9 -2
- data/lib/hexapdf/type/acro_form/text_field.rb +9 -2
- data/lib/hexapdf/type/annotation.rb +71 -1
- data/lib/hexapdf/type/annotations/appearance_generator.rb +348 -0
- data/lib/hexapdf/type/annotations/border_effect.rb +99 -0
- data/lib/hexapdf/type/annotations/border_styling.rb +160 -0
- data/lib/hexapdf/type/annotations/circle.rb +65 -0
- data/lib/hexapdf/type/annotations/interior_color.rb +84 -0
- data/lib/hexapdf/type/annotations/line.rb +490 -0
- data/lib/hexapdf/type/annotations/square.rb +65 -0
- data/lib/hexapdf/type/annotations/square_circle.rb +77 -0
- data/lib/hexapdf/type/annotations/widget.rb +52 -116
- data/lib/hexapdf/type/annotations.rb +8 -0
- data/lib/hexapdf/type/form.rb +2 -2
- data/lib/hexapdf/version.rb +1 -1
- data/lib/hexapdf/writer.rb +0 -1
- data/lib/hexapdf/xref_section.rb +7 -4
- data/test/hexapdf/content/test_graphics_state.rb +2 -3
- data/test/hexapdf/content/test_operator.rb +4 -5
- data/test/hexapdf/digital_signature/test_cms_handler.rb +7 -8
- data/test/hexapdf/digital_signature/test_handler.rb +2 -3
- data/test/hexapdf/digital_signature/test_pkcs1_handler.rb +1 -2
- data/test/hexapdf/document/test_annotations.rb +55 -0
- data/test/hexapdf/document/test_layout.rb +24 -2
- data/test/hexapdf/font/test_true_type_wrapper.rb +7 -0
- data/test/hexapdf/font/test_type1_wrapper.rb +7 -0
- data/test/hexapdf/font/true_type/test_subsetter.rb +10 -0
- data/test/hexapdf/layout/test_style.rb +27 -2
- data/test/hexapdf/task/test_optimize.rb +1 -1
- data/test/hexapdf/test_composer.rb +7 -0
- data/test/hexapdf/test_document.rb +11 -3
- data/test/hexapdf/test_object.rb +1 -1
- data/test/hexapdf/test_pdf_array.rb +36 -3
- data/test/hexapdf/test_stream.rb +1 -2
- data/test/hexapdf/test_xref_section.rb +1 -1
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +78 -3
- data/test/hexapdf/type/acro_form/test_button_field.rb +7 -6
- data/test/hexapdf/type/acro_form/test_field.rb +5 -0
- data/test/hexapdf/type/acro_form/test_form.rb +17 -1
- data/test/hexapdf/type/acro_form/test_java_script_actions.rb +21 -0
- data/test/hexapdf/type/acro_form/test_text_field.rb +7 -1
- data/test/hexapdf/type/annotations/test_appearance_generator.rb +482 -0
- data/test/hexapdf/type/annotations/test_border_effect.rb +59 -0
- data/test/hexapdf/type/annotations/test_border_styling.rb +114 -0
- data/test/hexapdf/type/annotations/test_interior_color.rb +37 -0
- data/test/hexapdf/type/annotations/test_line.rb +169 -0
- data/test/hexapdf/type/annotations/test_widget.rb +35 -81
- data/test/hexapdf/type/test_annotation.rb +55 -0
- data/test/hexapdf/type/test_form.rb +6 -0
- metadata +17 -2
@@ -0,0 +1,173 @@
|
|
1
|
+
# -*- encoding: utf-8; frozen_string_literal: true -*-
|
2
|
+
#
|
3
|
+
#--
|
4
|
+
# This file is part of HexaPDF.
|
5
|
+
#
|
6
|
+
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
7
|
+
# Copyright (C) 2014-2025 Thomas Leitner
|
8
|
+
#
|
9
|
+
# HexaPDF is free software: you can redistribute it and/or modify it
|
10
|
+
# under the terms of the GNU Affero General Public License version 3 as
|
11
|
+
# published by the Free Software Foundation with the addition of the
|
12
|
+
# following permission added to Section 15 as permitted in Section 7(a):
|
13
|
+
# FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
|
14
|
+
# THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
|
15
|
+
# INFRINGEMENT OF THIRD PARTY RIGHTS.
|
16
|
+
#
|
17
|
+
# HexaPDF is distributed in the hope that it will be useful, but WITHOUT
|
18
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
20
|
+
# License for more details.
|
21
|
+
#
|
22
|
+
# You should have received a copy of the GNU Affero General Public License
|
23
|
+
# along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
|
24
|
+
#
|
25
|
+
# The interactive user interfaces in modified source and object code
|
26
|
+
# versions of HexaPDF must display Appropriate Legal Notices, as required
|
27
|
+
# under Section 5 of the GNU Affero General Public License version 3.
|
28
|
+
#
|
29
|
+
# In accordance with Section 7(b) of the GNU Affero General Public
|
30
|
+
# License, a covered work must retain the producer line in every PDF that
|
31
|
+
# is created or manipulated using HexaPDF.
|
32
|
+
#
|
33
|
+
# If the GNU Affero General Public License doesn't fit your need,
|
34
|
+
# commercial licenses are available at <https://gettalong.at/hexapdf/>.
|
35
|
+
#++
|
36
|
+
|
37
|
+
require 'hexapdf/dictionary'
|
38
|
+
require 'hexapdf/error'
|
39
|
+
|
40
|
+
module HexaPDF
|
41
|
+
class Document
|
42
|
+
|
43
|
+
# This class provides methods for creating and managing the annotations of a PDF file.
|
44
|
+
#
|
45
|
+
# An annotation is an object that can be added to a certain location on a page, provides a
|
46
|
+
# visual appearance and allows for interaction with the user via keyboard and mouse.
|
47
|
+
#
|
48
|
+
# == Usage
|
49
|
+
#
|
50
|
+
# To create an annotation either call the general #create method or a specific creation method
|
51
|
+
# for an annotation type. After the annotation has been created customize it using the
|
52
|
+
# convenience methods on the annotation object. The last step should be the call to
|
53
|
+
# +regenerate_appearance+ so that the appearance is generated.
|
54
|
+
#
|
55
|
+
# See: PDF2.0 s12.5
|
56
|
+
class Annotations
|
57
|
+
|
58
|
+
include Enumerable
|
59
|
+
|
60
|
+
# Creates a new Annotations object for the given PDF document.
|
61
|
+
def initialize(document)
|
62
|
+
@document = document
|
63
|
+
end
|
64
|
+
|
65
|
+
# :call-seq:
|
66
|
+
# annotations.create(type, page, **options) -> annotation
|
67
|
+
#
|
68
|
+
# Creates a new annotation object with the given +type+ and +page+ by calling the respective
|
69
|
+
# +create_type+ method.
|
70
|
+
#
|
71
|
+
# The +options+ are passed on the specific annotation creation method.
|
72
|
+
def create(type, page, *args, **options)
|
73
|
+
method_name = "create_#{type}"
|
74
|
+
unless respond_to?(method_name)
|
75
|
+
raise ArgumentError, "Invalid type specified"
|
76
|
+
end
|
77
|
+
send("create_#{type}", page, *args, **options)
|
78
|
+
end
|
79
|
+
|
80
|
+
# :call-seq:
|
81
|
+
# annotations.create_line(page, start_point:, end_point:) -> annotation
|
82
|
+
#
|
83
|
+
# Creates a line annotation from +start_point+ to +end_point+ on the given page and returns
|
84
|
+
# it.
|
85
|
+
#
|
86
|
+
# The line uses a black color and a width of 1pt. It can be further styled using the
|
87
|
+
# convenience methods on the returned annotation object.
|
88
|
+
#
|
89
|
+
# Example:
|
90
|
+
#
|
91
|
+
# doc.annotations.create_line(doc.pages[0], start_point: [100, 100], end_point: [130, 180]).
|
92
|
+
# border_style(color: "blue", width: 2).
|
93
|
+
# leader_line_length(10).
|
94
|
+
# regenerate_appearance
|
95
|
+
#
|
96
|
+
# See: Type::Annotations::Line
|
97
|
+
def create_line(page, start_point:, end_point:)
|
98
|
+
create_and_add_to_page(:Line, page).
|
99
|
+
line(*start_point, *end_point).
|
100
|
+
border_style(color: 0, width: 1)
|
101
|
+
end
|
102
|
+
|
103
|
+
# :call-seq:
|
104
|
+
# annotations.create_rectangle(page, x, y, width, height) -> annotation
|
105
|
+
#
|
106
|
+
# Creates a rectangle (called "square" in the PDF specification) annotation with the
|
107
|
+
# lower-left corner at (+x+, +y+) and the given +width+ and +height+.
|
108
|
+
#
|
109
|
+
# The rectangle uses a black stroke color, no interior color and a line width of 1pt by
|
110
|
+
# default. It can be further styled using the convenience methods on the returned annotation
|
111
|
+
# object.
|
112
|
+
#
|
113
|
+
# Example:
|
114
|
+
#
|
115
|
+
# #>pdf-small
|
116
|
+
# doc.annotations.create_rectangle(doc.pages[0], 20, 20, 20, 60).
|
117
|
+
# regenerate_appearance
|
118
|
+
#
|
119
|
+
# doc.annotations.create_rectangle(doc.pages[0], 60, 20, 20, 60).
|
120
|
+
# border_style(color: "hp-blue", width: 2).
|
121
|
+
# interior_color("hp-orange").
|
122
|
+
# regenerate_appearance
|
123
|
+
#
|
124
|
+
# See: Type::Annotations::Square
|
125
|
+
def create_rectangle(page, x, y, w, h)
|
126
|
+
annot = create_and_add_to_page(:Square, page)
|
127
|
+
annot[:Rect] = [x, y, x + w, y + h]
|
128
|
+
annot.border_style(color: 0, width: 1)
|
129
|
+
annot
|
130
|
+
end
|
131
|
+
|
132
|
+
# :call-seq:
|
133
|
+
# annotations.create_ellipse(page, cx, cy, a:, b:) -> annotation
|
134
|
+
#
|
135
|
+
# Creates an ellipse (called "circle" in the PDF specification) annotation with the center
|
136
|
+
# point at (+cx+, +cy+), the semi-major axis +a+ and the semi-minor axis +b+.
|
137
|
+
#
|
138
|
+
# The ellipse uses a black stroke color, no interior color and a line width of 1pt by
|
139
|
+
# default. It can be further styled using the convenience methods on the returned annotation
|
140
|
+
# object.
|
141
|
+
#
|
142
|
+
# Example:
|
143
|
+
#
|
144
|
+
# #>pdf-small
|
145
|
+
# doc.annotations.create_ellipse(doc.pages[0], 30, 50, a: 15, b: 20).
|
146
|
+
# regenerate_appearance
|
147
|
+
#
|
148
|
+
# doc.annotations.create_ellipse(doc.pages[0], 70, 50, a: 15, b: 20).
|
149
|
+
# border_style(color: "hp-blue", width: 2).
|
150
|
+
# interior_color("hp-orange").
|
151
|
+
# regenerate_appearance
|
152
|
+
#
|
153
|
+
# See: Type::Annotations::Circle
|
154
|
+
def create_ellipse(page, x, y, a:, b:)
|
155
|
+
annot = create_and_add_to_page(:Circle, page)
|
156
|
+
annot[:Rect] = [x - a, y - b, x + a, y + b]
|
157
|
+
annot.border_style(color: 0, width: 1)
|
158
|
+
annot
|
159
|
+
end
|
160
|
+
|
161
|
+
private
|
162
|
+
|
163
|
+
# Returns the root of the destinations name tree.
|
164
|
+
def create_and_add_to_page(subtype, page)
|
165
|
+
annot = @document.add({Type: :Annot, Subtype: subtype})
|
166
|
+
(page[:Annots] ||= []) << annot
|
167
|
+
annot
|
168
|
+
end
|
169
|
+
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|
173
|
+
end
|
@@ -218,6 +218,19 @@ module HexaPDF
|
|
218
218
|
style
|
219
219
|
end
|
220
220
|
|
221
|
+
# Returns +true+ if a style with the given +name+ exists, else +false+.
|
222
|
+
#
|
223
|
+
# Example:
|
224
|
+
#
|
225
|
+
# layout.style(:header, font: 'Helvetica')
|
226
|
+
# layout.style?(:header) # => true
|
227
|
+
# layout.style?(:paragraph) # => false
|
228
|
+
#
|
229
|
+
# See: #style
|
230
|
+
def style?(name)
|
231
|
+
@styles.key?(name)
|
232
|
+
end
|
233
|
+
|
221
234
|
# :call-seq:
|
222
235
|
# layout.styles -> styles
|
223
236
|
# layout.styles(**mapping) -> styles
|
@@ -286,8 +299,9 @@ module HexaPDF
|
|
286
299
|
box_options[:children] = ChildrenCollector.collect(self, &block)
|
287
300
|
end
|
288
301
|
end
|
302
|
+
style = retrieve_style(style)
|
289
303
|
box_class_for_name(name).new(width: width, height: height,
|
290
|
-
style:
|
304
|
+
style: style, **style.box_options, **box_options, &box_block)
|
291
305
|
end
|
292
306
|
|
293
307
|
# Creates an array of HexaPDF::Layout::TextFragment objects for the given +text+.
|
@@ -354,7 +368,7 @@ module HexaPDF
|
|
354
368
|
box_style = (box_style ? retrieve_style(box_style) : style)
|
355
369
|
box_class_for_name(:text).new(items: text_fragments(text, style: style),
|
356
370
|
width: width, height: height, properties: properties,
|
357
|
-
style: box_style)
|
371
|
+
style: box_style, **box_style.box_options)
|
358
372
|
end
|
359
373
|
alias text text_box
|
360
374
|
|
@@ -457,7 +471,8 @@ module HexaPDF
|
|
457
471
|
end
|
458
472
|
end
|
459
473
|
box_class_for_name(:text).new(items: data, width: width, height: height,
|
460
|
-
properties: properties, style: box_style
|
474
|
+
properties: properties, style: box_style,
|
475
|
+
**box_style.box_options)
|
461
476
|
end
|
462
477
|
alias formatted_text formatted_text_box
|
463
478
|
|
@@ -479,7 +494,7 @@ module HexaPDF
|
|
479
494
|
style = retrieve_style(style, style_properties)
|
480
495
|
image = file.kind_of?(HexaPDF::Stream) ? file : @document.images.add(file)
|
481
496
|
box_class_for_name(:image).new(image: image, width: width, height: height,
|
482
|
-
properties: properties, style: style)
|
497
|
+
properties: properties, style: style, **style.box_options)
|
483
498
|
end
|
484
499
|
alias image image_box
|
485
500
|
|
@@ -608,7 +623,8 @@ module HexaPDF
|
|
608
623
|
end
|
609
624
|
box_class_for_name(:table).new(cells: cells, column_widths: column_widths, header: header,
|
610
625
|
footer: footer, cell_style: cell_style, width: width,
|
611
|
-
height: height, properties: properties, style: style
|
626
|
+
height: height, properties: properties, style: style,
|
627
|
+
**style.box_options)
|
612
628
|
end
|
613
629
|
alias table table_box
|
614
630
|
|
@@ -666,6 +682,22 @@ module HexaPDF
|
|
666
682
|
end
|
667
683
|
end
|
668
684
|
|
685
|
+
FONT_BOLD_VARIANT_MAPPER = { #:nodoc:
|
686
|
+
nil => {true => :bold, false: :none},
|
687
|
+
none: {true => :bold, false: :none},
|
688
|
+
bold: {true => :bold, false: :none},
|
689
|
+
italic: {true => :bold_italic, false: :italic},
|
690
|
+
bold_italic: {true => :bold_italic, false: :italic},
|
691
|
+
}
|
692
|
+
|
693
|
+
FONT_ITALIC_VARIANT_MAPPER = { #:nodoc:
|
694
|
+
nil => {true => :italic, false: :none},
|
695
|
+
none: {true => :italic, false: :none},
|
696
|
+
italic: {true => :italic, false: :none},
|
697
|
+
bold: {true => :bold_italic, false: :bold},
|
698
|
+
bold_italic: {true => :bold_italic, false: :bold},
|
699
|
+
}
|
700
|
+
|
669
701
|
# Retrieves the appropriate HexaPDF::Layout::Style object based on the +style+ and
|
670
702
|
# +properties+ arguments.
|
671
703
|
#
|
@@ -690,7 +722,14 @@ module HexaPDF
|
|
690
722
|
end
|
691
723
|
unless style.font.respond_to?(:pdf_object)
|
692
724
|
name, options = *style.font
|
693
|
-
|
725
|
+
options ||= {}
|
726
|
+
if style.font_bold?
|
727
|
+
options[:variant] = FONT_BOLD_VARIANT_MAPPER.dig(options[:variant], style.font_bold)
|
728
|
+
end
|
729
|
+
if style.font_italic?
|
730
|
+
options[:variant] = FONT_ITALIC_VARIANT_MAPPER.dig(options[:variant], style.font_italic)
|
731
|
+
end
|
732
|
+
style.font(@document.fonts.add(name, **options))
|
694
733
|
end
|
695
734
|
style
|
696
735
|
end
|
data/lib/hexapdf/document.rb
CHANGED
@@ -123,6 +123,7 @@ module HexaPDF
|
|
123
123
|
autoload(:Destinations, 'hexapdf/document/destinations')
|
124
124
|
autoload(:Layout, 'hexapdf/document/layout')
|
125
125
|
autoload(:Metadata, 'hexapdf/document/metadata')
|
126
|
+
autoload(:Annotations, 'hexapdf/document/annotations')
|
126
127
|
|
127
128
|
# :call-seq:
|
128
129
|
# Document.open(filename, **docargs) -> doc
|
@@ -539,6 +540,12 @@ module HexaPDF
|
|
539
540
|
@destinations ||= Destinations.new(self)
|
540
541
|
end
|
541
542
|
|
543
|
+
# Returns the Annotations object that provides convenience methods for working with annotation
|
544
|
+
# objects.
|
545
|
+
def annotations
|
546
|
+
@annotations ||= Annotations.new(self)
|
547
|
+
end
|
548
|
+
|
542
549
|
# Returns the Layout object that provides convenience methods for working with the
|
543
550
|
# HexaPDF::Layout classes for document layout.
|
544
551
|
def layout
|
@@ -726,8 +733,8 @@ module HexaPDF
|
|
726
733
|
end
|
727
734
|
|
728
735
|
# :call-seq:
|
729
|
-
# doc.write(filename, incremental: false, validate: true, update_fields: true, optimize: false) -> [start_xref, section]
|
730
|
-
# doc.write(io, incremental: false, validate: true, update_fields: true, optimize: false) -> [start_xref, section]
|
736
|
+
# doc.write(filename, incremental: false, validate: true, update_fields: true, optimize: false, compact: true) -> [start_xref, section]
|
737
|
+
# doc.write(io, incremental: false, validate: true, update_fields: true, optimize: false, compact: true) -> [start_xref, section]
|
731
738
|
#
|
732
739
|
# Writes the document to the given file (in case +io+ is a String) or IO stream. Returns the
|
733
740
|
# file position of the start of the last cross-reference section and the last XRefSection object
|
@@ -755,7 +762,20 @@ module HexaPDF
|
|
755
762
|
# optimize::
|
756
763
|
# Optimize the file size by using object and cross-reference streams. This will raise the PDF
|
757
764
|
# version to at least 1.5.
|
758
|
-
|
765
|
+
#
|
766
|
+
# compact::
|
767
|
+
# Compact the document by reducing it to a single revision and removing null and unused
|
768
|
+
# objects.
|
769
|
+
#
|
770
|
+
# The initial revision of a document has to contain objects with continuous numbering. If some
|
771
|
+
# object numbers refer to free entries, other PDF libraries/viewers might not work
|
772
|
+
# correctly. So continuous object numbers are assigned to stay compliant with the
|
773
|
+
# specification.
|
774
|
+
#
|
775
|
+
# Only change this argument to +false+ if you run the optimization task with 'compact: true'
|
776
|
+
# beforehand or if you know exactly what you do and what not compacting implies.
|
777
|
+
def write(file_or_io, incremental: false, validate: true, update_fields: true, optimize: false,
|
778
|
+
compact: true)
|
759
779
|
if update_fields
|
760
780
|
trailer.update_id
|
761
781
|
if @metadata
|
@@ -774,10 +794,11 @@ module HexaPDF
|
|
774
794
|
end
|
775
795
|
end
|
776
796
|
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
797
|
+
optimize_opts = {}
|
798
|
+
optimize_opts[:object_streams] = :generate if optimize
|
799
|
+
optimize_opts[:compact] = true if compact && !incremental
|
800
|
+
task(:optimize, **optimize_opts) unless optimize_opts.empty?
|
801
|
+
self.version = '1.5' if version < '1.5' if optimize
|
781
802
|
|
782
803
|
dispatch_message(:before_write)
|
783
804
|
|
data/lib/hexapdf/error.rb
CHANGED
@@ -94,9 +94,17 @@ module HexaPDF
|
|
94
94
|
end
|
95
95
|
|
96
96
|
def message # :nodoc:
|
97
|
-
"No glyph for #{glyph.str.inspect} in font '#{glyph.font_wrapper.wrapped_font.full_name}' " \
|
98
|
-
|
99
|
-
|
97
|
+
str = "No glyph for #{glyph.str.inspect} in font '#{glyph.font_wrapper.wrapped_font.full_name}' " \
|
98
|
+
"found. \n\n"
|
99
|
+
str << if glyph.font_wrapper.font_type == :Type1
|
100
|
+
"The used Type1 font only contains a very limited number of glyphs. TrueType " \
|
101
|
+
"fonts usually provide a much wider array of glyphs. Use the configuration option " \
|
102
|
+
"'font.map' to register appropriate font files. Also have a look at the " \
|
103
|
+
"'font.default' and 'font.fallback' options. "
|
104
|
+
else
|
105
|
+
"Maybe register another #{glyph.font_wrapper.font_type} font that contains the " \
|
106
|
+
"needed glyph and use it as fallback via the configuration option 'font.fallback'."
|
107
|
+
end
|
100
108
|
end
|
101
109
|
|
102
110
|
end
|
@@ -63,6 +63,16 @@ module HexaPDF
|
|
63
63
|
def use_glyph(glyph_id)
|
64
64
|
return @glyph_map[glyph_id] if @glyph_map.key?(glyph_id)
|
65
65
|
@last_id += 1
|
66
|
+
# Handle codes for ASCII characters \r (13), (, ) (40, 41) and \ (92) specially so that
|
67
|
+
# they never appear in the output (PDF serialization would need to escape them)
|
68
|
+
if @last_id == 13 || @last_id == 40 || @last_id == 92
|
69
|
+
@glyph_map[:"s#{@last_id}"] = @last_id
|
70
|
+
if @last_id == 40
|
71
|
+
@last_id += 1
|
72
|
+
@glyph_map[:"s#{@last_id}"] = @last_id
|
73
|
+
end
|
74
|
+
@last_id += 1
|
75
|
+
end
|
66
76
|
@glyph_map[glyph_id] = @last_id
|
67
77
|
end
|
68
78
|
|
@@ -107,7 +117,7 @@ module HexaPDF
|
|
107
117
|
locations = []
|
108
118
|
|
109
119
|
@glyph_map.each_key do |old_gid|
|
110
|
-
glyph = orig_glyf[old_gid]
|
120
|
+
glyph = orig_glyf[old_gid.kind_of?(Symbol) ? 0 : old_gid]
|
111
121
|
locations << table.size
|
112
122
|
data = glyph.raw_data
|
113
123
|
if glyph.compound?
|
@@ -166,7 +176,10 @@ module HexaPDF
|
|
166
176
|
# Adds the components of compound glyphs to the subset.
|
167
177
|
def add_glyph_components
|
168
178
|
glyf = @font[:glyf]
|
169
|
-
@glyph_map.keys.each
|
179
|
+
@glyph_map.keys.each do |gid|
|
180
|
+
next if gid.kind_of?(Symbol)
|
181
|
+
glyf[gid].components&.each {|cgid| use_glyph(cgid) }
|
182
|
+
end
|
170
183
|
end
|
171
184
|
|
172
185
|
end
|
data/lib/hexapdf/layout/style.rb
CHANGED
@@ -603,11 +603,36 @@ module HexaPDF
|
|
603
603
|
# style.update(**properties) -> style
|
604
604
|
#
|
605
605
|
# Updates the style's properties using the key-value pairs specified by the +properties+ hash.
|
606
|
+
#
|
607
|
+
# Also see: #merge
|
606
608
|
def update(**properties)
|
607
609
|
properties.each {|key, value| send(key, value) }
|
608
610
|
self
|
609
611
|
end
|
610
612
|
|
613
|
+
# Yields all set properties.
|
614
|
+
def each_property # :yield: property, value
|
615
|
+
return to_enum(__method__) unless block_given?
|
616
|
+
instance_variables.each do |iv|
|
617
|
+
(val = PROPERTIES[iv]) && yield(val, instance_variable_get(iv))
|
618
|
+
end
|
619
|
+
end
|
620
|
+
|
621
|
+
# :call-seq:
|
622
|
+
# style.merge(other_style) -> style
|
623
|
+
#
|
624
|
+
# Merges the set properties of the +other_style+ object into this one.
|
625
|
+
#
|
626
|
+
# Note that merging is done on a per-property basis. So if a complex property is set on
|
627
|
+
# +other_style+ and also on +self+, the +other_style+ value completely overwrites the one from
|
628
|
+
# +self+.
|
629
|
+
#
|
630
|
+
# Also see: #update
|
631
|
+
def merge(other)
|
632
|
+
other.each_property {|property, value| send(property, value) }
|
633
|
+
self
|
634
|
+
end
|
635
|
+
|
611
636
|
##
|
612
637
|
# :method: font
|
613
638
|
# :call-seq:
|
@@ -615,8 +640,9 @@ module HexaPDF
|
|
615
640
|
#
|
616
641
|
# The font to be used, must be set to a valid font wrapper object before it can be used.
|
617
642
|
#
|
618
|
-
# HexaPDF::
|
619
|
-
# to a font wrapper object before doing
|
643
|
+
# HexaPDF::Document::Layout handles this property - together with #font_bold and #font_italic
|
644
|
+
# - specially in that it resolves a set string or array to a font wrapper object before doing
|
645
|
+
# anything else with the style object.
|
620
646
|
#
|
621
647
|
# This is the only style property without a default value!
|
622
648
|
#
|
@@ -633,6 +659,48 @@ module HexaPDF
|
|
633
659
|
# composer.text("Courier Bold", font: "Courier bold")
|
634
660
|
# composer.text("Courier Bold also", font: ["Courier", variant: :bold])
|
635
661
|
|
662
|
+
##
|
663
|
+
# :method: font_bold
|
664
|
+
# :call-seq:
|
665
|
+
# font_bold(bold = false)
|
666
|
+
#
|
667
|
+
# Specifies whether the bold variant of the font is used.
|
668
|
+
#
|
669
|
+
# Note that this property only has affect if #font is not already set to a font wrapper
|
670
|
+
# object and if it is set explicitly (i.e. #font_bold? returns +true+).
|
671
|
+
#
|
672
|
+
# See #font, #font_italic
|
673
|
+
#
|
674
|
+
# Examples:
|
675
|
+
#
|
676
|
+
# #>pdf-composer100
|
677
|
+
# composer.text("Helvetica bold", font: "Helvetica", font_bold: true)
|
678
|
+
#
|
679
|
+
# helvetica_bold = composer.document.fonts.add("Helvetica", variant: :bold)
|
680
|
+
# composer.text("Helvetica bold", font: helvetica_bold, font_bold: false)
|
681
|
+
# composer.text("Helvetica", font: ["Helvetica", {variant: :bold}], font_bold: false)
|
682
|
+
|
683
|
+
##
|
684
|
+
# :method: font_italic
|
685
|
+
# :call-seq:
|
686
|
+
# font_italic(bold = false)
|
687
|
+
#
|
688
|
+
# Specifies whether the italic variant of the font is used.
|
689
|
+
#
|
690
|
+
# Note that this property only has affect if #font is not already set to a font wrapper
|
691
|
+
# object and if it is set explicitly (i.e. #font_italic? returns +true+).
|
692
|
+
#
|
693
|
+
# See #font, #font_bold.
|
694
|
+
#
|
695
|
+
# Examples:
|
696
|
+
#
|
697
|
+
# #>pdf-composer100
|
698
|
+
# composer.text("Helvetica italic", font: "Helvetica", font_italic: true)
|
699
|
+
#
|
700
|
+
# helvetica_bold = composer.document.fonts.add("Helvetica", variant: :italic)
|
701
|
+
# composer.text("Helvetica italic", font: helvetica_bold, font_italic: false)
|
702
|
+
# composer.text("Helvetica", font: ["Helvetica", {variant: :italic}], font_italic: false)
|
703
|
+
|
636
704
|
##
|
637
705
|
# :method: font_size
|
638
706
|
# :call-seq:
|
@@ -1021,7 +1089,7 @@ module HexaPDF
|
|
1021
1089
|
#
|
1022
1090
|
# This method can set the line spacing in two ways:
|
1023
1091
|
#
|
1024
|
-
# * Using
|
1092
|
+
# * Using the positional, mandatory argument +type+ and the optional +value+.
|
1025
1093
|
# * Or a hash with the keys +type+ and +value+.
|
1026
1094
|
#
|
1027
1095
|
# Note that the last line has no additional spacing after it by default. Set #last_line_gap
|
@@ -1422,8 +1490,33 @@ module HexaPDF
|
|
1422
1490
|
# composer.text("This is some longer text that does not appear in two lines.",
|
1423
1491
|
# height: 15, overflow: :truncate)
|
1424
1492
|
|
1425
|
-
|
1493
|
+
##
|
1494
|
+
# :method: box_options
|
1495
|
+
# :call-seq:
|
1496
|
+
# box_options(**options)
|
1497
|
+
#
|
1498
|
+
# Contains initialization arguments for the box instance that is created with this
|
1499
|
+
# style. Together with the other style properties this allows the complete specification of a
|
1500
|
+
# box instance just via a Style instance.
|
1501
|
+
#
|
1502
|
+
# Note that this property is only used by the HexaPDF::Document::Layout methods when a box
|
1503
|
+
# instance is created. If a box instance is created directly, this property has no effect.
|
1504
|
+
#
|
1505
|
+
# Examples:
|
1506
|
+
#
|
1507
|
+
# #>pdf-composer100
|
1508
|
+
# composer.style(:my_list, box_options: {marker_type: :decimal, item_spacing: 15})
|
1509
|
+
# composer.list(style: :my_list) do |list|
|
1510
|
+
# list.text("This is some text.")
|
1511
|
+
# list.text("This is some other text.")
|
1512
|
+
# end
|
1513
|
+
|
1514
|
+
|
1515
|
+
# :nodoc:
|
1516
|
+
PROPERTIES = [
|
1426
1517
|
[:font, "raise HexaPDF::Error, 'No font set'"],
|
1518
|
+
[:font_bold, false],
|
1519
|
+
[:font_italic, false],
|
1427
1520
|
[:font_size, 10],
|
1428
1521
|
[:line_height, nil],
|
1429
1522
|
[:character_spacing, 0],
|
@@ -1457,8 +1550,8 @@ module HexaPDF
|
|
1457
1550
|
[:text_valign, :top, {valid_values: [:top, :center, :bottom]}],
|
1458
1551
|
[:text_indent, 0],
|
1459
1552
|
[:line_spacing, "LineSpacing.new(type: :single)",
|
1460
|
-
{setter: "LineSpacing.new(**(value.kind_of?(Symbol) || value.kind_of?(Numeric)
|
1461
|
-
"{type: value, value: extra_arg} : value))",
|
1553
|
+
{setter: "LineSpacing.new(**(value.kind_of?(Symbol) || value.kind_of?(Numeric) || " \
|
1554
|
+
"value.kind_of?(LineSpacing) ? {type: value, value: extra_arg} : value))",
|
1462
1555
|
extra_args: ", extra_arg = nil"}],
|
1463
1556
|
[:last_line_gap, false, {valid_values: [true, false]}],
|
1464
1557
|
[:fill_horizontal, nil],
|
@@ -1475,6 +1568,7 @@ module HexaPDF
|
|
1475
1568
|
[:mask_mode, :default, {valid_values: [:default, :none, :box, :fill_horizontal,
|
1476
1569
|
:fill_frame_horizontal, :fill_vertical, :fill]}],
|
1477
1570
|
[:overflow, :error],
|
1571
|
+
[:box_options, {}],
|
1478
1572
|
].each do |name, default, options = {}|
|
1479
1573
|
default = default.inspect unless default.kind_of?(String)
|
1480
1574
|
setter = options.delete(:setter) || "value"
|
@@ -1500,7 +1594,7 @@ module HexaPDF
|
|
1500
1594
|
end
|
1501
1595
|
EOF
|
1502
1596
|
alias_method("#{name}=", name)
|
1503
|
-
end
|
1597
|
+
end.each_with_object({}) {|arr, hash| hash[:"@#{arr.first}"] = arr.first }
|
1504
1598
|
|
1505
1599
|
##
|
1506
1600
|
# :method: text_segmentation_algorithm
|
data/lib/hexapdf/object.rb
CHANGED
@@ -305,8 +305,8 @@ module HexaPDF
|
|
305
305
|
result
|
306
306
|
rescue HexaPDF::Error
|
307
307
|
raise
|
308
|
-
rescue StandardError
|
309
|
-
yield("
|
308
|
+
rescue StandardError => e
|
309
|
+
yield("Unexpected error encountered: #{e.message}", false, self) if block_given?
|
310
310
|
false
|
311
311
|
end
|
312
312
|
|
data/lib/hexapdf/pdf_array.rb
CHANGED
@@ -143,10 +143,32 @@ module HexaPDF
|
|
143
143
|
# array.reject! {|item| block } -> array or nil
|
144
144
|
# array.reject! -> Enumerator
|
145
145
|
#
|
146
|
-
# Deletes all elements from the array for which the block returns +true
|
147
|
-
# done, returns +nil+.
|
146
|
+
# Deletes all elements from the array for which the block returns +true+ and returns +self+. If
|
147
|
+
# no changes were done, returns +nil+.
|
148
148
|
def reject!
|
149
|
-
|
149
|
+
return to_enum(__method__) unless block_given?
|
150
|
+
value.reject! {|item| yield(process_entry(item)) } && self
|
151
|
+
end
|
152
|
+
|
153
|
+
# :call-seq:
|
154
|
+
# array.map! {|item| block } -> array
|
155
|
+
# array.map! -> Enumerator
|
156
|
+
#
|
157
|
+
# Maps all elements from the array in-place to the respective return value of the block+ and
|
158
|
+
# returns +self+.
|
159
|
+
def map!
|
160
|
+
return to_enum(__method__) unless block_given?
|
161
|
+
value.map! {|item| yield(process_entry(item)) }
|
162
|
+
self
|
163
|
+
end
|
164
|
+
|
165
|
+
# :call-seq:
|
166
|
+
# array.compact! -> array or nil
|
167
|
+
#
|
168
|
+
# Removes all +nil+ elements from the array. Returns +self+ if any elements were removed, +nil+
|
169
|
+
# otherwise.
|
170
|
+
def compact!
|
171
|
+
value.compact! && self
|
150
172
|
end
|
151
173
|
|
152
174
|
# :call-seq:
|
data/lib/hexapdf/tokenizer.rb
CHANGED
@@ -278,6 +278,9 @@ module HexaPDF
|
|
278
278
|
|
279
279
|
REFERENCE_RE = /[#{WHITESPACE}]+([+]?\d+)[#{WHITESPACE}]+R#{WHITESPACE_OR_DELIMITER_RE}/ # :nodoc:
|
280
280
|
|
281
|
+
WHITESPACE_OR_DELIMITER_LUT = [] # :nodoc:
|
282
|
+
(WHITESPACE + DELIMITER).each_byte {|x| WHITESPACE_OR_DELIMITER_LUT[x] = true }
|
283
|
+
|
281
284
|
# Parses the number (integer or real) at the current position.
|
282
285
|
#
|
283
286
|
# See: PDF2.0 s7.3.3
|
@@ -285,7 +288,7 @@ module HexaPDF
|
|
285
288
|
prepare_string_scanner(40)
|
286
289
|
pos = self.pos
|
287
290
|
if (tmp = @ss.scan_integer)
|
288
|
-
if @ss.eos? || @ss.
|
291
|
+
if @ss.eos? || WHITESPACE_OR_DELIMITER_LUT[@ss.peek_byte]
|
289
292
|
# Handle object references, see PDF2.0 s7.3.10
|
290
293
|
prepare_string_scanner(10)
|
291
294
|
if @ss.scan(REFERENCE_RE)
|