berkeley_library-tind 0.4.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.
Files changed (162) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/build.yml +18 -0
  3. data/.gitignore +388 -0
  4. data/.idea/inspectionProfiles/Project_Default.xml +20 -0
  5. data/.idea/misc.xml +4 -0
  6. data/.idea/modules.xml +8 -0
  7. data/.idea/tind.iml +138 -0
  8. data/.idea/vcs.xml +6 -0
  9. data/.rubocop.yml +334 -0
  10. data/.ruby-version +1 -0
  11. data/.simplecov +8 -0
  12. data/.yardopts +1 -0
  13. data/CHANGES.md +58 -0
  14. data/Dockerfile +57 -0
  15. data/Gemfile +3 -0
  16. data/Jenkinsfile +18 -0
  17. data/LICENSE.md +21 -0
  18. data/README.md +73 -0
  19. data/Rakefile +20 -0
  20. data/berkeley_library-tind.gemspec +50 -0
  21. data/bin/tind-export +14 -0
  22. data/docker-compose.yml +15 -0
  23. data/lib/berkeley_library/tind.rb +3 -0
  24. data/lib/berkeley_library/tind/api.rb +1 -0
  25. data/lib/berkeley_library/tind/api/api.rb +132 -0
  26. data/lib/berkeley_library/tind/api/api_exception.rb +131 -0
  27. data/lib/berkeley_library/tind/api/collection.rb +82 -0
  28. data/lib/berkeley_library/tind/api/date_range.rb +67 -0
  29. data/lib/berkeley_library/tind/api/format.rb +32 -0
  30. data/lib/berkeley_library/tind/api/search.rb +100 -0
  31. data/lib/berkeley_library/tind/config.rb +103 -0
  32. data/lib/berkeley_library/tind/export.rb +1 -0
  33. data/lib/berkeley_library/tind/export/column.rb +54 -0
  34. data/lib/berkeley_library/tind/export/column_group.rb +144 -0
  35. data/lib/berkeley_library/tind/export/column_group_list.rb +131 -0
  36. data/lib/berkeley_library/tind/export/column_width_calculator.rb +76 -0
  37. data/lib/berkeley_library/tind/export/config.rb +154 -0
  38. data/lib/berkeley_library/tind/export/csv_exporter.rb +29 -0
  39. data/lib/berkeley_library/tind/export/export.rb +47 -0
  40. data/lib/berkeley_library/tind/export/export_command.rb +168 -0
  41. data/lib/berkeley_library/tind/export/export_exception.rb +8 -0
  42. data/lib/berkeley_library/tind/export/export_format.rb +67 -0
  43. data/lib/berkeley_library/tind/export/exporter.rb +105 -0
  44. data/lib/berkeley_library/tind/export/filter.rb +52 -0
  45. data/lib/berkeley_library/tind/export/no_results_error.rb +7 -0
  46. data/lib/berkeley_library/tind/export/ods_exporter.rb +138 -0
  47. data/lib/berkeley_library/tind/export/row.rb +24 -0
  48. data/lib/berkeley_library/tind/export/row_metrics.rb +18 -0
  49. data/lib/berkeley_library/tind/export/table.rb +175 -0
  50. data/lib/berkeley_library/tind/export/table_metrics.rb +116 -0
  51. data/lib/berkeley_library/tind/marc.rb +1 -0
  52. data/lib/berkeley_library/tind/marc/xml_reader.rb +144 -0
  53. data/lib/berkeley_library/tind/module_info.rb +14 -0
  54. data/lib/berkeley_library/util/arrays.rb +178 -0
  55. data/lib/berkeley_library/util/logging.rb +1 -0
  56. data/lib/berkeley_library/util/ods/spreadsheet.rb +170 -0
  57. data/lib/berkeley_library/util/ods/xml/content_doc.rb +26 -0
  58. data/lib/berkeley_library/util/ods/xml/document_node.rb +57 -0
  59. data/lib/berkeley_library/util/ods/xml/element_node.rb +106 -0
  60. data/lib/berkeley_library/util/ods/xml/loext/table_protection.rb +26 -0
  61. data/lib/berkeley_library/util/ods/xml/manifest/file_entry.rb +42 -0
  62. data/lib/berkeley_library/util/ods/xml/manifest/manifest.rb +73 -0
  63. data/lib/berkeley_library/util/ods/xml/manifest_doc.rb +26 -0
  64. data/lib/berkeley_library/util/ods/xml/namespace.rb +46 -0
  65. data/lib/berkeley_library/util/ods/xml/office/automatic_styles.rb +181 -0
  66. data/lib/berkeley_library/util/ods/xml/office/body.rb +17 -0
  67. data/lib/berkeley_library/util/ods/xml/office/document_content.rb +98 -0
  68. data/lib/berkeley_library/util/ods/xml/office/document_styles.rb +39 -0
  69. data/lib/berkeley_library/util/ods/xml/office/font_face_decls.rb +30 -0
  70. data/lib/berkeley_library/util/ods/xml/office/scripts.rb +17 -0
  71. data/lib/berkeley_library/util/ods/xml/office/spreadsheet.rb +37 -0
  72. data/lib/berkeley_library/util/ods/xml/office/styles.rb +39 -0
  73. data/lib/berkeley_library/util/ods/xml/style/cell_style.rb +58 -0
  74. data/lib/berkeley_library/util/ods/xml/style/column_style.rb +36 -0
  75. data/lib/berkeley_library/util/ods/xml/style/default_style.rb +31 -0
  76. data/lib/berkeley_library/util/ods/xml/style/family.rb +85 -0
  77. data/lib/berkeley_library/util/ods/xml/style/font_face.rb +46 -0
  78. data/lib/berkeley_library/util/ods/xml/style/paragraph_properties.rb +30 -0
  79. data/lib/berkeley_library/util/ods/xml/style/row_style.rb +37 -0
  80. data/lib/berkeley_library/util/ods/xml/style/style.rb +44 -0
  81. data/lib/berkeley_library/util/ods/xml/style/table_cell_properties.rb +40 -0
  82. data/lib/berkeley_library/util/ods/xml/style/table_column_properties.rb +30 -0
  83. data/lib/berkeley_library/util/ods/xml/style/table_properties.rb +25 -0
  84. data/lib/berkeley_library/util/ods/xml/style/table_row_properties.rb +28 -0
  85. data/lib/berkeley_library/util/ods/xml/style/table_style.rb +27 -0
  86. data/lib/berkeley_library/util/ods/xml/style/text_properties.rb +52 -0
  87. data/lib/berkeley_library/util/ods/xml/styles_doc.rb +26 -0
  88. data/lib/berkeley_library/util/ods/xml/table/named_expressions.rb +17 -0
  89. data/lib/berkeley_library/util/ods/xml/table/repeatable.rb +38 -0
  90. data/lib/berkeley_library/util/ods/xml/table/table.rb +193 -0
  91. data/lib/berkeley_library/util/ods/xml/table/table_cell.rb +46 -0
  92. data/lib/berkeley_library/util/ods/xml/table/table_column.rb +43 -0
  93. data/lib/berkeley_library/util/ods/xml/table/table_row.rb +136 -0
  94. data/lib/berkeley_library/util/ods/xml/text/p.rb +118 -0
  95. data/lib/berkeley_library/util/paths.rb +111 -0
  96. data/lib/berkeley_library/util/stringios.rb +30 -0
  97. data/lib/berkeley_library/util/strings.rb +42 -0
  98. data/lib/berkeley_library/util/sys_exits.rb +15 -0
  99. data/lib/berkeley_library/util/times.rb +22 -0
  100. data/lib/berkeley_library/util/uris.rb +44 -0
  101. data/lib/berkeley_library/util/uris/appender.rb +162 -0
  102. data/lib/berkeley_library/util/uris/requester.rb +62 -0
  103. data/lib/berkeley_library/util/uris/validator.rb +32 -0
  104. data/rakelib/bundle.rake +8 -0
  105. data/rakelib/coverage.rake +11 -0
  106. data/rakelib/gem.rake +54 -0
  107. data/rakelib/rubocop.rake +18 -0
  108. data/rakelib/spec.rake +2 -0
  109. data/spec/.rubocop.yml +40 -0
  110. data/spec/berkeley_library/tind/api/api_exception_spec.rb +91 -0
  111. data/spec/berkeley_library/tind/api/api_spec.rb +143 -0
  112. data/spec/berkeley_library/tind/api/collection_spec.rb +74 -0
  113. data/spec/berkeley_library/tind/api/date_range_spec.rb +110 -0
  114. data/spec/berkeley_library/tind/api/format_spec.rb +54 -0
  115. data/spec/berkeley_library/tind/api/search_spec.rb +364 -0
  116. data/spec/berkeley_library/tind/config_spec.rb +86 -0
  117. data/spec/berkeley_library/tind/export/column_group_spec.rb +29 -0
  118. data/spec/berkeley_library/tind/export/column_spec.rb +43 -0
  119. data/spec/berkeley_library/tind/export/config_spec.rb +206 -0
  120. data/spec/berkeley_library/tind/export/export_command_spec.rb +169 -0
  121. data/spec/berkeley_library/tind/export/export_format_spec.rb +59 -0
  122. data/spec/berkeley_library/tind/export/export_matcher.rb +112 -0
  123. data/spec/berkeley_library/tind/export/export_spec.rb +150 -0
  124. data/spec/berkeley_library/tind/export/exporter_spec.rb +125 -0
  125. data/spec/berkeley_library/tind/export/row_spec.rb +118 -0
  126. data/spec/berkeley_library/tind/export/table_spec.rb +322 -0
  127. data/spec/berkeley_library/tind/marc/xml_reader_spec.rb +93 -0
  128. data/spec/berkeley_library/util/arrays_spec.rb +340 -0
  129. data/spec/berkeley_library/util/ods/spreadsheet_spec.rb +124 -0
  130. data/spec/berkeley_library/util/ods/xml/content_doc_spec.rb +121 -0
  131. data/spec/berkeley_library/util/ods/xml/manifest/file_entry_spec.rb +27 -0
  132. data/spec/berkeley_library/util/ods/xml/manifest/manifest_spec.rb +33 -0
  133. data/spec/berkeley_library/util/ods/xml/office/document_content_spec.rb +60 -0
  134. data/spec/berkeley_library/util/ods/xml/style/automatic_styles_spec.rb +37 -0
  135. data/spec/berkeley_library/util/ods/xml/style/family_spec.rb +57 -0
  136. data/spec/berkeley_library/util/ods/xml/table/table_row_spec.rb +179 -0
  137. data/spec/berkeley_library/util/ods/xml/table/table_spec.rb +218 -0
  138. data/spec/berkeley_library/util/paths_spec.rb +90 -0
  139. data/spec/berkeley_library/util/stringios_spec.rb +34 -0
  140. data/spec/berkeley_library/util/strings_spec.rb +27 -0
  141. data/spec/berkeley_library/util/times_spec.rb +39 -0
  142. data/spec/berkeley_library/util/uris_spec.rb +118 -0
  143. data/spec/data/collection-names.txt +438 -0
  144. data/spec/data/collections.json +4827 -0
  145. data/spec/data/disjoint-records.xml +187 -0
  146. data/spec/data/record-184453.xml +58 -0
  147. data/spec/data/record-184458.xml +63 -0
  148. data/spec/data/record-187888.xml +78 -0
  149. data/spec/data/records-api-search-cjk-p1.xml +6381 -0
  150. data/spec/data/records-api-search-cjk-p2.xml +5 -0
  151. data/spec/data/records-api-search-p1.xml +4506 -0
  152. data/spec/data/records-api-search-p2.xml +4509 -0
  153. data/spec/data/records-api-search-p3.xml +4506 -0
  154. data/spec/data/records-api-search-p4.xml +4509 -0
  155. data/spec/data/records-api-search-p5.xml +4506 -0
  156. data/spec/data/records-api-search-p6.xml +2436 -0
  157. data/spec/data/records-api-search-p7.xml +5 -0
  158. data/spec/data/records-api-search.xml +234 -0
  159. data/spec/data/records-manual-search.xml +547 -0
  160. data/spec/spec_helper.rb +30 -0
  161. data/test/profile/table_from_records_profile.rb +46 -0
  162. metadata +585 -0
