hexapdf 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -0
  3. data/CONTRIBUTERS +1 -1
  4. data/README.md +5 -5
  5. data/VERSION +1 -1
  6. data/examples/emoji-smile.png +0 -0
  7. data/examples/emoji-wink.png +0 -0
  8. data/examples/graphics.rb +9 -8
  9. data/examples/standard_pdf_fonts.rb +2 -1
  10. data/examples/text_box_alignment.rb +47 -0
  11. data/examples/text_box_inline_boxes.rb +56 -0
  12. data/examples/text_box_line_wrapping.rb +57 -0
  13. data/examples/text_box_shapes.rb +166 -0
  14. data/examples/text_box_styling.rb +72 -0
  15. data/examples/truetype.rb +3 -4
  16. data/lib/hexapdf/cli/optimize.rb +2 -2
  17. data/lib/hexapdf/configuration.rb +8 -6
  18. data/lib/hexapdf/content/canvas.rb +8 -5
  19. data/lib/hexapdf/content/parser.rb +3 -2
  20. data/lib/hexapdf/content/processor.rb +14 -3
  21. data/lib/hexapdf/document.rb +1 -0
  22. data/lib/hexapdf/document/fonts.rb +2 -1
  23. data/lib/hexapdf/document/pages.rb +23 -0
  24. data/lib/hexapdf/font/invalid_glyph.rb +78 -0
  25. data/lib/hexapdf/font/true_type/font.rb +14 -3
  26. data/lib/hexapdf/font/true_type/table.rb +1 -0
  27. data/lib/hexapdf/font/true_type/table/cmap.rb +1 -1
  28. data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +1 -0
  29. data/lib/hexapdf/font/true_type/table/glyf.rb +4 -0
  30. data/lib/hexapdf/font/true_type/table/kern.rb +170 -0
  31. data/lib/hexapdf/font/true_type/table/post.rb +5 -1
  32. data/lib/hexapdf/font/true_type_wrapper.rb +71 -24
  33. data/lib/hexapdf/font/type1/afm_parser.rb +3 -2
  34. data/lib/hexapdf/font/type1/character_metrics.rb +0 -9
  35. data/lib/hexapdf/font/type1/font.rb +11 -0
  36. data/lib/hexapdf/font/type1/font_metrics.rb +6 -1
  37. data/lib/hexapdf/font/type1_wrapper.rb +51 -7
  38. data/lib/hexapdf/font_loader/standard14.rb +1 -1
  39. data/lib/hexapdf/layout.rb +51 -0
  40. data/lib/hexapdf/layout/inline_box.rb +95 -0
  41. data/lib/hexapdf/layout/line_fragment.rb +333 -0
  42. data/lib/hexapdf/layout/numeric_refinements.rb +56 -0
  43. data/lib/hexapdf/layout/style.rb +365 -0
  44. data/lib/hexapdf/layout/text_box.rb +727 -0
  45. data/lib/hexapdf/layout/text_fragment.rb +206 -0
  46. data/lib/hexapdf/layout/text_shaper.rb +155 -0
  47. data/lib/hexapdf/task.rb +0 -1
  48. data/lib/hexapdf/task/dereference.rb +1 -1
  49. data/lib/hexapdf/tokenizer.rb +3 -2
  50. data/lib/hexapdf/type/font_descriptor.rb +2 -1
  51. data/lib/hexapdf/type/font_type0.rb +3 -1
  52. data/lib/hexapdf/type/form.rb +12 -4
  53. data/lib/hexapdf/version.rb +1 -1
  54. data/test/hexapdf/common_tokenizer_tests.rb +7 -0
  55. data/test/hexapdf/content/common.rb +8 -0
  56. data/test/hexapdf/content/test_canvas.rb +10 -22
  57. data/test/hexapdf/content/test_processor.rb +4 -1
  58. data/test/hexapdf/document/test_pages.rb +16 -0
  59. data/test/hexapdf/font/test_invalid_glyph.rb +34 -0
  60. data/test/hexapdf/font/test_true_type_wrapper.rb +25 -11
  61. data/test/hexapdf/font/test_type1_wrapper.rb +26 -10
  62. data/test/hexapdf/font/true_type/table/common.rb +27 -0
  63. data/test/hexapdf/font/true_type/table/test_cmap.rb +14 -20
  64. data/test/hexapdf/font/true_type/table/test_cmap_subtable.rb +7 -0
  65. data/test/hexapdf/font/true_type/table/test_glyf.rb +8 -6
  66. data/test/hexapdf/font/true_type/table/test_head.rb +9 -13
  67. data/test/hexapdf/font/true_type/table/test_hhea.rb +16 -23
  68. data/test/hexapdf/font/true_type/table/test_hmtx.rb +4 -7
  69. data/test/hexapdf/font/true_type/table/test_kern.rb +61 -0
  70. data/test/hexapdf/font/true_type/table/test_loca.rb +7 -13
  71. data/test/hexapdf/font/true_type/table/test_maxp.rb +4 -9
  72. data/test/hexapdf/font/true_type/table/test_name.rb +14 -17
  73. data/test/hexapdf/font/true_type/table/test_os2.rb +3 -5
  74. data/test/hexapdf/font/true_type/table/test_post.rb +21 -19
  75. data/test/hexapdf/font/true_type/test_font.rb +4 -0
  76. data/test/hexapdf/font/type1/common.rb +6 -0
  77. data/test/hexapdf/font/type1/test_afm_parser.rb +9 -0
  78. data/test/hexapdf/font/type1/test_font.rb +6 -0
  79. data/test/hexapdf/layout/test_inline_box.rb +40 -0
  80. data/test/hexapdf/layout/test_line_fragment.rb +206 -0
  81. data/test/hexapdf/layout/test_style.rb +143 -0
  82. data/test/hexapdf/layout/test_text_box.rb +640 -0
  83. data/test/hexapdf/layout/test_text_fragment.rb +208 -0
  84. data/test/hexapdf/layout/test_text_shaper.rb +64 -0
  85. data/test/hexapdf/task/test_dereference.rb +1 -0
  86. data/test/hexapdf/test_writer.rb +2 -2
  87. data/test/hexapdf/type/test_font_descriptor.rb +4 -2
  88. data/test/hexapdf/type/test_font_type0.rb +7 -0
  89. data/test/hexapdf/type/test_form.rb +12 -0
  90. metadata +29 -2
