hexapdf 0.4.0 → 0.5.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 +46 -0
- data/CONTRIBUTERS +1 -1
- data/README.md +5 -5
- data/VERSION +1 -1
- data/examples/emoji-smile.png +0 -0
- data/examples/emoji-wink.png +0 -0
- data/examples/graphics.rb +9 -8
- data/examples/standard_pdf_fonts.rb +2 -1
- data/examples/text_box_alignment.rb +47 -0
- data/examples/text_box_inline_boxes.rb +56 -0
- data/examples/text_box_line_wrapping.rb +57 -0
- data/examples/text_box_shapes.rb +166 -0
- data/examples/text_box_styling.rb +72 -0
- data/examples/truetype.rb +3 -4
- data/lib/hexapdf/cli/optimize.rb +2 -2
- data/lib/hexapdf/configuration.rb +8 -6
- data/lib/hexapdf/content/canvas.rb +8 -5
- data/lib/hexapdf/content/parser.rb +3 -2
- data/lib/hexapdf/content/processor.rb +14 -3
- data/lib/hexapdf/document.rb +1 -0
- data/lib/hexapdf/document/fonts.rb +2 -1
- data/lib/hexapdf/document/pages.rb +23 -0
- data/lib/hexapdf/font/invalid_glyph.rb +78 -0
- data/lib/hexapdf/font/true_type/font.rb +14 -3
- data/lib/hexapdf/font/true_type/table.rb +1 -0
- data/lib/hexapdf/font/true_type/table/cmap.rb +1 -1
- data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +1 -0
- data/lib/hexapdf/font/true_type/table/glyf.rb +4 -0
- data/lib/hexapdf/font/true_type/table/kern.rb +170 -0
- data/lib/hexapdf/font/true_type/table/post.rb +5 -1
- data/lib/hexapdf/font/true_type_wrapper.rb +71 -24
- data/lib/hexapdf/font/type1/afm_parser.rb +3 -2
- data/lib/hexapdf/font/type1/character_metrics.rb +0 -9
- data/lib/hexapdf/font/type1/font.rb +11 -0
- data/lib/hexapdf/font/type1/font_metrics.rb +6 -1
- data/lib/hexapdf/font/type1_wrapper.rb +51 -7
- data/lib/hexapdf/font_loader/standard14.rb +1 -1
- data/lib/hexapdf/layout.rb +51 -0
- data/lib/hexapdf/layout/inline_box.rb +95 -0
- data/lib/hexapdf/layout/line_fragment.rb +333 -0
- data/lib/hexapdf/layout/numeric_refinements.rb +56 -0
- data/lib/hexapdf/layout/style.rb +365 -0
- data/lib/hexapdf/layout/text_box.rb +727 -0
- data/lib/hexapdf/layout/text_fragment.rb +206 -0
- data/lib/hexapdf/layout/text_shaper.rb +155 -0
- data/lib/hexapdf/task.rb +0 -1
- data/lib/hexapdf/task/dereference.rb +1 -1
- data/lib/hexapdf/tokenizer.rb +3 -2
- data/lib/hexapdf/type/font_descriptor.rb +2 -1
- data/lib/hexapdf/type/font_type0.rb +3 -1
- data/lib/hexapdf/type/form.rb +12 -4
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/common_tokenizer_tests.rb +7 -0
- data/test/hexapdf/content/common.rb +8 -0
- data/test/hexapdf/content/test_canvas.rb +10 -22
- data/test/hexapdf/content/test_processor.rb +4 -1
- data/test/hexapdf/document/test_pages.rb +16 -0
- data/test/hexapdf/font/test_invalid_glyph.rb +34 -0
- data/test/hexapdf/font/test_true_type_wrapper.rb +25 -11
- data/test/hexapdf/font/test_type1_wrapper.rb +26 -10
- data/test/hexapdf/font/true_type/table/common.rb +27 -0
- data/test/hexapdf/font/true_type/table/test_cmap.rb +14 -20
- data/test/hexapdf/font/true_type/table/test_cmap_subtable.rb +7 -0
- data/test/hexapdf/font/true_type/table/test_glyf.rb +8 -6
- data/test/hexapdf/font/true_type/table/test_head.rb +9 -13
- data/test/hexapdf/font/true_type/table/test_hhea.rb +16 -23
- data/test/hexapdf/font/true_type/table/test_hmtx.rb +4 -7
- data/test/hexapdf/font/true_type/table/test_kern.rb +61 -0
- data/test/hexapdf/font/true_type/table/test_loca.rb +7 -13
- data/test/hexapdf/font/true_type/table/test_maxp.rb +4 -9
- data/test/hexapdf/font/true_type/table/test_name.rb +14 -17
- data/test/hexapdf/font/true_type/table/test_os2.rb +3 -5
- data/test/hexapdf/font/true_type/table/test_post.rb +21 -19
- data/test/hexapdf/font/true_type/test_font.rb +4 -0
- data/test/hexapdf/font/type1/common.rb +6 -0
- data/test/hexapdf/font/type1/test_afm_parser.rb +9 -0
- data/test/hexapdf/font/type1/test_font.rb +6 -0
- data/test/hexapdf/layout/test_inline_box.rb +40 -0
- data/test/hexapdf/layout/test_line_fragment.rb +206 -0
- data/test/hexapdf/layout/test_style.rb +143 -0
- data/test/hexapdf/layout/test_text_box.rb +640 -0
- data/test/hexapdf/layout/test_text_fragment.rb +208 -0
- data/test/hexapdf/layout/test_text_shaper.rb +64 -0
- data/test/hexapdf/task/test_dereference.rb +1 -0
- data/test/hexapdf/test_writer.rb +2 -2
- data/test/hexapdf/type/test_font_descriptor.rb +4 -2
- data/test/hexapdf/type/test_font_type0.rb +7 -0
- data/test/hexapdf/type/test_form.rb +12 -0
- metadata +29 -2
|
@@ -88,7 +88,7 @@ module HexaPDF
|
|
|
88
88
|
return nil if name.nil?
|
|
89
89
|
|
|
90
90
|
file = File.join(HexaPDF.data_dir, 'afm', "#{name}.afm")
|
|
91
|
-
font = HexaPDF::Font::Type1::Font.from_afm(file)
|
|
91
|
+
font = (@afm_font_cache ||= {})[file] ||= HexaPDF::Font::Type1::Font.from_afm(file)
|
|
92
92
|
HexaPDF::Font::Type1Wrapper.new(document, font, custom_encoding: custom_encoding)
|
|
93
93
|
end
|
|
94
94
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
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-2017 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
|
+
|
|
34
|
+
module HexaPDF
|
|
35
|
+
|
|
36
|
+
# == Overview
|
|
37
|
+
#
|
|
38
|
+
# The Layout module contains advanced text and layouting facilities that are built on top of the
|
|
39
|
+
# standard PDF functionality provided by the Content module.
|
|
40
|
+
module Layout
|
|
41
|
+
|
|
42
|
+
autoload(:Style, 'hexapdf/layout/style')
|
|
43
|
+
autoload(:TextFragment, 'hexapdf/layout/text_fragment')
|
|
44
|
+
autoload(:InlineBox, 'hexapdf/layout/inline_box')
|
|
45
|
+
autoload(:LineFragment, 'hexapdf/layout/line_fragment')
|
|
46
|
+
autoload(:TextShaper, 'hexapdf/layout/text_shaper')
|
|
47
|
+
autoload(:TextBox, 'hexapdf/layout/text_box')
|
|
48
|
+
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
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-2017 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
|
+
|
|
34
|
+
module HexaPDF
|
|
35
|
+
module Layout
|
|
36
|
+
|
|
37
|
+
# An InlineBox can be used as an item for a LineFragment so that inline graphics are possible.
|
|
38
|
+
# The box *must* have a fixed size!
|
|
39
|
+
class InlineBox
|
|
40
|
+
|
|
41
|
+
# The width of the box.
|
|
42
|
+
attr_reader :width
|
|
43
|
+
|
|
44
|
+
# The height of the box.
|
|
45
|
+
attr_reader :height
|
|
46
|
+
|
|
47
|
+
# The vertical alignment of the box.
|
|
48
|
+
#
|
|
49
|
+
# Can be any supported value except :text - see LineFragment for all possible values.
|
|
50
|
+
attr_reader :valign
|
|
51
|
+
|
|
52
|
+
# :call-seq:
|
|
53
|
+
# InlineBox.new(width, height, valign: :baseline) {|box, canvas| block} -> inline_box
|
|
54
|
+
#
|
|
55
|
+
# Creates a new InlineBox object that uses the provided block when it is asked to draw itself
|
|
56
|
+
# on a canvas (see #draw).
|
|
57
|
+
#
|
|
58
|
+
# Since the final location of the box is not known beforehand, the drawing operations inside
|
|
59
|
+
# the block should draw inside the rectangle (0, 0, width, height).
|
|
60
|
+
#
|
|
61
|
+
# The +valign+ argument can be used to specify the vertical alignment of the box relative to
|
|
62
|
+
# other items in the LineFragment - see #valign and LineFragment.
|
|
63
|
+
def initialize(width, height, valign: :baseline, &block)
|
|
64
|
+
@width = width
|
|
65
|
+
@height = height
|
|
66
|
+
@valign = valign
|
|
67
|
+
@draw_block = block
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# :call-seq:
|
|
71
|
+
# box.draw(canvas, x, y) -> block_result
|
|
72
|
+
#
|
|
73
|
+
# Draws the contents of the box onto the canvas at the position (x, y), and returns the result
|
|
74
|
+
# of the drawing block (see #initialize).
|
|
75
|
+
#
|
|
76
|
+
# The coordinate system is translated so that the origin is at (x, y) during the drawing
|
|
77
|
+
# operations.
|
|
78
|
+
def draw(canvas, x, y)
|
|
79
|
+
canvas.translate(x, y) { @draw_block.call(self, canvas) }
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# The minimum x-coordinate which is always 0.
|
|
83
|
+
def x_min
|
|
84
|
+
0
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# The maximum x-coordinate which is equivalent to the width of the box.
|
|
88
|
+
def x_max
|
|
89
|
+
width
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
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-2017 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
|
+
|
|
34
|
+
require 'hexapdf/error'
|
|
35
|
+
require 'hexapdf/layout/text_fragment'
|
|
36
|
+
|
|
37
|
+
module HexaPDF
|
|
38
|
+
module Layout
|
|
39
|
+
|
|
40
|
+
# A LineFragment describes a line of text and can contain TextFragment objects or InlineBox
|
|
41
|
+
# objects.
|
|
42
|
+
#
|
|
43
|
+
# The items of a line fragment are aligned along the x-axis which coincides with the text
|
|
44
|
+
# baseline. The vertical alignment is determined by the value of the #valign method:
|
|
45
|
+
#
|
|
46
|
+
# :text_top::
|
|
47
|
+
# Align the top of the box with the top of the text of the LineFragment.
|
|
48
|
+
#
|
|
49
|
+
# :text_bottom::
|
|
50
|
+
# Align the bottom of the box with the bottom of the text of the LineFragment.
|
|
51
|
+
#
|
|
52
|
+
# :baseline::
|
|
53
|
+
# Align the bottom of the box with the baseline of the LineFragment.
|
|
54
|
+
#
|
|
55
|
+
# :top::
|
|
56
|
+
# Align the top of the box with the top of the LineFragment.
|
|
57
|
+
#
|
|
58
|
+
# :bottom::
|
|
59
|
+
# Align the bottom of the box with the bottom of the LineFragment.
|
|
60
|
+
#
|
|
61
|
+
# :text::
|
|
62
|
+
# This is a special alignment value for text fragment objects. The text fragment is aligned
|
|
63
|
+
# on the baseline and its minimum and maximum y-coordinates are used when calculating the
|
|
64
|
+
# line's #text_y_min and #text_y_max.
|
|
65
|
+
#
|
|
66
|
+
# This value may be used by other objects if they should be handled similar to text
|
|
67
|
+
# fragments, e.g. graphical representation of characters (think: emoji fonts).
|
|
68
|
+
#
|
|
69
|
+
# == Item Requirements
|
|
70
|
+
#
|
|
71
|
+
# Each item of a line fragment has to respond to the following methods:
|
|
72
|
+
#
|
|
73
|
+
# #x_min:: The minimum x-coordinate of the item.
|
|
74
|
+
# #x_max:: The maximum x-coordinate of the item.
|
|
75
|
+
# #width:: The width of the item.
|
|
76
|
+
# #valign:: The vertical alignment of the item (see above).
|
|
77
|
+
# #draw(canvas, x, y):: Should draw the item onto the canvas at the position (x, y).
|
|
78
|
+
#
|
|
79
|
+
# If an item has a vertical alignment of :text, it additionally has to respond to the following
|
|
80
|
+
# methods:
|
|
81
|
+
#
|
|
82
|
+
# #y_min:: The minimum y-coordinate of the item.
|
|
83
|
+
# #y_max:: The maximum y-coordinate of the item.
|
|
84
|
+
#
|
|
85
|
+
# Otherwise (i.e. a vertical alignment different from :text), the following method must be
|
|
86
|
+
# implemented:
|
|
87
|
+
#
|
|
88
|
+
# #height:: The height of the item.
|
|
89
|
+
class LineFragment
|
|
90
|
+
|
|
91
|
+
# Helper class for calculating the needed vertical dimensions of a line.
|
|
92
|
+
class HeightCalculator
|
|
93
|
+
|
|
94
|
+
# Creates a new calculator with the given initial items.
|
|
95
|
+
def initialize(items = [])
|
|
96
|
+
reset
|
|
97
|
+
items.each {|item| add(item)}
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Adds a new item to be considered when calculating the various dimensions.
|
|
101
|
+
def add(item)
|
|
102
|
+
case item.valign
|
|
103
|
+
when :text
|
|
104
|
+
@text_y_min = item.y_min if item.y_min < @text_y_min
|
|
105
|
+
@text_y_max = item.y_max if item.y_max > @text_y_max
|
|
106
|
+
when :baseline
|
|
107
|
+
@max_base_height = item.height if @max_base_height < item.height
|
|
108
|
+
when :top
|
|
109
|
+
@max_top_height = item.height if @max_top_height < item.height
|
|
110
|
+
when :text_top
|
|
111
|
+
@max_text_top_height = item.height if @max_text_top_height < item.height
|
|
112
|
+
when :bottom
|
|
113
|
+
@max_bottom_height = item.height if @max_bottom_height < item.height
|
|
114
|
+
when :text_bottom
|
|
115
|
+
@max_text_bottom_height = item.height if @max_text_bottom_height < item.height
|
|
116
|
+
else
|
|
117
|
+
raise HexaPDF::Error, "Unknown inline box alignment #{item.valign}"
|
|
118
|
+
end
|
|
119
|
+
self
|
|
120
|
+
end
|
|
121
|
+
alias_method :<<, :add
|
|
122
|
+
|
|
123
|
+
# Returns the result of the calculations, the array [y_min, y_max, text_y_min, text_y_max].
|
|
124
|
+
#
|
|
125
|
+
# See LineFragment for their meaning.
|
|
126
|
+
def result
|
|
127
|
+
y_min = [@text_y_max - @max_text_top_height, @text_y_min].min
|
|
128
|
+
y_max = [@text_y_min + @max_text_bottom_height, @max_base_height, @text_y_max].max
|
|
129
|
+
y_min = [y_max - @max_top_height, y_min].min
|
|
130
|
+
y_max = [y_min + @max_bottom_height, y_max].max
|
|
131
|
+
|
|
132
|
+
[y_min, y_max, @text_y_min, @text_y_max]
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Resets the calculation.
|
|
136
|
+
def reset
|
|
137
|
+
@text_y_min = 0
|
|
138
|
+
@text_y_max = 0
|
|
139
|
+
@max_base_height = 0
|
|
140
|
+
@max_top_height = 0
|
|
141
|
+
@max_text_top_height = 0
|
|
142
|
+
@max_bottom_height = 0
|
|
143
|
+
@max_text_bottom_height = 0
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Returns the height of the line as if +item+ was part of it but doesn't change the internal
|
|
147
|
+
# state.
|
|
148
|
+
def simulate_height(item)
|
|
149
|
+
text_y_min = @text_y_min
|
|
150
|
+
text_y_max = @text_y_max
|
|
151
|
+
max_base_height = @max_base_height
|
|
152
|
+
max_top_height = @max_top_height
|
|
153
|
+
max_text_top_height = @max_text_top_height
|
|
154
|
+
max_bottom_height = @max_bottom_height
|
|
155
|
+
max_text_bottom_height = @max_text_bottom_height
|
|
156
|
+
y_min, y_max, = add(item).result
|
|
157
|
+
y_max - y_min
|
|
158
|
+
ensure
|
|
159
|
+
@text_y_min = text_y_min
|
|
160
|
+
@text_y_max = text_y_max
|
|
161
|
+
@max_base_height = max_base_height
|
|
162
|
+
@max_top_height = max_top_height
|
|
163
|
+
@max_text_top_height = max_text_top_height
|
|
164
|
+
@max_bottom_height = max_bottom_height
|
|
165
|
+
@max_text_bottom_height = max_text_bottom_height
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# The items: TextFragment and InlineBox objects
|
|
171
|
+
attr_accessor :items
|
|
172
|
+
|
|
173
|
+
# An optional horizontal offset that should be taken into account when positioning the line.
|
|
174
|
+
attr_accessor :x_offset
|
|
175
|
+
|
|
176
|
+
# An optional vertical offset that should be taken into account when positioning the line.
|
|
177
|
+
#
|
|
178
|
+
# For the first line in a paragraph this describes the offset from the top of the box to the
|
|
179
|
+
# top of the line. For all other lines it describes the offset from the previous baseline to
|
|
180
|
+
# the baseline of this line.
|
|
181
|
+
attr_accessor :y_offset
|
|
182
|
+
|
|
183
|
+
# Creates a new LineFragment object, adding all given items to it.
|
|
184
|
+
def initialize(items = [])
|
|
185
|
+
@items = []
|
|
186
|
+
items.each {|i| add(i)}
|
|
187
|
+
@x_offset = 0
|
|
188
|
+
@y_offset = 0
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Adds the given item at the end of the item list.
|
|
192
|
+
#
|
|
193
|
+
# If both the item and the last item in the item list are TextFragment objects and they have
|
|
194
|
+
# the same style, they are combined.
|
|
195
|
+
#
|
|
196
|
+
# Note: The cache is not cleared!
|
|
197
|
+
def add(item)
|
|
198
|
+
last = @items.last
|
|
199
|
+
if last.class == item.class && item.kind_of?(TextFragment) && last.style == item.style
|
|
200
|
+
if last.items.frozen?
|
|
201
|
+
@items[-1] = last = last.dup
|
|
202
|
+
last.items = last.items.dup
|
|
203
|
+
end
|
|
204
|
+
last.items[last.items.length, 0] = item.items
|
|
205
|
+
last.clear_cache
|
|
206
|
+
else
|
|
207
|
+
@items << item
|
|
208
|
+
end
|
|
209
|
+
self
|
|
210
|
+
end
|
|
211
|
+
alias :<< :add
|
|
212
|
+
|
|
213
|
+
# :call-seq:
|
|
214
|
+
# line_fragment.each {|item, x, y| block }
|
|
215
|
+
#
|
|
216
|
+
# Yields each item together with its horizontal offset from 0 and vertical offset from the
|
|
217
|
+
# baseline.
|
|
218
|
+
def each
|
|
219
|
+
x = 0
|
|
220
|
+
@items.each do |item|
|
|
221
|
+
y = case item.valign
|
|
222
|
+
when :text, :baseline then 0
|
|
223
|
+
when :top then y_max - item.height
|
|
224
|
+
when :text_top then text_y_max - item.height
|
|
225
|
+
when :text_bottom then text_y_min
|
|
226
|
+
when :bottom then y_min
|
|
227
|
+
else
|
|
228
|
+
raise HexaPDF::Error, "Unknown inline box alignment #{item.valign}"
|
|
229
|
+
end
|
|
230
|
+
yield(item, x, y)
|
|
231
|
+
x += item.width
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# The minimum x-coordinate of the whole line.
|
|
236
|
+
def x_min
|
|
237
|
+
@items[0].x_min
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# The maximum x-coordinate of the whole line.
|
|
241
|
+
def x_max
|
|
242
|
+
@x_max ||= width + (items[-1].x_max - items[-1].width)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# The minimum y-coordinate of any item of the line.
|
|
246
|
+
#
|
|
247
|
+
# It is always lower than or equal to zero.
|
|
248
|
+
def y_min
|
|
249
|
+
@y_min ||= calculate_y_dimensions[0]
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# The minimum y-coordinate of any TextFragment item of the line.
|
|
253
|
+
def text_y_min
|
|
254
|
+
@text_y_min ||= calculate_y_dimensions[2]
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# The maximum y-coordinate of any item of the line.
|
|
258
|
+
#
|
|
259
|
+
# It is always greater than or equal to zero.
|
|
260
|
+
def y_max
|
|
261
|
+
@y_max ||= calculate_y_dimensions[1]
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# The maximum y-coordinate of any TextFragment item of the line.
|
|
265
|
+
def text_y_max
|
|
266
|
+
@text_y_max ||= calculate_y_dimensions[3]
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# The width of the line fragment.
|
|
270
|
+
def width
|
|
271
|
+
@width ||= @items.sum(&:width)
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# The height of the line fragment.
|
|
275
|
+
def height
|
|
276
|
+
y_max - y_min
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# Specifies that this line should not be justified if line justification is used.
|
|
280
|
+
def ignore_justification!
|
|
281
|
+
@ignore_justification = true
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# Returns +true+ if justification should be ignored for this line.
|
|
285
|
+
def ignore_justification?
|
|
286
|
+
defined?(@ignore_justification) && @ignore_justification
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# :call-seq:
|
|
290
|
+
# line_fragment.clear_cache -> line_fragment
|
|
291
|
+
#
|
|
292
|
+
# Clears all cached values.
|
|
293
|
+
#
|
|
294
|
+
# This method needs to be called if the fragment's items are changed!
|
|
295
|
+
def clear_cache
|
|
296
|
+
@x_max = @y_min = @y_max = @text_y_min = @text_y_max = @width = nil
|
|
297
|
+
self
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
private
|
|
301
|
+
|
|
302
|
+
# :call-seq:
|
|
303
|
+
# line_fragment.calculate_y_dimensions -> [y_min, y_max, text_y_min, text_y_max]
|
|
304
|
+
#
|
|
305
|
+
# Calculates all y-values and returns them as array.
|
|
306
|
+
#
|
|
307
|
+
# The following algorithm is used for the calculations:
|
|
308
|
+
#
|
|
309
|
+
# 1. Calculate #text_y_min and #text_y_max by using only the items with valign :text.
|
|
310
|
+
#
|
|
311
|
+
# 2. Calculate the temporary #y_min by using either the maximum height of all items with
|
|
312
|
+
# valign :text_top subtraced from #text_y_max, or #text_y_min, whichever is smaller.
|
|
313
|
+
#
|
|
314
|
+
# For the temporary #y_max, use either the maximum height of all items with valign equal to
|
|
315
|
+
# :text_bottom added to #text_y_min, or the maximum height of all items with valign
|
|
316
|
+
# :baseline, or #text_y_max, whichever is larger.
|
|
317
|
+
#
|
|
318
|
+
# 3. Calculate the final #y_min by using either the maximum height of all items with valign
|
|
319
|
+
# :top subtracted from the temporary #y_min, or the temporary #y_min, whichever is smaller.
|
|
320
|
+
#
|
|
321
|
+
# Calculate the final #y_max by using either the maximum height of all items with valign
|
|
322
|
+
# :bottom added to #y_min, or the temporary #y_max, whichever is larger.
|
|
323
|
+
#
|
|
324
|
+
# In certain cases there is no unique solution to the values of #y_min and #y_max, for
|
|
325
|
+
# example, it depends on the order of the calculations in part 3.
|
|
326
|
+
def calculate_y_dimensions
|
|
327
|
+
@y_min, @y_max, @text_y_min, @text_y_max = HeightCalculator.new(@items).result
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
end
|
|
333
|
+
end
|