uniword 1.0.10 → 1.2.4

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 (204) hide show
  1. checksums.yaml +4 -4
  2. data/config/font_metadata.yml +56 -0
  3. data/lib/uniword/accessibility/rules/descriptive_headings_rule.rb +1 -1
  4. data/lib/uniword/assembly/toc.rb +4 -14
  5. data/lib/uniword/assembly/variable_substitutor.rb +3 -4
  6. data/lib/uniword/batch/stages/normalize_styles_stage.rb +4 -2
  7. data/lib/uniword/builder/base_builder.rb +25 -0
  8. data/lib/uniword/builder/chart_builder.rb +33 -14
  9. data/lib/uniword/builder/deterministic_id.rb +17 -0
  10. data/lib/uniword/builder/document_builder.rb +129 -34
  11. data/lib/uniword/builder/footnote_builder.rb +22 -23
  12. data/lib/uniword/builder/header_footer_builder.rb +10 -10
  13. data/lib/uniword/builder/image_builder.rb +24 -16
  14. data/lib/uniword/builder/list_builder.rb +2 -1
  15. data/lib/uniword/builder/paragraph_builder.rb +95 -20
  16. data/lib/uniword/builder/run_utils.rb +97 -0
  17. data/lib/uniword/builder/section_builder.rb +40 -67
  18. data/lib/uniword/builder/table_builder.rb +32 -13
  19. data/lib/uniword/builder/table_cell_builder.rb +19 -0
  20. data/lib/uniword/builder.rb +27 -19
  21. data/lib/uniword/cli/diff_cli.rb +0 -1
  22. data/lib/uniword/cli/generate_cli.rb +0 -1
  23. data/lib/uniword/cli/headers_cli.rb +0 -1
  24. data/lib/uniword/cli/images_cli.rb +0 -1
  25. data/lib/uniword/cli/main.rb +6 -7
  26. data/lib/uniword/cli/protect_cli.rb +0 -1
  27. data/lib/uniword/cli/resources_cli.rb +0 -1
  28. data/lib/uniword/cli/review_cli.rb +0 -1
  29. data/lib/uniword/cli/spellcheck_cli.rb +0 -1
  30. data/lib/uniword/cli/styleset_cli.rb +0 -1
  31. data/lib/uniword/cli/template_cli.rb +0 -1
  32. data/lib/uniword/cli/theme_cli.rb +0 -1
  33. data/lib/uniword/cli/toc_cli.rb +0 -1
  34. data/lib/uniword/cli/watermark_cli.rb +0 -1
  35. data/lib/uniword/cli.rb +6 -17
  36. data/lib/uniword/diff/document_differ.rb +23 -5
  37. data/lib/uniword/diff/package_differ.rb +0 -1
  38. data/lib/uniword/diff.rb +4 -0
  39. data/lib/uniword/document_factory.rb +16 -24
  40. data/lib/uniword/docx/document_statistics.rb +42 -54
  41. data/lib/uniword/docx/id_allocator.rb +143 -0
  42. data/lib/uniword/docx/package.rb +42 -6
  43. data/lib/uniword/docx/package_defaults.rb +16 -25
  44. data/lib/uniword/docx/package_serialization.rb +131 -127
  45. data/lib/uniword/docx/reconciler/body.rb +197 -0
  46. data/lib/uniword/docx/reconciler/fix_codes.rb +74 -0
  47. data/lib/uniword/docx/reconciler/helpers.rb +337 -0
  48. data/lib/uniword/docx/reconciler/notes.rb +301 -0
  49. data/lib/uniword/docx/reconciler/package_structure.rb +322 -0
  50. data/lib/uniword/docx/reconciler/parts.rb +484 -0
  51. data/lib/uniword/docx/reconciler/referential_integrity.rb +449 -0
  52. data/lib/uniword/docx/reconciler/tables.rb +162 -0
  53. data/lib/uniword/docx/reconciler/theme.rb +141 -0
  54. data/lib/uniword/docx/reconciler.rb +44 -1069
  55. data/lib/uniword/docx.rb +3 -0
  56. data/lib/uniword/drawingml/color_scheme.rb +38 -5
  57. data/lib/uniword/hyperlink.rb +27 -26
  58. data/lib/uniword/infrastructure/zip_packager.rb +41 -38
  59. data/lib/uniword/lazy_loader.rb +36 -62
  60. data/lib/uniword/math.rb +3 -86
  61. data/lib/uniword/mhtml/document.rb +1 -3
  62. data/lib/uniword/model_attribute_access.rb +27 -0
  63. data/lib/uniword/ooxml/namespaces.rb +62 -2
  64. data/lib/uniword/ooxml/relationships/package_relationships.rb +7 -0
  65. data/lib/uniword/presentationml/slide_id.rb +0 -1
  66. data/lib/uniword/presentationml/slide_master_id.rb +0 -1
  67. data/lib/uniword/properties/bold.rb +0 -2
  68. data/lib/uniword/properties/boolean_formatting.rb +0 -2
  69. data/lib/uniword/properties/borders.rb +1 -1
  70. data/lib/uniword/properties/italic.rb +0 -2
  71. data/lib/uniword/properties/numbering_properties.rb +1 -1
  72. data/lib/uniword/properties/style_reference.rb +13 -0
  73. data/lib/uniword/quality/rules/heading_hierarchy_rule.rb +1 -1
  74. data/lib/uniword/quality/rules/style_consistency_rule.rb +1 -1
  75. data/lib/uniword/review/review_manager.rb +0 -2
  76. data/lib/uniword/template/helpers/loop_helper.rb +2 -2
  77. data/lib/uniword/template/variable_resolver.rb +14 -1
  78. data/lib/uniword/theme/theme_xml_parser.rb +1 -1
  79. data/lib/uniword/toc/toc_generator.rb +3 -14
  80. data/lib/uniword/transformation/mhtml_element_renderer.rb +94 -37
  81. data/lib/uniword/transformation/mhtml_metadata_builder.rb +1 -1
  82. data/lib/uniword/transformation/mhtml_style_builder.rb +9 -2
  83. data/lib/uniword/transformation/ooxml_to_html_converter.rb +3 -5
  84. data/lib/uniword/transformation/ooxml_to_mhtml_converter.rb +7 -15
  85. data/lib/uniword/transformation/yaml_css_generator.rb +815 -0
  86. data/lib/uniword/transformation.rb +2 -0
  87. data/lib/uniword/validation/checkers/internal_link_checker.rb +2 -3
  88. data/lib/uniword/version.rb +1 -1
  89. data/lib/uniword/visitor/text_extractor.rb +2 -1
  90. data/lib/uniword/watermark/manager.rb +2 -2
  91. data/lib/uniword/wordprocessingml/allow_png.rb +14 -0
  92. data/lib/uniword/wordprocessingml/body.rb +53 -18
  93. data/lib/uniword/wordprocessingml/body_div.rb +17 -0
  94. data/lib/uniword/wordprocessingml/div_border.rb +23 -0
  95. data/lib/uniword/wordprocessingml/div_borders.rb +24 -0
  96. data/lib/uniword/wordprocessingml/divs_child.rb +17 -0
  97. data/lib/uniword/wordprocessingml/document_root.rb +6 -3
  98. data/lib/uniword/wordprocessingml/endnote.rb +1 -1
  99. data/lib/uniword/wordprocessingml/endnotes.rb +4 -0
  100. data/lib/uniword/wordprocessingml/footer.rb +4 -0
  101. data/lib/uniword/wordprocessingml/footer_reference.rb +0 -1
  102. data/lib/uniword/wordprocessingml/footnote.rb +1 -1
  103. data/lib/uniword/wordprocessingml/footnotes.rb +4 -0
  104. data/lib/uniword/wordprocessingml/header.rb +4 -0
  105. data/lib/uniword/wordprocessingml/header_reference.rb +0 -1
  106. data/lib/uniword/wordprocessingml/hyperlink.rb +2 -2
  107. data/lib/uniword/wordprocessingml/instr_text.rb +20 -5
  108. data/lib/uniword/wordprocessingml/level.rb +2 -0
  109. data/lib/uniword/wordprocessingml/mar_bottom.rb +17 -0
  110. data/lib/uniword/wordprocessingml/mar_left.rb +17 -0
  111. data/lib/uniword/wordprocessingml/mar_right.rb +17 -0
  112. data/lib/uniword/wordprocessingml/mar_top.rb +17 -0
  113. data/lib/uniword/wordprocessingml/num.rb +4 -0
  114. data/lib/uniword/wordprocessingml/numbering.rb +1 -22
  115. data/lib/uniword/wordprocessingml/optimize_for_browser.rb +14 -0
  116. data/lib/uniword/wordprocessingml/page_defaults.rb +39 -0
  117. data/lib/uniword/wordprocessingml/paragraph.rb +16 -13
  118. data/lib/uniword/wordprocessingml/paragraph_properties.rb +12 -8
  119. data/lib/uniword/wordprocessingml/run.rb +32 -36
  120. data/lib/uniword/wordprocessingml/run_properties/merging.rb +45 -0
  121. data/lib/uniword/wordprocessingml/run_properties.rb +8 -5
  122. data/lib/uniword/wordprocessingml/section_properties.rb +3 -3
  123. data/lib/uniword/wordprocessingml/separator_char.rb +21 -0
  124. data/lib/uniword/wordprocessingml/settings.rb +19 -17
  125. data/lib/uniword/wordprocessingml/styles/style_definition.rb +1 -1
  126. data/lib/uniword/wordprocessingml/styles/style_library.rb +16 -0
  127. data/lib/uniword/wordprocessingml/table_defaults.rb +44 -0
  128. data/lib/uniword/wordprocessingml/text.rb +8 -2
  129. data/lib/uniword/wordprocessingml/web_div.rb +41 -0
  130. data/lib/uniword/wordprocessingml/web_divs.rb +18 -0
  131. data/lib/uniword/wordprocessingml/web_encoding.rb +17 -0
  132. data/lib/uniword/wordprocessingml/web_settings.rb +5 -21
  133. data/lib/uniword/wordprocessingml/zoom.rb +2 -0
  134. data/lib/uniword/wordprocessingml.rb +23 -0
  135. data/lib/uniword.rb +27 -0
  136. metadata +47 -70
  137. data/lib/uniword/math/accent.rb +0 -25
  138. data/lib/uniword/math/accent_properties.rb +0 -25
  139. data/lib/uniword/math/argument_properties.rb +0 -22
  140. data/lib/uniword/math/bar.rb +0 -25
  141. data/lib/uniword/math/bar_properties.rb +0 -25
  142. data/lib/uniword/math/begin_char.rb +0 -22
  143. data/lib/uniword/math/border_box.rb +0 -25
  144. data/lib/uniword/math/border_box_properties.rb +0 -39
  145. data/lib/uniword/math/box.rb +0 -25
  146. data/lib/uniword/math/box_properties.rb +0 -33
  147. data/lib/uniword/math/char.rb +0 -22
  148. data/lib/uniword/math/control_properties.rb +0 -27
  149. data/lib/uniword/math/degree.rb +0 -26
  150. data/lib/uniword/math/delimiter.rb +0 -25
  151. data/lib/uniword/math/delimiter_properties.rb +0 -33
  152. data/lib/uniword/math/denominator.rb +0 -26
  153. data/lib/uniword/math/element.rb +0 -61
  154. data/lib/uniword/math/end_char.rb +0 -22
  155. data/lib/uniword/math/equation_array.rb +0 -25
  156. data/lib/uniword/math/equation_array_properties.rb +0 -33
  157. data/lib/uniword/math/fraction.rb +0 -27
  158. data/lib/uniword/math/fraction_properties.rb +0 -25
  159. data/lib/uniword/math/function.rb +0 -27
  160. data/lib/uniword/math/function_name.rb +0 -26
  161. data/lib/uniword/math/function_properties.rb +0 -23
  162. data/lib/uniword/math/group_char.rb +0 -25
  163. data/lib/uniword/math/group_char_properties.rb +0 -29
  164. data/lib/uniword/math/lim.rb +0 -26
  165. data/lib/uniword/math/lower_limit.rb +0 -27
  166. data/lib/uniword/math/lower_limit_properties.rb +0 -23
  167. data/lib/uniword/math/math_break.rb +0 -22
  168. data/lib/uniword/math/math_font.rb +0 -22
  169. data/lib/uniword/math/math_properties.rb +0 -53
  170. data/lib/uniword/math/math_run.rb +0 -42
  171. data/lib/uniword/math/math_run_properties.rb +0 -37
  172. data/lib/uniword/math/math_simple_int_val.rb +0 -24
  173. data/lib/uniword/math/math_simple_val.rb +0 -24
  174. data/lib/uniword/math/math_style.rb +0 -22
  175. data/lib/uniword/math/math_text.rb +0 -23
  176. data/lib/uniword/math/matrix.rb +0 -25
  177. data/lib/uniword/math/matrix_column.rb +0 -23
  178. data/lib/uniword/math/matrix_column_properties.rb +0 -24
  179. data/lib/uniword/math/matrix_columns.rb +0 -23
  180. data/lib/uniword/math/matrix_properties.rb +0 -37
  181. data/lib/uniword/math/matrix_row.rb +0 -23
  182. data/lib/uniword/math/nary.rb +0 -29
  183. data/lib/uniword/math/nary_properties.rb +0 -33
  184. data/lib/uniword/math/numerator.rb +0 -26
  185. data/lib/uniword/math/o_math.rb +0 -65
  186. data/lib/uniword/math/o_math_para.rb +0 -25
  187. data/lib/uniword/math/o_math_para_properties.rb +0 -22
  188. data/lib/uniword/math/phantom.rb +0 -25
  189. data/lib/uniword/math/phantom_properties.rb +0 -33
  190. data/lib/uniword/math/pre_sub_superscript.rb +0 -29
  191. data/lib/uniword/math/pre_sub_superscript_properties.rb +0 -23
  192. data/lib/uniword/math/radical.rb +0 -27
  193. data/lib/uniword/math/radical_properties.rb +0 -25
  194. data/lib/uniword/math/separator_char.rb +0 -22
  195. data/lib/uniword/math/sub.rb +0 -26
  196. data/lib/uniword/math/sub_superscript.rb +0 -29
  197. data/lib/uniword/math/sub_superscript_properties.rb +0 -25
  198. data/lib/uniword/math/subscript.rb +0 -27
  199. data/lib/uniword/math/subscript_properties.rb +0 -23
  200. data/lib/uniword/math/sup.rb +0 -26
  201. data/lib/uniword/math/superscript.rb +0 -27
  202. data/lib/uniword/math/superscript_properties.rb +0 -23
  203. data/lib/uniword/math/upper_limit.rb +0 -27
  204. data/lib/uniword/math/upper_limit_properties.rb +0 -23