@@ -93,4 +93,8 @@ describe HexaPDF::Font::TrueType::Font do
93
93
  it "is able to return the ID of the missing glyph" do
94
94
  assert_equal(0, @font.missing_glyph_id)
95
95
  end
96
+
97
+ it "returns the features available for a font" do
98
+ assert(@font.features.empty?)
99
+ end
96
100
  end
@@ -0,0 +1,6 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'hexapdf/font/type1'
4
+
5
+ FONT_TIMES = HexaPDF::Font::Type1::Font.from_afm(File.join(HexaPDF.data_dir, 'afm', "Times-Roman.afm"))
6
+ FONT_SYMBOL = HexaPDF::Font::Type1::Font.from_afm(File.join(HexaPDF.data_dir, 'afm', "Symbol.afm"))
@@ -1,6 +1,7 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
 
3
3
  require 'test_helper'
4
+ require_relative 'common'
4
5
  require 'hexapdf/font/type1'
5
6
  require 'hexapdf/data_dir'
6
7
  require 'tempfile'
@@ -38,6 +39,14 @@ describe HexaPDF::Font::Type1::AFMParser do
38
39
  end
39
40
  end
40
41
 
42
+ it "extracts kerning and ligature information" do
43
+ metrics = FONT_TIMES.metrics
44
+ glyph = metrics.character_metrics[:f]
45
+ assert_equal([20, 0, 383, 683], glyph.bbox)
46
+ assert_equal(-20, metrics.kerning_pairs.dig(:f, :i))
47
+ assert_equal(:fi, metrics.ligature_pairs.dig(:f, :i))
48
+ end
49
+
41
50
  it "calculates an ascender and descender value from the font bounding box if necessary" do
42
51
  metrics = HexaPDF::Font::Type1::AFMParser.parse(File.join(HexaPDF.data_dir, 'afm/Symbol.afm'))
43
52
  assert_equal(metrics.bounding_box[1], metrics.descender)
@@ -1,6 +1,7 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
 
3
3
  require 'test_helper'
4
+ require_relative 'common'
4
5
  require 'hexapdf/font/type1'
5
6
 
6
7
  describe HexaPDF::Font::Type1::Font do
@@ -65,4 +66,9 @@ describe HexaPDF::Font::Type1::Font do
65
66
  it "is able to return the ID of the missing glyph" do
