arrolio 0.1.0 → 0.1.2
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/.rubocop.yml +2 -2
- data/Rakefile +15 -0
- data/TODO.complete/56-generic-adapter-mece-decomposition.md +20 -0
- data/TODO.complete/60-adapter-fidelity-footnotes-notes-examples.md +92 -55
- data/TODO.complete/70-ttf-font-metrics-parity.md +68 -0
- data/TODO.complete/71-cover-layout-parity.md +45 -0
- data/TODO.complete/72-table-layout-parity.md +49 -0
- data/TODO.complete/73-toc-leaders-parity.md +55 -0
- data/TODO.complete/74-svg-image-rendering-parity.md +76 -0
- data/TODO.complete/75-mathml-formula-rendering-parity.md +52 -0
- data/TODO.complete/76-odd-even-page-templates-parity.md +50 -0
- data/TODO.complete/77-text-formatting-parity.md +55 -0
- data/TODO.complete/78-bibliography-formatting-parity.md +62 -0
- data/TODO.complete/79-line-breaking-parity.md +80 -0
- data/TODO.complete/80-parity-diff-harness-in-ci.md +68 -0
- data/TODO.complete/81-conditional-style-refinements.md +70 -0
- data/TODO.complete/82-semantic-presentation-dedup.md +65 -0
- data/TODO.complete/83-nested-list-support.md +74 -0
- data/TODO.complete/84-figure-svg-rendering.md +70 -0
- data/TODO.complete/85-locality-reference-formatting.md +48 -0
- data/TODO.complete/86-list-item-text-overflow.md +80 -0
- data/TODO.complete/87-odd-even-page-templates.md +55 -0
- data/TODO.complete/88-hyphenation-support.md +52 -0
- data/TODO.complete/89-table-rowspan.md +49 -0
- data/TODO.complete/90-mathml-stacked-layout.md +65 -0
- data/TODO.complete/91-parity-diff-diagnostics.md +50 -0
- data/TODO.complete/92-table-cell-height-parity.md +66 -0
- data/TODO.complete/93-figure-text-searchability.md +49 -0
- data/TODO.complete/94-fo-space-resolution.md +63 -0
- data/TODO.complete/95-title-header-placement.md +55 -0
- data/TODO.complete/96-pagination-drift-parity.md +517 -0
- data/TODO.complete/README.md +58 -38
- data/lib/arrolio/config_driven_pipeline.rb +45 -1
- data/lib/arrolio/content/document.rb +4 -3
- data/lib/arrolio/content/section.rb +3 -0
- data/lib/arrolio/content/table.rb +25 -14
- data/lib/arrolio/embedded_block_extractor.rb +54 -0
- data/lib/arrolio/engine/paged.rb +109 -28
- data/lib/arrolio/flavor/manifest.rb +0 -1
- data/lib/arrolio/flavor/registry.rb +1 -1
- data/lib/arrolio/flowable.rb +21 -0
- data/lib/arrolio/flowables/figure_flowable.rb +17 -0
- data/lib/arrolio/flowables/footnote_marker_flowable.rb +32 -13
- data/lib/arrolio/flowables/group_flowable.rb +57 -0
- data/lib/arrolio/flowables/list_flowable.rb +46 -18
- data/lib/arrolio/flowables/note_flowable.rb +84 -9
- data/lib/arrolio/flowables/page_sequence_start.rb +8 -4
- data/lib/arrolio/flowables/positioned_block.rb +55 -0
- data/lib/arrolio/flowables/rotated_text.rb +51 -0
- data/lib/arrolio/flowables/table_flowable.rb +303 -71
- data/lib/arrolio/flowables/text_flowable.rb +67 -11
- data/lib/arrolio/flowables/toc_line_flowable.rb +70 -7
- data/lib/arrolio/flowables.rb +4 -0
- data/lib/arrolio/font/embedder.rb +22 -3
- data/lib/arrolio/font_scanner.rb +133 -0
- data/lib/arrolio/generic_adapter/document_extraction.rb +101 -0
- data/lib/arrolio/generic_adapter/footnote_extraction.rb +81 -0
- data/lib/arrolio/generic_adapter/heading_extraction.rb +92 -0
- data/lib/arrolio/generic_adapter/inline_run_collection.rb +136 -0
- data/lib/arrolio/generic_adapter/list_conversion.rb +70 -0
- data/lib/arrolio/generic_adapter/metadata_extraction.rb +46 -0
- data/lib/arrolio/generic_adapter/table_conversion.rb +105 -0
- data/lib/arrolio/generic_adapter.rb +263 -317
- data/lib/arrolio/generic_flow_builder/bibliography.rb +47 -0
- data/lib/arrolio/generic_flow_builder/figures.rb +90 -0
- data/lib/arrolio/generic_flow_builder/lists.rb +66 -0
- data/lib/arrolio/generic_flow_builder/notes.rb +61 -0
- data/lib/arrolio/generic_flow_builder/sequences.rb +102 -0
- data/lib/arrolio/generic_flow_builder/tables.rb +28 -0
- data/lib/arrolio/generic_flow_builder/terms.rb +89 -0
- data/lib/arrolio/generic_flow_builder.rb +92 -187
- data/lib/arrolio/glyph_measurer.rb +16 -5
- data/lib/arrolio/inline_run.rb +2 -1
- data/lib/arrolio/layout_spec/page_template.rb +14 -8
- data/lib/arrolio/math_ml/inline_run_extractor.rb +272 -0
- data/lib/arrolio/math_ml.rb +7 -0
- data/lib/arrolio/output/page.rb +5 -4
- data/lib/arrolio/output/placed_box.rb +5 -2
- data/lib/arrolio/renderer/pdf/assets.rb +94 -0
- data/lib/arrolio/renderer/pdf/font_embedding.rb +77 -0
- data/lib/arrolio/renderer/pdf/metadata.rb +52 -0
- data/lib/arrolio/renderer/pdf.rb +140 -215
- data/lib/arrolio/style/definition.rb +7 -3
- data/lib/arrolio/style/registry.rb +6 -1
- data/lib/arrolio/table/auto_layout.rb +19 -10
- data/lib/arrolio/table/grid.rb +105 -0
- data/lib/arrolio/table.rb +1 -0
- data/lib/arrolio/text_layout/greedy.rb +15 -1
- data/lib/arrolio/text_layout/knuth_plass/breaker.rb +216 -92
- data/lib/arrolio/text_layout/knuth_plass/item_builder.rb +31 -17
- data/lib/arrolio/text_layout/knuth_plass.rb +3 -0
- data/lib/arrolio/text_layout/line.rb +10 -2
- data/lib/arrolio/version.rb +1 -1
- data/lib/arrolio.rb +8 -4
- data/scripts/parity_check.rb +52 -0
- data/scripts/parity_diff.rb +89 -0
- data/scripts/xsl_profiles/standoc.yml +8 -2
- data/scripts/xsl_to_config.rb +28 -1
- metadata +91 -5
|
@@ -5,57 +5,77 @@ module Arrolio
|
|
|
5
5
|
# Hanging-indent list. Each item has a marker column (fixed
|
|
6
6
|
# width) and a body column (wraps under body, not marker).
|
|
7
7
|
class ListFlowable < Flowable
|
|
8
|
-
attr_reader :items, :kind, :marker_width, :body_indent
|
|
8
|
+
attr_reader :items, :kind, :marker_width, :body_indent,
|
|
9
|
+
:marker_indent, :item_spacing
|
|
9
10
|
|
|
10
11
|
def initialize(items, kind: :bullet, style: Style::Definition.new,
|
|
11
|
-
marker_width: 18.0, body_indent: 6.0
|
|
12
|
+
marker_width: 18.0, body_indent: 6.0,
|
|
13
|
+
marker_indent: 0.0, item_spacing: 0.0)
|
|
12
14
|
super(style: style)
|
|
13
15
|
@kind = kind.to_sym
|
|
14
16
|
@marker_width = marker_width.to_f
|
|
15
17
|
@body_indent = body_indent.to_f
|
|
18
|
+
@marker_indent = marker_indent.to_f
|
|
19
|
+
@item_spacing = item_spacing.to_f
|
|
16
20
|
@items = items
|
|
17
21
|
freeze
|
|
18
22
|
end
|
|
19
23
|
|
|
20
24
|
def height(width, context = nil)
|
|
21
|
-
@items.sum do |item|
|
|
25
|
+
body_h = @items.sum do |item|
|
|
22
26
|
_, body_flowables = item
|
|
23
|
-
|
|
24
|
-
body_flowables.sum { |f| f.height(body_width, context) }
|
|
27
|
+
body_flowables.sum { |f| f.height(body_width(width), context) }
|
|
25
28
|
end
|
|
29
|
+
body_h + ([@items.length - 1, 0].max * @item_spacing)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# What must stay with a preceding keep-with-next flowable:
|
|
33
|
+
# the first item — the lead-in paragraph keeps with the
|
|
34
|
+
# list's first line, not the whole list.
|
|
35
|
+
def min_keep_height(width, context = nil)
|
|
36
|
+
_, body_flowables = @items.first
|
|
37
|
+
return 0.0 unless body_flowables&.any?
|
|
38
|
+
|
|
39
|
+
body_flowables.sum { |f| f.height(body_width(width), context) }
|
|
26
40
|
end
|
|
27
41
|
|
|
28
42
|
def emit(x, y, width, context = nil)
|
|
29
43
|
boxes = []
|
|
30
44
|
consumed = 0.0
|
|
31
45
|
cursor = y
|
|
32
|
-
|
|
33
|
-
|
|
46
|
+
marker_x = x + @marker_indent
|
|
47
|
+
body_x = x + @marker_indent + @marker_width + @body_indent
|
|
34
48
|
|
|
35
49
|
@items.each_with_index do |item, idx|
|
|
36
50
|
marker_text, body_flowables = item
|
|
37
51
|
marker = marker_text || default_marker(idx)
|
|
38
52
|
|
|
39
53
|
if marker.is_a?(String)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
mboxes, = marker_flowable(marker, x, cursor, width, context).emit(x, cursor, width, context)
|
|
54
|
+
unless marker.empty?
|
|
55
|
+
mboxes, = marker_flowable(marker, marker_x, cursor, width, context)
|
|
56
|
+
.emit(marker_x, cursor, width, context)
|
|
44
57
|
boxes.concat(mboxes)
|
|
45
58
|
end
|
|
46
59
|
elsif marker
|
|
47
60
|
# marker is a Flowable (e.g. NoteFlowable passes a TextFlowable)
|
|
48
|
-
mboxes, = marker.emit(
|
|
61
|
+
mboxes, = marker.emit(marker_x, cursor, width, context)
|
|
49
62
|
boxes.concat(mboxes)
|
|
50
63
|
end
|
|
51
64
|
|
|
65
|
+
item_h = 0.0
|
|
52
66
|
body_flowables.each do |f|
|
|
53
|
-
fh = f.height(body_width, context)
|
|
54
|
-
fboxes, = f.emit(body_x, cursor, body_width, context)
|
|
67
|
+
fh = f.height(body_width(width), context)
|
|
68
|
+
fboxes, = f.emit(body_x, cursor, body_width(width), context)
|
|
55
69
|
boxes.concat(fboxes)
|
|
56
70
|
cursor -= fh
|
|
57
|
-
|
|
71
|
+
item_h += fh
|
|
58
72
|
end
|
|
73
|
+
|
|
74
|
+
if idx < @items.length - 1 && @item_spacing.positive?
|
|
75
|
+
cursor -= @item_spacing
|
|
76
|
+
item_h += @item_spacing
|
|
77
|
+
end
|
|
78
|
+
consumed += item_h
|
|
59
79
|
end
|
|
60
80
|
|
|
61
81
|
[boxes, consumed]
|
|
@@ -72,7 +92,7 @@ module Arrolio
|
|
|
72
92
|
|
|
73
93
|
@items.each do |item|
|
|
74
94
|
_, body_flowables = item
|
|
75
|
-
item_h = body_flowables.sum { |f| f.height(width
|
|
95
|
+
item_h = body_flowables.sum { |f| f.height(body_width(width), context) }
|
|
76
96
|
if used + item_h <= remaining_height || head_items.empty?
|
|
77
97
|
head_items << item
|
|
78
98
|
used += item_h
|
|
@@ -86,20 +106,28 @@ module Arrolio
|
|
|
86
106
|
else
|
|
87
107
|
self.class.new(head_items, kind: @kind, style: @style,
|
|
88
108
|
marker_width: @marker_width,
|
|
89
|
-
body_indent: @body_indent
|
|
109
|
+
body_indent: @body_indent,
|
|
110
|
+
marker_indent: @marker_indent,
|
|
111
|
+
item_spacing: @item_spacing)
|
|
90
112
|
end
|
|
91
113
|
tail = if tail_items.empty?
|
|
92
114
|
nil
|
|
93
115
|
else
|
|
94
116
|
self.class.new(tail_items, kind: @kind, style: @style,
|
|
95
117
|
marker_width: @marker_width,
|
|
96
|
-
body_indent: @body_indent
|
|
118
|
+
body_indent: @body_indent,
|
|
119
|
+
marker_indent: @marker_indent,
|
|
120
|
+
item_spacing: @item_spacing)
|
|
97
121
|
end
|
|
98
122
|
[head, tail]
|
|
99
123
|
end
|
|
100
124
|
|
|
101
125
|
private
|
|
102
126
|
|
|
127
|
+
def body_width(width)
|
|
128
|
+
width - @marker_indent - @marker_width - @body_indent
|
|
129
|
+
end
|
|
130
|
+
|
|
103
131
|
def default_marker(idx)
|
|
104
132
|
case @kind
|
|
105
133
|
when :ordered then "#{idx + 1}."
|
|
@@ -2,17 +2,92 @@
|
|
|
2
2
|
|
|
3
3
|
module Arrolio
|
|
4
4
|
module Flowables
|
|
5
|
-
# Note / example callout
|
|
6
|
-
#
|
|
5
|
+
# Note / example callout. Two label layouts:
|
|
6
|
+
#
|
|
7
|
+
# - :hanging (notes) — italic label in the marker column, body
|
|
8
|
+
# wraps on the same first line (hanging indent). Implemented on
|
|
9
|
+
# ListFlowable's hanging-indent infrastructure.
|
|
10
|
+
# - :block (examples) — the label is a heading on its own line
|
|
11
|
+
# at the column edge ("Example:"), body flows below it indented
|
|
12
|
+
# by +body_indent+. Emitted sequentially: the label line's
|
|
13
|
+
# height is part of the flow, so page breaking sees the whole
|
|
14
|
+
# block.
|
|
7
15
|
class NoteFlowable < ListFlowable
|
|
16
|
+
BLOCK_LABEL_GAP = 6.0
|
|
17
|
+
|
|
18
|
+
attr_reader :label_mode
|
|
19
|
+
|
|
8
20
|
def initialize(label_text, body_flowables, style: Style::Definition.new,
|
|
9
|
-
marker_width: 22.0, body_indent: 6.0
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
21
|
+
marker_width: 22.0, body_indent: 6.0, label_style: nil,
|
|
22
|
+
label_mode: :hanging)
|
|
23
|
+
@label_mode = label_mode.to_sym
|
|
24
|
+
effective_label_style = label_style || style.with(font_size: style.font_size)
|
|
25
|
+
if @label_mode == :block
|
|
26
|
+
@block_label = TextFlowable.new(
|
|
27
|
+
[InlineRun.new("#{label_text}:", style: effective_label_style)],
|
|
28
|
+
style: effective_label_style
|
|
29
|
+
)
|
|
30
|
+
@block_body = Array(body_flowables)
|
|
31
|
+
@block_indent = body_indent.to_f
|
|
32
|
+
super([[nil, []]], kind: :note, style: style)
|
|
33
|
+
else
|
|
34
|
+
marker_run = InlineRun.new("#{label_text} ", style: effective_label_style)
|
|
35
|
+
marker_flow = TextFlowable.new([marker_run], style: effective_label_style)
|
|
36
|
+
super([[marker_flow, Array(body_flowables)]],
|
|
37
|
+
kind: :note, style: style,
|
|
38
|
+
marker_width: marker_width, body_indent: body_indent)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def height(width, context = nil)
|
|
43
|
+
return block_height(width, context) if @label_mode == :block
|
|
44
|
+
|
|
45
|
+
super
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def emit(x, y, width, context = nil)
|
|
49
|
+
return block_emit(x, y, width, context) if @label_mode == :block
|
|
50
|
+
|
|
51
|
+
super
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Notes stay intact across page breaks: if the whole note does
|
|
55
|
+
# not fit, it moves to the next page. ListFlowable#do_split is
|
|
56
|
+
# not usable here — it rebuilds via self.class.new(items, ...)
|
|
57
|
+
# which does not match this constructor's signature.
|
|
58
|
+
def do_split(width, remaining_height, context = nil)
|
|
59
|
+
total = height(width, context)
|
|
60
|
+
if total <= remaining_height
|
|
61
|
+
[self, nil]
|
|
62
|
+
else
|
|
63
|
+
[nil, self]
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def block_height(width, context = nil)
|
|
70
|
+
@block_label.height(width, context) + BLOCK_LABEL_GAP +
|
|
71
|
+
@block_body.sum { |f| f.height(width - @block_indent, context) }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def block_emit(x, y, width, context = nil)
|
|
75
|
+
boxes = []
|
|
76
|
+
consumed = 0.0
|
|
77
|
+
cursor = y
|
|
78
|
+
|
|
79
|
+
label_boxes, label_h = @block_label.emit(x, cursor, width, context)
|
|
80
|
+
boxes.concat(label_boxes)
|
|
81
|
+
cursor -= label_h + BLOCK_LABEL_GAP
|
|
82
|
+
consumed += label_h + BLOCK_LABEL_GAP
|
|
83
|
+
|
|
84
|
+
@block_body.each do |flow|
|
|
85
|
+
fboxes, fh = flow.emit(x + @block_indent, cursor, width - @block_indent, context)
|
|
86
|
+
boxes.concat(fboxes)
|
|
87
|
+
cursor -= fh
|
|
88
|
+
consumed += fh
|
|
89
|
+
end
|
|
90
|
+
[boxes, consumed]
|
|
16
91
|
end
|
|
17
92
|
end
|
|
18
93
|
end
|
|
@@ -7,17 +7,21 @@ module Arrolio
|
|
|
7
7
|
# until the next PageSequenceStart.
|
|
8
8
|
class PageSequenceStart < Flowable
|
|
9
9
|
attr_reader :role, :header_template, :footer_template,
|
|
10
|
-
:header_align, :footer_align, :initial_page_number
|
|
10
|
+
:header_align, :footer_align, :initial_page_number,
|
|
11
|
+
:title_template
|
|
11
12
|
|
|
13
|
+
# +header_align+ nil means "no flavor opinion" — the engine
|
|
14
|
+
# picks left/right from page parity (XSL-FO odd/even headers).
|
|
12
15
|
def initialize(role:, header_template: nil, footer_template: nil,
|
|
13
|
-
header_align:
|
|
14
|
-
initial_page_number: nil)
|
|
16
|
+
header_align: nil, footer_align: :center,
|
|
17
|
+
initial_page_number: nil, title_template: nil)
|
|
15
18
|
@role = role.to_sym
|
|
16
19
|
@header_template = header_template
|
|
17
20
|
@footer_template = footer_template
|
|
18
|
-
@header_align = header_align
|
|
21
|
+
@header_align = header_align&.to_sym
|
|
19
22
|
@footer_align = footer_align.to_sym
|
|
20
23
|
@initial_page_number = initial_page_number
|
|
24
|
+
@title_template = title_template
|
|
21
25
|
end
|
|
22
26
|
|
|
23
27
|
def height(_width, _context = nil) = 0.0
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Arrolio
|
|
4
|
+
module Flowables
|
|
5
|
+
# Absolute-positioned container for cover-page layouts. Renders
|
|
6
|
+
# its children at a fixed (top, left) offset within the page,
|
|
7
|
+
# regardless of the normal top-down flow.
|
|
8
|
+
#
|
|
9
|
+
# The engine treats PositionedBlock as zero-height in the flow
|
|
10
|
+
# (consumed = 0), so the next flowable continues at the same
|
|
11
|
+
# y-coordinate. The block's children are placed at page-relative
|
|
12
|
+
# coordinates computed from +top+ and +left+.
|
|
13
|
+
#
|
|
14
|
+
# Used by cover layouts to place the title border-box, the
|
|
15
|
+
# organisation footer block, and similar non-flow regions.
|
|
16
|
+
class PositionedBlock < Flowable
|
|
17
|
+
attr_reader :children, :top, :left, :width, :height_value
|
|
18
|
+
|
|
19
|
+
# +children:+ Array of Flowable to render at the fixed position.
|
|
20
|
+
# +top:+ Distance from the page's top edge (points).
|
|
21
|
+
# +left:+ Distance from the page's left edge (points).
|
|
22
|
+
# +width:+ Width of the block (points).
|
|
23
|
+
# +height:+ Height of the block (points); content overflowing
|
|
24
|
+
# is clipped at render time.
|
|
25
|
+
def initialize(children:, top:, left:, width:, height:,
|
|
26
|
+
style: Style::Definition.new)
|
|
27
|
+
super(style: style)
|
|
28
|
+
@children = Array(children)
|
|
29
|
+
@top = top.to_f
|
|
30
|
+
@left = left.to_f
|
|
31
|
+
@width = width.to_f
|
|
32
|
+
@height_value = height.to_f
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def height(_width, _context = nil)
|
|
36
|
+
0.0
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def emit(x, y, _region_width, context = nil)
|
|
40
|
+
page_top = y + (y).abs # approximate page-top from current y
|
|
41
|
+
block_y = page_top - @top - @height_value
|
|
42
|
+
block_x = x + @left
|
|
43
|
+
boxes = []
|
|
44
|
+
cursor = block_y + @height_value
|
|
45
|
+
@children.each do |child|
|
|
46
|
+
child_boxes, consumed = child.emit(block_x, cursor, @width, context)
|
|
47
|
+
boxes.concat(child_boxes)
|
|
48
|
+
cursor -= consumed
|
|
49
|
+
end
|
|
50
|
+
# The block consumes zero flow height — siblings continue at y.
|
|
51
|
+
[boxes, 0.0]
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Arrolio
|
|
4
|
+
module Flowables
|
|
5
|
+
# Text rendered at a fixed rotation around its baseline. Used
|
|
6
|
+
# for cover layouts where vertical text runs along the page
|
|
7
|
+
# edge (some flavors put the docidentifier + edition rotated
|
|
8
|
+
# 90° on the left margin).
|
|
9
|
+
#
|
|
10
|
+
# +angle+ is in degrees, clockwise from horizontal. Common
|
|
11
|
+
# values: 90 (text reads top-to-bottom), 270 (bottom-to-top).
|
|
12
|
+
#
|
|
13
|
+
# The flowable consumes zero vertical flow space (it's
|
|
14
|
+
# absolutely positioned via PositionedBlock or similar) so
|
|
15
|
+
# siblings continue normally.
|
|
16
|
+
class RotatedText < Flowable
|
|
17
|
+
attr_reader :text, :angle
|
|
18
|
+
|
|
19
|
+
def initialize(text, style: Style::Definition.new, angle: 90)
|
|
20
|
+
super(style: style)
|
|
21
|
+
@text = text.to_s
|
|
22
|
+
@angle = angle.to_f
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def height(_width, _context = nil)
|
|
26
|
+
GlyphMeasurer.new(font_name: @style.font_name)
|
|
27
|
+
.line_height(font_size: @style.font_size,
|
|
28
|
+
line_spacing: @style.line_spacing)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def emit(x, y, width, _context = nil)
|
|
32
|
+
line_h = height(width)
|
|
33
|
+
measurer = GlyphMeasurer.new(font_name: @style.font_name)
|
|
34
|
+
text_w = measurer.width_of_string(@text, font_size: @style.font_size)
|
|
35
|
+
|
|
36
|
+
box = Output::PlacedBox.new(
|
|
37
|
+
x: x, y: y - line_h,
|
|
38
|
+
width: text_w, height: line_h,
|
|
39
|
+
kind: :rotated_text,
|
|
40
|
+
data: {
|
|
41
|
+
text: @text,
|
|
42
|
+
angle: @angle,
|
|
43
|
+
line_height: line_h,
|
|
44
|
+
style: @style
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
[[box], 0.0]
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|