prawnx 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/COPYING +2 -0
- data/GPLv2 +339 -0
- data/GPLv3 +674 -0
- data/LICENSE +65 -0
- data/LICENSE-prawn-svg +21 -0
- data/README.md +107 -0
- data/data/fonts/Courier-Bold.afm +342 -0
- data/data/fonts/Courier-BoldOblique.afm +342 -0
- data/data/fonts/Courier-Oblique.afm +342 -0
- data/data/fonts/Courier.afm +342 -0
- data/data/fonts/Helvetica-Bold.afm +2827 -0
- data/data/fonts/Helvetica-BoldOblique.afm +2827 -0
- data/data/fonts/Helvetica-Oblique.afm +3051 -0
- data/data/fonts/Helvetica.afm +3051 -0
- data/data/fonts/MustRead.html +19 -0
- data/data/fonts/Symbol.afm +213 -0
- data/data/fonts/Times-Bold.afm +2588 -0
- data/data/fonts/Times-BoldItalic.afm +2384 -0
- data/data/fonts/Times-Italic.afm +2667 -0
- data/data/fonts/Times-Roman.afm +2419 -0
- data/data/fonts/ZapfDingbats.afm +225 -0
- data/lib/pdf/core.rb +4 -0
- data/lib/prawn/measurement_extensions.rb +4 -0
- data/lib/prawn/svg/attributes/clip_path.rb +46 -0
- data/lib/prawn/svg/attributes/mask.rb +16 -0
- data/lib/prawn/svg/attributes/opacity.rb +30 -0
- data/lib/prawn/svg/attributes/space.rb +10 -0
- data/lib/prawn/svg/attributes/stroke.rb +50 -0
- data/lib/prawn/svg/attributes/transform.rb +10 -0
- data/lib/prawn/svg/attributes.rb +6 -0
- data/lib/prawn/svg/calculators/arc_to_bezier_curve.rb +57 -0
- data/lib/prawn/svg/calculators/aspect_ratio.rb +60 -0
- data/lib/prawn/svg/calculators/document_sizing.rb +103 -0
- data/lib/prawn/svg/calculators/path_length.rb +168 -0
- data/lib/prawn/svg/calculators/pixels.rb +57 -0
- data/lib/prawn/svg/color.rb +240 -0
- data/lib/prawn/svg/css/font_face_parser.rb +72 -0
- data/lib/prawn/svg/css/font_family_parser.rb +39 -0
- data/lib/prawn/svg/css/font_parser.rb +46 -0
- data/lib/prawn/svg/css/selector_parser.rb +178 -0
- data/lib/prawn/svg/css/stylesheets.rb +233 -0
- data/lib/prawn/svg/css/values_parser.rb +68 -0
- data/lib/prawn/svg/document.rb +170 -0
- data/lib/prawn/svg/elements/anchor.rb +10 -0
- data/lib/prawn/svg/elements/base.rb +312 -0
- data/lib/prawn/svg/elements/bbox_scaling.rb +78 -0
- data/lib/prawn/svg/elements/call_duplicator.rb +37 -0
- data/lib/prawn/svg/elements/circle.rb +25 -0
- data/lib/prawn/svg/elements/clip_path.rb +26 -0
- data/lib/prawn/svg/elements/container.rb +10 -0
- data/lib/prawn/svg/elements/direct_render_base.rb +27 -0
- data/lib/prawn/svg/elements/ellipse.rb +22 -0
- data/lib/prawn/svg/elements/gradient.rb +184 -0
- data/lib/prawn/svg/elements/ignored.rb +5 -0
- data/lib/prawn/svg/elements/image.rb +177 -0
- data/lib/prawn/svg/elements/line.rb +27 -0
- data/lib/prawn/svg/elements/marker.rb +72 -0
- data/lib/prawn/svg/elements/mask.rb +65 -0
- data/lib/prawn/svg/elements/path.rb +294 -0
- data/lib/prawn/svg/elements/pattern.rb +150 -0
- data/lib/prawn/svg/elements/polygon.rb +25 -0
- data/lib/prawn/svg/elements/polyline.rb +23 -0
- data/lib/prawn/svg/elements/rect.rb +64 -0
- data/lib/prawn/svg/elements/root.rb +22 -0
- data/lib/prawn/svg/elements/switch.rb +92 -0
- data/lib/prawn/svg/elements/text.rb +99 -0
- data/lib/prawn/svg/elements/text_component.rb +346 -0
- data/lib/prawn/svg/elements/text_node.rb +308 -0
- data/lib/prawn/svg/elements/text_path.rb +153 -0
- data/lib/prawn/svg/elements/use.rb +142 -0
- data/lib/prawn/svg/elements/viewport.rb +29 -0
- data/lib/prawn/svg/elements.rb +46 -0
- data/lib/prawn/svg/extension.rb +25 -0
- data/lib/prawn/svg/font.rb +20 -0
- data/lib/prawn/svg/font_metrics.rb +144 -0
- data/lib/prawn/svg/font_registry.rb +315 -0
- data/lib/prawn/svg/funciri.rb +14 -0
- data/lib/prawn/svg/gradient_renderer.rb +332 -0
- data/lib/prawn/svg/gradients.rb +53 -0
- data/lib/prawn/svg/interface.rb +72 -0
- data/lib/prawn/svg/length.rb +43 -0
- data/lib/prawn/svg/link_renderer.rb +40 -0
- data/lib/prawn/svg/loaders/data.rb +17 -0
- data/lib/prawn/svg/loaders/file.rb +111 -0
- data/lib/prawn/svg/loaders/web.rb +24 -0
- data/lib/prawn/svg/paint.rb +67 -0
- data/lib/prawn/svg/pathable.rb +153 -0
- data/lib/prawn/svg/pattern_renderer.rb +112 -0
- data/lib/prawn/svg/pdfmatrix.rb +37 -0
- data/lib/prawn/svg/percentage.rb +24 -0
- data/lib/prawn/svg/properties.rb +329 -0
- data/lib/prawn/svg/renderer.rb +249 -0
- data/lib/prawn/svg/state.rb +24 -0
- data/lib/prawn/svg/transform_parser.rb +78 -0
- data/lib/prawn/svg/ttc.rb +35 -0
- data/lib/prawn/svg/ttf.rb +95 -0
- data/lib/prawn/svg/url_loader.rb +46 -0
- data/lib/prawn/svg/version.rb +5 -0
- data/lib/prawn/table.rb +4 -0
- data/lib/prawn-svg.rb +53 -0
- data/lib/prawn.rb +10 -0
- data/lib/prawnx/annotations.rb +21 -0
- data/lib/prawnx/document/bounding_box.rb +193 -0
- data/lib/prawnx/document/column_box.rb +91 -0
- data/lib/prawnx/document/internals.rb +144 -0
- data/lib/prawnx/document/span.rb +25 -0
- data/lib/prawnx/document.rb +253 -0
- data/lib/prawnx/encoding.rb +29 -0
- data/lib/prawnx/errors.rb +37 -0
- data/lib/prawnx/font.rb +214 -0
- data/lib/prawnx/fonts/afm.rb +171 -0
- data/lib/prawnx/fonts/otf.rb +50 -0
- data/lib/prawnx/fonts/ttf.rb +410 -0
- data/lib/prawnx/graphic_state.rb +41 -0
- data/lib/prawnx/graphics/color.rb +148 -0
- data/lib/prawnx/graphics/patterns.rb +100 -0
- data/lib/prawnx/graphics/style.rb +171 -0
- data/lib/prawnx/graphics.rb +208 -0
- data/lib/prawnx/grid.rb +216 -0
- data/lib/prawnx/images/image.rb +244 -0
- data/lib/prawnx/images/png_decoder.rb +65 -0
- data/lib/prawnx/images.rb +115 -0
- data/lib/prawnx/measurement_extensions.rb +40 -0
- data/lib/prawnx/measurements.rb +62 -0
- data/lib/prawnx/outline.rb +146 -0
- data/lib/prawnx/page.rb +125 -0
- data/lib/prawnx/page_geometry.rb +27 -0
- data/lib/prawnx/pdf/outline_node.rb +53 -0
- data/lib/prawnx/pdf/reference.rb +31 -0
- data/lib/prawnx/pdf/writer.rb +105 -0
- data/lib/prawnx/pdf.rb +66 -0
- data/lib/prawnx/pdf_a/srgb_profile.rb +61 -0
- data/lib/prawnx/pdf_a.rb +130 -0
- data/lib/prawnx/pdf_core.rb +25 -0
- data/lib/prawnx/repeater.rb +79 -0
- data/lib/prawnx/security.rb +108 -0
- data/lib/prawnx/stamp.rb +74 -0
- data/lib/prawnx/table/cell/kinds.rb +246 -0
- data/lib/prawnx/table/cell.rb +331 -0
- data/lib/prawnx/table/cells.rb +208 -0
- data/lib/prawnx/table.rb +296 -0
- data/lib/prawnx/text/box.rb +16 -0
- data/lib/prawnx/text/formatted/arranger.rb +217 -0
- data/lib/prawnx/text/formatted/box.rb +312 -0
- data/lib/prawnx/text/formatted/fragment.rb +205 -0
- data/lib/prawnx/text/formatted/line_wrap.rb +204 -0
- data/lib/prawnx/text/formatted/parser.rb +149 -0
- data/lib/prawnx/text/formatted/wrap.rb +103 -0
- data/lib/prawnx/text.rb +281 -0
- data/lib/prawnx/transformation_stack.rb +43 -0
- data/lib/prawnx/true_type/cff_subset.rb +157 -0
- data/lib/prawnx/true_type/file.rb +371 -0
- data/lib/prawnx/true_type/open_type_layout.rb +189 -0
- data/lib/prawnx/true_type/simple_subsets.rb +94 -0
- data/lib/prawnx/true_type/subset.rb +187 -0
- data/lib/prawnx/version.rb +7 -0
- data/lib/prawnx/view.rb +28 -0
- data/lib/prawnx.rb +102 -0
- metadata +251 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
module Prawn::SVG
|
|
2
|
+
class Elements::Text < Elements::DirectRenderBase
|
|
3
|
+
Cursor = Struct.new(:x, :y)
|
|
4
|
+
|
|
5
|
+
def parse
|
|
6
|
+
@root_component = Elements::TextComponent.new(document, source, [], state.dup)
|
|
7
|
+
@root_component.process
|
|
8
|
+
|
|
9
|
+
reintroduce_trailing_and_leading_whitespace if @root_component.children
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def render(prawn, renderer)
|
|
13
|
+
return unless @root_component.children
|
|
14
|
+
|
|
15
|
+
origin_x = @root_component.x_values.first
|
|
16
|
+
origin_y = @root_component.y_values.first
|
|
17
|
+
|
|
18
|
+
@root_component.lay_out(prawn)
|
|
19
|
+
|
|
20
|
+
translate_x =
|
|
21
|
+
case @root_component.computed_properties.text_anchor
|
|
22
|
+
when 'middle'
|
|
23
|
+
-@root_component.calculated_width / 2.0
|
|
24
|
+
when 'end'
|
|
25
|
+
-@root_component.calculated_width
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
cursor = Cursor.new(0, document.sizing.output_height)
|
|
29
|
+
|
|
30
|
+
if vertical_writing_mode?
|
|
31
|
+
render_vertical(prawn, renderer, cursor, translate_x, origin_x, origin_y)
|
|
32
|
+
else
|
|
33
|
+
@root_component.render_component(prawn, renderer, cursor, translate_x)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def vertical_writing_mode?
|
|
40
|
+
wm = @root_component.computed_properties.writing_mode
|
|
41
|
+
['vertical-rl', 'vertical-lr'].include?(wm)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def render_vertical(prawn, renderer, cursor, translate_x, origin_x, origin_y)
|
|
45
|
+
origin_x ||= 0
|
|
46
|
+
origin_y ||= document.sizing.output_height
|
|
47
|
+
|
|
48
|
+
prawn.rotate(-90, origin: [origin_x, origin_y]) do
|
|
49
|
+
@root_component.render_component(prawn, renderer, cursor, translate_x)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def reintroduce_trailing_and_leading_whitespace
|
|
54
|
+
text_nodes = []
|
|
55
|
+
build_text_node_queue(text_nodes, @root_component)
|
|
56
|
+
|
|
57
|
+
remove_whitespace_only_text_nodes_and_start_and_end(text_nodes)
|
|
58
|
+
remove_text_nodes_that_are_completely_empty(text_nodes)
|
|
59
|
+
apportion_leading_and_trailing_spaces(text_nodes)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def build_text_node_queue(queue, component)
|
|
63
|
+
return unless component.children
|
|
64
|
+
|
|
65
|
+
component.children.each do |element|
|
|
66
|
+
case element
|
|
67
|
+
when Elements::TextNode
|
|
68
|
+
queue << element
|
|
69
|
+
else
|
|
70
|
+
build_text_node_queue(queue, element)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def remove_whitespace_only_text_nodes_and_start_and_end(text_nodes)
|
|
76
|
+
text_nodes.pop while text_nodes.last && text_nodes.last.text.empty?
|
|
77
|
+
text_nodes.shift while text_nodes.first && text_nodes.first.text.empty?
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def remove_text_nodes_that_are_completely_empty(text_nodes)
|
|
81
|
+
text_nodes.reject! do |text_node|
|
|
82
|
+
text_node.text.empty? && !text_node.trailing_space? && !text_node.leading_space?
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def apportion_leading_and_trailing_spaces(text_nodes)
|
|
87
|
+
text_nodes.each_cons(2) do |a, b|
|
|
88
|
+
if a.text.empty?
|
|
89
|
+
# Empty strings can only get a leading space from the previous non-empty text,
|
|
90
|
+
# and never get a trailing space
|
|
91
|
+
elsif a.trailing_space?
|
|
92
|
+
a.text += ' '
|
|
93
|
+
elsif b.leading_space?
|
|
94
|
+
b.text = " #{b.text}"
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
module Prawn::SVG
|
|
2
|
+
class Elements::TextComponent < Elements::DirectRenderBase
|
|
3
|
+
attr_reader :children, :parent_component
|
|
4
|
+
attr_reader :x_values, :y_values, :dx, :dy, :rotation, :text_length, :length_adjust
|
|
5
|
+
attr_reader :font, :fallback_fonts
|
|
6
|
+
attr_reader :inside_clip_path
|
|
7
|
+
|
|
8
|
+
def initialize(document, source, _calls, state, parent_component = nil)
|
|
9
|
+
if parent_component.nil? && source.name != 'text'
|
|
10
|
+
raise SkipElementError, 'attempted to <use> a component inside a text element, this is not supported'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
super(document, source, [], state)
|
|
14
|
+
@parent_component = parent_component
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def parse
|
|
18
|
+
@inside_clip_path = state.inside_clip_path
|
|
19
|
+
|
|
20
|
+
@x_values = parse_wsp('x').map { |n| x(n) }
|
|
21
|
+
@y_values = parse_wsp('y').map { |n| y(n) }
|
|
22
|
+
@dx = parse_wsp('dx').map { |n| x_pixels(n) }
|
|
23
|
+
@dy = parse_wsp('dy').map { |n| y_pixels(n) }
|
|
24
|
+
@rotation = parse_wsp('rotate').map(&:to_f)
|
|
25
|
+
@text_length = normalize_length(attributes['textLength'])
|
|
26
|
+
@length_adjust = attributes['lengthAdjust']
|
|
27
|
+
|
|
28
|
+
@font, @fallback_fonts = select_fonts
|
|
29
|
+
|
|
30
|
+
@children = svg_text_children.flat_map do |child|
|
|
31
|
+
if child.node_type == :text
|
|
32
|
+
build_text_node(child)
|
|
33
|
+
else
|
|
34
|
+
case child.name
|
|
35
|
+
when 'tspan', 'tref'
|
|
36
|
+
build_child(child)
|
|
37
|
+
when 'textPath'
|
|
38
|
+
build_text_path(child)
|
|
39
|
+
else
|
|
40
|
+
warnings << "Unknown tag '#{child.name}' inside text tag; ignoring"
|
|
41
|
+
[]
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def lay_out(prawn)
|
|
48
|
+
with_svg_fonts(prawn) do
|
|
49
|
+
@children.each do |child|
|
|
50
|
+
child.lay_out(prawn)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
if @text_length
|
|
55
|
+
flexible_width, fixed_width = total_flexible_and_fixed_width
|
|
56
|
+
|
|
57
|
+
if flexible_width.positive?
|
|
58
|
+
target_width = [@text_length - fixed_width, 0].max
|
|
59
|
+
factor = target_width / flexible_width
|
|
60
|
+
apply_factor_to_base_width(factor)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def render_component(prawn, renderer, cursor, translate_x = nil, accumulated_baseline_shift = 0)
|
|
66
|
+
raise SkipElementQuietly if computed_properties.display == 'none'
|
|
67
|
+
|
|
68
|
+
add_yield_call do
|
|
69
|
+
prawn.translate(translate_x, 0) if translate_x
|
|
70
|
+
|
|
71
|
+
size = computed_properties.numeric_font_size
|
|
72
|
+
|
|
73
|
+
y_offset = dominant_baseline_offset(prawn, size || prawn.font_size) || 0
|
|
74
|
+
|
|
75
|
+
with_svg_fonts(prawn) do
|
|
76
|
+
total_baseline_shift = accumulated_baseline_shift + baseline_shift_offset(prawn, size)
|
|
77
|
+
y_offset += total_baseline_shift
|
|
78
|
+
|
|
79
|
+
children.each do |child|
|
|
80
|
+
case child
|
|
81
|
+
when Elements::TextNode
|
|
82
|
+
child.render(prawn, size, cursor, y_offset)
|
|
83
|
+
when self.class
|
|
84
|
+
prawn.save_graphics_state
|
|
85
|
+
child.render_component(prawn, renderer, cursor, nil, total_baseline_shift)
|
|
86
|
+
prawn.restore_graphics_state
|
|
87
|
+
else
|
|
88
|
+
raise
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
renderer.render_calls(prawn, base_calls)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def calculated_width
|
|
98
|
+
children.reduce(0) { |total, child| total + child.calculated_width }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def current_length_adjust_is_scaling?
|
|
102
|
+
if @text_length
|
|
103
|
+
@length_adjust == 'spacingAndGlyphs'
|
|
104
|
+
elsif parent_component
|
|
105
|
+
parent_component.current_length_adjust_is_scaling?
|
|
106
|
+
else
|
|
107
|
+
false
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Resolves the effective kerning setting from the SVG 1.1 kerning property and the
|
|
112
|
+
# SVG 2 font-kerning property. kerning takes precedence when non-default; otherwise
|
|
113
|
+
# font-kerning is used.
|
|
114
|
+
def kerning_enabled?
|
|
115
|
+
if computed_properties.kerning == 'auto'
|
|
116
|
+
computed_properties.font_kerning != 'none'
|
|
117
|
+
else
|
|
118
|
+
false
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def kerning_pixels
|
|
123
|
+
if computed_properties.kerning == 'auto'
|
|
124
|
+
nil
|
|
125
|
+
else
|
|
126
|
+
x_pixels(computed_properties.kerning)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def letter_spacing_pixels
|
|
131
|
+
if computed_properties.letter_spacing == 'normal'
|
|
132
|
+
nil
|
|
133
|
+
else
|
|
134
|
+
x_pixels(computed_properties.letter_spacing)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def word_spacing_pixels
|
|
139
|
+
if computed_properties.word_spacing == 'normal'
|
|
140
|
+
nil
|
|
141
|
+
else
|
|
142
|
+
x_pixels(computed_properties.word_spacing)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
protected
|
|
147
|
+
|
|
148
|
+
def build_text_node(child)
|
|
149
|
+
if state.preserve_space
|
|
150
|
+
text = child.value.tr("\n\t", ' ')
|
|
151
|
+
else
|
|
152
|
+
text = child.value.tr("\n", '').tr("\t", ' ')
|
|
153
|
+
leading = text[0] == ' '
|
|
154
|
+
trailing = text[-1] == ' '
|
|
155
|
+
text = text.strip.gsub(/ {2,}/, ' ')
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
Elements::TextNode.new(self, text, leading, trailing)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def build_child(child)
|
|
162
|
+
component = self.class.new(document, child, [], state.dup, self)
|
|
163
|
+
component.process
|
|
164
|
+
component
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def build_text_path(child)
|
|
168
|
+
component = Elements::TextPath.new(document, child, [], state.dup, self)
|
|
169
|
+
component.process
|
|
170
|
+
return [] unless component.children
|
|
171
|
+
|
|
172
|
+
component
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def svg_text_children
|
|
176
|
+
text_children.select do |child|
|
|
177
|
+
child.node_type == :text || (
|
|
178
|
+
child.node_type == :element &&
|
|
179
|
+
[SVG_NAMESPACE, ''].include?(child.namespace)
|
|
180
|
+
|
|
181
|
+
)
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def text_children
|
|
186
|
+
if name == 'tref'
|
|
187
|
+
reference = find_referenced_element
|
|
188
|
+
reference ? reference.source.children : []
|
|
189
|
+
else
|
|
190
|
+
source.children
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def find_referenced_element
|
|
195
|
+
href = href_attribute
|
|
196
|
+
|
|
197
|
+
if href && href[0..0] == '#'
|
|
198
|
+
element = document.elements_by_id[href[1..]]
|
|
199
|
+
element if element.name == 'text'
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def select_fonts
|
|
204
|
+
font_style = :italic if computed_properties.font_style == 'italic'
|
|
205
|
+
font_weight = computed_properties.font_weight
|
|
206
|
+
font_stretch = computed_properties.font_stretch
|
|
207
|
+
fonts = []
|
|
208
|
+
|
|
209
|
+
font_family_names.each do |name|
|
|
210
|
+
font = document.font_registry.load(name, font_weight, font_style, font_stretch)
|
|
211
|
+
next unless font
|
|
212
|
+
next if fonts.any? { |existing| existing.name == font.name && existing.subfamily == font.subfamily }
|
|
213
|
+
|
|
214
|
+
fonts << font
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
_, *fallback_font_names = fonts.map(&:name).uniq
|
|
218
|
+
|
|
219
|
+
return [fonts.first, fallback_font_names] if fonts.any?
|
|
220
|
+
|
|
221
|
+
warnings << "Font family '#{computed_properties.font_family}' style '#{computed_properties.font_style}' is not a known font, and the fallback font could not be found."
|
|
222
|
+
[]
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def with_svg_fonts(prawn)
|
|
226
|
+
prawn.save_font do
|
|
227
|
+
if font
|
|
228
|
+
prawn.font(font.name, style: font.subfamily)
|
|
229
|
+
prawn.fallback_fonts(fallback_fonts)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
yield
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def font_family_names
|
|
237
|
+
names = CSS::FontFamilyParser.parse(computed_properties.font_family.to_s)
|
|
238
|
+
names << document.fallback_font_name if document.fallback_font_name
|
|
239
|
+
names.map(&:strip).reject(&:empty?)
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def total_flexible_and_fixed_width
|
|
243
|
+
flexible = fixed = 0
|
|
244
|
+
@children.each do |child|
|
|
245
|
+
child.total_flexible_and_fixed_width.tap do |a, b|
|
|
246
|
+
flexible += a
|
|
247
|
+
fixed += b
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
[flexible, fixed]
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def apply_factor_to_base_width(factor)
|
|
254
|
+
@children.each do |child|
|
|
255
|
+
if child.is_a?(Elements::TextNode)
|
|
256
|
+
child.chunks.reject(&:fixed_width).each do |chunk|
|
|
257
|
+
chunk.fixed_width = chunk.base_width * factor
|
|
258
|
+
end
|
|
259
|
+
elsif child.is_a?(self.class)
|
|
260
|
+
child.apply_factor_to_base_width(factor)
|
|
261
|
+
else
|
|
262
|
+
raise
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# overridden from Base, we don't want to call fill/stroke as draw_text does this for us
|
|
268
|
+
def apply_drawing_call; end
|
|
269
|
+
|
|
270
|
+
# overridden from Base, transforms can't be applied to tspan elements
|
|
271
|
+
def transformable?
|
|
272
|
+
source.name != 'tspan'
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# overridden from Base, we want the id to point to the Text element
|
|
276
|
+
def add_to_elements_by_id?
|
|
277
|
+
source.name != 'text'
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def dominant_baseline_offset(prawn, size)
|
|
281
|
+
baseline = effective_baseline
|
|
282
|
+
return nil if baseline.nil?
|
|
283
|
+
|
|
284
|
+
case baseline
|
|
285
|
+
when 'middle'
|
|
286
|
+
-FontMetrics.x_height_in_points(prawn, size) / 2.0
|
|
287
|
+
when 'mathematical'
|
|
288
|
+
-((prawn.font.ascender + prawn.font.descender) / prawn.font_size) * size / 2.0
|
|
289
|
+
when 'central'
|
|
290
|
+
-((prawn.font.ascender - prawn.font.descender) / prawn.font_size) * size / 2.0
|
|
291
|
+
when 'hanging'
|
|
292
|
+
-(prawn.font.ascender / prawn.font_size) * size
|
|
293
|
+
when 'text-before-edge', 'before-edge'
|
|
294
|
+
(-(prawn.font.ascender / prawn.font_size) * size) - half_leading(prawn, size)
|
|
295
|
+
when 'text-after-edge', 'after-edge', 'ideographic'
|
|
296
|
+
((prawn.font.descender / prawn.font_size) * size) + half_leading(prawn, size)
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def effective_baseline
|
|
301
|
+
ab = computed_properties.alignment_baseline
|
|
302
|
+
if ab && ab != 'auto' && ab != 'baseline'
|
|
303
|
+
ab
|
|
304
|
+
else
|
|
305
|
+
db = computed_properties.dominant_baseline
|
|
306
|
+
db unless ['auto', 'alphabetic'].include?(db)
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def baseline_shift_offset(prawn, font_size)
|
|
311
|
+
value = computed_properties.baseline_shift
|
|
312
|
+
return 0 if value.nil? || value == 'baseline'
|
|
313
|
+
|
|
314
|
+
case value
|
|
315
|
+
when 'super'
|
|
316
|
+
sub_super_offset(prawn, font_size)
|
|
317
|
+
when 'sub'
|
|
318
|
+
-sub_super_offset(prawn, font_size)
|
|
319
|
+
when Percentage
|
|
320
|
+
parent_font_size = parent_component&.computed_properties&.numeric_font_size || font_size
|
|
321
|
+
value.to_factor * parent_font_size
|
|
322
|
+
when Length
|
|
323
|
+
value.to_pixels(nil, font_size)
|
|
324
|
+
else
|
|
325
|
+
0
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def half_leading(prawn, size)
|
|
330
|
+
(size - ((prawn.font.ascender + prawn.font.descender) / prawn.font_size * size)) / 2.0
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def sub_super_offset(prawn, font_size)
|
|
334
|
+
ascender_ratio = prawn.font.ascender / prawn.font_size
|
|
335
|
+
ascender_ratio * font_size / 1.2
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
def normalize_length(length)
|
|
339
|
+
x_pixels(length) if length&.match(/\d/)
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def parse_wsp(name)
|
|
343
|
+
(attributes[name] || '').split(COMMA_WSP_REGEXP)
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
end
|