66
67
  assert_equal(:'.notdef', @font.missing_glyph_id)
67
68
  end
69
+
70
+ it "returns the features available for a font" do
71
+ assert_equal([:kern, :liga].to_set, FONT_TIMES.features)
72
+ assert(FONT_SYMBOL.features.empty?)
73
+ end
68
74
  end
@@ -0,0 +1,40 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/layout/inline_box'
5
+
6
+ describe HexaPDF::Layout::InlineBox do
7
+ before do
8
+ @box = HexaPDF::Layout::InlineBox.new(10, 15) {|box, canvas| [box, canvas]}
9
+ end
10
+
11
+ describe "draw" do
12
+ before do
13
+ @canvas = Object.new
14
+ @canvas.define_singleton_method(:translate) {|x, y, &block| [x, y, block.call] }
15
+ end
16
+
17
+ it "returns the value of the drawing block" do
18
+ assert_equal([1, 2, [@box, @canvas]], @box.draw(@canvas, 1, 2))
19
+ end
20
+ end
21
+
22
+ describe "valign" do
23
+ it "has a default value of :baseline" do
24
+ assert_equal(:baseline, @box.valign)
25
+ end
26
+
27
+ it "can be changed on creation" do
28
+ box = HexaPDF::Layout::InlineBox.new(10, 15, valign: :test) {}
29
+ assert_equal(:test, box.valign)
30
+ end
31
+ end
32
+
33
+ it "returns 0 for x_min" do
34
+ assert_equal(0, @box.x_min)
35
+ end
36
+
37
+ it "returns width for x_max" do
38
+ assert_equal(10, @box.x_max)
39
+ end
40
+ end
@@ -0,0 +1,206 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/document'
5
+
6
+ describe HexaPDF::Layout::LineFragment::HeightCalculator do
7
+ before do
8
+ @calc = HexaPDF::Layout::LineFragment::HeightCalculator.new
9
+ end
10
+
11
+ it "simulate the height as if an item was added" do
12
+ @calc << HexaPDF::Layout::InlineBox.new(10, 20, valign: :baseline) {}
13
+ assert_equal([0, 20, 0, 0], @calc.result)
14
+ new_item = HexaPDF::Layout::InlineBox.new(10, 30, valign: :top) {}
15
+ assert_equal(30, @calc.simulate_height(new_item))
16
+ assert_equal([0, 20, 0, 0], @calc.result)
17
+ end
18
+ end
19
+
20
+ describe HexaPDF::Layout::LineFragment do
21
+ before do
22
+ @doc = HexaPDF::Document.new
23
+ @font = @doc.fonts.load("Times", custom_encoding: true)
24
+ @line = HexaPDF::Layout::LineFragment.new
25
+ end
26
+
27
+ def setup_fragment(text)
28
+ HexaPDF::Layout::TextFragment.create(text, font: @font, font_size: 10)
29
+ end
30
+
31
+ def setup_box(width, height, valign = :baseline)
32
+ HexaPDF::Layout::InlineBox.new(width, height, valign: valign) {}
33
+ end
34
+
35
+ describe "initialize" do
36
+ it "allows setting the items of the line fragment" do
37
+ frag1 = setup_fragment("Hello")
38
+ frag2 = HexaPDF::Layout::TextFragment.new(items: frag1.items.slice!(3, 2), style: frag1.style)
39
+ line = HexaPDF::Layout::LineFragment.new([frag1, frag2])
40
+ assert_equal(1, line.items.count)
41
+ assert_equal(5, line.items[0].items.count)
42
+ end
43
+ end
44
+
45
+ describe "add" do
46
+ it "adds items to the line" do
47
+ @line << :test << :other
48
+ assert_equal([:test, :other], @line.items)
49
+ end
50
+
51
+ it "combines text fragments if possible" do
52
+ frag1 = setup_fragment("Home")
53
+ frag2 = HexaPDF::Layout::TextFragment.new(items: frag1.items.slice!(2, 2), style: frag1.style)
54
+ @line << setup_fragment("o") << :other << frag1 << frag2
55
+ assert_equal(3, @line.items.length)
56
+ assert_equal(4, @line.items.last.items.length)
57
+ end
58
+
59
+ it "duplicates the first of two combinable text fragments if its items are frozen" do
60
+ frag1 = setup_fragment("Home")
61
+ frag2 = HexaPDF::Layout::TextFragment.new(items: frag1.items.slice!(2, 2), style: frag1.style)
62
+ frag1.items.freeze
63
+ frag2.items.freeze
64
+
65
+ @line << setup_fragment("o") << frag1 << frag2 << :other
66
+ assert_equal(3, @line.items.length)
67
+ assert_equal(4, @line.items[-2].items.length)
68
+ end
69
+ end
70
+
71
+ describe "with text fragments" do
72
+ before do
73
+ @frag_h = setup_fragment("H")
74
+ @frag_y = setup_fragment("y")
75
+ @line << @frag_h << @frag_y << @frag_h
76
+ end
77
+
78
+ it "calculates the various x/y values correctly" do
79
+ assert_equal(@frag_h.x_min, @line.x_min)
80
+ assert_equal(@frag_h.width + @frag_y.width + @frag_h.x_max, @line.x_max)
81
+ assert_equal(@frag_y.y_min, @line.y_min)
82
+ assert_equal(@frag_h.y_max, @line.y_max)
83
+ assert_equal(@frag_y.y_min, @line.text_y_min)
84
+ assert_equal(@frag_h.y_max, @line.text_y_max)
85
+ assert_equal(2 * @frag_h.width + @frag_y.width, @line.width)
86
+ assert_equal(@frag_h.y_max - @frag_y.y_min, @line.height)
87
+ end
88
+
89
+ describe "and with inline boxes" do
90
+ it "x_min is correct if an inline box is the first item" do
91
+ @line.items.unshift(setup_box(10, 10))
92
+ assert_equal(0, @line.x_min)
93
+ end
94
+
95
+ it "x_max is correct if an inline box is the last item" do
96
+ @line << setup_box(10, 10)
97
+ assert_equal(@line.width, @line.x_max)
98
+ end
99
+
100
+ it "doesn't change text_y_min/text_y_max" do
101
+ text_y_min, text_y_max = @line.text_y_min, @line.text_y_max
102
+ @line << setup_box(10, 30, :text_top) << setup_box(10, 30, :text_bottom)
103
+ @line.clear_cache
104
+ assert_equal(text_y_min, @line.text_y_min)
105
+ assert_equal(text_y_max, @line.text_y_max)
106
+ end
107
+
108
+ it "y values are not changed if all boxes are smaller than the text's height" do
109
+ *y_values = @line.y_min, @line.y_max, @line.text_y_min, @line.text_y_max
110
+ @line << setup_box(10, 5, :baseline)
111
+ @line.clear_cache
112
+ assert_equal(y_values, [@line.y_min, @line.y_max, @line.text_y_min, @line.text_y_max])
113
+ end
114
+
115
+ it "changes y_max to fit if baseline boxes are higher than the text" do
116
+ y_min = @line.y_min
117
+ box = setup_box(10, 50, :baseline)
118
+ @line.add(box)
119
+
120
+ @line.clear_cache
121
+ assert_equal(50, @line.y_max)
122
+ assert_equal(y_min, @line.y_min)
123
+ end
124
+
125
+ it "changes y_max to fit if text_bottom boxes are higher than the text" do
126
+ y_min = @line.y_min
127
+ box = setup_box(10, 50, :text_bottom)
128
+ @line.add(box)
129
+
130
+ @line.clear_cache
131
+ assert_equal(50 + @line.text_y_min, @line.y_max)
132
+ assert_equal(y_min, @line.y_min)
133
+ end
134
+
135
+ it "changes y_max to fit if bottom boxes are higher than the text" do
136
+ y_min = @line.y_min
137
+ box = setup_box(10, 50, :bottom)
138
+ @line.add(box)
139
+
140
+ @line.clear_cache
141
+ assert_equal(50 + @line.text_y_min, @line.y_max)
142
+ assert_equal(y_min, @line.y_min)
143
+ end
144
+
145
+ it "changes y_min to fit if text_top/top boxes are higher than the text" do
146
+ y_max = @line.y_max
147
+ box = setup_box(10, 50, :text_top)
148
+ @line.add(box)
149
+
150
+ @line.clear_cache
151
+ assert_equal(@line.text_y_max - 50, @line.y_min)
152
+ assert_equal(y_max, @line.y_max)
153
+
154
+ box.instance_variable_set(:@valign, :top)
155
+ @line.clear_cache
156
+ assert_equal(@line.text_y_max - 50, @line.y_min)
157
+ assert_equal(y_max, @line.y_max)
158
+ end
159
+
160
+ it "changes y_min/y_max to fit if boxes are aligned in both directions" do
161
+ @line << setup_box(10, 20, :text_top) <<
162
+ setup_box(10, 20, :text_bottom) <<
163
+ setup_box(10, 20, :top) <<
164
+ setup_box(10, 70, :bottom)
165
+ assert_equal(@line.text_y_max - 20, @line.y_min)
166
+ assert_equal(@line.text_y_max - 20 + 70, @line.y_max)
167
+ end
168
+ end
169
+ end
170
+
171
+ it "fails when accessing a vertical measurement if an item uses an invalid valign value" do
172
+ @line << setup_box(10, 20, :invalid)
173
+ assert_raises(HexaPDF::Error) { @line.y_min }
174
+ end
175
+
176
+ describe "each" do
177
+ it "iterates over all items and yields them with their offset values" do
178
+ @line << setup_fragment("H") <<
179
+ setup_box(10, 10, :top) <<
180
+ setup_box(10, 10, :text_top) <<
181
+ setup_box(10, 10, :baseline) <<
182
+ setup_box(10, 10, :text_bottom) <<
183
+ setup_box(10, 10, :bottom)
184
+ result = [
185
+ [@line.items[0], 0, 0],
186
+ [@line.items[1], @line.items[0].width, @line.y_max - 10],
187
+ [@line.items[2], @line.items[0].width + 10, @line.text_y_max - 10],
188
+ [@line.items[3], @line.items[0].width + 20, 0],
189
+ [@line.items[4], @line.items[0].width + 30, @line.text_y_min],
190
+ [@line.items[5], @line.items[0].width + 40, @line.y_min],
191
+ ]
192
+ assert_equal(result, @line.to_enum(:each).map {|*a| a})
193
+ end
194
+
195
+ it "fails if an item uses an invalid valign value" do
196
+ @line << setup_box(10, 10, :invalid)
197
+ assert_raises(HexaPDF::Error) { @line.each {} }
198
+ end
199
+ end
200
+
201
+ it "allows ignoring line justification" do
202
+ refute(@line.ignore_justification?)
203
+ @line.ignore_justification!
204
+ assert(@line.ignore_justification?)
205
+ end
206
+ end
@@ -0,0 +1,143 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/layout/style'
5
+ require 'hexapdf/layout/text_box'
6
+
7
+ describe HexaPDF::Layout::Style::LineSpacing do
8
+ before do
9
+ @line1 = Object.new
10
+ @line1.define_singleton_method(:y_min) { - 1}
11
+ @line1.define_singleton_method(:y_max) { 2 }
12
+ @line2 = Object.new
13
+ @line2.define_singleton_method(:y_min) { -3 }
14
+ @line2.define_singleton_method(:y_max) { 4 }
15
+ end
16
+
17
+ def line_spacing(type, value = nil)
18
+ HexaPDF::Layout::Style::LineSpacing.new(type, value: value)
19
+ end
20
+
21
+ it "allows single line spacing" do
22
+ obj = line_spacing(:single)
23
+ assert_equal(:proportional, obj.type)
24
+ assert_equal(1, obj.value)
25
+ assert_equal(1 + 4, obj.baseline_distance(@line1, @line2))
26
+ assert_equal(0, obj.gap(@line1, @line2))
27
+ end
28
+
29
+ it "allows double line spacing" do
30
+ obj = line_spacing(:double)
31
+ assert_equal(:proportional, obj.type)
32
+ assert_equal(2, obj.value)
33
+ assert_equal((1 + 4) * 2, obj.baseline_distance(@line1, @line2))
34
+ assert_equal(1 + 4, obj.gap(@line1, @line2))
35
+ end
36
+
37
+ it "allows proportional line spacing" do
38
+ obj = line_spacing(:proportional, 1.5)
39
+ assert_equal(:proportional, obj.type)
40
+ assert_equal(1.5, obj.value)
41
+ assert_equal((1 + 4) * 1.5, obj.baseline_distance(@line1, @line2))
42
+ assert_equal((1 + 4) * 0.5, obj.gap(@line1, @line2))
43
+ end
44
+
45
+ it "allows fixed line spacing" do
46
+ obj = line_spacing(:fixed, 7)
47
+ assert_equal(:fixed, obj.type)
48
+ assert_equal(7, obj.value)
49
+ assert_equal(7, obj.baseline_distance(@line1, @line2))
50
+ assert_equal(7 - 1 - 4, obj.gap(@line1, @line2))
51
+ end
52
+
53
+ it "allows line spacing using a leading value" do
54
+ obj = line_spacing(:leading, 3)
55
+ assert_equal(:leading, obj.type)
56
+ assert_equal(3, obj.value)
57
+ assert_equal(1 + 4 + 3, obj.baseline_distance(@line1, @line2))
58
+ assert_equal(3, obj.gap(@line1, @line2))
59
+ end
60
+
61
+ it "allows using a LineSpacing object as type" do
62
+ obj = line_spacing(line_spacing(:single))
63
+ assert_equal(:proportional, obj.type)
64
+ end
65
+
66
+ it "raises an error if a value is needed and none is provided" do
67
+ assert_raises(ArgumentError) { line_spacing(:proportional) }
68
+ end
69
+
70
+ it "raises an error if an invalid type is provided" do
71
+ assert_raises(ArgumentError) { line_spacing(:invalid) }
72
+ end
73
+ end
74
+
75
+ describe HexaPDF::Layout::Style do
76
+ before do
77
+ @style = HexaPDF::Layout::Style.new
78
+ end
79
+
80
+ it "can assign values on initialization" do
81
+ style = HexaPDF::Layout::Style.new(font_size: 10)
82
+ assert_equal(10, style.font_size)
83
+ end
84
+
85
+ it "has several dynamically generated properties with default values" do
86
+ assert_raises(HexaPDF::Error) { @style.font }
87
+ assert_equal(10, @style.font_size)
88
+ assert_equal(0, @style.character_spacing)
89
+ assert_equal(0, @style.word_spacing)
90
+ assert_equal(100, @style.horizontal_scaling)
91
+ assert_equal(0, @style.text_rise)
92
+ assert_equal({}, @style.font_features)
93
+ assert_equal(:left, @style.align)
94
+ assert_equal(:top, @style.valign)
95
+ end
96
+
97
+ it "can set and retrieve line spacing objects" do
98
+ assert_equal([:proportional, 1], [@style.line_spacing.type, @style.line_spacing.value])
99
+ @style.line_spacing = :double
100
+ assert_equal([:proportional, 2], [@style.line_spacing.type, @style.line_spacing.value])
101
+ end
102
+
103
+ it "can set and retrieve text segmentation algorithms" do
104
+ assert_equal(HexaPDF::Layout::TextBox::SimpleTextSegmentation,
105
+ @style.text_segmentation_algorithm)
106
+ block = proc { :y }
107
+ @style.text_segmentation_algorithm(&block)
108
+ assert_equal(block, @style.text_segmentation_algorithm)
109
+ end
110
+
111
+ it "can set and retrieve line wrapping algorithms" do
112
+ assert_equal(HexaPDF::Layout::TextBox::SimpleLineWrapping,
113
+ @style.text_line_wrapping_algorithm)
114
+ @style.text_line_wrapping_algorithm(:callable)
115
+ assert_equal(:callable, @style.text_line_wrapping_algorithm)
116
+ end
117
+
118
+ it "has methods for some derived and cached values" do
119
+ assert_equal(0.01, @style.scaled_font_size)
120
+ assert_equal(0, @style.scaled_character_spacing)
121
+ assert_equal(0, @style.scaled_word_spacing)
122
+ assert_equal(1, @style.scaled_horizontal_scaling)
123
+
124
+ wrapped_font = Object.new
125
+ wrapped_font.define_singleton_method(:ascender) { 600 }
126
+ wrapped_font.define_singleton_method(:descender) { -100 }
127
+ font = Object.new
128
+ font.define_singleton_method(:scaling_factor) { 1 }
129
+ font.define_singleton_method(:wrapped_font) { wrapped_font }
130
+ @style.font = font
131
+
132
+ assert_equal(6, @style.scaled_font_ascender)
133
+ assert_equal(-1, @style.scaled_font_descender)
134
+ end
135
+
136
+ it "can clear cached values" do
137
+ assert_equal(0.01, @style.scaled_font_size)
138
+ @style.font_size = 20
139
+ assert_equal(0.01, @style.scaled_font_size)
140
+ @style.clear_cache
141
+ assert_equal(0.02, @style.scaled_font_size)
142
+ end
143
+ end