uniword 1.0.11 → 1.2.5

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