hexapdf 1.2.0 → 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 +45 -0
- data/README.md +1 -1
- data/lib/hexapdf/cli/inspect.rb +13 -4
- data/lib/hexapdf/composer.rb +14 -0
- data/lib/hexapdf/configuration.rb +5 -0
- data/lib/hexapdf/document/annotations.rb +60 -2
- data/lib/hexapdf/document/layout.rb +45 -6
- data/lib/hexapdf/error.rb +11 -3
- data/lib/hexapdf/font/true_type/subsetter.rb +15 -2
- 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/annotation.rb +12 -0
- data/lib/hexapdf/type/annotations/appearance_generator.rb +75 -0
- data/lib/hexapdf/type/annotations/border_effect.rb +99 -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 +4 -35
- 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 +50 -20
- data/lib/hexapdf/type/annotations.rb +5 -0
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/document/test_annotations.rb +22 -0
- data/test/hexapdf/document/test_layout.rb +24 -2
- data/test/hexapdf/font/true_type/test_subsetter.rb +10 -0
- data/test/hexapdf/layout/test_style.rb +27 -2
- data/test/hexapdf/test_composer.rb +7 -0
- data/test/hexapdf/test_object.rb +1 -1
- data/test/hexapdf/test_pdf_array.rb +36 -3
- 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/annotations/test_appearance_generator.rb +84 -0
- data/test/hexapdf/type/annotations/test_border_effect.rb +59 -0
- data/test/hexapdf/type/annotations/test_interior_color.rb +37 -0
- data/test/hexapdf/type/annotations/test_line.rb +0 -20
- data/test/hexapdf/type/annotations/test_widget.rb +35 -0
- metadata +9 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e0e469ae650b98b48c88b662dab27cb405b676c706639c8c1cf358d6aefc8f53
|
4
|
+
data.tar.gz: d170526233e1e9aa37403c1bd8d2aa38697641c1b4fb4d4a5f6d8f02f299585b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fd18408ed2c2474e3395bf59b3a0281cb23005d16bf7d5b9d93f673dfa131fc215f342018d0cdce2de053db85c000a68c28a76ad980b0432b1a159bd51b1ed0c
|
7
|
+
data.tar.gz: c58b13ed27c980bb9670b9521a12b1640fc3960aea1188f6135951cfd1503a28e7633cb3f6be8c63d478d64a88e0bbdc9b84bdbc982aa3ec000aeac1a8627597
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,48 @@
|
|
1
|
+
## 1.3.0 - 2025-04-23
|
2
|
+
|
3
|
+
### Added
|
4
|
+
|
5
|
+
* [HexaPDF::Type::Annotations::Square] for rectangle annotations as well as
|
6
|
+
[HexaPDF::Document::Annotations#create_rectangle]
|
7
|
+
* [HexaPDF::Type::Annotations::Circle] for ellipse annotations as well as
|
8
|
+
[HexaPDF::Document::Annotations#create_ellipse]
|
9
|
+
* Basic appearance generation for push button fields
|
10
|
+
* [HexaPDF::Type::Annotation::BorderEffect] type class
|
11
|
+
* [HexaPDF::Type::Annotations::BorderEffect] module that provides convenience
|
12
|
+
access to the border effect dictionary
|
13
|
+
* [HexaPDF::Document::Layout#style?] and [HexaPDF::Composer#style?] for checking
|
14
|
+
whether a given style (name) exists
|
15
|
+
* [HexaPDF::Layout::Style#each_property] for iterating over all set properties
|
16
|
+
* [HexaPDF::Layout::Style#merge] for merging another style instance
|
17
|
+
* [HexaPDF::Layout::Style#box_options] for specifying box initialization options
|
18
|
+
* [HexaPDF::Layout::Style#font_bold] and [HexaPDF::Layout::Style#font_italic]
|
19
|
+
for setting bold and/or italic variants independently of the font name
|
20
|
+
* [HexaPDF::PDFArray#map!] for mapping elements in-place
|
21
|
+
* [HexaPDF::PDFArray#compact!] for removing `nil` elements
|
22
|
+
|
23
|
+
### Changed
|
24
|
+
|
25
|
+
* **Breaking change**: [HexaPDF::Type::Annotations::Widget::MarkerStyle::new]
|
26
|
+
got a new positional argument
|
27
|
+
* [HexaPDF::Type::Annotations::Widget#marker_style] to allow setting and
|
28
|
+
retrieving the font for push buttons
|
29
|
+
* Extracted `#interior_color` from [HexaPDF::Type::Annotations::Line] into
|
30
|
+
[HexaPDF::Type::Annotations::InteriorColor]
|
31
|
+
* CLI command `hexapdf inspect` to support decoding Form XObject streams
|
32
|
+
* [HexaPDF::Layout::Style#line_spacing] to accept a `LineSpacing` object when
|
33
|
+
setting the value
|
34
|
+
|
35
|
+
### Fixed
|
36
|
+
|
37
|
+
* Text extraction with macOS Preview due a bug in Preview
|
38
|
+
* [HexaPDF::PDFArray#reject!] to work according to documented method signature
|
39
|
+
* [HexaPDF::Type::AcroForm::Field#create_widget] to ensure the proper type
|
40
|
+
class is stored in the document in case an embedded widget is extracted
|
41
|
+
* [HexaPDF::Type::AcroForm::Form] validation to ensure that all field objects in
|
42
|
+
the field hierarchy are using a field type class
|
43
|
+
* [HexaPDF::Type::AcroForm::Form] validation to delete merged fields
|
44
|
+
|
45
|
+
|
1
46
|
## 1.2.0 - 2025-02-10
|
2
47
|
|
3
48
|
### Added
|
data/README.md
CHANGED
@@ -82,7 +82,7 @@ canvas.text("Hello World!", at: [20, 400])
|
|
82
82
|
doc.write("hello-world.pdf")
|
83
83
|
~~~
|
84
84
|
|
85
|
-
For detailed information have a look at the [HexaPDF website][website] where you will the API
|
85
|
+
For detailed information have a look at the [HexaPDF website][website] where you will find the API
|
86
86
|
documentation, example code and more.
|
87
87
|
|
88
88
|
It is recommend to use the HTML API documentation provided by the HexaPDF website as it is enhanced
|
data/lib/hexapdf/cli/inspect.rb
CHANGED
@@ -188,12 +188,20 @@ module HexaPDF
|
|
188
188
|
end
|
189
189
|
serialize(obj.value, recursive: true) if obj
|
190
190
|
|
191
|
-
when 's', 'stream', 'raw', 'raw-stream'
|
191
|
+
when 's', 'stream', 'raw', 'raw-stream', 'sd'
|
192
192
|
if (obj = pdf_object_from_string_reference(data.shift) rescue $stderr.puts($!.message)) &&
|
193
193
|
obj.kind_of?(HexaPDF::Stream)
|
194
|
-
|
195
|
-
|
196
|
-
|
194
|
+
if command == 'sd'
|
195
|
+
if obj.respond_to?(:process_contents)
|
196
|
+
obj.process_contents(ContentProcessor.new)
|
197
|
+
else
|
198
|
+
$stderr.puts("Error: The object is not a Form XObject or page")
|
199
|
+
end
|
200
|
+
else
|
201
|
+
source = (command.start_with?('raw') ? obj.stream_source : obj.stream_decoder)
|
202
|
+
while source.alive? && (stream_data = source.resume)
|
203
|
+
$stdout.write(stream_data)
|
204
|
+
end
|
197
205
|
end
|
198
206
|
elsif command_parser.verbosity_info?
|
199
207
|
$stderr.puts("Note: Object has no stream data")
|
@@ -427,6 +435,7 @@ module HexaPDF
|
|
427
435
|
["OID[,GEN] | o[bject] OID[,GEN]", "Print object"],
|
428
436
|
["r[ecursive] OID[,GEN]", "Print object recursively"],
|
429
437
|
["s[tream] OID[,GEN]", "Print filtered stream"],
|
438
|
+
["sd OID[,GEN]", "Print the decoded stream of a Form XObject or page"],
|
430
439
|
["raw[-stream] OID[,GEN]", "Print raw stream"],
|
431
440
|
["rev[ision] [NUMBER]", "Print or extract revision"],
|
432
441
|
["x[ref] OID[,GEN]", "Print the cross-reference entry"],
|
data/lib/hexapdf/composer.rb
CHANGED
@@ -261,6 +261,20 @@ module HexaPDF
|
|
261
261
|
@document.layout.style(name, base: base, **properties)
|
262
262
|
end
|
263
263
|
|
264
|
+
# Returns +true+ if a style with the given +name+ exists, else +false+.
|
265
|
+
#
|
266
|
+
# See HexaPDF::Document::Layout#style for details; this method is just a thin wrapper around
|
267
|
+
# that method.
|
268
|
+
#
|
269
|
+
# Example:
|
270
|
+
#
|
271
|
+
# composer.style(:header, font: 'Helvetica')
|
272
|
+
# composer.style?(:header) # => true
|
273
|
+
# composer.style?(:paragraph) # => false
|
274
|
+
def style?(name)
|
275
|
+
@document.layout.style?(name)
|
276
|
+
end
|
277
|
+
|
264
278
|
# :call-seq:
|
265
279
|
# composer.styles -> styles
|
266
280
|
# composer.styles(**mapping) -> styles
|
@@ -709,6 +709,7 @@ module HexaPDF
|
|
709
709
|
XXAcroFormField: 'HexaPDF::Type::AcroForm::Field',
|
710
710
|
XXAppearanceDictionary: 'HexaPDF::Type::Annotation::AppearanceDictionary',
|
711
711
|
Border: 'HexaPDF::Type::Annotation::Border',
|
712
|
+
XXBorderEffect: 'HexaPDF::Type::Annotation::BorderEffect',
|
712
713
|
SigFieldLock: 'HexaPDF::Type::AcroForm::SignatureField::LockDictionary',
|
713
714
|
SV: 'HexaPDF::Type::AcroForm::SignatureField::SeedValueDictionary',
|
714
715
|
SVCert: 'HexaPDF::Type::AcroForm::SignatureField::CertificateSeedValueDictionary',
|
@@ -766,6 +767,8 @@ module HexaPDF
|
|
766
767
|
Link: 'HexaPDF::Type::Annotations::Link',
|
767
768
|
Widget: 'HexaPDF::Type::Annotations::Widget',
|
768
769
|
Line: 'HexaPDF::Type::Annotations::Line',
|
770
|
+
Square: 'HexaPDF::Type::Annotations::Square',
|
771
|
+
Circle: 'HexaPDF::Type::Annotations::Circle',
|
769
772
|
XML: 'HexaPDF::Type::Metadata',
|
770
773
|
GTS_PDFX: 'HexaPDF::Type::OutputIntent',
|
771
774
|
GTS_PDFA1: 'HexaPDF::Type::OutputIntent',
|
@@ -795,6 +798,8 @@ module HexaPDF
|
|
795
798
|
Link: 'HexaPDF::Type::Annotations::Link',
|
796
799
|
Widget: 'HexaPDF::Type::Annotations::Widget',
|
797
800
|
Line: 'HexaPDF::Type::Annotations::Line',
|
801
|
+
Square: 'HexaPDF::Type::Annotations::Square',
|
802
|
+
Circle: 'HexaPDF::Type::Annotations::Circle',
|
798
803
|
},
|
799
804
|
XXAcroFormField: {
|
800
805
|
Tx: 'HexaPDF::Type::AcroForm::TextField',
|
@@ -69,12 +69,12 @@ module HexaPDF
|
|
69
69
|
# +create_type+ method.
|
70
70
|
#
|
71
71
|
# The +options+ are passed on the specific annotation creation method.
|
72
|
-
def create(type, page, **options)
|
72
|
+
def create(type, page, *args, **options)
|
73
73
|
method_name = "create_#{type}"
|
74
74
|
unless respond_to?(method_name)
|
75
75
|
raise ArgumentError, "Invalid type specified"
|
76
76
|
end
|
77
|
-
send("create_#{type}", page, **options)
|
77
|
+
send("create_#{type}", page, *args, **options)
|
78
78
|
end
|
79
79
|
|
80
80
|
# :call-seq:
|
@@ -100,6 +100,64 @@ module HexaPDF
|
|
100
100
|
border_style(color: 0, width: 1)
|
101
101
|
end
|
102
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
|
+
|
103
161
|
private
|
104
162
|
|
105
163
|
# Returns the root of the destinations name tree.
|
@@ -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/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)
|