@@ -0,0 +1,484 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uniword
4
+ module Docx
5
+ class Reconciler
6
+ # Profile-dependent support parts reconciliation.
7
+ #
8
+ # Populates settings, font table, styles, numbering, web settings,
9
+ # and document/app/core properties with profile-appropriate defaults.
10
+ # Only runs when a profile is provided.
11
+ module Parts
12
+ def reconcile_settings
13
+ return unless profile
14
+
15
+ new_settings = package.settings.nil?
16
+ settings = package.settings
17
+ settings ||= begin
18
+ package.settings = Wordprocessingml::Settings.new
19
+ package.settings
20
+ end
21
+
22
+ rsid = generate_rsid
23
+
24
+ settings.zoom ||= Wordprocessingml::Zoom.new(percent: 100)
25
+ if new_settings && settings.do_not_display_page_boundaries.nil?
26
+ settings.do_not_display_page_boundaries =
27
+ Wordprocessingml::DoNotDisplayPageBoundaries.new
28
+ ensure_element_in_order(settings, "doNotDisplayPageBoundaries",
29
+ after: "zoom")
30
+ end
31
+ settings.proof_state ||= Wordprocessingml::ProofState.new(
32
+ spelling: "clean", grammar: "clean",
33
+ )
34
+ settings.default_tab_stop ||= Wordprocessingml::DefaultTabStop.new(val: "720")
35
+ settings.character_spacing_control ||=
36
+ Wordprocessingml::CharacterSpacingControl.new(val: "doNotCompress")
37
+
38
+ settings.compat ||= build_compat
39
+ settings.rsids ||= build_rsids(rsid)
40
+ settings.math_pr ||= build_math_pr
41
+ settings.theme_font_lang ||= Wordprocessingml::ThemeFontLang.new(
42
+ val: profile.lang,
43
+ east_asia: profile.east_asia_lang,
44
+ )
45
+ settings.clr_scheme_mapping ||= build_clr_scheme_mapping
46
+ settings.decimal_symbol ||= Wordprocessingml::DecimalSymbol.new(
47
+ val: profile.decimal_symbol,
48
+ )
49
+ settings.list_separator ||= Wordprocessingml::ListSeparator.new(
50
+ val: profile.list_separator,
51
+ )
52
+
53
+ unless settings.w14_doc_id
54
+ settings.w14_doc_id = Wordprocessingml::W14DocId.new(
55
+ val: hex_derive("w14_doc_id", 4),
56
+ )
57
+ record_fix(FixCodes::DOC_ID_GENERATED, "Generated w14:docId")
58
+ end
59
+ unless settings.w15_doc_id
60
+ raw = hex_derive("w15_doc_id", 16)
61
+ formatted = "#{raw[0..7]}-#{raw[8..11]}-#{raw[12..15]}-" \
62
+ "#{raw[16..19]}-#{raw[20..31]}"
63
+ settings.w15_doc_id = Wordprocessingml::W15DocId.new(
64
+ val: "{#{formatted.upcase}}",
65
+ )
66
+ record_fix(FixCodes::DOC_ID_GENERATED, "Generated w15:docId in GUID format")
67
+ end
68
+
69
+ set_mc_ignorable(settings)
70
+ end
71
+
72
+ def reconcile_font_table
73
+ return unless profile
74
+
75
+ font_table = package.font_table
76
+ font_table ||= begin
77
+ package.font_table = Wordprocessingml::FontTable.new
78
+ record_fix(FixCodes::FONT_TABLE_CREATED, "Created font table")
79
+ package.font_table
80
+ end
81
+
82
+ set_mc_ignorable(font_table)
83
+
84
+ return unless font_table.fonts.empty?
85
+
86
+ metadata = load_font_metadata
87
+ return unless metadata
88
+
89
+ font_names = font_names_for_profile
90
+ font_names.each do |name|
91
+ meta = metadata[name]
92
+ next unless meta
93
+
94
+ sig_data = meta["sig"] || {}
95
+ font = Wordprocessingml::Font.new(
96
+ name: name,
97
+ panose1: Wordprocessingml::Panose1.new(val: meta["panose1"]),
98
+ charset: Wordprocessingml::Charset.new(val: meta["charset"]),
99
+ family: Wordprocessingml::Family.new(val: meta["family"]),
100
+ pitch: Wordprocessingml::Pitch.new(val: meta["pitch"]),
101
+ sig: Wordprocessingml::Sig.new(
102
+ usb0: sig_data["usb0"], usb1: sig_data["usb1"],
103
+ usb2: sig_data["usb2"], usb3: sig_data["usb3"],
104
+ csb0: sig_data["csb0"], csb1: sig_data["csb1"]
105
+ ),
106
+ )
107
+
108
+ font.alt_name = Wordprocessingml::AltName.new(val: meta["alt_name"]) if meta["alt_name"]
109
+ font_table.fonts << font
110
+ end
111
+
112
+ set_mc_ignorable(font_table)
113
+ record_fix(FixCodes::FONT_TABLE_CREATED,
114
+ "Populated font table with profile fonts and signatures")
115
+ end
116
+
117
+ def reconcile_styles
118
+ return unless profile
119
+
120
+ styles = package.styles
121
+ styles ||= begin
122
+ package.styles = Wordprocessingml::StylesConfiguration.new(include_defaults: false)
123
+ package.styles
124
+ end
125
+
126
+ styles.doc_defaults ||= build_doc_defaults
127
+ styles.latent_styles ||= build_latent_styles
128
+
129
+ ensure_default_styles(styles)
130
+
131
+ set_mc_ignorable(styles)
132
+ record_fix(FixCodes::STYLE_DEFAULTS_ADDED,
133
+ "Ensured styles have docDefaults, latentStyles, and default styles")
134
+ end
135
+
136
+ def reconcile_numbering
137
+ return unless profile
138
+ return unless package.numbering
139
+
140
+ set_mc_ignorable(package.numbering, prefixes: FULL_IGNORABLE)
141
+
142
+ package.numbering.instances.each_with_index do |inst, idx|
143
+ next if inst.durable_id
144
+
145
+ raw = hex_derive("durableId:#{inst.num_id}:#{idx}", 4).to_i(16)
146
+ raw = raw - 0x100000000 if raw >= 0x80000000
147
+ inst.durable_id = raw.to_s
148
+ record_fix(FixCodes::NUMBERING_REFERENCED,
149
+ "Generated w16cid:durableId for numId=#{inst.num_id}")
150
+ end
151
+
152
+ package.numbering.instances.each do |inst|
153
+ next unless inst.abstract_num_id
154
+
155
+ abs_id = if inst.abstract_num_id.is_a?(Uniword::Wordprocessingml::AbstractNumId)
156
+ inst.abstract_num_id.val
157
+ else
158
+ inst.abstract_num_id
159
+ end
160
+ defn = package.numbering.definitions.find do |d|
161
+ d.abstract_num_id == abs_id
162
+ end
163
+ next if defn
164
+
165
+ record_fix(FixCodes::NUMBERING_REFERENCED, "Numbering instance numId=#{inst.num_id} references " \
166
+ "missing abstractNumId=#{abs_id}")
167
+ end
168
+ end
169
+
170
+ def reconcile_web_settings
171
+ return unless profile
172
+
173
+ ws = package.web_settings
174
+ ws ||= begin
175
+ package.web_settings = Wordprocessingml::WebSettings.new
176
+ package.web_settings
177
+ end
178
+
179
+ set_mc_ignorable(ws)
180
+ record_fix(FixCodes::MC_IGNORABLE, "Cleared mc:Ignorable on webSettings")
181
+ end
182
+
183
+ def reconcile_app_properties
184
+ return unless profile
185
+
186
+ app = package.app_properties
187
+ app ||= begin
188
+ package.app_properties = Ooxml::AppProperties.new
189
+ package.app_properties
190
+ end
191
+
192
+ app.template = "Normal.dotm"
193
+ app.application = profile.application_name
194
+ app.app_version = profile.app_version
195
+ if profile.user_company && !profile.user_company.empty?
196
+ app.company = profile.user_company
197
+ end
198
+
199
+ unless app.pages && !app.pages.to_s.empty?
200
+ stats = calculate_document_statistics
201
+ app.pages = stats[:pages].to_s
202
+ app.words = stats[:words].to_s
203
+ app.characters = stats[:characters].to_s
204
+ app.characters_with_spaces = stats[:characters_with_spaces].to_s
205
+ app.paragraphs = stats[:paragraphs].to_s
206
+ app.lines = stats[:lines].to_s
207
+ end
208
+
209
+ app.total_time = app.total_time || "0"
210
+ app.scale_crop = app.scale_crop || "false"
211
+ app.doc_security = app.doc_security || "0"
212
+ app.links_up_to_date = app.links_up_to_date || "false"
213
+ app.shared_doc = app.shared_doc || "false"
214
+ app.hyperlinks_changed = app.hyperlinks_changed || "false"
215
+
216
+ app.heading_pairs = nil
217
+ app.titles_of_parts = nil
218
+
219
+ record_fix(FixCodes::APP_PROPERTIES_ENSURED, "Ensured app properties with statistics")
220
+ end
221
+
222
+ def reconcile_core_properties
223
+ return unless profile
224
+
225
+ old_cp = package.core_properties
226
+ package.core_properties = if old_cp
227
+ Ooxml::CoreProperties.new(
228
+ title: old_cp.title,
229
+ subject: old_cp.subject,
230
+ creator: old_cp.creator,
231
+ keywords: old_cp.keywords,
232
+ description: old_cp.description,
233
+ last_modified_by: old_cp.last_modified_by,
234
+ revision: old_cp.revision,
235
+ created: old_cp.created,
236
+ modified: old_cp.modified,
237
+ )
238
+ else
239
+ Ooxml::CoreProperties.new
240
+ end
241
+ cp = package.core_properties
242
+
243
+ if profile.user_name && !profile.user_name.empty?
244
+ cp.last_modified_by = profile.user_name
245
+ cp.creator ||= profile.user_name
246
+ end
247
+
248
+ cp.last_modified_by ||= profile.application_name
249
+
250
+ now = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
251
+ cp.modified = Ooxml::Types::DctermsModifiedType.new(
252
+ value: now, type: "dcterms:W3CDTF",
253
+ )
254
+ cp.created ||= Ooxml::Types::DctermsCreatedType.new(
255
+ value: now, type: "dcterms:W3CDTF",
256
+ )
257
+
258
+ cp.revision = "1" unless cp.revision
259
+ record_fix(FixCodes::CORE_PROPERTIES_REBUILT, "Rebuilt core properties with namespace declarations")
260
+ end
261
+
262
+ private
263
+
264
+ def calculate_document_statistics
265
+ DocumentStatistics.new(package).calculate
266
+ end
267
+
268
+ def build_compat
269
+ Wordprocessingml::Compat.new(
270
+ use_fe_layout: Wordprocessingml::UseFELayout.new,
271
+ compatSetting: [
272
+ Wordprocessingml::CompatSetting.new(
273
+ name: "compatibilityMode",
274
+ uri: "http://schemas.microsoft.com/office/word",
275
+ val: profile.compat_mode,
276
+ ),
277
+ Wordprocessingml::CompatSetting.new(
278
+ name: "overrideTableStyleFontSizeAndJustification",
279
+ uri: "http://schemas.microsoft.com/office/word",
280
+ val: "1",
281
+ ),
282
+ Wordprocessingml::CompatSetting.new(
283
+ name: "enableOpenTypeFeatures",
284
+ uri: "http://schemas.microsoft.com/office/word",
285
+ val: "1",
286
+ ),
287
+ Wordprocessingml::CompatSetting.new(
288
+ name: "doNotFlipMirrorIndents",
289
+ uri: "http://schemas.microsoft.com/office/word",
290
+ val: "1",
291
+ ),
292
+ Wordprocessingml::CompatSetting.new(
293
+ name: "differentiateMultirowTableHeaders",
294
+ uri: "http://schemas.microsoft.com/office/word",
295
+ val: "1",
296
+ ),
297
+ Wordprocessingml::CompatSetting.new(
298
+ name: "useWord2013TrackBottomHyphenation",
299
+ uri: "http://schemas.microsoft.com/office/word",
300
+ val: "0",
301
+ ),
302
+ ],
303
+ )
304
+ end
305
+
306
+ def build_rsids(rsid)
307
+ root = "00#{hex_derive("rsid_root", 3)}"
308
+ Wordprocessingml::Rsids.new(
309
+ rsid_root: Wordprocessingml::RsidRoot.new(val: root),
310
+ rsid: [Wordprocessingml::Rsid.new(val: rsid)],
311
+ )
312
+ end
313
+
314
+ def build_math_pr
315
+ Wordprocessingml::MathPr.new(
316
+ math_font: Wordprocessingml::MathFont.new(val: "Cambria Math"),
317
+ brk_bin: Wordprocessingml::BrkBin.new(val: "before"),
318
+ brk_bin_sub: Wordprocessingml::BrkBinSub.new(val: "--"),
319
+ small_frac: Wordprocessingml::SmallFrac.new(val: "0"),
320
+ disp_def: Wordprocessingml::DispDef.new,
321
+ l_margin: Wordprocessingml::LMargin.new(val: "0"),
322
+ r_margin: Wordprocessingml::RMargin.new(val: "0"),
323
+ def_jc: Wordprocessingml::DefJc.new(val: "centerGroup"),
324
+ wrap_indent: Wordprocessingml::WrapIndent.new(val: "1440"),
325
+ int_lim: Wordprocessingml::IntLim.new(val: "subSup"),
326
+ nary_lim: Wordprocessingml::NaryLim.new(val: "undOvr"),
327
+ )
328
+ end
329
+
330
+ def build_clr_scheme_mapping
331
+ Wordprocessingml::ClrSchemeMapping.new(
332
+ bg1: "light1", t1: "dark1", bg2: "light2", t2: "dark2",
333
+ accent1: "accent1", accent2: "accent2", accent3: "accent3",
334
+ accent4: "accent4", accent5: "accent5", accent6: "accent6",
335
+ hyperlink: "hyperlink", followed_hyperlink: "followedHyperlink"
336
+ )
337
+ end
338
+
339
+ def build_doc_defaults
340
+ r_pr = Wordprocessingml::RunProperties.new(
341
+ fonts: Properties::RunFonts.new(
342
+ ascii_theme: "minorHAnsi",
343
+ east_asia_theme: "minorEastAsia",
344
+ h_ansi_theme: "minorHAnsi",
345
+ cs_theme: "minorBidi",
346
+ ),
347
+ kerning: Properties::Kerning.new(value: 2),
348
+ size: Properties::FontSize.new(value: 24),
349
+ size_cs: Properties::FontSize.new(value: 24),
350
+ language: Properties::Language.new(
351
+ val: profile.lang,
352
+ east_asia: profile.east_asia_lang,
353
+ bidi: profile.bidi_lang,
354
+ ),
355
+ ligatures: Uniword::Wordprocessingml2010::Ligatures.new(
356
+ val: "standardContextual",
357
+ ),
358
+ )
359
+
360
+ p_pr = Wordprocessingml::ParagraphProperties.new(
361
+ spacing: Properties::Spacing.new(after: 160, line: 278,
362
+ line_rule: "auto"),
363
+ )
364
+
365
+ Wordprocessingml::DocDefaults.new(
366
+ rPrDefault: Wordprocessingml::RPrDefault.new(rPr: r_pr),
367
+ pPrDefault: Wordprocessingml::PPrDefault.new(pPr: p_pr),
368
+ )
369
+ end
370
+
371
+ def build_latent_styles
372
+ config = load_latent_styles_config
373
+ return Wordprocessingml::LatentStyles.new(count: 0) unless config
374
+
375
+ exceptions = (config["exceptions"] || []).map do |ex|
376
+ attrs = { name: ex["name"] }
377
+ attrs[:ui_priority] = ex["uiPriority"].to_i if ex["uiPriority"]
378
+ attrs[:q_format] = ex["qFormat"] if ex["qFormat"]
379
+ attrs[:semi_hidden] = ex["semiHidden"] if ex["semiHidden"]
380
+ if ex["unhideWhenUsed"]
381
+ attrs[:unhide_when_used] = ex["unhideWhenUsed"]
382
+ end
383
+ attrs[:locked] = ex["locked"] if ex["locked"]
384
+ Wordprocessingml::LatentStylesException.new(attrs)
385
+ end
386
+
387
+ Wordprocessingml::LatentStyles.new(
388
+ def_locked_state: config["defLockedState"],
389
+ def_ui_priority: config["defUIPriority"].to_i,
390
+ def_semi_hidden: config["defSemiHidden"],
391
+ def_unhide_when_used: config["defUnhideWhenUsed"],
392
+ def_q_format: config["defQFormat"],
393
+ count: config["count"].to_i,
394
+ lsd_exception: exceptions,
395
+ )
396
+ end
397
+
398
+ def ensure_default_styles(styles)
399
+ style_ids = styles.styles.to_set(&:id)
400
+
401
+ unless style_ids.include?("Normal")
402
+ styles.add_style(Wordprocessingml::Style.new(
403
+ type: "paragraph", default: true, styleId: "Normal",
404
+ name: Wordprocessingml::StyleName.new(val: "Normal"),
405
+ qFormat: Properties::QuickFormat.new
406
+ ))
407
+ end
408
+
409
+ unless style_ids.include?("DefaultParagraphFont")
410
+ styles.add_style(Wordprocessingml::Style.new(
411
+ type: "character", default: true,
412
+ styleId: "DefaultParagraphFont",
413
+ name: Wordprocessingml::StyleName.new(val: "Default Paragraph Font"),
414
+ uiPriority: Wordprocessingml::UiPriority.new(val: 1),
415
+ semiHidden: Wordprocessingml::SemiHidden.new,
416
+ unhideWhenUsed: Wordprocessingml::UnhideWhenUsed.new
417
+ ))
418
+ end
419
+
420
+ dpf = styles.styles.find { |s| s.id == "DefaultParagraphFont" }
421
+ if dpf && !dpf.semiHidden && !allocator
422
+ dpf.semiHidden = Wordprocessingml::SemiHidden.new
423
+ ensure_element_in_order(dpf, "semiHidden", after: "uiPriority")
424
+ record_fix(FixCodes::SEMI_HIDDEN_ADDED, "Added semiHidden to DefaultParagraphFont style")
425
+ end
426
+
427
+ unless style_ids.include?("TableNormal")
428
+ tbl_pr = Wordprocessingml::TableProperties.new(
429
+ table_indent: Properties::TableIndent.new(value: 0, type: "dxa"),
430
+ table_cell_margin: Properties::TableCellMargin.new(
431
+ top: Properties::Margin.new(w: 0, type: "dxa"),
432
+ left: Properties::Margin.new(w: 108, type: "dxa"),
433
+ bottom: Properties::Margin.new(w: 0, type: "dxa"),
434
+ right: Properties::Margin.new(w: 108, type: "dxa"),
435
+ ),
436
+ )
437
+
438
+ styles.add_style(Wordprocessingml::Style.new(
439
+ type: "table", default: true, styleId: "TableNormal",
440
+ name: Wordprocessingml::StyleName.new(val: "Normal Table"),
441
+ uiPriority: Wordprocessingml::UiPriority.new(val: 99),
442
+ semiHidden: Wordprocessingml::SemiHidden.new,
443
+ unhideWhenUsed: Wordprocessingml::UnhideWhenUsed.new,
444
+ tblPr: tbl_pr
445
+ ))
446
+ end
447
+
448
+ return if style_ids.include?("NoList")
449
+
450
+ styles.add_style(Wordprocessingml::Style.new(
451
+ type: "numbering", default: true, styleId: "NoList",
452
+ name: Wordprocessingml::StyleName.new(val: "No List"),
453
+ uiPriority: Wordprocessingml::UiPriority.new(val: 99),
454
+ semiHidden: Wordprocessingml::SemiHidden.new,
455
+ unhideWhenUsed: Wordprocessingml::UnhideWhenUsed.new
456
+ ))
457
+ end
458
+
459
+ def font_names_for_profile
460
+ names = []
461
+ fs = profile.system.font_scheme
462
+ loc = profile.locale
463
+
464
+ names << fs&.minor_font if fs&.minor_font
465
+
466
+ ea_font = loc&.east_asian_font
467
+ ea_light = loc&.east_asian_light_font
468
+
469
+ if loc.east_asia_lang == "zh-CN"
470
+ ea_font ||= "DengXian"
471
+ ea_light ||= "DengXian Light"
472
+ end
473
+
474
+ names << ea_font if ea_font
475
+ names << "Times New Roman"
476
+ names << ea_light if ea_light
477
+ names << fs&.major_font if fs&.major_font
478
+
479
+ names.uniq
480
+ end
481
+ end
482
+ end
483
+ end
484
+ end