shoko 0.1.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 (294) hide show
  1. checksums.yaml +7 -0
  2. data/.bundle/config +4 -0
  3. data/.bundle/config.bak +3 -0
  4. data/.rspec_status +42 -0
  5. data/.rubocop.yml +124 -0
  6. data/Gemfile +19 -0
  7. data/LICENSE +21 -0
  8. data/README.md +82 -0
  9. data/Rakefile +29 -0
  10. data/bin/start +15 -0
  11. data/lib/shoko/adapters/book_sources/document_service.rb +201 -0
  12. data/lib/shoko/adapters/book_sources/download_service.rb +95 -0
  13. data/lib/shoko/adapters/book_sources/epub/epub_resource_loader.rb +137 -0
  14. data/lib/shoko/adapters/book_sources/epub/parsers/html_processor.rb +151 -0
  15. data/lib/shoko/adapters/book_sources/epub/parsers/metadata_extractor.rb +53 -0
  16. data/lib/shoko/adapters/book_sources/epub/parsers/opf/entry_reader.rb +77 -0
  17. data/lib/shoko/adapters/book_sources/epub/parsers/opf/metadata_extractor.rb +67 -0
  18. data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_context.rb +86 -0
  19. data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_document_index.rb +75 -0
  20. data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_document_scanner.rb +47 -0
  21. data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_extractor.rb +46 -0
  22. data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_label_resolver.rb +83 -0
  23. data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_list_item.rb +55 -0
  24. data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_result.rb +8 -0
  25. data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_selector.rb +100 -0
  26. data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_source_locator.rb +93 -0
  27. data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_traversal.rb +103 -0
  28. data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_walker.rb +56 -0
  29. data/lib/shoko/adapters/book_sources/epub/parsers/opf_processor.rb +102 -0
  30. data/lib/shoko/adapters/book_sources/epub/parsers/xhtml_content_parser.rb +661 -0
  31. data/lib/shoko/adapters/book_sources/epub/parsers/xml_text_normalizer.rb +41 -0
  32. data/lib/shoko/adapters/book_sources/epub_document.rb +253 -0
  33. data/lib/shoko/adapters/book_sources/epub_finder/directory_scanner.rb +134 -0
  34. data/lib/shoko/adapters/book_sources/epub_finder/scanner_context.rb +28 -0
  35. data/lib/shoko/adapters/book_sources/epub_finder.rb +161 -0
  36. data/lib/shoko/adapters/book_sources/epub_importer.rb +268 -0
  37. data/lib/shoko/adapters/book_sources/gutendex_client.rb +150 -0
  38. data/lib/shoko/adapters/book_sources/library_scanner.rb +93 -0
  39. data/lib/shoko/adapters/book_sources/source_fingerprint.rb +57 -0
  40. data/lib/shoko/adapters/input/annotations/mouse_handler.rb +84 -0
  41. data/lib/shoko/adapters/input/command_bridge.rb +148 -0
  42. data/lib/shoko/adapters/input/command_factory.rb +255 -0
  43. data/lib/shoko/adapters/input/commands.rb +60 -0
  44. data/lib/shoko/adapters/input/dispatcher.rb +69 -0
  45. data/lib/shoko/adapters/input/input_controller.rb +250 -0
  46. data/lib/shoko/adapters/input/key_definitions.rb +108 -0
  47. data/lib/shoko/adapters/input/validators/file_path_validator.rb +81 -0
  48. data/lib/shoko/adapters/input/validators/terminal_size_validator.rb +76 -0
  49. data/lib/shoko/adapters/monitoring/logger.rb +150 -0
  50. data/lib/shoko/adapters/monitoring/perf_tracer.rb +183 -0
  51. data/lib/shoko/adapters/monitoring/performance_monitor.rb +110 -0
  52. data/lib/shoko/adapters/output/clipboard/clipboard_service.rb +125 -0
  53. data/lib/shoko/adapters/output/formatting/formatting_service/line_assembler/image_builder.rb +149 -0
  54. data/lib/shoko/adapters/output/formatting/formatting_service/line_assembler/text_wrapper.rb +149 -0
  55. data/lib/shoko/adapters/output/formatting/formatting_service/line_assembler/tokenizer.rb +91 -0
  56. data/lib/shoko/adapters/output/formatting/formatting_service/line_assembler.rb +144 -0
  57. data/lib/shoko/adapters/output/formatting/formatting_service/plain_lines_builder.rb +54 -0
  58. data/lib/shoko/adapters/output/formatting/formatting_service.rb +247 -0
  59. data/lib/shoko/adapters/output/formatting/wrapping_service.rb +228 -0
  60. data/lib/shoko/adapters/output/instrumentation_service.rb +52 -0
  61. data/lib/shoko/adapters/output/kitty/image_transcoder.rb +71 -0
  62. data/lib/shoko/adapters/output/kitty/kitty_graphics.rb +114 -0
  63. data/lib/shoko/adapters/output/kitty/kitty_image_renderer.rb +239 -0
  64. data/lib/shoko/adapters/output/kitty/kitty_unicode_placeholders.rb +139 -0
  65. data/lib/shoko/adapters/output/kitty/kitty_unicode_placeholders_diacritic_codepoints.txt +26 -0
  66. data/lib/shoko/adapters/output/notification_service.rb +58 -0
  67. data/lib/shoko/adapters/output/render_registry.rb +45 -0
  68. data/lib/shoko/adapters/output/rendering/models/line_geometry.rb +60 -0
  69. data/lib/shoko/adapters/output/rendering/models/page_rendering_context.rb +22 -0
  70. data/lib/shoko/adapters/output/rendering/models/render_params.rb +28 -0
  71. data/lib/shoko/adapters/output/rendering/models/rendering_context.rb +58 -0
  72. data/lib/shoko/adapters/output/terminal/buffer.rb +275 -0
  73. data/lib/shoko/adapters/output/terminal/constants/terminal_defaults.rb +11 -0
  74. data/lib/shoko/adapters/output/terminal/input/decoder.rb +347 -0
  75. data/lib/shoko/adapters/output/terminal/input.rb +161 -0
  76. data/lib/shoko/adapters/output/terminal/output.rb +105 -0
  77. data/lib/shoko/adapters/output/terminal/terminal.rb +167 -0
  78. data/lib/shoko/adapters/output/terminal/terminal_sanitizer.rb +243 -0
  79. data/lib/shoko/adapters/output/terminal/terminal_service.rb +138 -0
  80. data/lib/shoko/adapters/output/terminal/text_metrics.rb +273 -0
  81. data/lib/shoko/adapters/output/ui/builders/page_setup_builder.rb +47 -0
  82. data/lib/shoko/adapters/output/ui/components/annotation_editor_overlay/footer_renderer.rb +80 -0
  83. data/lib/shoko/adapters/output/ui/components/annotation_editor_overlay/geometry.rb +61 -0
  84. data/lib/shoko/adapters/output/ui/components/annotation_editor_overlay/note_renderer.rb +86 -0
  85. data/lib/shoko/adapters/output/ui/components/annotation_editor_overlay_component.rb +234 -0
  86. data/lib/shoko/adapters/output/ui/components/annotations_overlay/list_renderer.rb +142 -0
  87. data/lib/shoko/adapters/output/ui/components/annotations_overlay_component.rb +185 -0
  88. data/lib/shoko/adapters/output/ui/components/base_component.rb +110 -0
  89. data/lib/shoko/adapters/output/ui/components/component_interface.rb +80 -0
  90. data/lib/shoko/adapters/output/ui/components/content_component.rb +61 -0
  91. data/lib/shoko/adapters/output/ui/components/enhanced_popup_menu.rb +191 -0
  92. data/lib/shoko/adapters/output/ui/components/footer_component.rb +120 -0
  93. data/lib/shoko/adapters/output/ui/components/header_component.rb +46 -0
  94. data/lib/shoko/adapters/output/ui/components/layouts/horizontal.rb +63 -0
  95. data/lib/shoko/adapters/output/ui/components/layouts/vertical.rb +73 -0
  96. data/lib/shoko/adapters/output/ui/components/main_menu_component.rb +103 -0
  97. data/lib/shoko/adapters/output/ui/components/reading/base_view_renderer.rb +199 -0
  98. data/lib/shoko/adapters/output/ui/components/reading/config_helpers.rb +42 -0
  99. data/lib/shoko/adapters/output/ui/components/reading/help_renderer.rb +62 -0
  100. data/lib/shoko/adapters/output/ui/components/reading/inline_segment_highlighter.rb +144 -0
  101. data/lib/shoko/adapters/output/ui/components/reading/kitty_image_line_renderer.rb +262 -0
  102. data/lib/shoko/adapters/output/ui/components/reading/line_content_composer.rb +114 -0
  103. data/lib/shoko/adapters/output/ui/components/reading/line_drawer.rb +87 -0
  104. data/lib/shoko/adapters/output/ui/components/reading/line_geometry_builder.rb +41 -0
  105. data/lib/shoko/adapters/output/ui/components/reading/rendered_lines_recorder.rb +64 -0
  106. data/lib/shoko/adapters/output/ui/components/reading/single_view_renderer.rb +156 -0
  107. data/lib/shoko/adapters/output/ui/components/reading/split_view_renderer.rb +221 -0
  108. data/lib/shoko/adapters/output/ui/components/reading/view_renderer_factory.rb +20 -0
  109. data/lib/shoko/adapters/output/ui/components/reading/wrapped_lines_fetcher.rb +139 -0
  110. data/lib/shoko/adapters/output/ui/components/rect.rb +15 -0
  111. data/lib/shoko/adapters/output/ui/components/render_style.rb +84 -0
  112. data/lib/shoko/adapters/output/ui/components/screen_component.rb +24 -0
  113. data/lib/shoko/adapters/output/ui/components/screens/annotation_detail_screen_component.rb +175 -0
  114. data/lib/shoko/adapters/output/ui/components/screens/annotation_edit_screen_component.rb +221 -0
  115. data/lib/shoko/adapters/output/ui/components/screens/annotation_editor_screen_component.rb +205 -0
  116. data/lib/shoko/adapters/output/ui/components/screens/annotation_rendering_helpers.rb +190 -0
  117. data/lib/shoko/adapters/output/ui/components/screens/annotations_screen_component.rb +266 -0
  118. data/lib/shoko/adapters/output/ui/components/screens/base_screen_component.rb +49 -0
  119. data/lib/shoko/adapters/output/ui/components/screens/browse_screen_component.rb +319 -0
  120. data/lib/shoko/adapters/output/ui/components/screens/download_books_screen_component.rb +340 -0
  121. data/lib/shoko/adapters/output/ui/components/screens/library_screen_component.rb +205 -0
  122. data/lib/shoko/adapters/output/ui/components/screens/loading_overlay_component.rb +49 -0
  123. data/lib/shoko/adapters/output/ui/components/screens/menu_screen_component.rb +107 -0
  124. data/lib/shoko/adapters/output/ui/components/screens/settings_screen_component.rb +238 -0
  125. data/lib/shoko/adapters/output/ui/components/sidebar/annotations_tab_renderer.rb +159 -0
  126. data/lib/shoko/adapters/output/ui/components/sidebar/bookmarks_tab_renderer.rb +139 -0
  127. data/lib/shoko/adapters/output/ui/components/sidebar/tab_header_component.rb +157 -0
  128. data/lib/shoko/adapters/output/ui/components/sidebar/toc_tab_renderer.rb +111 -0
  129. data/lib/shoko/adapters/output/ui/components/sidebar/toc_tab_support.rb +1606 -0
  130. data/lib/shoko/adapters/output/ui/components/sidebar_panel_component.rb +217 -0
  131. data/lib/shoko/adapters/output/ui/components/surface.rb +88 -0
  132. data/lib/shoko/adapters/output/ui/components/tooltip_overlay_component.rb +224 -0
  133. data/lib/shoko/adapters/output/ui/components/ui/box_drawer.rb +32 -0
  134. data/lib/shoko/adapters/output/ui/components/ui/list_helpers.rb +33 -0
  135. data/lib/shoko/adapters/output/ui/components/ui/overlay_layout.rb +79 -0
  136. data/lib/shoko/adapters/output/ui/components/ui/text_utils.rb +46 -0
  137. data/lib/shoko/adapters/output/ui/constants/highlighting.rb +21 -0
  138. data/lib/shoko/adapters/output/ui/constants/messages.rb +12 -0
  139. data/lib/shoko/adapters/output/ui/constants/themes.rb +79 -0
  140. data/lib/shoko/adapters/output/ui/constants/ui_constants.rb +85 -0
  141. data/lib/shoko/adapters/output/ui/rendering/frame_coordinator.rb +42 -0
  142. data/lib/shoko/adapters/output/ui/rendering/reader_render_coordinator.rb +169 -0
  143. data/lib/shoko/adapters/output/ui/rendering/render_pipeline.rb +55 -0
  144. data/lib/shoko/adapters/storage/atomic_file_writer.rb +43 -0
  145. data/lib/shoko/adapters/storage/background_worker.rb +66 -0
  146. data/lib/shoko/adapters/storage/book_cache_pipeline.rb +653 -0
  147. data/lib/shoko/adapters/storage/cache/epub/memory_cache.rb +99 -0
  148. data/lib/shoko/adapters/storage/cache/epub/persistence.rb +131 -0
  149. data/lib/shoko/adapters/storage/cache/epub/serializer/deserialize.rb +225 -0
  150. data/lib/shoko/adapters/storage/cache/epub/serializer/helpers.rb +63 -0
  151. data/lib/shoko/adapters/storage/cache/epub/serializer/serialize.rb +83 -0
  152. data/lib/shoko/adapters/storage/cache/epub/serializer.rb +5 -0
  153. data/lib/shoko/adapters/storage/cache/epub/source_reference.rb +58 -0
  154. data/lib/shoko/adapters/storage/cache_paths.rb +21 -0
  155. data/lib/shoko/adapters/storage/cache_pointer_manager.rb +60 -0
  156. data/lib/shoko/adapters/storage/config_paths.rb +30 -0
  157. data/lib/shoko/adapters/storage/epub_cache.rb +195 -0
  158. data/lib/shoko/adapters/storage/file_writer_service.rb +47 -0
  159. data/lib/shoko/adapters/storage/json_cache_store/chapters.rb +141 -0
  160. data/lib/shoko/adapters/storage/json_cache_store/layouts.rb +67 -0
  161. data/lib/shoko/adapters/storage/json_cache_store/manifest.rb +42 -0
  162. data/lib/shoko/adapters/storage/json_cache_store/payload_helpers.rb +113 -0
  163. data/lib/shoko/adapters/storage/json_cache_store/resources.rb +84 -0
  164. data/lib/shoko/adapters/storage/json_cache_store.rb +167 -0
  165. data/lib/shoko/adapters/storage/lazy_file_string.rb +65 -0
  166. data/lib/shoko/adapters/storage/pagination_cache.rb +127 -0
  167. data/lib/shoko/adapters/storage/recent_files.rb +78 -0
  168. data/lib/shoko/adapters/storage/repositories/annotation_repository.rb +182 -0
  169. data/lib/shoko/adapters/storage/repositories/base_repository.rb +81 -0
  170. data/lib/shoko/adapters/storage/repositories/bookmark_repository.rb +132 -0
  171. data/lib/shoko/adapters/storage/repositories/cached_library_repository.rb +129 -0
  172. data/lib/shoko/adapters/storage/repositories/config_repository.rb +262 -0
  173. data/lib/shoko/adapters/storage/repositories/progress_repository.rb +166 -0
  174. data/lib/shoko/adapters/storage/repositories/storage/annotation_file_store.rb +128 -0
  175. data/lib/shoko/adapters/storage/repositories/storage/bookmark_file_store.rb +109 -0
  176. data/lib/shoko/adapters/storage/repositories/storage/file_store_utils.rb +20 -0
  177. data/lib/shoko/adapters/storage/repositories/storage/progress_file_store.rb +59 -0
  178. data/lib/shoko/application/annotation_editor_overlay_session.rb +138 -0
  179. data/lib/shoko/application/cli.rb +134 -0
  180. data/lib/shoko/application/controllers/menu/input_controller.rb +189 -0
  181. data/lib/shoko/application/controllers/menu/state_controller.rb +642 -0
  182. data/lib/shoko/application/controllers/menu_controller.rb +469 -0
  183. data/lib/shoko/application/controllers/mouseable_reader.rb +377 -0
  184. data/lib/shoko/application/controllers/reader_controller.rb +449 -0
  185. data/lib/shoko/application/controllers/state_controller.rb +410 -0
  186. data/lib/shoko/application/controllers/ui_controller.rb +782 -0
  187. data/lib/shoko/application/dependency_container.rb +301 -0
  188. data/lib/shoko/application/infrastructure/event_bus.rb +80 -0
  189. data/lib/shoko/application/infrastructure/observer_state_store.rb +136 -0
  190. data/lib/shoko/application/infrastructure/state_store.rb +413 -0
  191. data/lib/shoko/application/main_menu/menu_progress_presenter.rb +83 -0
  192. data/lib/shoko/application/pending_jump_handler.rb +122 -0
  193. data/lib/shoko/application/reader_lifecycle.rb +65 -0
  194. data/lib/shoko/application/reader_startup_orchestrator.rb +113 -0
  195. data/lib/shoko/application/selectors/config_selectors.rb +62 -0
  196. data/lib/shoko/application/selectors/menu_selectors.rb +62 -0
  197. data/lib/shoko/application/selectors/reader_selectors.rb +186 -0
  198. data/lib/shoko/application/state/actions/base_action.rb +24 -0
  199. data/lib/shoko/application/state/actions/quit_to_menu_action.rb +16 -0
  200. data/lib/shoko/application/state/actions/switch_reader_mode_action.rb +22 -0
  201. data/lib/shoko/application/state/actions/toggle_view_mode_action.rb +31 -0
  202. data/lib/shoko/application/state/actions/update_annotation_editor_overlay_action.rb +27 -0
  203. data/lib/shoko/application/state/actions/update_annotations_action.rb +20 -0
  204. data/lib/shoko/application/state/actions/update_annotations_overlay_action.rb +27 -0
  205. data/lib/shoko/application/state/actions/update_bookmarks_action.rb +20 -0
  206. data/lib/shoko/application/state/actions/update_chapter_action.rb +24 -0
  207. data/lib/shoko/application/state/actions/update_config_action.rb +22 -0
  208. data/lib/shoko/application/state/actions/update_field_helpers.rb +26 -0
  209. data/lib/shoko/application/state/actions/update_menu_action.rb +21 -0
  210. data/lib/shoko/application/state/actions/update_message_action.rb +35 -0
  211. data/lib/shoko/application/state/actions/update_page_action.rb +21 -0
  212. data/lib/shoko/application/state/actions/update_pagination_state_action.rb +21 -0
  213. data/lib/shoko/application/state/actions/update_popup_menu_action.rb +27 -0
  214. data/lib/shoko/application/state/actions/update_reader_meta_action.rb +21 -0
  215. data/lib/shoko/application/state/actions/update_reader_mode_action.rb +20 -0
  216. data/lib/shoko/application/state/actions/update_rendered_lines_action.rb +40 -0
  217. data/lib/shoko/application/state/actions/update_selection_action.rb +27 -0
  218. data/lib/shoko/application/state/actions/update_selections_action.rb +21 -0
  219. data/lib/shoko/application/state/actions/update_sidebar_action.rb +34 -0
  220. data/lib/shoko/application/state/actions/update_ui_loading_action.rb +23 -0
  221. data/lib/shoko/application/ui/reader_view_model_builder.rb +74 -0
  222. data/lib/shoko/application/ui/view_models/reader_view_model.rb +177 -0
  223. data/lib/shoko/application/unified_application.rb +48 -0
  224. data/lib/shoko/application/use_cases/catalog_service.rb +117 -0
  225. data/lib/shoko/application/use_cases/commands/annotation_editor_commands.rb +105 -0
  226. data/lib/shoko/application/use_cases/commands/application_commands.rb +208 -0
  227. data/lib/shoko/application/use_cases/commands/base_command.rb +166 -0
  228. data/lib/shoko/application/use_cases/commands/bookmark_commands.rb +114 -0
  229. data/lib/shoko/application/use_cases/commands/conditional_navigation_commands.rb +57 -0
  230. data/lib/shoko/application/use_cases/commands/menu_commands.rb +170 -0
  231. data/lib/shoko/application/use_cases/commands/navigation_commands.rb +183 -0
  232. data/lib/shoko/application/use_cases/commands/reader_commands.rb +46 -0
  233. data/lib/shoko/application/use_cases/commands/sidebar_commands.rb +55 -0
  234. data/lib/shoko/application/use_cases/settings_service.rb +123 -0
  235. data/lib/shoko/core/events/annotation_events.rb +94 -0
  236. data/lib/shoko/core/events/base_domain_event.rb +169 -0
  237. data/lib/shoko/core/events/bookmark_events.rb +41 -0
  238. data/lib/shoko/core/events/domain_event_bus.rb +163 -0
  239. data/lib/shoko/core/events/progress_events.rb +108 -0
  240. data/lib/shoko/core/models/bookmark.rb +36 -0
  241. data/lib/shoko/core/models/bookmark_data.rb +10 -0
  242. data/lib/shoko/core/models/chapter.rb +25 -0
  243. data/lib/shoko/core/models/content_block.rb +44 -0
  244. data/lib/shoko/core/models/reader_settings.rb +20 -0
  245. data/lib/shoko/core/models/selection_anchor.rb +73 -0
  246. data/lib/shoko/core/models/toc_entry.rb +14 -0
  247. data/lib/shoko/core/ports/annotation_repository.rb +0 -0
  248. data/lib/shoko/core/ports/book_repository.rb +0 -0
  249. data/lib/shoko/core/ports/book_source.rb +0 -0
  250. data/lib/shoko/core/ports/bookmark_repository.rb +0 -0
  251. data/lib/shoko/core/ports/cache.rb +0 -0
  252. data/lib/shoko/core/ports/input_handler.rb +0 -0
  253. data/lib/shoko/core/ports/renderer.rb +0 -0
  254. data/lib/shoko/core/ports/storage.rb +0 -0
  255. data/lib/shoko/core/services/annotation_service.rb +102 -0
  256. data/lib/shoko/core/services/base_service.rb +60 -0
  257. data/lib/shoko/core/services/bookmark_service.rb +267 -0
  258. data/lib/shoko/core/services/coordinate_service.rb +265 -0
  259. data/lib/shoko/core/services/layout_service.rb +95 -0
  260. data/lib/shoko/core/services/navigation/absolute_change_applier.rb +96 -0
  261. data/lib/shoko/core/services/navigation/absolute_layout.rb +101 -0
  262. data/lib/shoko/core/services/navigation/absolute_strategy.rb +179 -0
  263. data/lib/shoko/core/services/navigation/context_builder.rb +52 -0
  264. data/lib/shoko/core/services/navigation/context_helpers.rb +63 -0
  265. data/lib/shoko/core/services/navigation/dynamic_change_applier.rb +50 -0
  266. data/lib/shoko/core/services/navigation/dynamic_strategy.rb +51 -0
  267. data/lib/shoko/core/services/navigation/image_offset_snapper.rb +150 -0
  268. data/lib/shoko/core/services/navigation/nav_context.rb +27 -0
  269. data/lib/shoko/core/services/navigation/state_updater.rb +29 -0
  270. data/lib/shoko/core/services/navigation/strategy_factory.rb +20 -0
  271. data/lib/shoko/core/services/navigation_service.rb +150 -0
  272. data/lib/shoko/core/services/page_calculator_service.rb +242 -0
  273. data/lib/shoko/core/services/pagination/internal/absolute_page_map_builder.rb +28 -0
  274. data/lib/shoko/core/services/pagination/internal/chapter_cache.rb +60 -0
  275. data/lib/shoko/core/services/pagination/internal/dynamic_page_map_builder.rb +157 -0
  276. data/lib/shoko/core/services/pagination/internal/layout_metrics_calculator.rb +73 -0
  277. data/lib/shoko/core/services/pagination/internal/page_hydrator.rb +145 -0
  278. data/lib/shoko/core/services/pagination/internal/pagination_workflow.rb +152 -0
  279. data/lib/shoko/core/services/pagination/page_info_calculator.rb +247 -0
  280. data/lib/shoko/core/services/pagination/pagination_cache_preloader.rb +173 -0
  281. data/lib/shoko/core/services/pagination/pagination_coordinator.rb +202 -0
  282. data/lib/shoko/core/services/pagination/pagination_orchestrator.rb +291 -0
  283. data/lib/shoko/core/services/pagination.rb +10 -0
  284. data/lib/shoko/core/services/progress_helper.rb +22 -0
  285. data/lib/shoko/core/services/selection_service.rb +126 -0
  286. data/lib/shoko/core/validator.rb +76 -0
  287. data/lib/shoko/shared/errors.rb +97 -0
  288. data/lib/shoko/shared/version.rb +5 -0
  289. data/lib/shoko/test_support/terminal_double.rb +175 -0
  290. data/lib/shoko/test_support/test_mode.rb +78 -0
  291. data/lib/shoko.rb +279 -0
  292. data/lib/zip.rb +732 -0
  293. data/zip.rb +5 -0
  294. metadata +370 -0
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shoko
4
+ module Adapters
5
+ module Output
6
+ module Kitty
7
+ # Helpers for emitting Kitty Unicode image placeholders (U+10EEEE) with diacritics.
8
+ # See: https://sw.kovidgoyal.net/kitty/graphics-protocol/#unicode-placeholders
9
+ module KittyUnicodePlaceholders
10
+ module_function
11
+
12
+ PLACEHOLDER_CHAR = "\u{10EEEE}"
13
+ RESET_FG = "\e[39m"
14
+ RESET_UNDERLINE = "\e[59m"
15
+ DIACRITIC_CODEPOINTS_PATH = ::File.join(__dir__, 'kitty_unicode_placeholders_diacritic_codepoints.txt')
16
+
17
+ def line(image_id:, grid:, placement_id: nil)
18
+ key = cache_key(image_id, grid, placement_id)
19
+ return '' unless key
20
+
21
+ cache = (@line_cache ||= {})
22
+ cache.fetch(key) { cache[key] = render_line(key) }
23
+ end
24
+
25
+ def diacritics
26
+ @diacritics ||= build_diacritics
27
+ end
28
+
29
+ def foreground(image_id)
30
+ rgb_sgr(image_id, sgr: 38, cache: (@fg_cache ||= {}))
31
+ end
32
+
33
+ def underline(placement_id)
34
+ rgb_sgr(placement_id, sgr: 58, cache: (@ul_cache ||= {}))
35
+ end
36
+
37
+ def build_diacritics
38
+ codepoints = load_diacritic_codepoints
39
+ loaded = codepoints.map { |hex| [Integer(hex, 16)].pack('U') }.freeze
40
+ raise ArgumentError, 'not enough diacritics to support Kitty placeholders' if loaded.length < 256
41
+
42
+ loaded
43
+ end
44
+
45
+ def cache_key(image_id, grid, placement_id)
46
+ image_i = positive_integer(image_id)
47
+ grid_i = valid_grid(grid)
48
+ return unless image_i && grid_i
49
+
50
+ placement_i = (integer_or_nil(placement_id) || 0).clamp(0, 0xFF_FF_FF)
51
+ (image_i << 40) | (placement_i << 16) | grid_i
52
+ end
53
+
54
+ def integer_or_nil(value)
55
+ Integer(value)
56
+ rescue ArgumentError, TypeError
57
+ nil
58
+ end
59
+
60
+ def load_diacritic_codepoints
61
+ ::File.read(DIACRITIC_CODEPOINTS_PATH, encoding: Encoding::UTF_8).split
62
+ end
63
+
64
+ def positive_integer(value)
65
+ int = integer_or_nil(value)
66
+ return unless int&.positive?
67
+
68
+ int & 0xFFFF_FFFF
69
+ end
70
+
71
+ def valid_grid(value)
72
+ grid_i = integer_or_nil(value)
73
+ return unless grid_i && (0..0xFFFF).cover?(grid_i) && (1..255).cover?(grid_i & 0xFF)
74
+
75
+ grid_i
76
+ end
77
+
78
+ def placeholder_cells(key)
79
+ marks = diacritics
80
+ cols = key & 0xFF
81
+ first = PLACEHOLDER_CHAR + marks.fetch((key >> 8) & 0xFF) + marks.fetch(0) + msb_diacritic(marks, key)
82
+ first + (cols > 1 ? (PLACEHOLDER_CHAR * (cols - 1)) : '')
83
+ end
84
+
85
+ def msb_diacritic(marks, key)
86
+ msb = (key >> 64) & 0xFF
87
+ msb.zero? ? '' : marks.fetch(msb)
88
+ end
89
+
90
+ def render_line(key)
91
+ cells = placeholder_cells(key)
92
+ wrap_sequences(key, cells)
93
+ end
94
+
95
+ def wrap_sequences(key, cells)
96
+ image_id = (key >> 40) & 0xFFFF_FFFF
97
+ placement_id = (key >> 16) & 0xFF_FF_FF
98
+ fg = foreground(image_id)
99
+
100
+ return "#{fg}#{cells}#{RESET_FG}" unless placement_id.positive?
101
+
102
+ "#{fg}#{underline(placement_id)}#{cells}#{RESET_FG}#{RESET_UNDERLINE}"
103
+ end
104
+
105
+ def rgb_sgr(value, sgr:, cache:)
106
+ bits = integer_or_nil(value)
107
+ return '' unless bits
108
+
109
+ bits &= 0xFF_FF_FF
110
+ cache.fetch(bits) { cache[bits] = build_rgb_sgr(sgr, bits) }
111
+ end
112
+
113
+ def build_rgb_sgr(sgr, bits)
114
+ red = (bits >> 16) & 0xFF
115
+ green = (bits >> 8) & 0xFF
116
+ blue = bits & 0xFF
117
+ "\e[#{sgr};2;#{red};#{green};#{blue}m"
118
+ end
119
+
120
+ private_class_method :build_diacritics,
121
+ :build_rgb_sgr,
122
+ :cache_key,
123
+ :diacritics,
124
+ :foreground,
125
+ :integer_or_nil,
126
+ :load_diacritic_codepoints,
127
+ :msb_diacritic,
128
+ :placeholder_cells,
129
+ :positive_integer,
130
+ :render_line,
131
+ :rgb_sgr,
132
+ :underline,
133
+ :valid_grid,
134
+ :wrap_sequences
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,26 @@
1
+ 0305 030D 030E 0310 0312 033D 033E 033F 0346 034A 034B 034C 0350 0351 0352 0357
2
+ 035B 0363 0364 0365 0366 0367 0368 0369 036A 036B 036C 036D 036E 036F 0483 0484
3
+ 0485 0486 0487 0592 0593 0594 0595 0597 0598 0599 059C 059D 059E 059F 05A0 05A1
4
+ 05A8 05A9 05AB 05AC 05AF 05C4 0610 0611 0612 0613 0614 0655 0656 0657 0658 0659
5
+ 065A 065B 065C 065D 065E 06D6 06D7 06D8 06D9 06DA 06DB 06DC 06DF 06E0 06E1 06E2
6
+ 06E4 06E7 06E8 06EA 06EB 06EC 06ED 0711 0730 0731 0732 0733 0734 0735 0736 0737
7
+ 0738 0739 073A 073B 073C 073D 073E 073F 0740 0741 0742 0743 0744 0745 0746 0747
8
+ 0748 0749 074A 07EB 07EC 07ED 07EE 07EF 07F0 07F1 07F3 0816 0817 0818 0819 081B
9
+ 081C 081D 081E 081F 0820 0821 0822 0823 0825 0826 0827 0829 082A 082B 082C 082D
10
+ 0859 085A 08E4 08E5 08E6 08E7 08E8 08E9 08EA 08EB 08EC 08ED 08EE 08EF 08F0 08F1
11
+ 08F2 08F3 08F4 08F5 08F6 08F7 08F8 08F9 08FA 08FB 08FC 08FD 08FE 093A 093C 094D
12
+ 0951 0952 0953 0954 0971 09BC 09CD 0A3C 0A4D 0ABC 0ACD 0B3C 0B4D 0BCD 0C4D 0CBC
13
+ 0CCD 0D3B 0D3C 0D4D 0DCA 0E38 0E39 0E3A 0E48 0E49 0E4A 0E4B 0EB8 0EB9 0EBA 0EC8
14
+ 0EC9 0ECA 0ECB 0F18 0F19 0F35 0F37 0F39 0F71 0F72 0F74 0F7A 0F7B 0F7C 0F7D 0F80
15
+ 0F82 0F83 0F84 0F86 0F87 0FC6 1037 1039 17C6 17C9 17CA 17CB 17CC 17CD 17CE 17CF
16
+ 17D0 17D1 17D2 17D3 17DD 1AB0 1AB1 1AB2 1AB3 1AB4 1AB5 1AB6 1AB7 1AB8 1AB9 1ABA
17
+ 1ABB 1ABC 1ABD 1ABF 1AC0 1AC1 1AC2 1AC3 1AC4 1AC5 1AC6 1AC7 1AC8 1AC9 1ACA 1ACB
18
+ 1ACC 1ACD 1ACE 1B6B 1B6D 1B6E 1B6F 1B70 1B71 1B72 1B73 1CD0 1CD1 1CD2 1CD4 1CD5
19
+ 1CD6 1CD7 1CD8 1CD9 1CDA 1CDB 1CDC 1CDD 1CDE 1CDF 1CE0 1CE2 1CE3 1CE4 1CE5 1CE6
20
+ 1CE7 1CE8 1CED 1CF4 1CF8 1CF9 1DC0 1DC1 1DC2 1DC3 1DC4 1DC5 1DC6 1DC7 1DC8 1DC9
21
+ 1DCB 1DCC 1DD1 1DD2 1DD3 1DD4 1DD5 1DD6 1DD7 1DD8 1DD9 1DDA 1DDB 1DDC 1DDD 1DDE
22
+ 1DDF 1DE0 1DE1 1DE2 1DE3 1DE4 1DE5 1DE6 1DFE 20D0 20D1 20D4 20D5 20D6 20D7 20DB
23
+ 20DC 20E1 20E7 20E9 20F0 2CEF 2CF0 2CF1 2DE0 2DE1 2DE2 2DE3 2DE4 2DE5 2DE6 2DE7
24
+ 2DE8 2DE9 2DEA 2DEB 2DEC 2DED 2DEE 2DEF 2DF0 2DF1 2DF2 2DF3 2DF4 2DF5 2DF6 2DF7
25
+ 2DF8 2DF9 2DFA 2DFB 2DFC 2DFD 2DFE 2DFF A66F A67C A67D A6F0 A6F1 A802 A806 A80B
26
+ A825 A826 A8C4 A8E0 A8E1 A8E2 A8E3 A8E4 A8E5
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../core/services/base_service.rb'
4
+
5
+ module Shoko
6
+ module Adapters::Output
7
+ # Centralizes ephemeral UI notifications (messages with auto-clear timers)
8
+ class NotificationService < BaseService
9
+ def initialize(dependencies)
10
+ super
11
+ @mutex = Mutex.new
12
+ @clear_deadline = nil
13
+ end
14
+
15
+ # Show a transient message and clear it after duration seconds
16
+ # @param state [Application::Infrastructure::ObserverStateStore]
17
+ # @param text [String]
18
+ # @param duration [Numeric]
19
+ def set_message(state, text, duration = 2)
20
+ state.dispatch(Shoko::Application::Actions::UpdateMessageAction.new(text))
21
+
22
+ duration_seconds = duration ? duration.to_f : 0.0
23
+ if duration_seconds <= 0
24
+ state.dispatch(Shoko::Application::Actions::ClearMessageAction.new)
25
+ @mutex.synchronize { @clear_deadline = nil }
26
+ return
27
+ end
28
+
29
+ cutoff = Process.clock_gettime(Process::CLOCK_MONOTONIC) + duration_seconds
30
+
31
+ @mutex.synchronize do
32
+ @clear_deadline = cutoff
33
+ end
34
+ end
35
+
36
+ # Clear the active message when the deadline has elapsed.
37
+ # Call on each render tick to avoid background threads.
38
+ def tick(state)
39
+ should_clear = false
40
+
41
+ @mutex.synchronize do
42
+ if @clear_deadline && Process.clock_gettime(Process::CLOCK_MONOTONIC) >= @clear_deadline
43
+ @clear_deadline = nil
44
+ should_clear = true
45
+ end
46
+ end
47
+
48
+ state.dispatch(Shoko::Application::Actions::ClearMessageAction.new) if should_clear
49
+ end
50
+
51
+ protected
52
+
53
+ def required_dependencies
54
+ []
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'monitor'
4
+
5
+ module Shoko
6
+ module Adapters::Output
7
+ # Lightweight registry for per-frame render metadata (rendered line geometry).
8
+ # This keeps large, frequently-updated hashes out of the global state store.
9
+ class RenderRegistry
10
+ class << self
11
+ # Global singleton for callers that do not receive DI wiring.
12
+ def current
13
+ @current ||= RenderRegistry.new
14
+ end
15
+
16
+ # Replace the global instance (used by tests).
17
+ def install(instance)
18
+ @current = instance
19
+ end
20
+ end
21
+
22
+ def initialize
23
+ @monitor = Monitor.new
24
+ @rendered_lines = {}
25
+ end
26
+
27
+ def write(lines)
28
+ return unless lines
29
+
30
+ @monitor.synchronize do
31
+ # Store the hash directly; callers control lifecycle per-frame.
32
+ @rendered_lines = lines
33
+ end
34
+ end
35
+
36
+ def clear
37
+ @monitor.synchronize { @rendered_lines = {} }
38
+ end
39
+
40
+ def lines
41
+ @monitor.synchronize { @rendered_lines }
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shoko
4
+ module Adapters
5
+ module Output
6
+ module Rendering
7
+ module Models
8
+ # Represents a single rendered cell (grapheme cluster) within a line box.
9
+ LineCell = Struct.new(:cluster, :char_start, :char_end, :display_width, :screen_x, keyword_init: true) do
10
+ def visible?
11
+ display_width.positive?
12
+ end
13
+ end
14
+
15
+ # Represents the geometry for a rendered line on screen. Holds the plain
16
+ # text, ANSI-styled text, and per-cell breakdown so selections/tooltips can
17
+ # share the same layout information as the renderer.
18
+ class LineGeometry
19
+ attr_reader :page_id, :column_id, :row, :column_origin, :line_offset,
20
+ :plain_text, :styled_text, :cells
21
+
22
+ def initialize(page_id:, column_id:, row:, column_origin:, line_offset:,
23
+ plain_text:, styled_text:, cells: [])
24
+ @page_id = page_id
25
+ @column_id = column_id
26
+ @row = row
27
+ @column_origin = column_origin
28
+ @line_offset = line_offset
29
+ @plain_text = plain_text.to_s
30
+ @styled_text = styled_text.to_s
31
+ @cells = cells || []
32
+ end
33
+
34
+ def key
35
+ "#{column_id}_#{line_offset}_#{row}"
36
+ end
37
+
38
+ def visible_width
39
+ @visible_width ||= cells.sum(&:display_width)
40
+ end
41
+
42
+ def to_h
43
+ {
44
+ page_id:,
45
+ column_id:,
46
+ row:,
47
+ column_origin:,
48
+ line_offset:,
49
+ plain_text:,
50
+ styled_text:,
51
+ visible_width:,
52
+ cells: cells.map(&:to_h),
53
+ }
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shoko
4
+ module Adapters
5
+ module Output
6
+ module Rendering
7
+ module Models
8
+ PageRenderingContext = Struct.new(
9
+ :lines, :offset, :dimensions, :position, :show_page_num,
10
+ keyword_init: true
11
+ )
12
+
13
+ FooterRenderingContext = Struct.new(
14
+ :height, :width, :doc, :chapter, :pages, :view_mode, :mode,
15
+ :line_spacing, :bookmarks,
16
+ keyword_init: true
17
+ )
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shoko
4
+ module Adapters
5
+ module Output
6
+ module Rendering
7
+ module Models
8
+ # Parameter object to reduce long parameter lists in renderers.
9
+ class RenderParams
10
+ attr_reader :start_row, :col_start, :col_width, :context,
11
+ :line_offset, :column_id, :page_id
12
+
13
+ def initialize(start_row:, col_start:, col_width:, context: nil,
14
+ line_offset: 0, column_id: 0, page_id: nil)
15
+ @start_row = start_row
16
+ @col_start = col_start
17
+ @col_width = col_width
18
+ @context = context
19
+ @line_offset = line_offset
20
+ @column_id = column_id
21
+ @page_id = page_id
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shoko
4
+ module Adapters
5
+ module Output
6
+ module Rendering
7
+ module Models
8
+ # Context object for rendering operations.
9
+ # Replaces direct controller dependency in renderers with structured data access.
10
+ class RenderingContext
11
+ attr_reader :document, :page_calculator, :state, :config, :view_model
12
+
13
+ def initialize(document:, state:, config:, view_model:, page_calculator: nil)
14
+ @document = document
15
+ @page_calculator = page_calculator
16
+ @state = state
17
+ @config = config
18
+ @view_model = view_model
19
+ freeze
20
+ end
21
+
22
+ # Convenience methods for common rendering needs
23
+ def current_chapter
24
+ @document&.get_chapter(@state.get(%i[reader current_chapter]))
25
+ end
26
+
27
+ def current_page_index
28
+ @state.get(%i[reader current_page_index])
29
+ end
30
+
31
+ def view_mode
32
+ Shoko::Application::Selectors::ConfigSelectors.view_mode(@state)
33
+ end
34
+
35
+ def page_numbering_mode
36
+ Shoko::Application::Selectors::ConfigSelectors.page_numbering_mode(@state)
37
+ end
38
+
39
+ # Dynamic mode page data access
40
+ def get_page_data(index)
41
+ return nil unless @page_calculator && page_numbering_mode == :dynamic
42
+
43
+ @page_calculator.get_page(index)
44
+ end
45
+
46
+ def total_pages
47
+ if @page_calculator && page_numbering_mode == :dynamic
48
+ @page_calculator.total_pages
49
+ else
50
+ @state.get(%i[reader total_pages])
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end