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
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Arrolio
|
|
4
|
+
class GenericAdapter
|
|
5
|
+
# Extracted conversion seam (TODO 56): one module per concern,
|
|
6
|
+
# included into the adapter. The class keeps dispatch and glue.
|
|
7
|
+
module MetadataExtraction
|
|
8
|
+
def extract_metadata(root)
|
|
9
|
+
config = @rules['metadata'] || {}
|
|
10
|
+
bibdata_path = config['root'] || 'bibdata'
|
|
11
|
+
bibdata = find_first(root, bibdata_path)
|
|
12
|
+
return {} unless bibdata
|
|
13
|
+
|
|
14
|
+
result = {}
|
|
15
|
+
(config['fields'] || {}).each do |key, xpath|
|
|
16
|
+
value = text_of(find_first(bibdata, xpath))
|
|
17
|
+
result[key.to_sym] = value if value && !value.empty?
|
|
18
|
+
end
|
|
19
|
+
derive_metadata_fields(result)
|
|
20
|
+
result
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def derive_metadata_fields(result)
|
|
24
|
+
lang = result[:language].to_s
|
|
25
|
+
version_date = result[:revision_date]
|
|
26
|
+
year = version_date&.[](0, 4)
|
|
27
|
+
result[:revision_year] = year if year&.match?(/\A\d{4}\z/)
|
|
28
|
+
return if lang.empty? && version_date.nil?
|
|
29
|
+
|
|
30
|
+
lang_initial = lang[0, 1].upcase
|
|
31
|
+
year_part = year&.match?(/\A\d{4}\z/) ? "#{year} " : ''
|
|
32
|
+
result[:edition_label] = "#{year_part}(#{lang_initial})"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def extract_cover(root)
|
|
36
|
+
metadata = extract_metadata(root)
|
|
37
|
+
config = @rules['cover'] || {}
|
|
38
|
+
result = {}
|
|
39
|
+
(config['fields'] || ['docidentifier', 'edition', 'title_main', 'title_part']).each do |field|
|
|
40
|
+
result[field.to_sym] = metadata[field.to_sym] if metadata[field.to_sym]
|
|
41
|
+
end
|
|
42
|
+
result.empty? ? nil : result
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Arrolio
|
|
4
|
+
class GenericAdapter
|
|
5
|
+
# Table conversion, split from the adapter body (TODO 56 MECE
|
|
6
|
+
# split). Converts <table> to Content::Table: rows and cells
|
|
7
|
+
# with colspan/rowspan/alignment, captions, and cell footnotes
|
|
8
|
+
# lifted to the table.
|
|
9
|
+
module TableConversion
|
|
10
|
+
def convert_table(elem)
|
|
11
|
+
header, header_footnotes = convert_table_rows(find_first(elem, selector('table_header')), true)
|
|
12
|
+
body, body_footnotes = convert_table_rows(find_first(elem, selector('table_body')), false)
|
|
13
|
+
caption = extract_table_caption(elem)
|
|
14
|
+
Content::Table.new(header: header, body: body, style_id: :table,
|
|
15
|
+
id: elem.attribute(selector('id_attribute'))&.value,
|
|
16
|
+
caption: caption,
|
|
17
|
+
footnotes: header_footnotes + body_footnotes)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# The caption carries inline content (math subscripts, xrefs) —
|
|
21
|
+
# flattening to text serializes stems to their raw asciimath
|
|
22
|
+
# form ("n_(\"LC\")"). Collect runs like figure captions do.
|
|
23
|
+
def extract_table_caption(elem)
|
|
24
|
+
name = find_first(elem, selector('figure_caption')) ||
|
|
25
|
+
find_first(elem, selector('figure_caption_fallback'))
|
|
26
|
+
return nil unless name
|
|
27
|
+
|
|
28
|
+
runs = collect_inline_runs(name)
|
|
29
|
+
runs.empty? ? nil : Content::Paragraph.new(runs, style_id: :figure_caption)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def convert_table_rows(parent, is_header)
|
|
33
|
+
return [[], []] unless parent
|
|
34
|
+
|
|
35
|
+
rows = []
|
|
36
|
+
footnotes = []
|
|
37
|
+
row_name = selector('table_row')
|
|
38
|
+
cell_names = Array(selector('table_cell'))
|
|
39
|
+
each_child(parent, row_name) do |tr|
|
|
40
|
+
cells = []
|
|
41
|
+
each_element(tr) do |cell|
|
|
42
|
+
next unless cell_names.include?(cell.name)
|
|
43
|
+
|
|
44
|
+
runs, cell_footnotes = cell_runs_with_footnotes(cell)
|
|
45
|
+
footnotes.concat(cell_footnotes)
|
|
46
|
+
cells << Content::Table::Cell.new(
|
|
47
|
+
[Content::Paragraph.new(runs, style_id: is_header ? :table_header_cell : :table_cell)],
|
|
48
|
+
colspan: (cell.attribute('colspan')&.value || 1).to_i,
|
|
49
|
+
rowspan: (cell.attribute('rowspan')&.value || 1).to_i,
|
|
50
|
+
style_id: is_header ? :table_header_cell : :table_cell,
|
|
51
|
+
align: cell.attribute('align')&.value,
|
|
52
|
+
valign: cell.attribute('valign')&.value
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
rows << Content::Table::Row.new(cells)
|
|
56
|
+
end
|
|
57
|
+
[rows, footnotes]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Footnotes referenced from table cells keep only their marker
|
|
61
|
+
# (a superscript run) in the cell; their body is collected onto
|
|
62
|
+
# the table and rendered below it — the FOP table-footnote
|
|
63
|
+
# convention. Inline collection skips the marker element's
|
|
64
|
+
# subtree so the body text never leaks into the cell.
|
|
65
|
+
def cell_runs_with_footnotes(cell)
|
|
66
|
+
fn_name = selector('footnote_marker')
|
|
67
|
+
runs = collect_inline_runs(cell, exclude: fn_name)
|
|
68
|
+
return [runs, []] unless fn_name
|
|
69
|
+
|
|
70
|
+
footnotes = []
|
|
71
|
+
cell.each_recursive do |node|
|
|
72
|
+
next unless node.is_a?(REXML::Element) && node.name == fn_name
|
|
73
|
+
|
|
74
|
+
marker = node.attribute('reference')&.value ||
|
|
75
|
+
node.attribute('id')&.value || ''
|
|
76
|
+
unless marker.empty?
|
|
77
|
+
runs << Content::InlineRun.new(
|
|
78
|
+
marker, style_id: :inline,
|
|
79
|
+
baseline_shift: Content::InlineRun::BASELINE_SUP,
|
|
80
|
+
font_size_scale: 0.7
|
|
81
|
+
)
|
|
82
|
+
end
|
|
83
|
+
footnotes << table_footnote(node, marker)
|
|
84
|
+
end
|
|
85
|
+
[runs, footnotes]
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def table_footnote(fn_elem, marker)
|
|
89
|
+
para_name = selector('paragraph')
|
|
90
|
+
body = []
|
|
91
|
+
each_element(fn_elem) do |child|
|
|
92
|
+
next unless child.name == para_name
|
|
93
|
+
|
|
94
|
+
body << convert_paragraph(child)
|
|
95
|
+
end
|
|
96
|
+
Content::Footnote.new(
|
|
97
|
+
marker: marker,
|
|
98
|
+
body: body,
|
|
99
|
+
id: fn_elem.attribute(selector('id_attribute'))&.value
|
|
100
|
+
)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|