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.
- checksums.yaml +7 -0
- data/.bundle/config +4 -0
- data/.bundle/config.bak +3 -0
- data/.rspec_status +42 -0
- data/.rubocop.yml +124 -0
- data/Gemfile +19 -0
- data/LICENSE +21 -0
- data/README.md +82 -0
- data/Rakefile +29 -0
- data/bin/start +15 -0
- data/lib/shoko/adapters/book_sources/document_service.rb +201 -0
- data/lib/shoko/adapters/book_sources/download_service.rb +95 -0
- data/lib/shoko/adapters/book_sources/epub/epub_resource_loader.rb +137 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/html_processor.rb +151 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/metadata_extractor.rb +53 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/entry_reader.rb +77 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/metadata_extractor.rb +67 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_context.rb +86 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_document_index.rb +75 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_document_scanner.rb +47 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_extractor.rb +46 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_label_resolver.rb +83 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_list_item.rb +55 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_result.rb +8 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_selector.rb +100 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_source_locator.rb +93 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_traversal.rb +103 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_walker.rb +56 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf_processor.rb +102 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/xhtml_content_parser.rb +661 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/xml_text_normalizer.rb +41 -0
- data/lib/shoko/adapters/book_sources/epub_document.rb +253 -0
- data/lib/shoko/adapters/book_sources/epub_finder/directory_scanner.rb +134 -0
- data/lib/shoko/adapters/book_sources/epub_finder/scanner_context.rb +28 -0
- data/lib/shoko/adapters/book_sources/epub_finder.rb +161 -0
- data/lib/shoko/adapters/book_sources/epub_importer.rb +268 -0
- data/lib/shoko/adapters/book_sources/gutendex_client.rb +150 -0
- data/lib/shoko/adapters/book_sources/library_scanner.rb +93 -0
- data/lib/shoko/adapters/book_sources/source_fingerprint.rb +57 -0
- data/lib/shoko/adapters/input/annotations/mouse_handler.rb +84 -0
- data/lib/shoko/adapters/input/command_bridge.rb +148 -0
- data/lib/shoko/adapters/input/command_factory.rb +255 -0
- data/lib/shoko/adapters/input/commands.rb +60 -0
- data/lib/shoko/adapters/input/dispatcher.rb +69 -0
- data/lib/shoko/adapters/input/input_controller.rb +250 -0
- data/lib/shoko/adapters/input/key_definitions.rb +108 -0
- data/lib/shoko/adapters/input/validators/file_path_validator.rb +81 -0
- data/lib/shoko/adapters/input/validators/terminal_size_validator.rb +76 -0
- data/lib/shoko/adapters/monitoring/logger.rb +150 -0
- data/lib/shoko/adapters/monitoring/perf_tracer.rb +183 -0
- data/lib/shoko/adapters/monitoring/performance_monitor.rb +110 -0
- data/lib/shoko/adapters/output/clipboard/clipboard_service.rb +125 -0
- data/lib/shoko/adapters/output/formatting/formatting_service/line_assembler/image_builder.rb +149 -0
- data/lib/shoko/adapters/output/formatting/formatting_service/line_assembler/text_wrapper.rb +149 -0
- data/lib/shoko/adapters/output/formatting/formatting_service/line_assembler/tokenizer.rb +91 -0
- data/lib/shoko/adapters/output/formatting/formatting_service/line_assembler.rb +144 -0
- data/lib/shoko/adapters/output/formatting/formatting_service/plain_lines_builder.rb +54 -0
- data/lib/shoko/adapters/output/formatting/formatting_service.rb +247 -0
- data/lib/shoko/adapters/output/formatting/wrapping_service.rb +228 -0
- data/lib/shoko/adapters/output/instrumentation_service.rb +52 -0
- data/lib/shoko/adapters/output/kitty/image_transcoder.rb +71 -0
- data/lib/shoko/adapters/output/kitty/kitty_graphics.rb +114 -0
- data/lib/shoko/adapters/output/kitty/kitty_image_renderer.rb +239 -0
- data/lib/shoko/adapters/output/kitty/kitty_unicode_placeholders.rb +139 -0
- data/lib/shoko/adapters/output/kitty/kitty_unicode_placeholders_diacritic_codepoints.txt +26 -0
- data/lib/shoko/adapters/output/notification_service.rb +58 -0
- data/lib/shoko/adapters/output/render_registry.rb +45 -0
- data/lib/shoko/adapters/output/rendering/models/line_geometry.rb +60 -0
- data/lib/shoko/adapters/output/rendering/models/page_rendering_context.rb +22 -0
- data/lib/shoko/adapters/output/rendering/models/render_params.rb +28 -0
- data/lib/shoko/adapters/output/rendering/models/rendering_context.rb +58 -0
- data/lib/shoko/adapters/output/terminal/buffer.rb +275 -0
- data/lib/shoko/adapters/output/terminal/constants/terminal_defaults.rb +11 -0
- data/lib/shoko/adapters/output/terminal/input/decoder.rb +347 -0
- data/lib/shoko/adapters/output/terminal/input.rb +161 -0
- data/lib/shoko/adapters/output/terminal/output.rb +105 -0
- data/lib/shoko/adapters/output/terminal/terminal.rb +167 -0
- data/lib/shoko/adapters/output/terminal/terminal_sanitizer.rb +243 -0
- data/lib/shoko/adapters/output/terminal/terminal_service.rb +138 -0
- data/lib/shoko/adapters/output/terminal/text_metrics.rb +273 -0
- data/lib/shoko/adapters/output/ui/builders/page_setup_builder.rb +47 -0
- data/lib/shoko/adapters/output/ui/components/annotation_editor_overlay/footer_renderer.rb +80 -0
- data/lib/shoko/adapters/output/ui/components/annotation_editor_overlay/geometry.rb +61 -0
- data/lib/shoko/adapters/output/ui/components/annotation_editor_overlay/note_renderer.rb +86 -0
- data/lib/shoko/adapters/output/ui/components/annotation_editor_overlay_component.rb +234 -0
- data/lib/shoko/adapters/output/ui/components/annotations_overlay/list_renderer.rb +142 -0
- data/lib/shoko/adapters/output/ui/components/annotations_overlay_component.rb +185 -0
- data/lib/shoko/adapters/output/ui/components/base_component.rb +110 -0
- data/lib/shoko/adapters/output/ui/components/component_interface.rb +80 -0
- data/lib/shoko/adapters/output/ui/components/content_component.rb +61 -0
- data/lib/shoko/adapters/output/ui/components/enhanced_popup_menu.rb +191 -0
- data/lib/shoko/adapters/output/ui/components/footer_component.rb +120 -0
- data/lib/shoko/adapters/output/ui/components/header_component.rb +46 -0
- data/lib/shoko/adapters/output/ui/components/layouts/horizontal.rb +63 -0
- data/lib/shoko/adapters/output/ui/components/layouts/vertical.rb +73 -0
- data/lib/shoko/adapters/output/ui/components/main_menu_component.rb +103 -0
- data/lib/shoko/adapters/output/ui/components/reading/base_view_renderer.rb +199 -0
- data/lib/shoko/adapters/output/ui/components/reading/config_helpers.rb +42 -0
- data/lib/shoko/adapters/output/ui/components/reading/help_renderer.rb +62 -0
- data/lib/shoko/adapters/output/ui/components/reading/inline_segment_highlighter.rb +144 -0
- data/lib/shoko/adapters/output/ui/components/reading/kitty_image_line_renderer.rb +262 -0
- data/lib/shoko/adapters/output/ui/components/reading/line_content_composer.rb +114 -0
- data/lib/shoko/adapters/output/ui/components/reading/line_drawer.rb +87 -0
- data/lib/shoko/adapters/output/ui/components/reading/line_geometry_builder.rb +41 -0
- data/lib/shoko/adapters/output/ui/components/reading/rendered_lines_recorder.rb +64 -0
- data/lib/shoko/adapters/output/ui/components/reading/single_view_renderer.rb +156 -0
- data/lib/shoko/adapters/output/ui/components/reading/split_view_renderer.rb +221 -0
- data/lib/shoko/adapters/output/ui/components/reading/view_renderer_factory.rb +20 -0
- data/lib/shoko/adapters/output/ui/components/reading/wrapped_lines_fetcher.rb +139 -0
- data/lib/shoko/adapters/output/ui/components/rect.rb +15 -0
- data/lib/shoko/adapters/output/ui/components/render_style.rb +84 -0
- data/lib/shoko/adapters/output/ui/components/screen_component.rb +24 -0
- data/lib/shoko/adapters/output/ui/components/screens/annotation_detail_screen_component.rb +175 -0
- data/lib/shoko/adapters/output/ui/components/screens/annotation_edit_screen_component.rb +221 -0
- data/lib/shoko/adapters/output/ui/components/screens/annotation_editor_screen_component.rb +205 -0
- data/lib/shoko/adapters/output/ui/components/screens/annotation_rendering_helpers.rb +190 -0
- data/lib/shoko/adapters/output/ui/components/screens/annotations_screen_component.rb +266 -0
- data/lib/shoko/adapters/output/ui/components/screens/base_screen_component.rb +49 -0
- data/lib/shoko/adapters/output/ui/components/screens/browse_screen_component.rb +319 -0
- data/lib/shoko/adapters/output/ui/components/screens/download_books_screen_component.rb +340 -0
- data/lib/shoko/adapters/output/ui/components/screens/library_screen_component.rb +205 -0
- data/lib/shoko/adapters/output/ui/components/screens/loading_overlay_component.rb +49 -0
- data/lib/shoko/adapters/output/ui/components/screens/menu_screen_component.rb +107 -0
- data/lib/shoko/adapters/output/ui/components/screens/settings_screen_component.rb +238 -0
- data/lib/shoko/adapters/output/ui/components/sidebar/annotations_tab_renderer.rb +159 -0
- data/lib/shoko/adapters/output/ui/components/sidebar/bookmarks_tab_renderer.rb +139 -0
- data/lib/shoko/adapters/output/ui/components/sidebar/tab_header_component.rb +157 -0
- data/lib/shoko/adapters/output/ui/components/sidebar/toc_tab_renderer.rb +111 -0
- data/lib/shoko/adapters/output/ui/components/sidebar/toc_tab_support.rb +1606 -0
- data/lib/shoko/adapters/output/ui/components/sidebar_panel_component.rb +217 -0
- data/lib/shoko/adapters/output/ui/components/surface.rb +88 -0
- data/lib/shoko/adapters/output/ui/components/tooltip_overlay_component.rb +224 -0
- data/lib/shoko/adapters/output/ui/components/ui/box_drawer.rb +32 -0
- data/lib/shoko/adapters/output/ui/components/ui/list_helpers.rb +33 -0
- data/lib/shoko/adapters/output/ui/components/ui/overlay_layout.rb +79 -0
- data/lib/shoko/adapters/output/ui/components/ui/text_utils.rb +46 -0
- data/lib/shoko/adapters/output/ui/constants/highlighting.rb +21 -0
- data/lib/shoko/adapters/output/ui/constants/messages.rb +12 -0
- data/lib/shoko/adapters/output/ui/constants/themes.rb +79 -0
- data/lib/shoko/adapters/output/ui/constants/ui_constants.rb +85 -0
- data/lib/shoko/adapters/output/ui/rendering/frame_coordinator.rb +42 -0
- data/lib/shoko/adapters/output/ui/rendering/reader_render_coordinator.rb +169 -0
- data/lib/shoko/adapters/output/ui/rendering/render_pipeline.rb +55 -0
- data/lib/shoko/adapters/storage/atomic_file_writer.rb +43 -0
- data/lib/shoko/adapters/storage/background_worker.rb +66 -0
- data/lib/shoko/adapters/storage/book_cache_pipeline.rb +653 -0
- data/lib/shoko/adapters/storage/cache/epub/memory_cache.rb +99 -0
- data/lib/shoko/adapters/storage/cache/epub/persistence.rb +131 -0
- data/lib/shoko/adapters/storage/cache/epub/serializer/deserialize.rb +225 -0
- data/lib/shoko/adapters/storage/cache/epub/serializer/helpers.rb +63 -0
- data/lib/shoko/adapters/storage/cache/epub/serializer/serialize.rb +83 -0
- data/lib/shoko/adapters/storage/cache/epub/serializer.rb +5 -0
- data/lib/shoko/adapters/storage/cache/epub/source_reference.rb +58 -0
- data/lib/shoko/adapters/storage/cache_paths.rb +21 -0
- data/lib/shoko/adapters/storage/cache_pointer_manager.rb +60 -0
- data/lib/shoko/adapters/storage/config_paths.rb +30 -0
- data/lib/shoko/adapters/storage/epub_cache.rb +195 -0
- data/lib/shoko/adapters/storage/file_writer_service.rb +47 -0
- data/lib/shoko/adapters/storage/json_cache_store/chapters.rb +141 -0
- data/lib/shoko/adapters/storage/json_cache_store/layouts.rb +67 -0
- data/lib/shoko/adapters/storage/json_cache_store/manifest.rb +42 -0
- data/lib/shoko/adapters/storage/json_cache_store/payload_helpers.rb +113 -0
- data/lib/shoko/adapters/storage/json_cache_store/resources.rb +84 -0
- data/lib/shoko/adapters/storage/json_cache_store.rb +167 -0
- data/lib/shoko/adapters/storage/lazy_file_string.rb +65 -0
- data/lib/shoko/adapters/storage/pagination_cache.rb +127 -0
- data/lib/shoko/adapters/storage/recent_files.rb +78 -0
- data/lib/shoko/adapters/storage/repositories/annotation_repository.rb +182 -0
- data/lib/shoko/adapters/storage/repositories/base_repository.rb +81 -0
- data/lib/shoko/adapters/storage/repositories/bookmark_repository.rb +132 -0
- data/lib/shoko/adapters/storage/repositories/cached_library_repository.rb +129 -0
- data/lib/shoko/adapters/storage/repositories/config_repository.rb +262 -0
- data/lib/shoko/adapters/storage/repositories/progress_repository.rb +166 -0
- data/lib/shoko/adapters/storage/repositories/storage/annotation_file_store.rb +128 -0
- data/lib/shoko/adapters/storage/repositories/storage/bookmark_file_store.rb +109 -0
- data/lib/shoko/adapters/storage/repositories/storage/file_store_utils.rb +20 -0
- data/lib/shoko/adapters/storage/repositories/storage/progress_file_store.rb +59 -0
- data/lib/shoko/application/annotation_editor_overlay_session.rb +138 -0
- data/lib/shoko/application/cli.rb +134 -0
- data/lib/shoko/application/controllers/menu/input_controller.rb +189 -0
- data/lib/shoko/application/controllers/menu/state_controller.rb +642 -0
- data/lib/shoko/application/controllers/menu_controller.rb +469 -0
- data/lib/shoko/application/controllers/mouseable_reader.rb +377 -0
- data/lib/shoko/application/controllers/reader_controller.rb +449 -0
- data/lib/shoko/application/controllers/state_controller.rb +410 -0
- data/lib/shoko/application/controllers/ui_controller.rb +782 -0
- data/lib/shoko/application/dependency_container.rb +301 -0
- data/lib/shoko/application/infrastructure/event_bus.rb +80 -0
- data/lib/shoko/application/infrastructure/observer_state_store.rb +136 -0
- data/lib/shoko/application/infrastructure/state_store.rb +413 -0
- data/lib/shoko/application/main_menu/menu_progress_presenter.rb +83 -0
- data/lib/shoko/application/pending_jump_handler.rb +122 -0
- data/lib/shoko/application/reader_lifecycle.rb +65 -0
- data/lib/shoko/application/reader_startup_orchestrator.rb +113 -0
- data/lib/shoko/application/selectors/config_selectors.rb +62 -0
- data/lib/shoko/application/selectors/menu_selectors.rb +62 -0
- data/lib/shoko/application/selectors/reader_selectors.rb +186 -0
- data/lib/shoko/application/state/actions/base_action.rb +24 -0
- data/lib/shoko/application/state/actions/quit_to_menu_action.rb +16 -0
- data/lib/shoko/application/state/actions/switch_reader_mode_action.rb +22 -0
- data/lib/shoko/application/state/actions/toggle_view_mode_action.rb +31 -0
- data/lib/shoko/application/state/actions/update_annotation_editor_overlay_action.rb +27 -0
- data/lib/shoko/application/state/actions/update_annotations_action.rb +20 -0
- data/lib/shoko/application/state/actions/update_annotations_overlay_action.rb +27 -0
- data/lib/shoko/application/state/actions/update_bookmarks_action.rb +20 -0
- data/lib/shoko/application/state/actions/update_chapter_action.rb +24 -0
- data/lib/shoko/application/state/actions/update_config_action.rb +22 -0
- data/lib/shoko/application/state/actions/update_field_helpers.rb +26 -0
- data/lib/shoko/application/state/actions/update_menu_action.rb +21 -0
- data/lib/shoko/application/state/actions/update_message_action.rb +35 -0
- data/lib/shoko/application/state/actions/update_page_action.rb +21 -0
- data/lib/shoko/application/state/actions/update_pagination_state_action.rb +21 -0
- data/lib/shoko/application/state/actions/update_popup_menu_action.rb +27 -0
- data/lib/shoko/application/state/actions/update_reader_meta_action.rb +21 -0
- data/lib/shoko/application/state/actions/update_reader_mode_action.rb +20 -0
- data/lib/shoko/application/state/actions/update_rendered_lines_action.rb +40 -0
- data/lib/shoko/application/state/actions/update_selection_action.rb +27 -0
- data/lib/shoko/application/state/actions/update_selections_action.rb +21 -0
- data/lib/shoko/application/state/actions/update_sidebar_action.rb +34 -0
- data/lib/shoko/application/state/actions/update_ui_loading_action.rb +23 -0
- data/lib/shoko/application/ui/reader_view_model_builder.rb +74 -0
- data/lib/shoko/application/ui/view_models/reader_view_model.rb +177 -0
- data/lib/shoko/application/unified_application.rb +48 -0
- data/lib/shoko/application/use_cases/catalog_service.rb +117 -0
- data/lib/shoko/application/use_cases/commands/annotation_editor_commands.rb +105 -0
- data/lib/shoko/application/use_cases/commands/application_commands.rb +208 -0
- data/lib/shoko/application/use_cases/commands/base_command.rb +166 -0
- data/lib/shoko/application/use_cases/commands/bookmark_commands.rb +114 -0
- data/lib/shoko/application/use_cases/commands/conditional_navigation_commands.rb +57 -0
- data/lib/shoko/application/use_cases/commands/menu_commands.rb +170 -0
- data/lib/shoko/application/use_cases/commands/navigation_commands.rb +183 -0
- data/lib/shoko/application/use_cases/commands/reader_commands.rb +46 -0
- data/lib/shoko/application/use_cases/commands/sidebar_commands.rb +55 -0
- data/lib/shoko/application/use_cases/settings_service.rb +123 -0
- data/lib/shoko/core/events/annotation_events.rb +94 -0
- data/lib/shoko/core/events/base_domain_event.rb +169 -0
- data/lib/shoko/core/events/bookmark_events.rb +41 -0
- data/lib/shoko/core/events/domain_event_bus.rb +163 -0
- data/lib/shoko/core/events/progress_events.rb +108 -0
- data/lib/shoko/core/models/bookmark.rb +36 -0
- data/lib/shoko/core/models/bookmark_data.rb +10 -0
- data/lib/shoko/core/models/chapter.rb +25 -0
- data/lib/shoko/core/models/content_block.rb +44 -0
- data/lib/shoko/core/models/reader_settings.rb +20 -0
- data/lib/shoko/core/models/selection_anchor.rb +73 -0
- data/lib/shoko/core/models/toc_entry.rb +14 -0
- data/lib/shoko/core/ports/annotation_repository.rb +0 -0
- data/lib/shoko/core/ports/book_repository.rb +0 -0
- data/lib/shoko/core/ports/book_source.rb +0 -0
- data/lib/shoko/core/ports/bookmark_repository.rb +0 -0
- data/lib/shoko/core/ports/cache.rb +0 -0
- data/lib/shoko/core/ports/input_handler.rb +0 -0
- data/lib/shoko/core/ports/renderer.rb +0 -0
- data/lib/shoko/core/ports/storage.rb +0 -0
- data/lib/shoko/core/services/annotation_service.rb +102 -0
- data/lib/shoko/core/services/base_service.rb +60 -0
- data/lib/shoko/core/services/bookmark_service.rb +267 -0
- data/lib/shoko/core/services/coordinate_service.rb +265 -0
- data/lib/shoko/core/services/layout_service.rb +95 -0
- data/lib/shoko/core/services/navigation/absolute_change_applier.rb +96 -0
- data/lib/shoko/core/services/navigation/absolute_layout.rb +101 -0
- data/lib/shoko/core/services/navigation/absolute_strategy.rb +179 -0
- data/lib/shoko/core/services/navigation/context_builder.rb +52 -0
- data/lib/shoko/core/services/navigation/context_helpers.rb +63 -0
- data/lib/shoko/core/services/navigation/dynamic_change_applier.rb +50 -0
- data/lib/shoko/core/services/navigation/dynamic_strategy.rb +51 -0
- data/lib/shoko/core/services/navigation/image_offset_snapper.rb +150 -0
- data/lib/shoko/core/services/navigation/nav_context.rb +27 -0
- data/lib/shoko/core/services/navigation/state_updater.rb +29 -0
- data/lib/shoko/core/services/navigation/strategy_factory.rb +20 -0
- data/lib/shoko/core/services/navigation_service.rb +150 -0
- data/lib/shoko/core/services/page_calculator_service.rb +242 -0
- data/lib/shoko/core/services/pagination/internal/absolute_page_map_builder.rb +28 -0
- data/lib/shoko/core/services/pagination/internal/chapter_cache.rb +60 -0
- data/lib/shoko/core/services/pagination/internal/dynamic_page_map_builder.rb +157 -0
- data/lib/shoko/core/services/pagination/internal/layout_metrics_calculator.rb +73 -0
- data/lib/shoko/core/services/pagination/internal/page_hydrator.rb +145 -0
- data/lib/shoko/core/services/pagination/internal/pagination_workflow.rb +152 -0
- data/lib/shoko/core/services/pagination/page_info_calculator.rb +247 -0
- data/lib/shoko/core/services/pagination/pagination_cache_preloader.rb +173 -0
- data/lib/shoko/core/services/pagination/pagination_coordinator.rb +202 -0
- data/lib/shoko/core/services/pagination/pagination_orchestrator.rb +291 -0
- data/lib/shoko/core/services/pagination.rb +10 -0
- data/lib/shoko/core/services/progress_helper.rb +22 -0
- data/lib/shoko/core/services/selection_service.rb +126 -0
- data/lib/shoko/core/validator.rb +76 -0
- data/lib/shoko/shared/errors.rb +97 -0
- data/lib/shoko/shared/version.rb +5 -0
- data/lib/shoko/test_support/terminal_double.rb +175 -0
- data/lib/shoko/test_support/test_mode.rb +78 -0
- data/lib/shoko.rb +279 -0
- data/lib/zip.rb +732 -0
- data/zip.rb +5 -0
- 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
|