@@ -0,0 +1,7 @@
1
+ module BerkeleyLibrary
2
+ module TIND
3
+ module Export
4
+ class NoResultsError < StandardError; end
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,138 @@
1
+ require 'berkeley_library/util/arrays'
2
+ require 'berkeley_library/tind/export/exporter'
3
+ require 'berkeley_library/tind/export/table_metrics'
4
+ require 'berkeley_library/util/ods/spreadsheet'
5
+
6
+ module BerkeleyLibrary
7
+ module TIND
8
+ module Export
9
+ # Exporter for OpenOffice/LibreOffice format
10
+ class ODSExporter < Exporter
11
+
12
+ LOCKED_CELL_COLOR = '#c0362c'.freeze
13
+
14
+ # Round column widths up to nearest quarter inch
15
+ EIGHTH = '1/8'.to_r
16
+
17
+ # Max column width before wrapping
18
+ MAX_COLUMN_WIDTH_INCHES = 3.0
19
+
20
+ # Exports {ExportBase#collection} as an OpenOffice/LibreOffice spreadsheet
21
+ # @overload export
22
+ # Exports to a new string.
23
+ # @return [String] a binary string containing the spreadsheet data.
24
+ # @overload export(out)
25
+ # Exports to the specified output stream.
26
+ # @param out [IO] the output stream
27
+ # @return[void]
28
+ # @overload export(path)
29
+ # Exports to the specified file. If `path` denotes a directory, the
30
+ # spreadsheet will be written as exploded, pretty-printed XML.
31
+ # @param path [String, Pathname] the path to the output file or directory
32
+ # @return[void]
33
+ # @see BerkeleyLibrary::Util::ODS::Spreadsheet#write_exploded_to
34
+ def export(out = nil)
35
+ populate_spreadsheet!
36
+ spreadsheet.write_to(out)
37
+ end
38
+
39
+ # ------------------------------------------------------------
40
+ # Private methods
41
+
42
+ private
43
+
44
+ # ------------------------------
45
+ # Private accessors
46
+
47
+ def spreadsheet
48
+ @spreadsheet ||= BerkeleyLibrary::Util::ODS::Spreadsheet.new
49
+ end
50
+
51
+ def table
52
+ @table ||= spreadsheet.add_table(collection, protected: false)
53
+ end
54
+
55
+ def table_metrics
56
+ @table_metrics ||= TableMetrics.new(export_table)
57
+ end
58
+
59
+ # @return [BerkeleyLibrary::Util::ODS::XML::Office::AutomaticStyles] the styles
60
+ def styles
61
+ spreadsheet.auto_styles
62
+ end
63
+
64
+ def color_for(col_index)
65
+ can_edit?(col_index) ? nil : LOCKED_CELL_COLOR
66
+ end
67
+
68
+ def header_cell_style_for(col_index)
69
+ @header_cell_styles ||= []
70
+ @header_cell_styles[col_index] ||= begin
71
+ find_or_create_cell_style(
72
+ color: color_for(col_index),
73
+ font_weight: 'bold'
74
+ )
75
+ end
76
+ end
77
+
78
+ def find_or_create_cell_style(color:, font_weight: nil, wrap: false)
79
+ styles.find_or_create_cell_style(color: color, font_weight: font_weight, wrap: wrap)
80
+ end
81
+
82
+ def column_style_for(col_index)
83
+ column_width = table_metrics.formatted_width(col_index)
84
+ @column_styles_by_width ||= {}
85
+ @column_styles_by_width[column_width] ||= styles.find_or_create_column_style(column_width)
86
+ end
87
+
88
+ # ------------------------------
89
+ # Private utility methods
90
+
91
+ def populate_spreadsheet!
92
+ logger.info("Populating spreadsheet for #{collection}")
93
+ populate_columns!
94
+ populate_rows!
95
+ end
96
+
97
+ def can_edit?(col_index)
98
+ raise ArgumentError, "Invalid column index: #{col_index.inspect}" unless (column = export_table.columns[col_index])
99
+
100
+ column.can_edit?
101
+ end
102
+
103
+ # @param table [BerkeleyLibrary::Util::ODS::XML::Table::Table] the table
104
+ def populate_columns!
105
+ export_table.columns.each_with_index do |export_column, col_index|
106
+ table.add_column_with_styles(
107
+ export_column.header,
108
+ column_style: column_style_for(col_index),
109
+ header_cell_style: header_cell_style_for(col_index)
110
+ )
111
+ end
112
+ end
113
+
114
+ # @param table [BerkeleyLibrary::Util::ODS::XML::Table::Table] the table
115
+ def populate_rows!
116
+ export_table.row_count.times(&method(:populate_row))
117
+ end
118
+
119
+ def populate_row(row_index)
120
+ export_row = export_table.rows[row_index]
121
+ row_metrics = table_metrics.row_metrics_for(row_index)
122
+ row_height = row_metrics.formatted_row_height
123
+ row = table.add_row(row_height)
124
+ populate_values(export_row, row_metrics, row)
125
+ end
126
+
127
+ def populate_values(export_row, row_metrics, row)
128
+ export_row.each_value.with_index do |v, col_index|
129
+ wrap = row_metrics.wrap?(col_index)
130
+ cell_style = find_or_create_cell_style(color: color_for(col_index), wrap: wrap)
131
+ row.set_value_at(col_index, v, cell_style)
132
+ end
133
+ end
134
+
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,24 @@
1
+ module BerkeleyLibrary
2
+ module TIND
3
+ module Export
4
+ class Row
5
+
6
+ attr_reader :columns
7
+ attr_reader :row_index
8
+
9
+ def initialize(columns, row_index)
10
+ @columns = columns
11
+ @row_index = row_index
12
+ end
13
+
14
+ def values
15
+ columns.map { |c| c.value_at(row_index) }
16
+ end
17
+
18
+ def each_value(&block)
19
+ columns.map { |c| c.value_at(row_index) }.each(&block)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,18 @@
1
+ module BerkeleyLibrary
2
+ module TIND
3
+ module Export
4
+ class RowMetrics
5
+ attr_reader :formatted_row_height
6
+
7
+ def initialize(formatted_row_height, wrap_columns)
8
+ @formatted_row_height = formatted_row_height
9
+ @wrap_columns = wrap_columns
10
+ end
11
+
12
+ def wrap?(col_index)
13
+ @wrap_columns.include?(col_index)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,175 @@
1
+ require 'csv'
2
+ require 'stringio'
3
+
4
+ require 'marc_extensions'
5
+ require 'berkeley_library/util/logging'
6
+ require 'berkeley_library/tind/export/column_group_list'
7
+
8
+ module BerkeleyLibrary
9
+ module TIND
10
+ module Export
11
+ class Table
12
+ include BerkeleyLibrary::Logging
13
+
14
+ # ------------------------------------------------------------
15
+ # Accessors
16
+
17
+ attr_reader :column_groups
18
+
19
+ # ------------------------------------------------------------
20
+ # Initializer
21
+
22
+ # Initializes a new Table
23
+ #
24
+ # @param exportable_only [Boolean] whether to filter out non-exportable fields
25
+ # @see Tags
26
+ def initialize(exportable_only: false)
27
+ @column_groups = ColumnGroupList.new(exportable_only: exportable_only)
28
+ end
29
+
30
+ # ------------------------------------------------------------
31
+ # Factory method
32
+
33
+ class << self
34
+ # Returns a new Table for the provided MARC records.
35
+ #
36
+ # @param records [Enumerable<MARC::Record>] the records
37
+ # @param freeze [Boolean] whether to freeze the table
38
+ # @param exportable_only [Boolean] whether to include only exportable fields
39
+ # @return [Table] the table
40
+ def from_records(records, freeze: false, exportable_only: false)
41
+ Table.new(exportable_only: exportable_only).tap do |table|
42
+ records.each { |r| table << r }
43
+ table.freeze if freeze
44
+ end
45
+ end
46
+ end
47
+
48
+ # ------------------------------------------------------------
49
+ # Cell accessors
50
+
51
+ def value_at(row, col)
52
+ return unless (column = columns[col])
53
+
54
+ column.value_at(row)
55
+ end
56
+
57
+ # ------------------------------------------------------------
58
+ # Column accessors
59
+
60
+ # The column headers
61
+ #
62
+ # @return [Array<String>] the column headers
63
+ def headers
64
+ columns.map(&:header)
65
+ end
66
+
67
+ # The columns
68
+ #
69
+ # @return [Array<Column>] the columns.
70
+ def columns
71
+ # NOTE: this isn't ||= because we only cache on #freeze
72
+ @columns || column_groups.map(&:columns).flatten
73
+ end
74
+
75
+ def column_count
76
+ columns.size
77
+ end
78
+
79
+ # ------------------------------------------------------------
80
+ # Row / MARC::Record accessors
81
+
82
+ # The rows
83
+ #
84
+ # @return [Array<Row>] the rows
85
+ def rows
86
+ # NOTE: this isn't ||= because we only cache on #freeze
87
+ # noinspection RubyYardReturnMatch
88
+ @rows || each_row.to_a
89
+ end
90
+
91
+ # @yieldparam row [Row] each row
92
+ def each_row
93
+ return to_enum(:each_row) unless block_given?
94
+
95
+ (0...row_count).each { |row| yield Row.new(columns, row) }
96
+ end
97
+
98
+ # The number of rows (records)
99
+ #
100
+ # @return [Integer] the number of rows
101
+ def row_count
102
+ marc_records.size
103
+ end
104
+
105
+ # The MARC records
106
+ #
107
+ # @return [Array<MARC::Record>] the records
108
+ def marc_records
109
+ @marc_records ||= []
110
+ end
111
+
112
+ def empty?
113
+ marc_records.empty?
114
+ end
115
+
116
+ # ------------------------------------------------------------
117
+ # Modifiers
118
+
119
+ # Adds the specified record
120
+ #
121
+ # @param marc_record [MARC::Record] the record to add
122
+ def <<(marc_record)
123
+ raise FrozenError, "can't modify frozen MARCTable" if frozen?
124
+
125
+ logger.warn('MARC record is not frozen') unless marc_record.frozen?
126
+ column_groups.add_data_fields(marc_record, marc_records.size)
127
+ marc_records << marc_record
128
+ log_record_added(marc_record)
129
+
130
+ self
131
+ end
132
+
133
+ # ------------------------------------------------------------
134
+ # Object overrides
135
+
136
+ def frozen?
137
+ [marc_records, column_groups].all?(&:frozen?) &&
138
+ [@rows, @columns].all? { |d| !d.nil? && d.frozen? }
139
+ end
140
+
141
+ def freeze
142
+ [marc_records, column_groups].each(&:freeze)
143
+ @columns ||= columns.freeze
144
+ @rows ||= rows.freeze
145
+ self
146
+ end
147
+
148
+ # ------------------------------------------------------------
149
+ # Misc. instance methods
150
+
151
+ # TODO: move to BerkeleyLibrary::TIND::Export::CSVExporter
152
+ def to_csv(out = nil)
153
+ return write_csv(out) if out
154
+
155
+ StringIO.new.tap { |io| write_csv(io) }.string
156
+ end
157
+
158
+ # ------------------------------------------------------------
159
+ # Private methods
160
+
161
+ private
162
+
163
+ def log_record_added(marc_record)
164
+ return logger.debug("Added record no. #{marc_record.record_id}: #{row_count} records total") if marc_record
165
+ end
166
+
167
+ def write_csv(out)
168
+ csv = out.respond_to?(:write) ? CSV.new(out) : CSV.open(out, 'wb')
169
+ csv << headers
170
+ each_row { |row| csv << row.values }
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,116 @@
1
+ require 'berkeley_library/util/logging'
2
+ require 'berkeley_library/tind/export/row_metrics'
3
+ require 'berkeley_library/tind/export/config'
4
+
5
+ module BerkeleyLibrary
6
+ module TIND
7
+ module Export
8
+ class TableMetrics
9
+ include BerkeleyLibrary::Logging
10
+ include Config
11
+
12
+ # @return [Table] the table
13
+ attr_reader :table
14
+
15
+ # Initializes a new set of metrics for the specified table.
16
+ #
17
+ # @param table [Table] the table
18
+ def initialize(table)
19
+ @table = table
20
+ end
21
+
22
+ def formatted_width(col_index)
23
+ inches_numeric = numeric_column_width(col_index)
24
+ format_inches(inches_numeric)
25
+ end
26
+
27
+ def row_metrics_for(row_index)
28
+ @metrics_by_row ||= []
29
+ @metrics_by_row[row_index] ||= calc_row_metrics(row_index)
30
+ end
31
+
32
+ # ------------------------------------------------------------
33
+ # Private methods
34
+
35
+ private
36
+
37
+ # ------------------------------
38
+ # Private accessors
39
+
40
+ def inch_format
41
+ @inch_format ||= "%0.#{format_digits}fin"
42
+ end
43
+
44
+ def numeric_column_width(col_index)
45
+ @numeric_column_widths ||= []
46
+ @numeric_column_widths[col_index] ||= calc_max_width(col_index)
47
+ end
48
+
49
+ def cell_widths_for_column(col_index)
50
+ @cell_widths_by_column ||= []
51
+ @cell_widths_by_column[col_index] ||= calc_cell_widths(col_index)
52
+ end
53
+
54
+ # ------------------------------
55
+ # Private utility methods
56
+
57
+ def calc_max_width(col_index)
58
+ header = table.columns[col_index].header
59
+ w_header = ColumnWidthCalculator.width_inches(header)
60
+ w_cell_max = cell_widths_for_column(col_index).max
61
+ w_max_actual = [w_header, w_cell_max].max
62
+ w_max_rounded = (w_max_actual / w_incr_in).ceil * w_incr_in
63
+ [w_max_rounded, max_col_width_in].min
64
+ end
65
+
66
+ def calc_cell_widths(col_index)
67
+ column = table.columns[col_index]
68
+ column.each_value.map { |v| ColumnWidthCalculator.width_inches(v) }
69
+ end
70
+
71
+ def format_inches(inches_numeric)
72
+ format(inch_format, inches_numeric)
73
+ end
74
+
75
+ def calc_row_metrics(row_index)
76
+ columns_to_wrap = Set.new
77
+ max_wrapped_lines = 1
78
+
79
+ table.column_count.times do |col_index|
80
+ wrapped_lines = wrapped_lines_for_cell(row_index, col_index)
81
+ max_wrapped_lines = [wrapped_lines, max_wrapped_lines].max
82
+ columns_to_wrap << col_index if wrapped_lines > 1
83
+ end
84
+
85
+ formatted_height = format_row_height(max_wrapped_lines)
86
+ RowMetrics.new(formatted_height, columns_to_wrap)
87
+ end
88
+
89
+ # TODO: something smarter, maybe guess at break locations?
90
+ def wrapped_lines_for_cell(row_index, col_index)
91
+ w_col = numeric_column_width(col_index)
92
+ w_cell = numeric_cell_width(row_index, col_index)
93
+ (w_cell / w_col).ceil
94
+ end
95
+
96
+ def numeric_cell_width(row_index, col_index)
97
+ cell_widths_by_row = cell_widths_for_column(col_index)
98
+ cell_widths_by_row[row_index] || 0
99
+ end
100
+
101
+ def numeric_line_height_inches
102
+ @numeric_line_height_inches ||= begin
103
+ h_exact_pts = font_size_pt * line_height_em
104
+ h_rounded_pts = (h_exact_pts / h_incr_pt).ceil * h_incr_pt
105
+ h_rounded_pts / 72.0
106
+ end
107
+ end
108
+
109
+ def format_row_height(lines)
110
+ @formatted_row_heights ||= []
111
+ @formatted_row_heights[lines] ||= format_inches(lines * numeric_line_height_inches)
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end