diff --git a/BUILD.gn b/BUILD.gn new file mode 100644 index 000000000000..4d34a462c9f1 --- /dev/null +++ b/BUILD.gn @@ -0,0 +1,975 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//build/config/ozone.gni") +import("//build/config/ui.gni") +import("//device/vr/buildflags/buildflags.gni") +import("//testing/libfuzzer/fuzzer_test.gni") +import("//testing/test.gni") + +if (is_android) { + import("//build/config/android/config.gni") + import("//build/config/android/rules.gni") +} + +# Several targets want to include this header file, and some of them are +# child dependencies of "gfx". Therefore, we separate it out here so multiple +# targets can all have a dependency for header checking purposes without +# creating circular dependencies. +source_set("gfx_export") { + sources = [ "gfx_export.h" ] +} + +# Used for color generation at build time without importing all the gfx. +component("color_utils") { + sources = [ + "color_palette.h", + "color_utils.cc", + "color_utils.h", + ] + defines = [ "GFX_IMPLEMENTATION" ] + public_deps = [ + ":gfx_export", + "//base", + "//skia", + "//ui/gfx/geometry", + ] +} + +component("gfx_skia") { + sources = [ + "gfx_skia_export.h", + "skia_util.cc", + "skia_util.h", + ] + configs += [ "//build/config/compiler:wexit_time_destructors" ] + public_deps = [ + "//base", + "//skia", + ] + defines = [ "GFX_SKIA_IMPLEMENTATION" ] +} + +component("gfx") { + sources = [ + "break_list.h", + "color_analysis.cc", + "color_analysis.h", + "color_transform.cc", + "color_transform.h", + "decorated_text.cc", + "decorated_text.h", + "delegated_ink_metadata.cc", + "delegated_ink_metadata.h", + "delegated_ink_point.cc", + "delegated_ink_point.h", + "extension_set.cc", + "extension_set.h", + "favicon_size.cc", + "favicon_size.h", + "font.cc", + "font.h", + "font_fallback.h", + "font_list.cc", + "font_list.h", + "font_list_impl.cc", + "font_list_impl.h", + "font_render_params.cc", + "font_render_params.h", + "font_util.cc", + "font_util.h", + "gdi_util.cc", + "gdi_util.h", + "gpu_extra_info.cc", + "gpu_extra_info.h", + "half_float.cc", + "half_float.h", + "icon_util.cc", + "icon_util.h", + "image/buffer_w_stream.cc", + "image/buffer_w_stream.h", + "image/image.cc", + "image/image.h", + "image/image_family.cc", + "image/image_family.h", + "image/image_platform.h", + "image/image_png_rep.cc", + "image/image_png_rep.h", + "image/image_skia.cc", + "image/image_skia.h", + "image/image_skia_rep.h", + "image/image_skia_source.cc", + "image/image_skia_source.h", + "image/image_util.cc", + "image/image_util.h", + "interpolated_transform.cc", + "interpolated_transform.h", + "nine_image_painter.cc", + "nine_image_painter.h", + "overlay_plane_data.cc", + "overlay_plane_data.h", + "overlay_transform_utils.cc", + "overlay_transform_utils.h", + "platform_font.h", + "rendering_pipeline.cc", + "rendering_pipeline.h", + "rendering_stage_scheduler.cc", + "rendering_stage_scheduler.h", + "scrollbar_size.cc", + "scrollbar_size.h", + "selection_model.cc", + "selection_model.h", + "sequential_id_generator.cc", + "sequential_id_generator.h", + "shadow_value.cc", + "shadow_value.h", + "skbitmap_operations.cc", + "skbitmap_operations.h", + "swap_result.cc", + "sys_color_change_listener.cc", + "sys_color_change_listener.h", + "text_constants.h", + "text_elider.cc", + "text_elider.h", + "text_utils.cc", + "text_utils.h", + "ui_gfx_exports.cc", + "utf16_indexing.cc", + "utf16_indexing.h", + "vector_icon_types.h", + "vector_icon_utils.cc", + "vector_icon_utils.h", + "video_types.h", + "vsync_provider.cc", + "vsync_provider.h", + ] + if (is_android) { + sources += [ + "android/android_surface_control_compat.cc", + "android/android_surface_control_compat.h", + "android/java_bitmap.cc", + "android/java_bitmap.h", + "android/view_configuration.cc", + "android/view_configuration.h", + ] + } + if (is_linux || is_chromeos) { + sources += [ + "font_fallback_linux.cc", + "font_fallback_linux.h", + "font_render_params_linux.cc", + "linux/fontconfig_util.cc", + "linux/fontconfig_util.h", + ] + } + if (is_mac) { + sources += [ + "canvas_paint_mac.h", + "canvas_paint_mac.mm", + "decorated_text_mac.h", + "decorated_text_mac.mm", + "font_fallback_mac.mm", + "font_render_params_mac.cc", + "image/image_mac.mm", + "image/image_skia_util_mac.h", + "image/image_skia_util_mac.mm", + "image/image_util_mac.mm", + "mac/coordinate_conversion.h", + "mac/coordinate_conversion.mm", + "mac/nswindow_frame_controls.h", + "mac/nswindow_frame_controls.mm", + "mac/scoped_cocoa_disable_screen_updates.h", + "mac/scoped_cocoa_disable_screen_updates.mm", + "path_mac.h", + "path_mac.mm", + "platform_font_mac.h", + "platform_font_mac.mm", + "scoped_cg_context_save_gstate_mac.h", + "scoped_ns_graphics_context_save_gstate_mac.h", + "scoped_ns_graphics_context_save_gstate_mac.mm", + ] + } + if (is_win) { + sources += [ + "font_fallback_win.cc", + "font_fallback_win.h", + "font_render_params_win.cc", + "path_win.cc", + "path_win.h", + "system_fonts_win.cc", + "system_fonts_win.h", + "win/crash_id_helper.cc", + "win/crash_id_helper.h", + "win/direct_write.cc", + "win/direct_write.h", + "win/hwnd_util.cc", + "win/hwnd_util.h", + "win/msg_util.h", + "win/physical_size.cc", + "win/physical_size.h", + "win/rendering_window_manager.cc", + "win/rendering_window_manager.h", + "win/scoped_set_map_mode.h", + "win/singleton_hwnd.cc", + "win/singleton_hwnd.h", + "win/singleton_hwnd_hot_key_observer.cc", + "win/singleton_hwnd_hot_key_observer.h", + "win/singleton_hwnd_observer.cc", + "win/singleton_hwnd_observer.h", + "win/text_analysis_source.cc", + "win/text_analysis_source.h", + "win/window_impl.cc", + "win/window_impl.h", + ] + } + if (is_ios) { + sources += [ + "image/image_ios.mm", + "image/image_skia_rep_ios.cc", + "image/image_skia_rep_ios.h", + "image/image_skia_util_ios.h", + "image/image_skia_util_ios.mm", + "image/image_util_ios.mm", + "ios/NSString+CrStringDrawing.h", + "ios/NSString+CrStringDrawing.mm", + "ios/uikit_util.h", + "ios/uikit_util.mm", + "platform_font_ios.h", + "platform_font_ios.mm", + "scoped_ui_graphics_push_context_ios.h", + "scoped_ui_graphics_push_context_ios.mm", + "text_utils_ios.mm", + ] + } + if (!is_ios) { + sources += [ + "blit.cc", + "blit.h", + "canvas.cc", + "canvas.h", + "canvas_skia.cc", + "canvas_skia_paint.h", + "image/canvas_image_source.cc", + "image/canvas_image_source.h", + "image/image_generic.cc", + "image/image_skia_operations.cc", + "image/image_skia_operations.h", + "image/image_skia_rep_default.cc", + "image/image_skia_rep_default.h", + "paint_throbber.cc", + "paint_throbber.h", + "scoped_canvas.cc", + "scoped_canvas.h", + "shadow_util.cc", + "shadow_util.h", + "skia_paint_util.cc", + "skia_paint_util.h", + ] + } + + configs += [ + "//build/config:precompiled_headers", + "//build/config/compiler:noshadowing", + "//build/config/compiler:wexit_time_destructors", + ] + + # This is part of the gfx component in the component build. + defines = [ "GFX_IMPLEMENTATION" ] + + public_deps = [ + ":color_space", + ":color_utils", + ":gfx_skia", + ":gfx_switches", + ":memory_buffer_sources", + ":native_widget_types", + ":resize_image_dimensions", + ":selection_bound_sources", + "//base", + "//skia", + "//skia:skcms", + "//third_party/icu", + "//ui/gfx/animation", + "//ui/gfx/codec", + "//ui/gfx/geometry", + "//ui/gfx/geometry:geometry_skia", + "//ui/gfx/range", + ] + deps = [ + ":gfx_export", + "//base", + "//base:base_static", + "//base:i18n", + "//base/third_party/dynamic_annotations", + "//build:chromeos_buildflags", + "//device/vr/buildflags", + "//mojo/public/cpp/bindings:struct_traits", + "//skia", + "//third_party/zlib", + ] + + if (!is_apple) { + sources += [ + "platform_font_skia.cc", + "platform_font_skia.h", + "skia_font_delegate.cc", + "skia_font_delegate.h", + ] + } + + # iOS. + if (is_ios) { + sources += [ "scoped_cg_context_save_gstate_mac.h" ] + } else { + public_deps += [ "//cc/paint" ] + deps += [ "//third_party:freetype_harfbuzz" ] + } + + # Android. + if (is_android) { + if (!is_debug) { + configs -= [ "//build/config/compiler:default_optimization" ] + configs += [ "//build/config/compiler:optimize_max" ] + } + + deps += [ ":gfx_jni_headers" ] + libs = [ + "android", + "jnigraphics", + ] + } + + if (is_android || is_fuchsia) { + sources += [ + "font_fallback_skia.cc", + "font_render_params_skia.cc", + ] + } + + if (is_android || is_fuchsia || is_win || is_mac) { + sources += [ + "font_fallback_skia_impl.cc", + "font_fallback_skia_impl.h", + ] + } + + if (!is_ios) { + sources += [ + "bidi_line_iterator.cc", + "bidi_line_iterator.h", + "harfbuzz_font_skia.cc", + "harfbuzz_font_skia.h", + "paint_vector_icon.cc", + "paint_vector_icon.h", + "render_text.cc", + "render_text.h", + "render_text_harfbuzz.cc", + "render_text_harfbuzz.h", + "text_utils_skia.cc", + ] + } + + # Windows. + if (is_win) { + libs = [ + "setupapi.lib", + "dwrite.lib", + ] + deps += [ "//components/crash/core/common" ] + } else { + sources -= [ + "gdi_util.cc", + "gdi_util.h", + "icon_util.cc", + "icon_util.h", + "sys_color_change_listener.cc", + "sys_color_change_listener.h", + ] + } + + # Linux. + if (is_linux || is_chromeos) { + deps += [ "//third_party/fontconfig" ] + } + + if (is_mac) { + frameworks = [ + "AppKit.framework", + "CoreFoundation.framework", + "CoreGraphics.framework", + "CoreText.framework", + "IOSurface.framework", + ] + } + + if ((!use_aura && !toolkit_views) || is_ios) { + sources -= [ + "nine_image_painter.cc", + "nine_image_painter.h", + ] + } + + if (use_ozone) { + deps += [ "//ui/ozone:buildflags" ] + } + + if (ozone_platform_x11 || use_x11) { + deps += [ "//ui/gfx/x" ] + } +} + +component("color_space") { + sources = [ + "color_space.cc", + "color_space.h", + "color_space_export.h", + "display_color_spaces.cc", + "display_color_spaces.h", + "hdr_static_metadata.cc", + "hdr_static_metadata.h", + "icc_profile.cc", + "icc_profile.h", + "skia_color_space_util.cc", + "skia_color_space_util.h", + ] + if (is_win) { + sources += [ + "color_space_win.cc", + "color_space_win.h", + ] + } + deps = [ + "//build:chromeos_buildflags", + "//skia:skcms", + "//ui/gfx:buffer_types", + ] + public_deps = [ + "//base", + "//skia", + ] + if (is_mac) { + sources += [ + "mac/display_icc_profiles.cc", + "mac/display_icc_profiles.h", + ] + frameworks = [ + "CoreFoundation.framework", + "CoreGraphics.framework", + ] + } + if (use_x11) { + deps += [ "//ui/gfx/x" ] + } + defines = [ "COLOR_SPACE_IMPLEMENTATION" ] +} + +# Depend on this to use image/resize_image_dimensions.h without pulling in +# all of gfx. +source_set("resize_image_dimensions") { + sources = [ "image/resize_image_dimensions.h" ] +} + +# Depend on this to use native_widget_types.h without pulling in all of gfx. +source_set("native_widget_types") { + public = [ "native_widget_types.h" ] + + public_deps = [ + ":gfx_export", + "//base", + ] + + deps = [ "//build:chromeos_buildflags" ] +} + +group("selection_bound") { + if (is_component_build) { + public_deps = [ ":gfx" ] + } else { + public_deps = [ ":selection_bound_sources" ] + } +} + +# Depend on this to use selection_bound.h without pulling in all of gfx. +# Cannot be a static_library in component builds due to exported functions +source_set("selection_bound_sources") { + visibility = [ ":*" ] + + sources = [ + "gfx_export.h", + "selection_bound.cc", + "selection_bound.h", + ] + + configs += [ "//build/config/compiler:wexit_time_destructors" ] + + defines = [ "GFX_IMPLEMENTATION" ] + + public_deps = [ + "//base", + "//ui/gfx/geometry", + ] +} + +# Depend on this to use buffer_types.h without pulling in all of gfx. +source_set("buffer_types") { + sources = [ "buffer_types.h" ] +} + +# The GPU memory buffer stuff is separate from "gfx" to allow GPU-related +# things to use these files without pulling in all of gfx, which includes large +# things like Skia. +# +# The structure here allows the memory buffer to be part of the gfx component +# in the component build, but be a separate source set in a static build. +group("memory_buffer") { + if (is_component_build) { + public_deps = [ ":gfx" ] + } else { + public_deps = [ ":memory_buffer_sources" ] + } +} + +# Cannot be a static_library in component builds due to exported functions +source_set("memory_buffer_sources") { + visibility = [ ":*" ] # Depend on through ":memory_buffer". + + # TODO(brettw) refactor this so these sources are in a coherent directory + # structure rather than random samplings of ui/gfx and ui/gfx/mac. + sources = [ + "buffer_format_util.cc", + "buffer_format_util.h", + "buffer_usage_util.cc", + "buffer_usage_util.h", + "ca_layer_params.cc", + "ca_layer_params.h", + "client_native_pixmap.h", + "client_native_pixmap_factory.h", + "generic_shared_memory_id.cc", + "generic_shared_memory_id.h", + "gfx_export.h", + "gpu_fence.cc", + "gpu_fence.h", + "gpu_fence_handle.cc", + "gpu_fence_handle.h", + "hdr_metadata.cc", + "hdr_metadata.h", + "native_pixmap.h", + "overlay_priority_hint.h", + "overlay_transform.h", + "surface_origin.h", + ] + + if (!is_ios) { + sources += [ + "gpu_memory_buffer.cc", + "gpu_memory_buffer.h", + ] + } + + configs += [ "//build/config/compiler:wexit_time_destructors" ] + + defines = [ "GFX_IMPLEMENTATION" ] + + public_deps = [ ":buffer_types" ] + + deps = [ + ":gfx_switches", + ":native_widget_types", + "//base", + "//build:chromecast_buildflags", + "//build:chromeos_buildflags", + "//ui/gfx/geometry", + ] + + if (is_linux || is_chromeos) { + sources += [ + "linux/client_native_pixmap_dmabuf.cc", + "linux/client_native_pixmap_dmabuf.h", + "linux/client_native_pixmap_factory_dmabuf.cc", + "linux/client_native_pixmap_factory_dmabuf.h", + "linux/native_pixmap_dmabuf.cc", + "linux/native_pixmap_dmabuf.h", + ] + + deps += [ "//build/config/linux/libdrm" ] + } + + if (is_linux || is_chromeos || is_android) { + deps += [ "//third_party/libsync" ] + } + + if (is_mac) { + sources += [ + "mac/io_surface.cc", + "mac/io_surface.h", + ] + + public_deps += [ "//ui/gfx:color_space" ] + } + + if (is_win) { + public_deps += [ "//ipc:message_support" ] + } + + if ((is_linux || is_chromeos || use_ozone) && !is_nacl) { + sources += [ + "native_pixmap_handle.cc", + "native_pixmap_handle.h", + ] + } +} + +# TODO(ccameron): This can be moved into a separate source_set. +component("gfx_switches") { + sources = [ + "switches.cc", + "switches.h", + "switches_export.h", + ] + + defines = [ "GFX_SWITCHES_IMPLEMENTATION" ] + + deps = [ "//base" ] +} + +static_library("test_support") { + testonly = true + sources = [ + "animation/animation_test_api.cc", + "animation/animation_test_api.h", + "animation/keyframe/test/animation_utils.cc", + "animation/keyframe/test/animation_utils.h", + "animation/test_animation_delegate.h", + "geometry/test/rect_test_util.cc", + "geometry/test/rect_test_util.h", + "geometry/test/size_test_util.h", + "geometry/test/transform_test_util.cc", + "geometry/test/transform_test_util.h", + "image/image_unittest_util.cc", + "image/image_unittest_util.h", + "test/font_fallback_test_data.cc", + "test/font_fallback_test_data.h", + "test/gfx_util.cc", + "test/gfx_util.h", + "test/icc_profiles.cc", + "test/icc_profiles.h", + ] + if (is_ios) { + sources += [ "image/image_unittest_util_ios.mm" ] + } + if (is_mac) { + sources += [ "image/image_unittest_util_mac.mm" ] + } + + public_deps = [ ":gfx" ] + + deps = [ + "//base", + "//base/test:test_support", + "//skia", + "//testing/gtest", + "//ui/gfx/animation", + "//ui/gfx/animation/keyframe", + "//ui/gfx/geometry", + ] + + if (!is_ios) { + sources += [ "render_text_test_api.h" ] + + deps += [ "//third_party:freetype_harfbuzz" ] + } +} + +if (is_mac) { + component("gfx_io_surface_hdr_metadata") { + sources = [ + "mac/io_surface_hdr_metadata.cc", + "mac/io_surface_hdr_metadata.h", + ] + defines = [ "IS_GFX_IO_SURFACE_HDR_METADATA_IMPL" ] + + # This is a separate component from the other sources because it depends on + # the mojo serialize and deserialize methods. + deps = [ + ":gfx", + "//ui/gfx/mojom:mojom", + ] + frameworks = [ + "CoreFoundation.framework", + "IOSurface.framework", + ] + } +} + +test("gfx_unittests") { + sources = [ + "animation/keyframe/keyframe_animation_unittest.cc", + "animation/keyframe/keyframed_animation_curve_unittest.cc", + "font_names_testing.cc", + "font_names_testing.h", + "font_unittest.cc", + "geometry/rrect_f_unittest.cc", + "geometry/transform_operations_unittest.cc", + "geometry/transform_unittest.cc", + "image/buffer_w_stream_unittest.cc", + "image/image_family_unittest.cc", + "image/image_skia_unittest.cc", + "image/image_unittest.cc", + "interpolated_transform_unittest.cc", + "test/run_all_unittests.cc", + "text_elider_unittest.cc", + "text_utils_unittest.cc", + ] + if (is_linux || is_chromeos) { + sources += [ + "font_fallback_linux_unittest.cc", + "font_render_params_linux_unittest.cc", + ] + } + if (is_mac) { + sources += [ + "font_fallback_mac_unittest.cc", + "image/image_mac_unittest.mm", + "mac/coordinate_conversion_unittest.mm", + "mac/io_surface_unittest.cc", + "path_mac_unittest.mm", + "platform_font_mac_unittest.mm", + "range/range_mac_unittest.mm", + ] + frameworks = [ "IOSurface.framework" ] + } + if (is_win) { + sources += [ "font_fallback_win_unittest.cc" ] + } + if (is_ios) { + sources += [ + "image/image_ios_unittest.mm", + "ios/NSString+CrStringDrawing_unittest.mm", + "ios/uikit_util_unittest.mm", + ] + } + if (is_android) { + sources += [ "android/android_surface_control_compat_unittest.cc" ] + } + + include_dirs = [ "//third_party/skia/include/private" ] + + data = [ "test/data/" ] + + if (!is_ios) { + sources += [ + "animation/animation_container_unittest.cc", + "animation/animation_runner_unittest.cc", + "animation/animation_unittest.cc", + "animation/multi_animation_unittest.cc", + "animation/slide_animation_unittest.cc", + "animation/tween_unittest.cc", + "blit_unittest.cc", + "break_list_unittest.cc", + "canvas_unittest.cc", + "codec/jpeg_codec_unittest.cc", + "codec/png_codec_unittest.cc", + "color_analysis_unittest.cc", + "color_space_unittest.cc", + "color_transform_unittest.cc", + "color_utils_unittest.cc", + "delegated_ink_unittest.cc", + "font_fallback_unittest.cc", + "font_list_unittest.cc", + "geometry/axis_transform2d_unittest.cc", + "geometry/box_unittest.cc", + "geometry/cubic_bezier_unittest.cc", + "geometry/insets_unittest.cc", + "geometry/matrix3_unittest.cc", + "geometry/point3_unittest.cc", + "geometry/point_unittest.cc", + "geometry/quad_unittest.cc", + "geometry/quaternion_unittest.cc", + "geometry/rect_f_unittest.cc", + "geometry/rect_unittest.cc", + "geometry/resize_utils_unittest.cc", + "geometry/rounded_corners_f_unittest.cc", + "geometry/size_unittest.cc", + "geometry/transform_util_unittest.cc", + "geometry/vector2d_unittest.cc", + "geometry/vector3d_unittest.cc", + "half_float_unittest.cc", + "icc_profile_unittest.cc", + "image/image_skia_operations_unittest.cc", + "image/image_util_unittest.cc", + "mojom/mojom_traits_unittest.cc", + "nine_image_painter_unittest.cc", + "overlay_transform_utils_unittest.cc", + "paint_vector_icon_unittest.cc", + "range/range_unittest.cc", + "selection_bound_unittest.cc", + "selection_model_unittest.cc", + "sequential_id_generator_unittest.cc", + "shadow_value_unittest.cc", + "skbitmap_operations_unittest.cc", + "skia_util_unittest.cc", + "skrect_conversion_unittest.cc", + "utf16_indexing_unittest.cc", + ] + } + + if (is_win) { + sources += [ "system_fonts_win_unittest.cc" ] + } + + if (is_linux || is_chromeos || is_android || is_fuchsia || is_win) { + sources += [ "platform_font_skia_unittest.cc" ] + } + + deps = [ + ":gfx", + ":test_support", + "//base", + "//base/test:test_support", + "//build:chromeos_buildflags", + "//skia", + "//skia:skcms", + "//testing/gtest", + "//third_party/icu:icuuc", + "//third_party/libpng", + "//third_party/zlib", + "//ui/base", + "//ui/gfx/animation", + "//ui/gfx/animation/keyframe", + "//ui/gfx/geometry", + "//ui/gfx/range", + ] + + if (!is_ios) { + sources += [ + "bidi_line_iterator_unittest.cc", + "render_text_unittest.cc", + ] + deps += [ "//third_party:freetype_harfbuzz" ] + } + + data_deps = [ "//ui/resources:ui_test_pak_data" ] + + if (is_apple) { + deps += [ "//ui/resources:ui_test_pak_bundle_data" ] + } + + if (is_mac) { + deps += [ ":gfx_io_surface_hdr_metadata" ] + } + + if (is_android) { + deps += [ "//ui/android:ui_java" ] + } + + if (is_android || is_fuchsia) { + sources += [ "font_fallback_skia_unittest.cc" ] + } + + if (!use_aura && !is_ios) { + sources -= [ "nine_image_painter_unittest.cc" ] + } + + if (is_win) { + sources += [ + "icon_util_unittest.cc", + "icon_util_unittests.rc", + "icon_util_unittests_resource.h", + "path_win_unittest.cc", + "win/crash_id_helper_unittest.cc", + "win/direct_write_unittest.cc", + "win/text_analysis_source_unittest.cc", + ] + + ldflags = [ + "/DELAYLOAD:d2d1.dll", + "/DELAYLOAD:d3d10_1.dll", + ] + + libs = [ + "d2d1.lib", + "d3d10_1.lib", + "dwrite.lib", + "imm32.lib", + "oleacc.lib", + ] + } + + if (!is_ios) { + deps += [ + "//cc/paint", + "//mojo/core/embedder", + "//mojo/public/cpp/bindings", + "//mojo/public/cpp/test_support:test_utils", + "//ui/gfx/geometry/mojom:unit_test", + "//ui/gfx/image/mojom:unit_test", + "//ui/gfx/mojom:test_interfaces", + "//ui/gfx/range/mojom:unit_test", + ] + } + + if (is_linux || is_chromeos) { + sources += [ + "linux/fontconfig_util_unittest.cc", + "linux/native_pixmap_dmabuf_unittest.cc", + ] + deps += [ "//third_party/fontconfig" ] + } + + if (is_fuchsia) { + deps += [ "//skia:test_fonts" ] + } +} + +if (is_android) { + generate_jni("gfx_jni_headers") { + sources = [ + "../android/java/src/org/chromium/ui/gfx/AdpfRenderingStageScheduler.java", + "../android/java/src/org/chromium/ui/gfx/Animation.java", + "../android/java/src/org/chromium/ui/gfx/BitmapHelper.java", + "../android/java/src/org/chromium/ui/gfx/ViewConfigurationHelper.java", + ] + } +} + +fuzzer_test("color_analysis_fuzzer") { + sources = [ "color_analysis_fuzzer.cc" ] + + deps = [ ":gfx" ] +} + +fuzzer_test("color_transform_fuzzer") { + sources = [ "color_transform_fuzzer.cc" ] + + dict = "//testing/libfuzzer/fuzzers/dicts/icc.dict" + + deps = [ ":gfx" ] + + libfuzzer_options = [ "max_len=4194304" ] +} + +fuzzer_test("render_text_fuzzer") { + sources = [ "render_text_fuzzer.cc" ] + + deps = [ + ":gfx", + "//base", + "//base/test:test_support", + ] + + dict = "test/data/render_text/unicode_text_fuzzer.dict" +} + +fuzzer_test("render_text_api_fuzzer") { + sources = [ "render_text_api_fuzzer.cc" ] + + deps = [ + ":gfx", + "//base", + "//base/test:test_support", + "//build:chromeos_buildflags", + ] + + dict = "test/data/render_text/unicode_text_fuzzer.dict" +} diff --git a/DEPS b/DEPS new file mode 100644 index 000000000000..bff94453a22f --- /dev/null +++ b/DEPS @@ -0,0 +1,20 @@ +include_rules = [ + "+base", + "+cc/base", + "+cc/paint", + "+device/vr/buildflags/buildflags.h", + "+skia/ext", + "+third_party/harfbuzz-ng", + "+third_party/skia", + "+third_party/test_fonts", + "+ui/ios", + "+ui/ozone/buildflags.h", + + "-testing/gmock", +] + +specific_include_rules = { + "delegated_ink_point\.h" : [ + "+mojo/public/cpp/bindings/struct_traits.h", + ], +} diff --git a/DIR_METADATA b/DIR_METADATA new file mode 100644 index 000000000000..b89d550e5036 --- /dev/null +++ b/DIR_METADATA @@ -0,0 +1,12 @@ +# Metadata information for this directory. +# +# For more information on DIR_METADATA files, see: +# https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md +# +# For the schema of this file, see Metadata message: +# https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto + +monorail { + component: "UI>GFX" +} +team_email: "graphics-dev@chromium.org" \ No newline at end of file diff --git a/OWNERS b/OWNERS new file mode 100644 index 000000000000..4bec2f1dd187 --- /dev/null +++ b/OWNERS @@ -0,0 +1,63 @@ +# For any other files, defer to ui/OWNERS. + +# RenderText and related classes. +per-file render_text*=etienneb@chromium.org +per-file render_text*=msw@chromium.org +per-file text_elider*=etienneb@chromium.org +per-file text_elider*=msw@chromium.org + +# Fonts and fallback fonts. +per-file font*=etienneb@chromium.org +per-file font*=robliao@chromium.org +per-file platform_font*=etienneb@chromium.org +per-file platform_font*=robliao@chromium.org + +# Color utils. +per-file color_palette.h=pkasting@chromium.org +per-file color_utils*=pkasting@chromium.org + +# Display and related classes. +per-file display*=oshima@chromium.org +per-file screen*=oshima@chromium.org + +# Canvas painting. +per-file canvas*=danakj@chromium.org + +# Overlay transforms. +per-file overlay*=spang@chromium.org + +# Overlay plane data. +per-file overlay_plane_data*=rjkroege@chromium.org + +# Transform, interpolated transform and transform util. +per-file transform*=danakj@chromium.org +per-file transform*=vollick@chromium.org +per-file interpolated_transform*=danakj@chromium.org +per-file interpolated_transform*=vollick@chromium.org + +# Skia geometry helpers. +per-file skia_util*=danakj@chromium.org +per-file skia_paint_util*=danakj@chromium.org + +# GPU memory buffer and GpuFence interfaces. +per-file gpu_fence*=dcastagna@chromium.org +per-file gpu_fence*=rjkroege@chromium.org +per-file gpu_fence*=spang@chromium.org +per-file gpu_memory_buffer*=dcastagna@chromium.org +per-file gpu_memory_buffer*=rjkroege@chromium.org +per-file gpu_memory_buffer*=spang@chromium.org +per-file buffer_format*=dcastagna@chromium.org +per-file buffer_format*=rjkroege@chromium.org +per-file buffer_format*=spang@chromium.org +per-file buffer_usage*=rjkroege@chromium.org +per-file *buffer_types.*=dcastagna@chromium.org +per-file *buffer_types.*=rjkroege@chromium.org +per-file *buffer_types.*=spang@chromium.org +per-file *native_pixmap*=rjkroege@chromium.org +per-file *native_pixmap*=spang@chromium.org + +# Vector icons. +per-file *vector_icon*=estade@chromium.org + +# If you're doing structural changes get a review from one of the OWNERS. +per-file BUILD.gn=* diff --git a/android/DEPS b/android/DEPS new file mode 100644 index 000000000000..841ceffd7f7b --- /dev/null +++ b/android/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+ui/gfx/gfx_jni_headers", +] diff --git a/android/OWNERS b/android/OWNERS new file mode 100644 index 000000000000..15a182a6c7b4 --- /dev/null +++ b/android/OWNERS @@ -0,0 +1 @@ +skyostil@chromium.org diff --git a/android/android_surface_control_compat.cc b/android/android_surface_control_compat.cc new file mode 100644 index 000000000000..a92788e7240f --- /dev/null +++ b/android/android_surface_control_compat.cc @@ -0,0 +1,693 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/android/android_surface_control_compat.h" + +#include +#include + +#include "base/android/build_info.h" +#include "base/atomic_sequence_num.h" +#include "base/bind.h" +#include "base/bind_post_task.h" +#include "base/debug/crash_logging.h" +#include "base/hash/md5_constexpr.h" +#include "base/logging.h" +#include "base/memory/ptr_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/system/sys_info.h" +#include "base/trace_event/trace_event.h" +#include "ui/gfx/color_space.h" + +extern "C" { +typedef struct ASurfaceTransactionStats ASurfaceTransactionStats; +typedef void (*ASurfaceTransaction_OnComplete)(void* context, + ASurfaceTransactionStats* stats); +typedef void (*ASurfaceTransaction_OnCommit)(void* context, + ASurfaceTransactionStats* stats); + +// ASurface +using pASurfaceControl_createFromWindow = + ASurfaceControl* (*)(ANativeWindow* parent, const char* name); +using pASurfaceControl_create = ASurfaceControl* (*)(ASurfaceControl* parent, + const char* name); +using pASurfaceControl_release = void (*)(ASurfaceControl*); + +// ASurfaceTransaction enums +enum { + ASURFACE_TRANSACTION_TRANSPARENCY_TRANSPARENT = 0, + ASURFACE_TRANSACTION_TRANSPARENCY_TRANSLUCENT = 1, + ASURFACE_TRANSACTION_TRANSPARENCY_OPAQUE = 2, +}; + +// ANativeWindow_FrameRateCompatibility enums +enum { + ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT = 0, + ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE = 1 +}; + +// ASurfaceTransaction +using pASurfaceTransaction_create = ASurfaceTransaction* (*)(void); +using pASurfaceTransaction_delete = void (*)(ASurfaceTransaction*); +using pASurfaceTransaction_apply = int64_t (*)(ASurfaceTransaction*); +using pASurfaceTransaction_setOnComplete = + void (*)(ASurfaceTransaction*, void* ctx, ASurfaceTransaction_OnComplete); +using pASurfaceTransaction_setOnCommit = void (*)(ASurfaceTransaction*, + void* ctx, + ASurfaceTransaction_OnCommit); +using pASurfaceTransaction_setVisibility = void (*)(ASurfaceTransaction*, + ASurfaceControl*, + int8_t visibility); +using pASurfaceTransaction_setZOrder = + void (*)(ASurfaceTransaction* transaction, ASurfaceControl*, int32_t z); +using pASurfaceTransaction_setBuffer = + void (*)(ASurfaceTransaction* transaction, + ASurfaceControl*, + AHardwareBuffer*, + int32_t fence_fd); +using pASurfaceTransaction_setGeometry = + void (*)(ASurfaceTransaction* transaction, + ASurfaceControl* surface, + const ARect& src, + const ARect& dst, + int32_t transform); +using pASurfaceTransaction_setPosition = + void (*)(ASurfaceTransaction* transaction, + ASurfaceControl* surface, + int32_t x, + int32_t y); +using pASurfaceTransaction_setScale = void (*)(ASurfaceTransaction* transaction, + ASurfaceControl* surface, + float x_scale, + float y_scale); +using pASurfaceTransaction_setCrop = void (*)(ASurfaceTransaction* transaction, + ASurfaceControl* surface, + const ARect& src); +using pASurfaceTransaction_setBufferTransparency = + void (*)(ASurfaceTransaction* transaction, + ASurfaceControl* surface, + int8_t transparency); +using pASurfaceTransaction_setDamageRegion = + void (*)(ASurfaceTransaction* transaction, + ASurfaceControl* surface, + const ARect rects[], + uint32_t count); +using pASurfaceTransaction_setBufferDataSpace = + void (*)(ASurfaceTransaction* transaction, + ASurfaceControl* surface, + uint64_t data_space); +using pASurfaceTransaction_setFrameRate = + void (*)(ASurfaceTransaction* transaction, + ASurfaceControl* surface_control, + float frameRate, + int8_t compatibility); +using pASurfaceTransaction_reparent = void (*)(ASurfaceTransaction*, + ASurfaceControl* surface_control, + ASurfaceControl* new_parent); +// ASurfaceTransactionStats +using pASurfaceTransactionStats_getPresentFenceFd = + int (*)(ASurfaceTransactionStats* stats); +using pASurfaceTransactionStats_getLatchTime = + int64_t (*)(ASurfaceTransactionStats* stats); +using pASurfaceTransactionStats_getASurfaceControls = + void (*)(ASurfaceTransactionStats* stats, + ASurfaceControl*** surface_controls, + size_t* size); +using pASurfaceTransactionStats_releaseASurfaceControls = + void (*)(ASurfaceControl** surface_controls); +using pASurfaceTransactionStats_getPreviousReleaseFenceFd = + int (*)(ASurfaceTransactionStats* stats, ASurfaceControl* surface_control); +} + +namespace gfx { +namespace { + +base::AtomicSequenceNumber g_next_transaction_id; + +uint64_t g_agb_required_usage_bits = AHARDWAREBUFFER_USAGE_COMPOSER_OVERLAY; + +#define LOAD_FUNCTION(lib, func) \ + do { \ + func##Fn = reinterpret_cast(dlsym(lib, #func)); \ + if (!func##Fn) { \ + supported = false; \ + LOG(ERROR) << "Unable to load function " << #func; \ + } \ + } while (0) + +#define LOAD_FUNCTION_MAYBE(lib, func) \ + do { \ + func##Fn = reinterpret_cast(dlsym(lib, #func)); \ + } while (0) + +struct SurfaceControlMethods { + public: + static SurfaceControlMethods& GetImpl(bool load_functions) { + static SurfaceControlMethods instance(load_functions); + return instance; + } + + static const SurfaceControlMethods& Get() { + return GetImpl(/*load_functions=*/true); + } + + void InitWithStubs() { + struct TransactionStub { + ASurfaceTransaction_OnComplete on_complete = nullptr; + void* on_complete_ctx = nullptr; + ASurfaceTransaction_OnCommit on_commit = nullptr; + void* on_commit_ctx = nullptr; + }; + + ASurfaceTransaction_createFn = []() { + return reinterpret_cast(new TransactionStub); + }; + ASurfaceTransaction_deleteFn = [](ASurfaceTransaction* transaction) { + delete reinterpret_cast(transaction); + }; + ASurfaceTransaction_applyFn = [](ASurfaceTransaction* transaction) { + auto* stub = reinterpret_cast(transaction); + + if (stub->on_commit) + stub->on_commit(stub->on_commit_ctx, nullptr); + stub->on_commit = nullptr; + stub->on_commit_ctx = nullptr; + + if (stub->on_complete) + stub->on_complete(stub->on_complete_ctx, nullptr); + stub->on_complete = nullptr; + stub->on_complete_ctx = nullptr; + + return static_cast(0); + }; + + ASurfaceTransaction_setOnCompleteFn = + [](ASurfaceTransaction* transaction, void* ctx, + ASurfaceTransaction_OnComplete callback) { + auto* stub = reinterpret_cast(transaction); + stub->on_complete = callback; + stub->on_complete_ctx = ctx; + }; + + ASurfaceTransaction_setOnCommitFn = + [](ASurfaceTransaction* transaction, void* ctx, + ASurfaceTransaction_OnCommit callback) { + auto* stub = reinterpret_cast(transaction); + stub->on_commit = callback; + stub->on_commit_ctx = ctx; + }; + } + + SurfaceControlMethods(bool load_functions) { + if (!load_functions) + return; + + void* main_dl_handle = dlopen("libandroid.so", RTLD_NOW); + if (!main_dl_handle) { + LOG(ERROR) << "Couldnt load android so"; + supported = false; + return; + } + + LOAD_FUNCTION(main_dl_handle, ASurfaceControl_createFromWindow); + LOAD_FUNCTION(main_dl_handle, ASurfaceControl_create); + LOAD_FUNCTION(main_dl_handle, ASurfaceControl_release); + + LOAD_FUNCTION(main_dl_handle, ASurfaceTransaction_create); + LOAD_FUNCTION(main_dl_handle, ASurfaceTransaction_delete); + LOAD_FUNCTION(main_dl_handle, ASurfaceTransaction_apply); + LOAD_FUNCTION(main_dl_handle, ASurfaceTransaction_setOnComplete); + LOAD_FUNCTION_MAYBE(main_dl_handle, ASurfaceTransaction_setOnCommit); + LOAD_FUNCTION(main_dl_handle, ASurfaceTransaction_reparent); + LOAD_FUNCTION(main_dl_handle, ASurfaceTransaction_setVisibility); + LOAD_FUNCTION(main_dl_handle, ASurfaceTransaction_setZOrder); + LOAD_FUNCTION(main_dl_handle, ASurfaceTransaction_setBuffer); + LOAD_FUNCTION(main_dl_handle, ASurfaceTransaction_setGeometry); + LOAD_FUNCTION_MAYBE(main_dl_handle, ASurfaceTransaction_setPosition); + LOAD_FUNCTION_MAYBE(main_dl_handle, ASurfaceTransaction_setScale); + LOAD_FUNCTION_MAYBE(main_dl_handle, ASurfaceTransaction_setCrop); + LOAD_FUNCTION(main_dl_handle, ASurfaceTransaction_setBufferTransparency); + LOAD_FUNCTION(main_dl_handle, ASurfaceTransaction_setDamageRegion); + LOAD_FUNCTION(main_dl_handle, ASurfaceTransaction_setBufferDataSpace); + LOAD_FUNCTION_MAYBE(main_dl_handle, ASurfaceTransaction_setFrameRate); + + LOAD_FUNCTION(main_dl_handle, ASurfaceTransactionStats_getPresentFenceFd); + LOAD_FUNCTION(main_dl_handle, ASurfaceTransactionStats_getLatchTime); + LOAD_FUNCTION(main_dl_handle, ASurfaceTransactionStats_getASurfaceControls); + LOAD_FUNCTION(main_dl_handle, + ASurfaceTransactionStats_releaseASurfaceControls); + LOAD_FUNCTION(main_dl_handle, + ASurfaceTransactionStats_getPreviousReleaseFenceFd); + } + + ~SurfaceControlMethods() = default; + + bool supported = true; + // Surface methods. + pASurfaceControl_createFromWindow ASurfaceControl_createFromWindowFn; + pASurfaceControl_create ASurfaceControl_createFn; + pASurfaceControl_release ASurfaceControl_releaseFn; + + // Transaction methods. + pASurfaceTransaction_create ASurfaceTransaction_createFn; + pASurfaceTransaction_delete ASurfaceTransaction_deleteFn; + pASurfaceTransaction_apply ASurfaceTransaction_applyFn; + pASurfaceTransaction_setOnComplete ASurfaceTransaction_setOnCompleteFn; + pASurfaceTransaction_setOnCommit ASurfaceTransaction_setOnCommitFn; + pASurfaceTransaction_reparent ASurfaceTransaction_reparentFn; + pASurfaceTransaction_setVisibility ASurfaceTransaction_setVisibilityFn; + pASurfaceTransaction_setZOrder ASurfaceTransaction_setZOrderFn; + pASurfaceTransaction_setBuffer ASurfaceTransaction_setBufferFn; + pASurfaceTransaction_setGeometry ASurfaceTransaction_setGeometryFn; + pASurfaceTransaction_setPosition ASurfaceTransaction_setPositionFn; + pASurfaceTransaction_setScale ASurfaceTransaction_setScaleFn; + pASurfaceTransaction_setCrop ASurfaceTransaction_setCropFn; + pASurfaceTransaction_setBufferTransparency + ASurfaceTransaction_setBufferTransparencyFn; + pASurfaceTransaction_setDamageRegion ASurfaceTransaction_setDamageRegionFn; + pASurfaceTransaction_setBufferDataSpace + ASurfaceTransaction_setBufferDataSpaceFn; + pASurfaceTransaction_setFrameRate ASurfaceTransaction_setFrameRateFn; + + // TransactionStats methods. + pASurfaceTransactionStats_getPresentFenceFd + ASurfaceTransactionStats_getPresentFenceFdFn; + pASurfaceTransactionStats_getLatchTime + ASurfaceTransactionStats_getLatchTimeFn; + pASurfaceTransactionStats_getASurfaceControls + ASurfaceTransactionStats_getASurfaceControlsFn; + pASurfaceTransactionStats_releaseASurfaceControls + ASurfaceTransactionStats_releaseASurfaceControlsFn; + pASurfaceTransactionStats_getPreviousReleaseFenceFd + ASurfaceTransactionStats_getPreviousReleaseFenceFdFn; +}; + +ARect RectToARect(const gfx::Rect& rect) { + return ARect{rect.x(), rect.y(), rect.right(), rect.bottom()}; +} + +int32_t OverlayTransformToWindowTransform(gfx::OverlayTransform transform) { + // Note that the gfx::OverlayTransform expresses rotations in anticlockwise + // direction while the ANativeWindow rotations are in clockwise direction. + switch (transform) { + case gfx::OVERLAY_TRANSFORM_INVALID: + DCHECK(false) << "Invalid Transform"; + return ANATIVEWINDOW_TRANSFORM_IDENTITY; + case gfx::OVERLAY_TRANSFORM_NONE: + return ANATIVEWINDOW_TRANSFORM_IDENTITY; + case gfx::OVERLAY_TRANSFORM_FLIP_HORIZONTAL: + return ANATIVEWINDOW_TRANSFORM_MIRROR_HORIZONTAL; + case gfx::OVERLAY_TRANSFORM_FLIP_VERTICAL: + return ANATIVEWINDOW_TRANSFORM_MIRROR_VERTICAL; + case gfx::OVERLAY_TRANSFORM_ROTATE_90: + return ANATIVEWINDOW_TRANSFORM_ROTATE_270; + case gfx::OVERLAY_TRANSFORM_ROTATE_180: + return ANATIVEWINDOW_TRANSFORM_ROTATE_180; + case gfx::OVERLAY_TRANSFORM_ROTATE_270: + return ANATIVEWINDOW_TRANSFORM_ROTATE_90; + }; + NOTREACHED(); + return ANATIVEWINDOW_TRANSFORM_IDENTITY; +} + +uint64_t ColorSpaceToADataSpace(const gfx::ColorSpace& color_space) { + if (!color_space.IsValid() || color_space == gfx::ColorSpace::CreateSRGB()) + return ADATASPACE_SRGB; + + if (color_space == gfx::ColorSpace::CreateSCRGBLinear()) + return ADATASPACE_SCRGB_LINEAR; + + if (color_space == gfx::ColorSpace::CreateDisplayP3D65()) + return ADATASPACE_DISPLAY_P3; + + // TODO(khushalsagar): Check if we can support BT2020 using + // ADATASPACE_BT2020_PQ. + return ADATASPACE_UNKNOWN; +} + +SurfaceControl::TransactionStats ToTransactionStats( + ASurfaceTransactionStats* stats) { + SurfaceControl::TransactionStats transaction_stats; + + // In unit tests we don't have stats. + if (!stats) + return transaction_stats; + + transaction_stats.present_fence = base::ScopedFD( + SurfaceControlMethods::Get().ASurfaceTransactionStats_getPresentFenceFdFn( + stats)); + transaction_stats.latch_time = + base::TimeTicks() + + base::Nanoseconds( + SurfaceControlMethods::Get().ASurfaceTransactionStats_getLatchTimeFn( + stats)); + if (transaction_stats.latch_time == base::TimeTicks()) + transaction_stats.latch_time = base::TimeTicks::Now(); + + ASurfaceControl** surface_controls = nullptr; + size_t size = 0u; + SurfaceControlMethods::Get().ASurfaceTransactionStats_getASurfaceControlsFn( + stats, &surface_controls, &size); + transaction_stats.surface_stats.resize(size); + for (size_t i = 0u; i < size; ++i) { + transaction_stats.surface_stats[i].surface = surface_controls[i]; + int fence_fd = SurfaceControlMethods::Get() + .ASurfaceTransactionStats_getPreviousReleaseFenceFdFn( + stats, surface_controls[i]); + if (fence_fd != -1) { + transaction_stats.surface_stats[i].fence = base::ScopedFD(fence_fd); + } + } + SurfaceControlMethods::Get() + .ASurfaceTransactionStats_releaseASurfaceControlsFn(surface_controls); + + return transaction_stats; +} + +struct TransactionAckCtx { + int id = 0; + SurfaceControl::Transaction::OnCompleteCb callback; + SurfaceControl::Transaction::OnCommitCb latch_callback; +}; + +uint64_t GetTraceIdForTransaction(int transaction_id) { + constexpr uint64_t kMask = + base::MD5Hash64Constexpr("SurfaceControl::Transaction"); + return kMask ^ transaction_id; +} + +// Note that the framework API states that this callback can be dispatched on +// any thread (in practice it should be a binder thread). +void OnTransactionCompletedOnAnyThread(void* context, + ASurfaceTransactionStats* stats) { + auto* ack_ctx = static_cast(context); + auto transaction_stats = ToTransactionStats(stats); + TRACE_EVENT_NESTABLE_ASYNC_END0("gpu,benchmark", "SurfaceControlTransaction", + ack_ctx->id); + TRACE_EVENT_WITH_FLOW0( + "toplevel.flow", "gfx::SurfaceControlTransaction completed", + GetTraceIdForTransaction(ack_ctx->id), TRACE_EVENT_FLAG_FLOW_IN); + + std::move(ack_ctx->callback).Run(std::move(transaction_stats)); + delete ack_ctx; +} + +// Note that the framework API states that this callback can be dispatched on +// any thread (in practice it should be a binder thread). +void OnTransactiOnCommittedOnAnyThread(void* context, + ASurfaceTransactionStats* stats) { + auto* ack_ctx = static_cast(context); + TRACE_EVENT_INSTANT0("gpu,benchmark", "SurfaceControlTransaction committed", + TRACE_EVENT_SCOPE_THREAD); + + std::move(ack_ctx->latch_callback).Run(); + delete ack_ctx; +} + +} // namespace + +// static +bool SurfaceControl::IsSupported() { + const auto* build_info = base::android::BuildInfo::GetInstance(); + + // Disabled on Samsung devices due to a platform bug fixed in R. + int min_sdk_version = base::android::SDK_VERSION_Q; + if (base::EqualsCaseInsensitiveASCII(build_info->manufacturer(), "samsung")) + min_sdk_version = base::android::SDK_VERSION_R; + + if (build_info->sdk_int() < min_sdk_version) + return false; + + CHECK(SurfaceControlMethods::Get().supported); + return true; +} + +bool SurfaceControl::SupportsColorSpace(const gfx::ColorSpace& color_space) { + return ColorSpaceToADataSpace(color_space) != ADATASPACE_UNKNOWN; +} + +uint64_t SurfaceControl::RequiredUsage() { + if (!IsSupported()) + return 0u; + return g_agb_required_usage_bits; +} + +void SurfaceControl::EnableQualcommUBWC() { + g_agb_required_usage_bits |= AHARDWAREBUFFER_USAGE_VENDOR_0; +} + +bool SurfaceControl::SupportsSetFrameRate() { + // TODO(khushalsagar): Assert that this function is always available on R. + return IsSupported() && + SurfaceControlMethods::Get().ASurfaceTransaction_setFrameRateFn != + nullptr; +} + +bool SurfaceControl::SupportsOnCommit() { + return IsSupported() && + SurfaceControlMethods::Get().ASurfaceTransaction_setOnCommitFn != + nullptr; +} + +void SurfaceControl::SetStubImplementationForTesting() { + SurfaceControlMethods::GetImpl(/*load_functions=*/false).InitWithStubs(); +} + +void SurfaceControl::ApplyTransaction(ASurfaceTransaction* transaction) { + SurfaceControlMethods::Get().ASurfaceTransaction_applyFn(transaction); +} + +scoped_refptr SurfaceControl::Surface::WrapUnowned( + ASurfaceControl* surface) { + scoped_refptr result = + base::MakeRefCounted(); + result->surface_ = surface; + return result; +} + +SurfaceControl::Surface::Surface() = default; + +SurfaceControl::Surface::Surface(const Surface& parent, const char* name) { + owned_surface_ = SurfaceControlMethods::Get().ASurfaceControl_createFn( + parent.surface(), name); + if (!owned_surface_) + LOG(ERROR) << "Failed to create ASurfaceControl : " << name; + surface_ = owned_surface_; +} + +SurfaceControl::Surface::Surface(ANativeWindow* parent, const char* name) { + owned_surface_ = + SurfaceControlMethods::Get().ASurfaceControl_createFromWindowFn(parent, + name); + if (!owned_surface_) + LOG(ERROR) << "Failed to create ASurfaceControl : " << name; + surface_ = owned_surface_; +} + +SurfaceControl::Surface::~Surface() { + if (owned_surface_) + SurfaceControlMethods::Get().ASurfaceControl_releaseFn(owned_surface_); +} + +SurfaceControl::SurfaceStats::SurfaceStats() = default; +SurfaceControl::SurfaceStats::~SurfaceStats() = default; + +SurfaceControl::SurfaceStats::SurfaceStats(SurfaceStats&& other) = default; +SurfaceControl::SurfaceStats& SurfaceControl::SurfaceStats::operator=( + SurfaceStats&& other) = default; + +SurfaceControl::TransactionStats::TransactionStats() = default; +SurfaceControl::TransactionStats::~TransactionStats() = default; + +SurfaceControl::TransactionStats::TransactionStats(TransactionStats&& other) = + default; +SurfaceControl::TransactionStats& SurfaceControl::TransactionStats::operator=( + TransactionStats&& other) = default; + +SurfaceControl::Transaction::Transaction() + : id_(g_next_transaction_id.GetNext()) { + transaction_ = SurfaceControlMethods::Get().ASurfaceTransaction_createFn(); + DCHECK(transaction_); +} + +SurfaceControl::Transaction::~Transaction() { + if (transaction_) + SurfaceControlMethods::Get().ASurfaceTransaction_deleteFn(transaction_); +} + +SurfaceControl::Transaction::Transaction(Transaction&& other) + : id_(other.id_), + transaction_(other.transaction_), + on_commit_cb_(std::move(other.on_commit_cb_)), + on_complete_cb_(std::move(other.on_complete_cb_)) { + other.transaction_ = nullptr; + other.id_ = 0; +} + +SurfaceControl::Transaction& SurfaceControl::Transaction::operator=( + Transaction&& other) { + if (transaction_) + SurfaceControlMethods::Get().ASurfaceTransaction_deleteFn(transaction_); + + transaction_ = other.transaction_; + id_ = other.id_; + on_commit_cb_ = std::move(other.on_commit_cb_); + on_complete_cb_ = std::move(other.on_complete_cb_); + + other.transaction_ = nullptr; + other.id_ = 0; + return *this; +} + +void SurfaceControl::Transaction::SetVisibility(const Surface& surface, + bool show) { + SurfaceControlMethods::Get().ASurfaceTransaction_setVisibilityFn( + transaction_, surface.surface(), show); +} + +void SurfaceControl::Transaction::SetZOrder(const Surface& surface, int32_t z) { + SurfaceControlMethods::Get().ASurfaceTransaction_setZOrderFn( + transaction_, surface.surface(), z); +} + +void SurfaceControl::Transaction::SetBuffer(const Surface& surface, + AHardwareBuffer* buffer, + base::ScopedFD fence_fd) { + SurfaceControlMethods::Get().ASurfaceTransaction_setBufferFn( + transaction_, surface.surface(), buffer, + fence_fd.is_valid() ? fence_fd.release() : -1); +} + +void SurfaceControl::Transaction::SetGeometry(const Surface& surface, + const gfx::Rect& src, + const gfx::Rect& dst, + gfx::OverlayTransform transform) { + SurfaceControlMethods::Get().ASurfaceTransaction_setGeometryFn( + transaction_, surface.surface(), RectToARect(src), RectToARect(dst), + OverlayTransformToWindowTransform(transform)); +} + +void SurfaceControl::Transaction::SetPosition(const Surface& surface, + const gfx::Point& position) { + CHECK(SurfaceControlMethods::Get().ASurfaceTransaction_setPositionFn); + SurfaceControlMethods::Get().ASurfaceTransaction_setPositionFn( + transaction_, surface.surface(), position.x(), position.y()); +} + +void SurfaceControl::Transaction::SetScale(const Surface& surface, + const float sx, + float sy) { + CHECK(SurfaceControlMethods::Get().ASurfaceTransaction_setScaleFn); + SurfaceControlMethods::Get().ASurfaceTransaction_setScaleFn( + transaction_, surface.surface(), sx, sy); +} + +void SurfaceControl::Transaction::SetCrop(const Surface& surface, + const gfx::Rect& rect) { + CHECK(SurfaceControlMethods::Get().ASurfaceTransaction_setCropFn); + SurfaceControlMethods::Get().ASurfaceTransaction_setCropFn( + transaction_, surface.surface(), RectToARect(rect)); +} + +void SurfaceControl::Transaction::SetOpaque(const Surface& surface, + bool opaque) { + int8_t transparency = opaque ? ASURFACE_TRANSACTION_TRANSPARENCY_OPAQUE + : ASURFACE_TRANSACTION_TRANSPARENCY_TRANSLUCENT; + SurfaceControlMethods::Get().ASurfaceTransaction_setBufferTransparencyFn( + transaction_, surface.surface(), transparency); +} + +void SurfaceControl::Transaction::SetDamageRect(const Surface& surface, + const gfx::Rect& rect) { + auto a_rect = RectToARect(rect); + SurfaceControlMethods::Get().ASurfaceTransaction_setDamageRegionFn( + transaction_, surface.surface(), &a_rect, 1u); +} + +void SurfaceControl::Transaction::SetColorSpace( + const Surface& surface, + const gfx::ColorSpace& color_space) { + auto data_space = ColorSpaceToADataSpace(color_space); + + // Log the data space in crash keys for debugging crbug.com/997592. + static auto* kCrashKey = base::debug::AllocateCrashKeyString( + "data_space_for_buffer", base::debug::CrashKeySize::Size256); + auto crash_key_value = base::NumberToString(data_space); + base::debug::ScopedCrashKeyString scoped_crash_key(kCrashKey, + crash_key_value); + + SurfaceControlMethods::Get().ASurfaceTransaction_setBufferDataSpaceFn( + transaction_, surface.surface(), data_space); +} + +void SurfaceControl::Transaction::SetFrameRate(const Surface& surface, + float frame_rate) { + DCHECK(SupportsSetFrameRate()); + + // We always used fixed source here since a non-default value is only used for + // videos which have a fixed playback rate. + SurfaceControlMethods::Get().ASurfaceTransaction_setFrameRateFn( + transaction_, surface.surface(), frame_rate, + ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE); +} + +void SurfaceControl::Transaction::SetParent(const Surface& surface, + Surface* new_parent) { + SurfaceControlMethods::Get().ASurfaceTransaction_reparentFn( + transaction_, surface.surface(), + new_parent ? new_parent->surface() : nullptr); +} + +void SurfaceControl::Transaction::SetOnCompleteCb( + OnCompleteCb cb, + scoped_refptr task_runner) { + TRACE_EVENT_WITH_FLOW0( + "toplevel.flow", "gfx::SurfaceControl::Transaction::SetOnCompleteCb", + GetTraceIdForTransaction(id_), TRACE_EVENT_FLAG_FLOW_OUT); + + DCHECK(!on_complete_cb_); + on_complete_cb_ = base::BindPostTask(std::move(task_runner), std::move(cb)); +} + +void SurfaceControl::Transaction::SetOnCommitCb( + OnCommitCb cb, + scoped_refptr task_runner) { + DCHECK(!on_commit_cb_); + on_commit_cb_ = base::BindPostTask(std::move(task_runner), std::move(cb)); +} + +void SurfaceControl::Transaction::Apply() { + TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("gpu,benchmark", + "SurfaceControlTransaction", id_); + + PrepareCallbacks(); + SurfaceControlMethods::Get().ASurfaceTransaction_applyFn(transaction_); +} + +ASurfaceTransaction* SurfaceControl::Transaction::GetTransaction() { + PrepareCallbacks(); + return transaction_; +} + +void SurfaceControl::Transaction::PrepareCallbacks() { + if (on_commit_cb_) { + TransactionAckCtx* ack_ctx = new TransactionAckCtx; + ack_ctx->latch_callback = std::move(on_commit_cb_); + ack_ctx->id = id_; + + SurfaceControlMethods::Get().ASurfaceTransaction_setOnCommitFn( + transaction_, ack_ctx, &OnTransactiOnCommittedOnAnyThread); + } + + if (on_complete_cb_) { + TransactionAckCtx* ack_ctx = new TransactionAckCtx; + ack_ctx->callback = std::move(on_complete_cb_); + ack_ctx->id = id_; + + SurfaceControlMethods::Get().ASurfaceTransaction_setOnCompleteFn( + transaction_, ack_ctx, &OnTransactionCompletedOnAnyThread); + } +} + +} // namespace gfx diff --git a/android/android_surface_control_compat.h b/android/android_surface_control_compat.h new file mode 100644 index 000000000000..d5eee6fd4370 --- /dev/null +++ b/android/android_surface_control_compat.h @@ -0,0 +1,175 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_ANDROID_ANDROID_SURFACE_CONTROL_COMPAT_H_ +#define UI_GFX_ANDROID_ANDROID_SURFACE_CONTROL_COMPAT_H_ + +#include +#include + +#include +#include + +#include "base/files/scoped_file.h" +#include "base/memory/ref_counted.h" +#include "base/single_thread_task_runner.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/gfx_export.h" +#include "ui/gfx/overlay_transform.h" + +extern "C" { +typedef struct ASurfaceControl ASurfaceControl; +typedef struct ASurfaceTransaction ASurfaceTransaction; +} + +namespace gfx { +class ColorSpace; + +class GFX_EXPORT SurfaceControl { + public: + // Check if the platform is capable of supporting the low-level SurfaceControl + // API. See also gpu/config/gpu_util's GetAndroidSurfaceControlFeatureStatus + // which checks other prerequisites such as Gpufence support before declaring + // support for the high-level SurfaceControl feature in Chrome. + static bool IsSupported(); + + // Returns true if overlays with |color_space| are supported by the platform. + static bool SupportsColorSpace(const gfx::ColorSpace& color_space); + + // Returns the usage flags required for using an AHardwareBuffer with the + // SurfaceControl API, if it is supported. + static uint64_t RequiredUsage(); + + // Enables usage bits requires for getting UBWC on Qualcomm devices. Must be + // called early at process startup, before any buffer allocations are made. + static void EnableQualcommUBWC(); + + // Returns true if tagging a surface with a frame rate value is supported. + static bool SupportsSetFrameRate(); + + // Returns true if OnCommit callback is supported. + static bool SupportsOnCommit(); + + // Applies transaction. Used to emulate webview functor interface, where we + // pass raw ASurfaceTransaction object. For use inside Chromium use + // Transaction class below instead. + static void ApplyTransaction(ASurfaceTransaction* transaction); + + static void SetStubImplementationForTesting(); + + class GFX_EXPORT Surface : public base::RefCounted { + public: + // Wraps ASurfaceControl, but doesn't transfer ownership. Will not release + // in dtor. + static scoped_refptr WrapUnowned(ASurfaceControl* surface); + + Surface(); + Surface(const Surface& parent, const char* name); + Surface(ANativeWindow* parent, const char* name); + + Surface(const Surface&) = delete; + Surface& operator=(const Surface&) = delete; + + ASurfaceControl* surface() const { return surface_; } + + private: + friend class base::RefCounted; + ~Surface(); + + ASurfaceControl* surface_ = nullptr; + ASurfaceControl* owned_surface_ = nullptr; + }; + + struct GFX_EXPORT SurfaceStats { + SurfaceStats(); + ~SurfaceStats(); + + SurfaceStats(SurfaceStats&& other); + SurfaceStats& operator=(SurfaceStats&& other); + + ASurfaceControl* surface = nullptr; + + // The fence which is signaled when the reads for the previous buffer for + // the given |surface| are finished. + base::ScopedFD fence; + }; + + struct GFX_EXPORT TransactionStats { + public: + TransactionStats(); + + TransactionStats(const TransactionStats&) = delete; + TransactionStats& operator=(const TransactionStats&) = delete; + + ~TransactionStats(); + + TransactionStats(TransactionStats&& other); + TransactionStats& operator=(TransactionStats&& other); + + // The fence which is signaled when this transaction is presented by the + // display. + base::ScopedFD present_fence; + std::vector surface_stats; + base::TimeTicks latch_time; + }; + + class GFX_EXPORT Transaction { + public: + Transaction(); + + Transaction(const Transaction&) = delete; + Transaction& operator=(const Transaction&) = delete; + + ~Transaction(); + + Transaction(Transaction&& other); + Transaction& operator=(Transaction&& other); + + void SetVisibility(const Surface& surface, bool show); + void SetZOrder(const Surface& surface, int32_t z); + void SetBuffer(const Surface& surface, + AHardwareBuffer* buffer, + base::ScopedFD fence_fd); + void SetGeometry(const Surface& surface, + const gfx::Rect& src, + const gfx::Rect& dst, + gfx::OverlayTransform transform); + void SetOpaque(const Surface& surface, bool opaque); + void SetDamageRect(const Surface& surface, const gfx::Rect& rect); + void SetColorSpace(const Surface& surface, + const gfx::ColorSpace& color_space); + void SetFrameRate(const Surface& surface, float frame_rate); + void SetParent(const Surface& surface, Surface* new_parent); + void SetPosition(const Surface& surface, const gfx::Point& position); + void SetScale(const Surface& surface, float sx, float sy); + void SetCrop(const Surface& surface, const gfx::Rect& rect); + + // Sets the callback which will be dispatched when the transaction is acked + // by the framework. + // |task_runner| provides an optional task runner on which the callback + // should be run. + using OnCompleteCb = base::OnceCallback; + void SetOnCompleteCb( + OnCompleteCb cb, + scoped_refptr task_runner); + + using OnCommitCb = base::OnceClosure; + void SetOnCommitCb(OnCommitCb cb, + scoped_refptr task_runner); + + void Apply(); + ASurfaceTransaction* GetTransaction(); + + private: + void PrepareCallbacks(); + + int id_; + ASurfaceTransaction* transaction_; + OnCommitCb on_commit_cb_; + OnCompleteCb on_complete_cb_; + }; +}; +} // namespace gfx + +#endif // UI_GFX_ANDROID_ANDROID_SURFACE_CONTROL_COMPAT_H_ diff --git a/android/android_surface_control_compat_unittest.cc b/android/android_surface_control_compat_unittest.cc new file mode 100644 index 000000000000..fd7d1819a21d --- /dev/null +++ b/android/android_surface_control_compat_unittest.cc @@ -0,0 +1,163 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/android/android_surface_control_compat.h" + +#include "base/callback.h" +#include "base/run_loop.h" +#include "base/test/task_environment.h" +#include "base/threading/thread_task_runner_handle.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace gfx { +namespace { + +class SurfaceControlTransactionTest : public testing::Test { + public: + SurfaceControlTransactionTest() { + gfx::SurfaceControl::SetStubImplementationForTesting(); + } + + protected: + struct CallbackContext { + CallbackContext(bool* called, bool* destroyed) + : called(called), destroyed(destroyed) {} + ~CallbackContext() { *destroyed = true; } + bool* called; + bool* destroyed; + }; + + SurfaceControl::Transaction::OnCompleteCb CreateOnCompleteCb( + bool* called, + bool* destroyed) { + return base::BindOnce( + [](std::unique_ptr context, + SurfaceControl::TransactionStats stats) { + DCHECK(!*context->called); + *context->called = true; + }, + std::make_unique(called, destroyed)); + } + + SurfaceControl::Transaction::OnCommitCb CreateOnCommitCb(bool* called, + bool* destroyed) { + return base::BindOnce( + [](std::unique_ptr context) { + DCHECK(!*context->called); + *context->called = true; + }, + std::make_unique(called, destroyed)); + } + + void RunRemainingTasks() { + base::RunLoop runloop; + base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, + runloop.QuitClosure()); + runloop.Run(); + } + + base::test::SingleThreadTaskEnvironment task_environment; +}; + +TEST_F(SurfaceControlTransactionTest, CallbackCalledAfterApply) { + bool on_complete_called = false; + bool on_commit_called = false; + bool on_commit_destroyed = false; + bool on_complete_destroyed = false; + + gfx::SurfaceControl::Transaction transaction; + transaction.SetOnCompleteCb( + CreateOnCompleteCb(&on_complete_called, &on_complete_destroyed), + base::ThreadTaskRunnerHandle::Get()); + transaction.SetOnCommitCb( + CreateOnCommitCb(&on_commit_called, &on_commit_destroyed), + base::ThreadTaskRunnerHandle::Get()); + + // Nothing should have been called yet. + EXPECT_FALSE(on_complete_called); + EXPECT_FALSE(on_commit_called); + + transaction.Apply(); + RunRemainingTasks(); + + // After apply callbacks should be called. + EXPECT_TRUE(on_complete_called); + EXPECT_TRUE(on_commit_called); + + // As this is Once callback naturally it's context should have been destroyed. + EXPECT_TRUE(on_complete_destroyed); + EXPECT_TRUE(on_commit_destroyed); +} + +TEST_F(SurfaceControlTransactionTest, CallbackDestroyedWithoutApply) { + bool on_complete_called = false; + bool on_commit_called = false; + bool on_commit_destroyed = false; + bool on_complete_destroyed = false; + + { + SurfaceControl::Transaction transaction; + transaction.SetOnCompleteCb( + CreateOnCompleteCb(&on_complete_called, &on_complete_destroyed), + base::ThreadTaskRunnerHandle::Get()); + transaction.SetOnCommitCb( + CreateOnCommitCb(&on_commit_called, &on_commit_destroyed), + base::ThreadTaskRunnerHandle::Get()); + + // Nothing should have been called yet. + EXPECT_FALSE(on_complete_called); + EXPECT_FALSE(on_commit_called); + } + + RunRemainingTasks(); + + // Apply wasn't called, but transaction left the scope, so the callback + // contexts should have been destroyed. + EXPECT_TRUE(on_complete_destroyed); + EXPECT_TRUE(on_commit_destroyed); +} + +TEST_F(SurfaceControlTransactionTest, CallbackSetupAfterGetTransaction) { + bool on_complete_called = false; + bool on_commit_called = false; + bool on_commit_destroyed = false; + bool on_complete_destroyed = false; + + gfx::SurfaceControl::Transaction transaction; + transaction.SetOnCompleteCb( + CreateOnCompleteCb(&on_complete_called, &on_complete_destroyed), + base::ThreadTaskRunnerHandle::Get()); + transaction.SetOnCommitCb( + CreateOnCommitCb(&on_commit_called, &on_commit_destroyed), + base::ThreadTaskRunnerHandle::Get()); + + // Nothing should have been called yet. + EXPECT_FALSE(on_complete_called); + EXPECT_FALSE(on_commit_called); + + auto* asurfacetransaction = transaction.GetTransaction(); + + // Should be no task to run, but calling this to make sure nothing is + // scheduled that can call callbacks. + RunRemainingTasks(); + + // And not yet. + EXPECT_FALSE(on_complete_called); + EXPECT_FALSE(on_commit_called); + + // This is usually called by framework. + SurfaceControl::ApplyTransaction(asurfacetransaction); + RunRemainingTasks(); + + // After apply callbacks should be called. + EXPECT_TRUE(on_complete_called); + EXPECT_TRUE(on_commit_called); + + // As this is Once callback naturally it's context should have been destroyed. + EXPECT_TRUE(on_complete_destroyed); + EXPECT_TRUE(on_commit_destroyed); +} + +} // namespace +} // namespace gfx diff --git a/android/java_bitmap.cc b/android/java_bitmap.cc new file mode 100644 index 000000000000..e107a55c86b6 --- /dev/null +++ b/android/java_bitmap.cc @@ -0,0 +1,148 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/android/java_bitmap.h" + +#include + +#include "base/android/jni_string.h" +#include "base/bits.h" +#include "base/check_op.h" +#include "base/notreached.h" +#include "base/numerics/safe_conversions.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/gfx_jni_headers/BitmapHelper_jni.h" + +using base::android::AttachCurrentThread; +using base::android::ConvertUTF8ToJavaString; +using base::android::ScopedJavaLocalRef; +using base::android::JavaRef; + +namespace gfx { +namespace { + +int SkColorTypeToBitmapFormat(SkColorType color_type) { + switch (color_type) { + case kN32_SkColorType: + return BITMAP_FORMAT_ARGB_8888; + case kRGB_565_SkColorType: + return BITMAP_FORMAT_RGB_565; + default: + // A bad format can cause out-of-bounds issues when copying pixels into or + // out of the java bitmap's pixel buffer. + CHECK_NE(color_type, color_type); + } + return BITMAP_FORMAT_NO_CONFIG; +} + +SkColorType BitmapFormatToSkColorType(BitmapFormat bitmap_format) { + switch (bitmap_format) { + case BITMAP_FORMAT_ALPHA_8: + return kAlpha_8_SkColorType; + case BITMAP_FORMAT_ARGB_4444: + return kARGB_4444_SkColorType; + case BITMAP_FORMAT_ARGB_8888: + return kN32_SkColorType; + case BITMAP_FORMAT_RGB_565: + return kRGB_565_SkColorType; + case BITMAP_FORMAT_NO_CONFIG: + default: + CHECK_NE(bitmap_format, bitmap_format); + return kUnknown_SkColorType; + } +} + +// Wraps a Java bitmap as an SkPixmap. Since the pixmap references the +// underlying pixel data in the Java bitmap directly, it is only valid as long +// as |bitmap| is. +SkPixmap WrapJavaBitmapAsPixmap(const JavaBitmap& bitmap) { + auto color_type = BitmapFormatToSkColorType(bitmap.format()); + auto image_info = + SkImageInfo::Make(bitmap.size().width(), bitmap.size().height(), + color_type, kPremul_SkAlphaType); + return SkPixmap(image_info, bitmap.pixels(), bitmap.bytes_per_row()); +} + +} // namespace + +#define ASSERT_ENUM_EQ(a, b) \ + static_assert(static_cast(a) == static_cast(b), "") + +// BitmapFormat has the same values as AndroidBitmapFormat, for simplicitly, so +// that SkColorTypeToBitmapFormat() and the JavaBitmap::format() have the same +// values. +ASSERT_ENUM_EQ(BITMAP_FORMAT_NO_CONFIG, ANDROID_BITMAP_FORMAT_NONE); +ASSERT_ENUM_EQ(BITMAP_FORMAT_ALPHA_8, ANDROID_BITMAP_FORMAT_A_8); +ASSERT_ENUM_EQ(BITMAP_FORMAT_ARGB_4444, ANDROID_BITMAP_FORMAT_RGBA_4444); +ASSERT_ENUM_EQ(BITMAP_FORMAT_ARGB_8888, ANDROID_BITMAP_FORMAT_RGBA_8888); +ASSERT_ENUM_EQ(BITMAP_FORMAT_RGB_565, ANDROID_BITMAP_FORMAT_RGB_565); + +JavaBitmap::JavaBitmap(const JavaRef& bitmap) + : bitmap_(bitmap), pixels_(NULL) { + int err = + AndroidBitmap_lockPixels(AttachCurrentThread(), bitmap_.obj(), &pixels_); + DCHECK(!err); + DCHECK(pixels_); + + AndroidBitmapInfo info; + err = AndroidBitmap_getInfo(AttachCurrentThread(), bitmap_.obj(), &info); + DCHECK(!err); + size_ = gfx::Size(info.width, info.height); + format_ = static_cast(info.format); + bytes_per_row_ = info.stride; + byte_count_ = Java_BitmapHelper_getByteCount(AttachCurrentThread(), bitmap_); +} + +JavaBitmap::~JavaBitmap() { + int err = AndroidBitmap_unlockPixels(AttachCurrentThread(), bitmap_.obj()); + DCHECK(!err); +} + +ScopedJavaLocalRef ConvertToJavaBitmap(const SkBitmap& skbitmap, + OomBehavior reaction) { + DCHECK(!skbitmap.isNull()); + DCHECK_GT(skbitmap.width(), 0); + DCHECK_GT(skbitmap.height(), 0); + + int java_bitmap_format = SkColorTypeToBitmapFormat(skbitmap.colorType()); + + ScopedJavaLocalRef jbitmap = Java_BitmapHelper_createBitmap( + AttachCurrentThread(), skbitmap.width(), skbitmap.height(), + java_bitmap_format, reaction == OomBehavior::kReturnNullOnOom); + if (!jbitmap) { + DCHECK_EQ(OomBehavior::kReturnNullOnOom, reaction); + return jbitmap; + } + + JavaBitmap dst_lock(jbitmap); + SkPixmap dst = WrapJavaBitmapAsPixmap(dst_lock); + skbitmap.readPixels(dst); + return jbitmap; +} + +SkBitmap CreateSkBitmapFromJavaBitmap(const JavaBitmap& jbitmap) { + DCHECK(!jbitmap.size().IsEmpty()); + DCHECK_GT(jbitmap.bytes_per_row(), 0U); + DCHECK(jbitmap.pixels()); + + // Ensure 4 byte stride alignment since the texture upload code in the + // compositor relies on this. + SkPixmap src = WrapJavaBitmapAsPixmap(jbitmap); + const size_t min_row_bytes = src.info().minRowBytes(); + const size_t row_bytes = base::bits::AlignUp(min_row_bytes, 4u); + + SkBitmap skbitmap; + skbitmap.allocPixels(src.info(), row_bytes); + skbitmap.writePixels(src); + return skbitmap; +} + +SkColorType ConvertToSkiaColorType(const JavaRef& bitmap_config) { + BitmapFormat jbitmap_format = + static_cast(Java_BitmapHelper_getBitmapFormatForConfig( + AttachCurrentThread(), bitmap_config)); + return BitmapFormatToSkColorType(jbitmap_format); +} + +} // namespace gfx diff --git a/android/java_bitmap.h b/android/java_bitmap.h new file mode 100644 index 000000000000..72ec80b86130 --- /dev/null +++ b/android/java_bitmap.h @@ -0,0 +1,81 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_ANDROID_JAVA_BITMAP_H_ +#define UI_GFX_ANDROID_JAVA_BITMAP_H_ + +#include +#include + +#include "base/android/scoped_java_ref.h" +#include "base/macros.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +// A Java counterpart will be generated for this enum. +// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.ui.gfx +// The order and values here match AndroidBitmapFormat, as verified +// by static_asserts in java_bitmap.cc. +enum BitmapFormat { + BITMAP_FORMAT_NO_CONFIG = 0, + BITMAP_FORMAT_ARGB_8888 = 1, + BITMAP_FORMAT_RGB_565 = 4, + BITMAP_FORMAT_ARGB_4444 = 7, + BITMAP_FORMAT_ALPHA_8 = 8, +}; + +// This class wraps a JNI AndroidBitmap object to make it easier to use. It +// handles locking and unlocking of the underlying pixels, along with wrapping +// various JNI methods. +class GFX_EXPORT JavaBitmap { + public: + explicit JavaBitmap(const base::android::JavaRef& bitmap); + + JavaBitmap(const JavaBitmap&) = delete; + JavaBitmap& operator=(const JavaBitmap&) = delete; + + ~JavaBitmap(); + + inline void* pixels() { return pixels_; } + inline const void* pixels() const { return pixels_; } + inline const gfx::Size& size() const { return size_; } + inline BitmapFormat format() const { return format_; } + inline uint32_t bytes_per_row() const { return bytes_per_row_; } + inline int byte_count() const { return byte_count_; } + + private: + base::android::ScopedJavaGlobalRef bitmap_; + void* pixels_; + gfx::Size size_; + BitmapFormat format_; + uint32_t bytes_per_row_; + int byte_count_; +}; + +enum class OomBehavior { + kCrashOnOom, + kReturnNullOnOom, +}; + +// Converts |skbitmap| to a Java-backed bitmap (android.graphics.Bitmap). +// Note: |skbitmap| is assumed to be non-null, non-empty and one of RGBA_8888 or +// RGB_565 formats. +GFX_EXPORT base::android::ScopedJavaLocalRef ConvertToJavaBitmap( + const SkBitmap& skbitmap, + OomBehavior reaction = OomBehavior::kCrashOnOom); + +// Converts |bitmap| to an SkBitmap of the same size and format. +// Note: |jbitmap| is assumed to be non-null, non-empty and of format RGBA_8888. +GFX_EXPORT SkBitmap CreateSkBitmapFromJavaBitmap(const JavaBitmap& jbitmap); + +// Returns a Skia color type value for the requested input java Bitmap.Config. +GFX_EXPORT SkColorType +ConvertToSkiaColorType(const base::android::JavaRef& jbitmap_config); + +} // namespace gfx + +#endif // UI_GFX_ANDROID_JAVA_BITMAP_H_ diff --git a/android/view_configuration.cc b/android/view_configuration.cc new file mode 100644 index 000000000000..63978096d5a9 --- /dev/null +++ b/android/view_configuration.cc @@ -0,0 +1,179 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/android/view_configuration.h" + +#include "base/android/jni_android.h" +#include "base/lazy_instance.h" +#include "base/macros.h" +#include "base/synchronization/lock.h" +#include "ui/gfx/gfx_jni_headers/ViewConfigurationHelper_jni.h" + +using base::android::AttachCurrentThread; +using base::android::JavaParamRef; + +namespace gfx { + +namespace { + +struct ViewConfigurationData { + ViewConfigurationData() + : double_tap_timeout_in_ms_(0), + long_press_timeout_in_ms_(0), + tap_timeout_in_ms_(0), + max_fling_velocity_in_dips_s_(0), + min_fling_velocity_in_dips_s_(0), + touch_slop_in_dips_(0), + double_tap_slop_in_dips_(0), + min_scaling_span_in_dips_(0) { + JNIEnv* env = AttachCurrentThread(); + j_view_configuration_helper_.Reset( + Java_ViewConfigurationHelper_createWithListener(env)); + + double_tap_timeout_in_ms_ = + Java_ViewConfigurationHelper_getDoubleTapTimeout(env); + long_press_timeout_in_ms_ = + Java_ViewConfigurationHelper_getLongPressTimeout(env); + tap_timeout_in_ms_ = Java_ViewConfigurationHelper_getTapTimeout(env); + + Update(Java_ViewConfigurationHelper_getMaximumFlingVelocity( + env, j_view_configuration_helper_), + Java_ViewConfigurationHelper_getMinimumFlingVelocity( + env, j_view_configuration_helper_), + Java_ViewConfigurationHelper_getTouchSlop( + env, j_view_configuration_helper_), + Java_ViewConfigurationHelper_getDoubleTapSlop( + env, j_view_configuration_helper_), + Java_ViewConfigurationHelper_getMinScalingSpan( + env, j_view_configuration_helper_)); + } + + ViewConfigurationData(const ViewConfigurationData&) = delete; + ViewConfigurationData& operator=(const ViewConfigurationData&) = delete; + + ~ViewConfigurationData() {} + + void SynchronizedUpdate(float maximum_fling_velocity, + float minimum_fling_velocity, + float touch_slop, + float double_tap_slop, + float min_scaling_span) { + base::AutoLock autolock(lock_); + Update(maximum_fling_velocity, minimum_fling_velocity, touch_slop, + double_tap_slop, min_scaling_span); + } + + int double_tap_timeout_in_ms() const { return double_tap_timeout_in_ms_; } + int long_press_timeout_in_ms() const { return long_press_timeout_in_ms_; } + int tap_timeout_in_ms() const { return tap_timeout_in_ms_; } + + int max_fling_velocity_in_dips_s() { + base::AutoLock autolock(lock_); + return max_fling_velocity_in_dips_s_; + } + + int min_fling_velocity_in_dips_s() { + base::AutoLock autolock(lock_); + return min_fling_velocity_in_dips_s_; + } + + int touch_slop_in_dips() { + base::AutoLock autolock(lock_); + return touch_slop_in_dips_; + } + + int double_tap_slop_in_dips() { + base::AutoLock autolock(lock_); + return double_tap_slop_in_dips_; + } + + int min_scaling_span_in_dips() { + base::AutoLock autolock(lock_); + return min_scaling_span_in_dips_; + } + + private: + void Update(float maximum_fling_velocity, + float minimum_fling_velocity, + float touch_slop, + float double_tap_slop, + float min_scaling_span) { + DCHECK_LE(minimum_fling_velocity, maximum_fling_velocity); + max_fling_velocity_in_dips_s_ = maximum_fling_velocity; + min_fling_velocity_in_dips_s_ = minimum_fling_velocity; + touch_slop_in_dips_ = touch_slop; + double_tap_slop_in_dips_ = double_tap_slop; + min_scaling_span_in_dips_ = min_scaling_span; + } + + base::Lock lock_; + base::android::ScopedJavaGlobalRef j_view_configuration_helper_; + + // These values will remain constant throughout the lifetime of the app, so + // read-access needn't be synchronized. + int double_tap_timeout_in_ms_; + int long_press_timeout_in_ms_; + int tap_timeout_in_ms_; + + // These values may vary as view-specific parameters change, so read/write + // access must be synchronized. + int max_fling_velocity_in_dips_s_; + int min_fling_velocity_in_dips_s_; + int touch_slop_in_dips_; + int double_tap_slop_in_dips_; + int min_scaling_span_in_dips_; +}; + +// Leaky to allow access from any thread. +base::LazyInstance::Leaky g_view_configuration = + LAZY_INSTANCE_INITIALIZER; + +} // namespace + +static void JNI_ViewConfigurationHelper_UpdateSharedViewConfiguration( + JNIEnv* env, + const JavaParamRef& obj, + jfloat maximum_fling_velocity, + jfloat minimum_fling_velocity, + jfloat touch_slop, + jfloat double_tap_slop, + jfloat min_scaling_span) { + g_view_configuration.Get().SynchronizedUpdate( + maximum_fling_velocity, minimum_fling_velocity, touch_slop, + double_tap_slop, min_scaling_span); +} + +int ViewConfiguration::GetDoubleTapTimeoutInMs() { + return g_view_configuration.Get().double_tap_timeout_in_ms(); +} + +int ViewConfiguration::GetLongPressTimeoutInMs() { + return g_view_configuration.Get().long_press_timeout_in_ms(); +} + +int ViewConfiguration::GetTapTimeoutInMs() { + return g_view_configuration.Get().tap_timeout_in_ms(); +} + +int ViewConfiguration::GetMaximumFlingVelocityInDipsPerSecond() { + return g_view_configuration.Get().max_fling_velocity_in_dips_s(); +} + +int ViewConfiguration::GetMinimumFlingVelocityInDipsPerSecond() { + return g_view_configuration.Get().min_fling_velocity_in_dips_s(); +} + +int ViewConfiguration::GetTouchSlopInDips() { + return g_view_configuration.Get().touch_slop_in_dips(); +} + +int ViewConfiguration::GetDoubleTapSlopInDips() { + return g_view_configuration.Get().double_tap_slop_in_dips(); +} + +int ViewConfiguration::GetMinScalingSpanInDips() { + return g_view_configuration.Get().min_scaling_span_in_dips(); +} + +} // namespace gfx diff --git a/android/view_configuration.h b/android/view_configuration.h new file mode 100644 index 000000000000..e7353de3d079 --- /dev/null +++ b/android/view_configuration.h @@ -0,0 +1,33 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_ANDROID_VIEW_CONFIGURATION_H_ +#define UI_GFX_ANDROID_VIEW_CONFIGURATION_H_ + +#include + +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +// Provides access to Android's ViewConfiguration for gesture-related constants. +// Note: All methods may be safely called from any thread. +class GFX_EXPORT ViewConfiguration { + public: + static int GetDoubleTapTimeoutInMs(); + static int GetLongPressTimeoutInMs(); + static int GetTapTimeoutInMs(); + + static int GetMaximumFlingVelocityInDipsPerSecond(); + static int GetMinimumFlingVelocityInDipsPerSecond(); + + static int GetTouchSlopInDips(); + static int GetDoubleTapSlopInDips(); + + static int GetMinScalingSpanInDips(); +}; + +} // namespace gfx + +#endif // UI_GFX_ANDROID_VIEW_CONFIGURATION_H_ diff --git a/animation/BUILD.gn b/animation/BUILD.gn new file mode 100644 index 000000000000..78761079e49f --- /dev/null +++ b/animation/BUILD.gn @@ -0,0 +1,88 @@ +# Copyright 2017 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//build/config/chromeos/ui_mode.gni") +import("//build/config/ui.gni") + +if (is_android) { + import("//build/config/android/config.gni") + import("//build/config/android/rules.gni") +} + +component("animation") { + sources = [ + "animation.cc", + "animation.h", + "animation_container.cc", + "animation_container.h", + "animation_container_element.h", + "animation_container_observer.h", + "animation_delegate.h", + "animation_delegate_notifier.h", + "animation_export.h", + "animation_runner.cc", + "animation_runner.h", + "linear_animation.cc", + "linear_animation.h", + "multi_animation.cc", + "multi_animation.h", + "slide_animation.cc", + "slide_animation.h", + "tween.cc", + "tween.h", + ] + + if (is_android) { + sources += [ "animation_android.cc" ] + } + + if (is_mac) { + sources += [ "animation_mac.mm" ] + } + + if (is_win) { + sources += [ "animation_win.cc" ] + } + + if (is_linux || is_chromeos_lacros) { + sources += [ + "animation_linux.cc", + "animation_settings_provider_linux.cc", + "animation_settings_provider_linux.h", + ] + } + + if (!is_android) { + sources += [ + "throb_animation.cc", + "throb_animation.h", + ] + } + + if (is_mac) { + frameworks = [ + "AppKit.framework", + "CoreFoundation.framework", + "CoreGraphics.framework", + "CoreText.framework", + "IOSurface.framework", + ] + } + + deps = [ + "//base", + "//build:chromeos_buildflags", + "//skia", + "//ui/gfx:gfx_export", + "//ui/gfx:gfx_switches", + "//ui/gfx/geometry", + "//ui/gfx/geometry:geometry_skia", + ] + + if (is_android) { + deps += [ "//ui/gfx:gfx_jni_headers" ] + } + + defines = [ "ANIMATION_IMPLEMENTATION" ] +} diff --git a/animation/DEPS b/animation/DEPS new file mode 100644 index 000000000000..841ceffd7f7b --- /dev/null +++ b/animation/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+ui/gfx/gfx_jni_headers", +] diff --git a/animation/DIR_METADATA b/animation/DIR_METADATA new file mode 100644 index 000000000000..bc9e7170e4eb --- /dev/null +++ b/animation/DIR_METADATA @@ -0,0 +1,12 @@ +# Metadata information for this directory. +# +# For more information on DIR_METADATA files, see: +# https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md +# +# For the schema of this file, see Metadata message: +# https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto + +monorail { + component: "Internals>Compositing>Animation" +} +team_email: "threaded-rendering-dev@chromium.org" \ No newline at end of file diff --git a/animation/OWNERS b/animation/OWNERS new file mode 100644 index 000000000000..a56274ecf420 --- /dev/null +++ b/animation/OWNERS @@ -0,0 +1,2 @@ +flackr@chromium.org +vollick@chromium.org diff --git a/animation/animation.cc b/animation/animation.cc new file mode 100644 index 000000000000..c07fb280929a --- /dev/null +++ b/animation/animation.cc @@ -0,0 +1,169 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/animation/animation.h" + +#include + +#include "base/command_line.h" +#include "build/build_config.h" +#include "build/chromeos_buildflags.h" +#include "ui/gfx/animation/animation_container.h" +#include "ui/gfx/animation/animation_delegate.h" +#include "ui/gfx/animation/tween.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/switches.h" + +namespace gfx { + +// static +Animation::RichAnimationRenderMode Animation::rich_animation_rendering_mode_ = + RichAnimationRenderMode::PLATFORM; + +// static +absl::optional Animation::prefers_reduced_motion_; + +Animation::Animation(base::TimeDelta timer_interval) + : timer_interval_(timer_interval), + is_animating_(false), + delegate_(nullptr) {} + +Animation::~Animation() { + // Don't send out notification from the destructor. Chances are the delegate + // owns us and is being deleted as well. + if (is_animating_) + container_->Stop(this); +} + +void Animation::Start() { + if (is_animating_) + return; + + if (!container_) { + container_ = base::MakeRefCounted(); + if (delegate_) + delegate_->AnimationContainerWasSet(container_.get()); + } + + is_animating_ = true; + + container_->Start(this); + + AnimationStarted(); +} + +void Animation::Stop() { + if (!is_animating_) + return; + + is_animating_ = false; + + // Notify the container first as the delegate may delete us. + container_->Stop(this); + + AnimationStopped(); + + if (delegate_) { + if (ShouldSendCanceledFromStop()) + delegate_->AnimationCanceled(this); + else + delegate_->AnimationEnded(this); + } +} + +double Animation::CurrentValueBetween(double start, double target) const { + return Tween::DoubleValueBetween(GetCurrentValue(), start, target); +} + +int Animation::CurrentValueBetween(int start, int target) const { + return Tween::IntValueBetween(GetCurrentValue(), start, target); +} + +gfx::Rect Animation::CurrentValueBetween(const gfx::Rect& start_bounds, + const gfx::Rect& target_bounds) const { + return Tween::RectValueBetween( + GetCurrentValue(), start_bounds, target_bounds); +} + +void Animation::SetContainer(AnimationContainer* container) { + if (container == container_.get()) + return; + + if (is_animating_) + container_->Stop(this); + + if (container) + container_ = container; + else + container_ = new AnimationContainer(); + + if (delegate_) + delegate_->AnimationContainerWasSet(container_.get()); + + if (is_animating_) + container_->Start(this); +} + +bool Animation::ShouldRenderRichAnimation() { + if (rich_animation_rendering_mode_ == RichAnimationRenderMode::PLATFORM) + return ShouldRenderRichAnimationImpl(); + return rich_animation_rendering_mode_ == + RichAnimationRenderMode::FORCE_ENABLED; +} + +#if defined(OS_ANDROID) || BUILDFLAG(IS_CHROMEOS_ASH) || defined(OS_IOS) || \ + defined(OS_FUCHSIA) +// static +bool Animation::ShouldRenderRichAnimationImpl() { + return true; + // Defined in platform specific file for Windows and OSX and Linux. +} + +// static +bool Animation::ScrollAnimationsEnabledBySystem() { + return true; + // Defined in platform specific files for Windows and OSX and Linux. +} + +#if !defined(OS_ANDROID) +// static +void Animation::UpdatePrefersReducedMotion() { + // prefers_reduced_motion_ should only be modified on the UI thread. + // TODO(crbug.com/927163): DCHECK this assertion once tests are well-behaved. + + // By default, we assume that animations are enabled, to avoid impacting the + // experience for users on systems that don't have APIs for reduced motion. + prefers_reduced_motion_ = false; +} +#endif // !defined(OS_ANDROID) +#endif // defined(OS_ANDROID) || BUILDFLAG(IS_CHROMEOS_ASH) || defined(OS_IOS) + // || defined(OS_FUCHSIA) + +// static +bool Animation::PrefersReducedMotion() { + // --force-prefers-reduced-motion must always override + // |prefers_reduced_motion_|, so check it first. + if (base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kForcePrefersReducedMotion)) { + return true; + } + + if (!prefers_reduced_motion_.has_value()) + UpdatePrefersReducedMotion(); + return prefers_reduced_motion_.value(); +} + +bool Animation::ShouldSendCanceledFromStop() { + return false; +} + +void Animation::SetStartTime(base::TimeTicks start_time) { + start_time_ = start_time; +} + +base::TimeDelta Animation::GetTimerInterval() const { + return timer_interval_; +} + +} // namespace gfx diff --git a/animation/animation.h b/animation/animation.h new file mode 100644 index 000000000000..e665d1f7f7f7 --- /dev/null +++ b/animation/animation.h @@ -0,0 +1,147 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_ANIMATION_ANIMATION_H_ +#define UI_GFX_ANIMATION_ANIMATION_H_ + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/time/time.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/gfx/animation/animation_container_element.h" +#include "ui/gfx/animation/animation_export.h" + +namespace gfx { +class Rect; +} + +namespace gfx { + +class AnimationContainer; +class AnimationDelegate; +class AnimationTestApi; + +// Base class used in implementing animations. You only need use this class if +// you're implementing a new animation type, otherwise you'll likely want one of +// LinearAnimation, SlideAnimation, ThrobAnimation or MultiAnimation. +// +// To subclass override Step, which is invoked as the animation progresses and +// GetCurrentValue() to return the value appropriate to the animation. +class ANIMATION_EXPORT Animation : public AnimationContainerElement { + public: + // Used with SetRichAnimationRenderMode() to force enable/disable rich + // animations during tests. + enum class RichAnimationRenderMode { + PLATFORM, + FORCE_ENABLED, + FORCE_DISABLED + }; + + explicit Animation(base::TimeDelta timer_interval); + + Animation(const Animation&) = delete; + Animation& operator=(const Animation&) = delete; + + ~Animation() override; + + // Starts the animation. Does nothing if the animation is already running. + void Start(); + + // Stops the animation. Does nothing if the animation is not running. + void Stop(); + + // Gets the value for the current state, according to the animation + // curve in use. + virtual double GetCurrentValue() const = 0; + + // Convenience for returning a value between |start| and |target| based on + // the current value. This is (target - start) * GetCurrentValue() + start. + double CurrentValueBetween(double start, double target) const; + int CurrentValueBetween(int start, int target) const; + gfx::Rect CurrentValueBetween(const gfx::Rect& start_bounds, + const gfx::Rect& target_bounds) const; + + // Sets the delegate. + void set_delegate(AnimationDelegate* delegate) { delegate_ = delegate; } + + // Sets the container used to manage the timer. A value of NULL results in + // creating a new AnimationContainer. + void SetContainer(AnimationContainer* container); + + bool is_animating() const { return is_animating_; } + + base::TimeDelta timer_interval() const { return timer_interval_; } + + // Returns true if rich animations should be rendered. + // Looks at session type (e.g. remote desktop) and accessibility settings + // to give guidance for heavy animations such as "start download" arrow. + static bool ShouldRenderRichAnimation(); + + // Determines on a per-platform basis whether scroll animations (e.g. produced + // by home/end key) should be enabled. Should only be called from the browser + // process. + static bool ScrollAnimationsEnabledBySystem(); + + // Determines whether the user desires reduced motion based on platform APIs. + // Should only be called from the browser process, on the UI thread. + static bool PrefersReducedMotion(); + static void UpdatePrefersReducedMotion(); + static void SetPrefersReducedMotionForTesting(bool prefers_reduced_motion) { + prefers_reduced_motion_ = prefers_reduced_motion; + } + + protected: + // Invoked from Start to allow subclasses to prepare for the animation. + virtual void AnimationStarted() {} + + // Invoked from Stop after we're removed from the container but before the + // delegate has been invoked. + virtual void AnimationStopped() {} + + // Invoked from stop to determine if cancel should be invoked. If this returns + // true the delegate is notified the animation was canceled, otherwise the + // delegate is notified the animation stopped. + virtual bool ShouldSendCanceledFromStop(); + + AnimationContainer* container() { return container_.get(); } + base::TimeTicks start_time() const { return start_time_; } + AnimationDelegate* delegate() { return delegate_; } + + // AnimationContainer::Element overrides + void SetStartTime(base::TimeTicks start_time) override; + void Step(base::TimeTicks time_now) override = 0; + base::TimeDelta GetTimerInterval() const override; + + private: + friend class AnimationTestApi; + + static bool ShouldRenderRichAnimationImpl(); + + // The mode in which to render rich animations. + static RichAnimationRenderMode rich_animation_rendering_mode_; + + // Interval for the animation. + const base::TimeDelta timer_interval_; + + // If true we're running. + bool is_animating_; + + // Our delegate; may be null. + AnimationDelegate* delegate_; + + // Container we're in. If non-null we're animating. + scoped_refptr container_; + + // Time we started at. + base::TimeTicks start_time_; + + // Obtaining the PrefersReducedMotion system setting can be expensive, so it + // is cached in this boolean. + static absl::optional prefers_reduced_motion_; +}; + +} // namespace gfx + +#endif // UI_GFX_ANIMATION_ANIMATION_H_ diff --git a/animation/animation_android.cc b/animation/animation_android.cc new file mode 100644 index 000000000000..1637e2d0b069 --- /dev/null +++ b/animation/animation_android.cc @@ -0,0 +1,23 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/animation/animation.h" + +#include "base/android/jni_android.h" +#include "ui/gfx/gfx_jni_headers/Animation_jni.h" + +using base::android::AttachCurrentThread; + +namespace gfx { + +// static +void Animation::UpdatePrefersReducedMotion() { + // prefers_reduced_motion_ should only be modified on the UI thread. + // TODO(crbug.com/927163): DCHECK this assertion once tests are well-behaved. + + JNIEnv* env = AttachCurrentThread(); + prefers_reduced_motion_ = Java_Animation_prefersReducedMotion(env); +} + +} // namespace gfx diff --git a/animation/animation_container.cc b/animation/animation_container.cc new file mode 100644 index 000000000000..dbfbd6b804c6 --- /dev/null +++ b/animation/animation_container.cc @@ -0,0 +1,146 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/animation/animation_container.h" + +#include "base/bind.h" +#include "ui/gfx/animation/animation_container_element.h" +#include "ui/gfx/animation/animation_container_observer.h" + +using base::TimeTicks; + +namespace gfx { + +AnimationContainer::AnimationContainer() = default; + +AnimationContainer::~AnimationContainer() { + if (observer_) + observer_->AnimationContainerShuttingDown(this); + + // The animations own us and stop themselves before being deleted. If they're + // still running, something is wrong. + DCHECK(!is_running()); +} + +void AnimationContainer::Start(AnimationContainerElement* element) { + DCHECK(elements_.count(element) == 0); // Start should only be invoked if the + // element isn't running. + + if (!is_running()) { + last_tick_time_ = base::TimeTicks::Now(); + SetMinTimerInterval(element->GetTimerInterval()); + min_timer_interval_count_ = 1; + } else if (element->GetTimerInterval() < min_timer_interval_) { + SetMinTimerInterval(element->GetTimerInterval()); + min_timer_interval_count_ = 1; + } else if (element->GetTimerInterval() == min_timer_interval_) { + min_timer_interval_count_++; + } + + element->SetStartTime(last_tick_time_); + elements_.insert(element); +} + +void AnimationContainer::Stop(AnimationContainerElement* element) { + DCHECK(elements_.count(element) > 0); // The element must be running. + + base::TimeDelta interval = element->GetTimerInterval(); + elements_.erase(element); + + if (!is_running()) { + runner_->Stop(); + min_timer_interval_count_ = 0; + if (observer_) + observer_->AnimationContainerEmpty(this); + } else if (interval == min_timer_interval_) { + min_timer_interval_count_--; + + // If the last element at the current (minimum) timer interval has been + // removed then go find the new minimum and the number of elements at that + // same minimum. + if (min_timer_interval_count_ == 0) { + std::pair interval_count = + GetMinIntervalAndCount(); + DCHECK(interval_count.first > min_timer_interval_); + SetMinTimerInterval(interval_count.first); + min_timer_interval_count_ = interval_count.second; + } + } +} + +void AnimationContainer::SetAnimationRunner( + std::unique_ptr runner) { + has_custom_animation_runner_ = !!runner; + runner_ = has_custom_animation_runner_ + ? std::move(runner) + : AnimationRunner::CreateDefaultAnimationRunner(); + if (is_running()) + RestartTimer(base::TimeTicks::Now() - last_tick_time_); +} + +void AnimationContainer::Run(base::TimeTicks current_time) { + // We notify the observer after updating all the elements. If all the elements + // are deleted as a result of updating then our ref count would go to zero and + // we would be deleted before we notify our observer. We add a reference to + // ourself here to make sure we're still valid after running all the elements. + scoped_refptr this_ref(this); + + last_tick_time_ = current_time; + + // Make a copy of the elements to iterate over so that if any elements are + // removed as part of invoking Step there aren't any problems. + Elements elements = elements_; + + for (Elements::const_iterator i = elements.begin(); + i != elements.end(); ++i) { + // Make sure the element is still valid. + if (elements_.find(*i) != elements_.end()) + (*i)->Step(current_time); + } + + if (observer_) + observer_->AnimationContainerProgressed(this); +} + +void AnimationContainer::SetMinTimerInterval(base::TimeDelta delta) { + // This doesn't take into account how far along the current element is, but + // that shouldn't be a problem for uses of Animation/AnimationContainer. + runner_->Stop(); + min_timer_interval_ = delta; + RestartTimer(base::TimeDelta()); +} + +void AnimationContainer::RestartTimer(base::TimeDelta elapsed) { + runner_->Start( + min_timer_interval_, elapsed, + base::BindRepeating(&AnimationContainer::Run, base::Unretained(this))); +} + +std::pair AnimationContainer::GetMinIntervalAndCount() + const { + DCHECK(is_running()); + + // Find the minimum interval and the number of elements sharing that same + // interval. It is tempting to create a map of intervals -> counts in order to + // make this O(log n) instead of O(n). However, profiling shows that this + // offers no practical performance gain (the most common case is that all + // elements in the set share the same interval). + base::TimeDelta min; + size_t count = 1; + auto i = elements_.begin(); + min = (*i)->GetTimerInterval(); + for (++i; i != elements_.end(); ++i) { + auto interval = (*i)->GetTimerInterval(); + if (interval < min) { + min = interval; + count = 1; + } else if (interval == min) { + count++; + } + } + + return std::make_pair(min, count); +} + +} // namespace gfx diff --git a/animation/animation_container.h b/animation/animation_container.h new file mode 100644 index 000000000000..fc80028596ba --- /dev/null +++ b/animation/animation_container.h @@ -0,0 +1,122 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_ANIMATION_ANIMATION_CONTAINER_H_ +#define UI_GFX_ANIMATION_ANIMATION_CONTAINER_H_ + +#include +#include + +#include "base/containers/flat_set.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/time/time.h" +#include "ui/gfx/animation/animation_export.h" +#include "ui/gfx/animation/animation_runner.h" + +namespace gfx { + +class AnimationContainerElement; +class AnimationContainerObserver; + +// AnimationContainer is used by Animation to manage the underlying +// AnimationRunner. Internally each Animation creates a single +// AnimationContainer. You can group a set of Animations into the same +// AnimationContainer by way of Animation::SetContainer. Grouping a set of +// Animations into the same AnimationContainer ensures they all update and start +// at the same time. +// +// AnimationContainer is ref counted. Each Animation contained within the +// AnimationContainer own it. +class ANIMATION_EXPORT AnimationContainer + : public base::RefCounted { + public: + AnimationContainer(); + + AnimationContainer(const AnimationContainer&) = delete; + AnimationContainer& operator=(const AnimationContainer&) = delete; + + // Invoked by Animation when it needs to start. Starts the timer if necessary. + // NOTE: This is invoked by Animation for you, you shouldn't invoke this + // directly. + void Start(AnimationContainerElement* animation); + + // Invoked by Animation when it needs to stop. If there are no more animations + // running the timer stops. + // NOTE: This is invoked by Animation for you, you shouldn't invoke this + // directly. + void Stop(AnimationContainerElement* animation); + + void set_observer(AnimationContainerObserver* observer) { + observer_ = observer; + } + + // The time the last animation ran at. + base::TimeTicks last_tick_time() const { return last_tick_time_; } + + // Are there any timers running? + bool is_running() const { return !elements_.empty(); } + + void SetAnimationRunner(std::unique_ptr runner); + AnimationRunner* animation_runner_for_testing() { return runner_.get(); } + bool has_custom_animation_runner() const { + return has_custom_animation_runner_; + } + + private: + friend class AnimationContainerTestApi; + friend class base::RefCounted; + + // This set is usually quite small so a flat_set is the most obvious choice. + // However, in extreme cases this can grow to 100s or even 1000s of elements. + // Since this set is duplicated on every call to 'Run' and indexed very + // frequently the cache locality of the vector is more important than the + // costlier (but rarer) insertion. Profiling shows that flat_set continues to + // perform best in these cases (up to 12x faster than std::set). + typedef base::flat_set Elements; + + ~AnimationContainer(); + + // Timer callback method. + void Run(base::TimeTicks current_time); + + // Sets min_timer_interval_ and restarts the timer. + void SetMinTimerInterval(base::TimeDelta delta); + + // Restarts the timer, assuming |elapsed| has already elapsed out of the timer + // interval. + void RestartTimer(base::TimeDelta elapsed); + + // Returns the min timer interval of all the timers, and the count of timers + // at that interval. + std::pair GetMinIntervalAndCount() const; + + // Represents one of two possible values: + // . If only a single animation has been started and the timer hasn't yet + // fired this is the time the animation was added. + // . The time the last animation ran at (::Run was invoked). + base::TimeTicks last_tick_time_ = base::TimeTicks::Now(); + + // Set of elements (animations) being managed. + Elements elements_; + + // Minimum interval the timers run at, plus the number of timers that have + // been seen at that interval. The most common case is for all of the + // animations to run at 60Hz, in which case all of the intervals are the same. + // This acts as a cache of size 1, and when an animation stops and is removed + // it means that the linear scan for the new minimum timer can almost always + // be avoided. + base::TimeDelta min_timer_interval_; + size_t min_timer_interval_count_ = 0; + + std::unique_ptr runner_ = + AnimationRunner::CreateDefaultAnimationRunner(); + bool has_custom_animation_runner_ = false; + + AnimationContainerObserver* observer_ = nullptr; +}; + +} // namespace gfx + +#endif // UI_GFX_ANIMATION_ANIMATION_CONTAINER_H_ diff --git a/animation/animation_container_element.h b/animation/animation_container_element.h new file mode 100644 index 000000000000..6e39ef4a3ab3 --- /dev/null +++ b/animation/animation_container_element.h @@ -0,0 +1,34 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_ANIMATION_ANIMATION_CONTAINER_ELEMENT_H_ +#define UI_GFX_ANIMATION_ANIMATION_CONTAINER_ELEMENT_H_ + +#include "base/time/time.h" +#include "ui/gfx/animation/animation_export.h" + +namespace gfx { + +// Interface for the elements the AnimationContainer contains. This is +// implemented by Animation. +class ANIMATION_EXPORT AnimationContainerElement { + public: + // Sets the start of the animation. This is invoked from + // AnimationContainer::Start. + virtual void SetStartTime(base::TimeTicks start_time) = 0; + + // Invoked when the animation is to progress. + virtual void Step(base::TimeTicks time_now) = 0; + + // Returns the time interval of the animation. If an Element needs to change + // this it should first invoke Stop, then Start. + virtual base::TimeDelta GetTimerInterval() const = 0; + + protected: + virtual ~AnimationContainerElement() {} +}; + +} // namespace gfx + +#endif // UI_GFX_ANIMATION_ANIMATION_CONTAINER_ELEMENT_H_ diff --git a/animation/animation_container_observer.h b/animation/animation_container_observer.h new file mode 100644 index 000000000000..77d9cb4ed1cb --- /dev/null +++ b/animation/animation_container_observer.h @@ -0,0 +1,34 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_ANIMATION_ANIMATION_CONTAINER_OBSERVER_H_ +#define UI_GFX_ANIMATION_ANIMATION_CONTAINER_OBSERVER_H_ + +#include "ui/gfx/animation/animation_export.h" + +namespace gfx { + +class AnimationContainer; + +// The observer is notified after every update of the animations managed by +// the container. +class ANIMATION_EXPORT AnimationContainerObserver { + public: + // Invoked on every tick of the timer managed by the container and after + // all the animations have updated. + virtual void AnimationContainerProgressed(AnimationContainer* container) {} + + // Invoked when no more animations are being managed by this container. + virtual void AnimationContainerEmpty(AnimationContainer* container) {} + + // Invoked from AnimationContainer's destructor. + virtual void AnimationContainerShuttingDown(AnimationContainer* container) {} + + protected: + virtual ~AnimationContainerObserver() {} +}; + +} // namespace gfx + +#endif // UI_GFX_ANIMATION_ANIMATION_CONTAINER_OBSERVER_H_ diff --git a/animation/animation_container_unittest.cc b/animation/animation_container_unittest.cc new file mode 100644 index 000000000000..9322e210d79f --- /dev/null +++ b/animation/animation_container_unittest.cc @@ -0,0 +1,175 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/animation/animation_container.h" + +#include + +#include "base/macros.h" +#include "base/run_loop.h" +#include "base/test/task_environment.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/animation/animation_container_observer.h" +#include "ui/gfx/animation/animation_test_api.h" +#include "ui/gfx/animation/linear_animation.h" +#include "ui/gfx/animation/test_animation_delegate.h" + +namespace gfx { + +namespace { + +class FakeAnimationContainerObserver : public AnimationContainerObserver { + public: + FakeAnimationContainerObserver() + : progressed_count_(0), + empty_(false) { + } + + FakeAnimationContainerObserver(const FakeAnimationContainerObserver&) = + delete; + FakeAnimationContainerObserver& operator=( + const FakeAnimationContainerObserver&) = delete; + + int progressed_count() const { return progressed_count_; } + bool empty() const { return empty_; } + + private: + void AnimationContainerProgressed(AnimationContainer* container) override { + progressed_count_++; + } + + // Invoked when no more animations are being managed by this container. + void AnimationContainerEmpty(AnimationContainer* container) override { + empty_ = true; + } + + void AnimationContainerShuttingDown(AnimationContainer* container) override {} + + int progressed_count_; + bool empty_; +}; + +class TestAnimation : public LinearAnimation { + public: + explicit TestAnimation(AnimationDelegate* delegate) + : LinearAnimation(base::Milliseconds(20), 20, delegate) {} + + TestAnimation(const TestAnimation&) = delete; + TestAnimation& operator=(const TestAnimation&) = delete; + + void AnimateToState(double state) override {} + + using LinearAnimation::duration; +}; + +} // namespace + +class AnimationContainerTest: public testing::Test { + protected: + AnimationContainerTest() + : task_environment_( + base::test::SingleThreadTaskEnvironment::MainThreadType::UI) {} + + private: + base::test::SingleThreadTaskEnvironment task_environment_; +}; + +// Makes sure the animation ups the ref count of the container and releases it +// appropriately. +TEST_F(AnimationContainerTest, Ownership) { + TestAnimationDelegate delegate; + scoped_refptr container(new AnimationContainer()); + std::unique_ptr animation(new TestAnimation(&delegate)); + animation->SetContainer(container.get()); + // Setting the container should up the ref count. + EXPECT_FALSE(container->HasOneRef()); + + animation.reset(); + + // Releasing the animation should decrement the ref count. + EXPECT_TRUE(container->HasOneRef()); +} + +// Makes sure multiple animations are managed correctly. +TEST_F(AnimationContainerTest, Multi) { + TestAnimationDelegate delegate1; + TestAnimationDelegate delegate2; + + scoped_refptr container(new AnimationContainer()); + TestAnimation animation1(&delegate1); + TestAnimation animation2(&delegate2); + animation1.SetContainer(container.get()); + animation2.SetContainer(container.get()); + + // Start both animations. + animation1.Start(); + EXPECT_TRUE(container->is_running()); + animation2.Start(); + EXPECT_TRUE(container->is_running()); + + // Run the message loop the delegate quits the message loop when notified. + base::RunLoop().Run(); + + // Both timers should have finished. + EXPECT_TRUE(delegate1.finished()); + EXPECT_TRUE(delegate2.finished()); + + // And the container should no longer be runnings. + EXPECT_FALSE(container->is_running()); +} + +// Makes sure observer is notified appropriately. +TEST_F(AnimationContainerTest, Observer) { + FakeAnimationContainerObserver observer; + TestAnimationDelegate delegate1; + + scoped_refptr container(new AnimationContainer()); + container->set_observer(&observer); + TestAnimation animation1(&delegate1); + animation1.SetContainer(container.get()); + + // Start the animation. + animation1.Start(); + EXPECT_TRUE(container->is_running()); + + // Run the message loop. The delegate quits the message loop when notified. + base::RunLoop().Run(); + + EXPECT_EQ(1, observer.progressed_count()); + + // The timer should have finished. + EXPECT_TRUE(delegate1.finished()); + + EXPECT_TRUE(observer.empty()); + + // And the container should no longer be running. + EXPECT_FALSE(container->is_running()); + + container->set_observer(NULL); +} + +// Tests that calling SetAnimationRunner() keeps running animations at their +// current point. +TEST_F(AnimationContainerTest, AnimationsRunAcrossRunnerChange) { + TestAnimationDelegate delegate; + auto container = base::MakeRefCounted(); + AnimationContainerTestApi test_api(container.get()); + TestAnimation animation(&delegate); + animation.SetContainer(container.get()); + + animation.Start(); + test_api.IncrementTime(animation.duration() / 2); + EXPECT_FALSE(delegate.finished()); + + container->SetAnimationRunner(nullptr); + AnimationRunner* runner = container->animation_runner_for_testing(); + ASSERT_TRUE(runner); + ASSERT_FALSE(runner->step_is_null_for_testing()); + EXPECT_FALSE(delegate.finished()); + + test_api.IncrementTime(animation.duration() / 2); + EXPECT_TRUE(delegate.finished()); +} + +} // namespace gfx diff --git a/animation/animation_delegate.h b/animation/animation_delegate.h new file mode 100644 index 000000000000..b582d16ef640 --- /dev/null +++ b/animation/animation_delegate.h @@ -0,0 +1,39 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_ANIMATION_ANIMATION_DELEGATE_H_ +#define UI_GFX_ANIMATION_ANIMATION_DELEGATE_H_ + +#include "ui/gfx/animation/animation_export.h" + +namespace gfx { + +class Animation; +class AnimationContainer; + +// AnimationDelegate +// +// Implement this interface when you want to receive notifications about the +// state of an animation. +class ANIMATION_EXPORT AnimationDelegate { + public: + virtual ~AnimationDelegate() {} + + // Called when an animation has completed. + virtual void AnimationEnded(const Animation* animation) {} + + // Called when an animation has progressed. + virtual void AnimationProgressed(const Animation* animation) {} + + // Called when an animation has been canceled. + virtual void AnimationCanceled(const Animation* animation) {} + + // Called when an animation container has been set. This gives a chance to + // set a custom animation runner. + virtual void AnimationContainerWasSet(AnimationContainer* container) {} +}; + +} // namespace gfx + +#endif // UI_GFX_ANIMATION_ANIMATION_DELEGATE_H_ diff --git a/animation/animation_delegate_notifier.h b/animation/animation_delegate_notifier.h new file mode 100644 index 000000000000..7983a70fc398 --- /dev/null +++ b/animation/animation_delegate_notifier.h @@ -0,0 +1,55 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_ANIMATION_ANIMATION_DELEGATE_NOTIFIER_H_ +#define UI_GFX_ANIMATION_ANIMATION_DELEGATE_NOTIFIER_H_ + +#include "base/check.h" +#include "ui/gfx/animation/animation_delegate.h" + +namespace gfx { + +// AnimationDelegateNotifier adapts AnimationDelegate (which is used by +// inheritance) into an object that is used by composition. This can be useful +// to compose the functionality of an AnimationDelegate subclass into an object +// that inherits directly from AnimationDelegate. +template +class AnimationDelegateNotifier : public AnimationDelegateType { + public: + template + AnimationDelegateNotifier(gfx::AnimationDelegate* owner, Args&&... args) + : AnimationDelegateType(std::forward(args)...), owner_(owner) { + DCHECK(owner_); + } + + ~AnimationDelegateNotifier() override = default; + + // AnimationDelegateType: + void AnimationEnded(const Animation* animation) override { + AnimationDelegateType::AnimationEnded(animation); + owner_->AnimationEnded(animation); + } + + void AnimationProgressed(const Animation* animation) override { + AnimationDelegateType::AnimationProgressed(animation); + owner_->AnimationProgressed(animation); + } + + void AnimationCanceled(const Animation* animation) override { + AnimationDelegateType::AnimationCanceled(animation); + owner_->AnimationCanceled(animation); + } + + void AnimationContainerWasSet(AnimationContainer* container) override { + AnimationDelegateType::AnimationContainerWasSet(container); + owner_->AnimationContainerWasSet(container); + } + + private: + gfx::AnimationDelegate* const owner_; +}; + +} // namespace gfx + +#endif // UI_GFX_ANIMATION_ANIMATION_DELEGATE_NOTIFIER_H_ diff --git a/animation/animation_export.h b/animation/animation_export.h new file mode 100644 index 000000000000..0b03b1b59516 --- /dev/null +++ b/animation/animation_export.h @@ -0,0 +1,29 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_ANIMATION_ANIMATION_EXPORT_H_ +#define UI_GFX_ANIMATION_ANIMATION_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(ANIMATION_IMPLEMENTATION) +#define ANIMATION_EXPORT __declspec(dllexport) +#else +#define ANIMATION_EXPORT __declspec(dllimport) +#endif // defined(ANIMATION_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(ANIMATION_IMPLEMENTATION) +#define ANIMATION_EXPORT __attribute__((visibility("default"))) +#else +#define ANIMATION_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define ANIMATION_EXPORT +#endif + +#endif // UI_GFX_ANIMATION_ANIMATION_EXPORT_H_ diff --git a/animation/animation_linux.cc b/animation/animation_linux.cc new file mode 100644 index 000000000000..0bcce53e57f3 --- /dev/null +++ b/animation/animation_linux.cc @@ -0,0 +1,37 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/animation/animation.h" + +#include "ui/gfx/animation/animation_settings_provider_linux.h" + +namespace gfx { + +namespace { + +// GTK only has a global setting for whether animations should be enabled. So +// use it for all of the specific settings that Chrome needs. +bool AnimationsEnabled() { + auto* provider = AnimationSettingsProviderLinux::GetInstance(); + return !provider || provider->AnimationsEnabled(); +} + +} // namespace + +// static +bool Animation::ShouldRenderRichAnimationImpl() { + return AnimationsEnabled(); +} + +// static +bool Animation::ScrollAnimationsEnabledBySystem() { + return AnimationsEnabled(); +} + +// static +void Animation::UpdatePrefersReducedMotion() { + prefers_reduced_motion_ = !AnimationsEnabled(); +} + +} // namespace gfx diff --git a/animation/animation_mac.mm b/animation/animation_mac.mm new file mode 100644 index 000000000000..175ceff333e4 --- /dev/null +++ b/animation/animation_mac.mm @@ -0,0 +1,54 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/animation/animation.h" + +#import + +#include "base/mac/mac_util.h" +#include "base/task/current_thread.h" + +// Only available since 10.12. +@interface NSWorkspace (AvailableSinceSierra) +@property(readonly) BOOL accessibilityDisplayShouldReduceMotion; +@end + +namespace gfx { + +// static +bool Animation::ShouldRenderRichAnimationImpl() { + return !PrefersReducedMotion(); +} + +// static +bool Animation::ScrollAnimationsEnabledBySystem() { + // Because of sandboxing, OS settings should only be queried from the browser + // process. + DCHECK(base::CurrentUIThread::IsSet() || base::CurrentIOThread::IsSet()); + + bool enabled = false; + id value = nil; + value = [[NSUserDefaults standardUserDefaults] + objectForKey:@"NSScrollAnimationEnabled"]; + if (value) + enabled = [value boolValue]; + return enabled; +} + +// static +void Animation::UpdatePrefersReducedMotion() { + // prefers_reduced_motion_ should only be modified on the UI thread. + // TODO(crbug.com/927163): DCHECK this assertion once tests are well-behaved. + + // We default to assuming that animations are enabled, to avoid impacting the + // experience for users on pre-10.12 systems. + prefers_reduced_motion_ = false; + SEL sel = @selector(accessibilityDisplayShouldReduceMotion); + if ([[NSWorkspace sharedWorkspace] respondsToSelector:sel]) { + prefers_reduced_motion_ = + [[NSWorkspace sharedWorkspace] accessibilityDisplayShouldReduceMotion]; + } +} + +} // namespace gfx diff --git a/animation/animation_runner.cc b/animation/animation_runner.cc new file mode 100644 index 000000000000..eebf9e40f699 --- /dev/null +++ b/animation/animation_runner.cc @@ -0,0 +1,85 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/animation/animation_runner.h" + +#include + +#include "base/timer/timer.h" + +namespace { + +// A default AnimationRunner based on base::Timer. +// TODO(https://crbug.com/953585): Remove this altogether. +class DefaultAnimationRunner : public gfx::AnimationRunner { + public: + DefaultAnimationRunner() = default; + ~DefaultAnimationRunner() override = default; + + // gfx::AnimationRunner: + void Stop() override; + + protected: + // gfx::AnimationRunner: + void OnStart(base::TimeDelta min_interval, base::TimeDelta elapsed) override; + + private: + void OnTimerTick(); + + base::OneShotTimer timer_; + base::TimeDelta min_interval_; +}; + +void DefaultAnimationRunner::Stop() { + timer_.Stop(); +} + +void DefaultAnimationRunner::OnStart(base::TimeDelta min_interval, + base::TimeDelta elapsed) { + min_interval_ = min_interval; + timer_.Start(FROM_HERE, min_interval - elapsed, this, + &DefaultAnimationRunner::OnTimerTick); +} + +void DefaultAnimationRunner::OnTimerTick() { + // This is effectively a RepeatingTimer. It's possible to use a true + // RepeatingTimer for this, but since OnStart() may need to use a OneShotTimer + // anyway (when |elapsed| is nonzero), it's just more complicated. + timer_.Start(FROM_HERE, min_interval_, this, + &DefaultAnimationRunner::OnTimerTick); + // Call Step() after timer_.Start() in case Step() calls Stop(). + Step(base::TimeTicks::Now()); +} + +} // namespace + +namespace gfx { + +// static +std::unique_ptr +AnimationRunner::CreateDefaultAnimationRunner() { + return std::make_unique(); +} + +AnimationRunner::~AnimationRunner() = default; + +void AnimationRunner::Start( + base::TimeDelta min_interval, + base::TimeDelta elapsed, + base::RepeatingCallback step) { + step_ = std::move(step); + OnStart(min_interval, elapsed); +} + +AnimationRunner::AnimationRunner() = default; + +void AnimationRunner::Step(base::TimeTicks tick) { + step_.Run(tick); +} + +void AnimationRunner::SetAnimationTimeForTesting(base::TimeTicks time) { + step_.Run(time); +} + +} // namespace gfx diff --git a/animation/animation_runner.h b/animation/animation_runner.h new file mode 100644 index 000000000000..988bc6cceac5 --- /dev/null +++ b/animation/animation_runner.h @@ -0,0 +1,64 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_ANIMATION_ANIMATION_RUNNER_H_ +#define UI_GFX_ANIMATION_ANIMATION_RUNNER_H_ + +#include + +#include "base/callback.h" +#include "base/time/time.h" +#include "ui/gfx/animation/animation_export.h" + +namespace gfx { + +// Interface for custom animation runner. CompositorAnimationRunner can control +// animation tick with this. +class ANIMATION_EXPORT AnimationRunner { + public: + // Creates a default AnimationRunner based on base::Timer. Ideally, + // we should prefer the compositor-based animation runner to this. + // TODO(https://crbug.com/953585): Remove this altogether. + static std::unique_ptr CreateDefaultAnimationRunner(); + + AnimationRunner(const AnimationRunner&) = delete; + AnimationRunner& operator=(const AnimationRunner&) = delete; + virtual ~AnimationRunner(); + + // Sets the provided |step| callback, then calls OnStart() with the provided + // |min_interval| and |elapsed| time to allow the subclass to actually begin + // animating. Subclasses are expected to call Step() periodically to drive the + // animation. + void Start(base::TimeDelta min_interval, + base::TimeDelta elapsed, + base::RepeatingCallback step); + + // Called when subclasses don't need to call Step() anymore. + virtual void Stop() = 0; + + bool step_is_null_for_testing() const { return step_.is_null(); } + + protected: + AnimationRunner(); + + // Called when subclasses should start calling Step() periodically to + // drive the animation. + virtual void OnStart(base::TimeDelta min_interval, + base::TimeDelta elapsed) = 0; + + // Advances the animation based on |tick|. + void Step(base::TimeTicks tick); + + private: + friend class AnimationContainerTestApi; + + // Advances the animation manually for testing. + void SetAnimationTimeForTesting(base::TimeTicks time); + + base::RepeatingCallback step_; +}; + +} // namespace gfx + +#endif // UI_GFX_ANIMATION_ANIMATION_RUNNER_H_ diff --git a/animation/animation_runner_unittest.cc b/animation/animation_runner_unittest.cc new file mode 100644 index 000000000000..632740e802ab --- /dev/null +++ b/animation/animation_runner_unittest.cc @@ -0,0 +1,37 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/animation/animation_runner.h" + +#include "base/test/bind.h" +#include "base/test/task_environment.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace gfx { +namespace { + +using AnimationRunnerTest = testing::Test; + +// Verifies that calling Stop() during Step() actually stops the timer. +TEST(AnimationRunnerTest, StopDuringStep) { + base::test::TaskEnvironment task_environment( + base::test::TaskEnvironment::TimeSource::MOCK_TIME); + + auto runner = AnimationRunner::CreateDefaultAnimationRunner(); + constexpr auto kDelay = base::Milliseconds(20); + int call_count = 0; + runner->Start(kDelay, base::TimeDelta(), + base::BindLambdaForTesting([&](base::TimeTicks ticks) { + ++call_count; + runner->Stop(); + })); + EXPECT_EQ(0, call_count); + task_environment.FastForwardBy(kDelay); + EXPECT_EQ(1, call_count); + task_environment.FastForwardBy(kDelay); + EXPECT_EQ(1, call_count); +} + +} // namespace +} // namespace gfx diff --git a/animation/animation_settings_provider_linux.cc b/animation/animation_settings_provider_linux.cc new file mode 100644 index 000000000000..e4d4c6c2b673 --- /dev/null +++ b/animation/animation_settings_provider_linux.cc @@ -0,0 +1,30 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/animation/animation_settings_provider_linux.h" + +#include "base/check_op.h" + +namespace gfx { + +// static +AnimationSettingsProviderLinux* AnimationSettingsProviderLinux::instance_ = + nullptr; + +// static +AnimationSettingsProviderLinux* AnimationSettingsProviderLinux::GetInstance() { + return instance_; +} + +AnimationSettingsProviderLinux::AnimationSettingsProviderLinux() { + DCHECK(!instance_); + instance_ = this; +} + +AnimationSettingsProviderLinux::~AnimationSettingsProviderLinux() { + DCHECK_EQ(instance_, this); + instance_ = nullptr; +} + +} // namespace gfx diff --git a/animation/animation_settings_provider_linux.h b/animation/animation_settings_provider_linux.h new file mode 100644 index 000000000000..0deac366974c --- /dev/null +++ b/animation/animation_settings_provider_linux.h @@ -0,0 +1,36 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_ANIMATION_ANIMATION_SETTINGS_PROVIDER_LINUX_H_ +#define UI_GFX_ANIMATION_ANIMATION_SETTINGS_PROVIDER_LINUX_H_ + +#include "base/macros.h" +#include "ui/gfx/animation/animation_export.h" + +namespace gfx { + +class ANIMATION_EXPORT AnimationSettingsProviderLinux { + public: + AnimationSettingsProviderLinux(const AnimationSettingsProviderLinux&) = + delete; + AnimationSettingsProviderLinux& operator=( + const AnimationSettingsProviderLinux&) = delete; + + virtual ~AnimationSettingsProviderLinux(); + + // Indicates if animations are enabled by the toolkit. + virtual bool AnimationsEnabled() const = 0; + + static AnimationSettingsProviderLinux* GetInstance(); + + protected: + AnimationSettingsProviderLinux(); + + private: + static AnimationSettingsProviderLinux* instance_; +}; + +} // namespace gfx + +#endif // UI_GFX_ANIMATION_ANIMATION_SETTINGS_PROVIDER_LINUX_H_ diff --git a/animation/animation_test_api.cc b/animation/animation_test_api.cc new file mode 100644 index 000000000000..365990c56e04 --- /dev/null +++ b/animation/animation_test_api.cc @@ -0,0 +1,48 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/animation/animation_test_api.h" + +#include "base/time/time.h" +#include "ui/gfx/animation/animation.h" + +namespace gfx { + +// static +std::unique_ptr> +AnimationTestApi::SetRichAnimationRenderMode( + Animation::RichAnimationRenderMode mode) { + DCHECK(Animation::rich_animation_rendering_mode_ == + Animation::RichAnimationRenderMode::PLATFORM); + return std::make_unique>( + &Animation::rich_animation_rendering_mode_, mode); +} + +AnimationTestApi::AnimationTestApi(Animation* animation) + : animation_(animation) {} + +AnimationTestApi::~AnimationTestApi() {} + +void AnimationTestApi::SetStartTime(base::TimeTicks ticks) { + animation_->SetStartTime(ticks); +} + +void AnimationTestApi::Step(base::TimeTicks ticks) { + animation_->Step(ticks); +} + +AnimationContainerTestApi::AnimationContainerTestApi( + AnimationContainer* container) + : container_(container) { + container_->runner_->Stop(); +} + +AnimationContainerTestApi::~AnimationContainerTestApi() = default; + +void AnimationContainerTestApi::IncrementTime(base::TimeDelta delta) { + container_->runner_->SetAnimationTimeForTesting(container_->last_tick_time() + + delta); +} + +} // namespace gfx diff --git a/animation/animation_test_api.h b/animation/animation_test_api.h new file mode 100644 index 000000000000..0794046ccd6e --- /dev/null +++ b/animation/animation_test_api.h @@ -0,0 +1,61 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_ANIMATION_ANIMATION_TEST_API_H_ +#define UI_GFX_ANIMATION_ANIMATION_TEST_API_H_ + +#include + +#include "base/auto_reset.h" +#include "base/macros.h" +#include "ui/gfx/animation/animation.h" +#include "ui/gfx/animation/animation_container.h" +#include "ui/gfx/animation/animation_export.h" + +namespace gfx { + +// Class to provide access to Animation internals for testing. +class AnimationTestApi { + public: + // Sets the rich animation rendering mode. Allows rich animations to be force + // enabled/disabled during tests. + static std::unique_ptr> + SetRichAnimationRenderMode(Animation::RichAnimationRenderMode mode); + + explicit AnimationTestApi(Animation* animation); + + AnimationTestApi(const AnimationTestApi&) = delete; + AnimationTestApi& operator=(const AnimationTestApi&) = delete; + + ~AnimationTestApi(); + + // Sets the start of the animation. + void SetStartTime(base::TimeTicks ticks); + + // Manually steps the animation forward + void Step(base::TimeTicks ticks); + + private: + Animation* animation_; +}; + +// For manual animation time control in tests. Creating this object will +// pause the AnimationRunner of |container| immediately. +class AnimationContainerTestApi { + public: + explicit AnimationContainerTestApi(AnimationContainer* container); + AnimationContainerTestApi(const AnimationContainerTestApi&) = delete; + AnimationContainerTestApi& operator=(const AnimationContainerTestApi&) = + delete; + ~AnimationContainerTestApi(); + + void IncrementTime(base::TimeDelta delta); + + private: + AnimationContainer* container_; +}; + +} // namespace gfx + +#endif // UI_GFX_ANIMATION_ANIMATION_TEST_API_H_ diff --git a/animation/animation_unittest.cc b/animation/animation_unittest.cc new file mode 100644 index 000000000000..130390dbe775 --- /dev/null +++ b/animation/animation_unittest.cc @@ -0,0 +1,177 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/command_line.h" +#include "base/run_loop.h" +#include "base/test/task_environment.h" +#include "build/build_config.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/animation/animation_delegate.h" +#include "ui/gfx/animation/linear_animation.h" +#include "ui/gfx/animation/test_animation_delegate.h" +#include "ui/gfx/switches.h" + +#if defined(OS_WIN) +#include +#endif + +namespace gfx { + +class AnimationTest : public testing::Test { + protected: + AnimationTest() + : task_environment_( + base::test::SingleThreadTaskEnvironment::MainThreadType::UI) {} + + private: + base::test::SingleThreadTaskEnvironment task_environment_; +}; + +namespace { + +/////////////////////////////////////////////////////////////////////////////// +// RunAnimation + +class RunAnimation : public LinearAnimation { + public: + RunAnimation(int frame_rate, AnimationDelegate* delegate) + : LinearAnimation(delegate, frame_rate) {} + + void AnimateToState(double state) override { + EXPECT_LE(0.0, state); + EXPECT_GE(1.0, state); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// CancelAnimation + +class CancelAnimation : public LinearAnimation { + public: + CancelAnimation(base::TimeDelta duration, + int frame_rate, + AnimationDelegate* delegate) + : LinearAnimation(duration, frame_rate, delegate) {} + + void AnimateToState(double state) override { + if (state >= 0.5) + Stop(); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// EndAnimation + +class EndAnimation : public LinearAnimation { + public: + EndAnimation(base::TimeDelta duration, + int frame_rate, + AnimationDelegate* delegate) + : LinearAnimation(duration, frame_rate, delegate) {} + + void AnimateToState(double state) override { + if (state >= 0.5) + End(); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// DeletingAnimationDelegate + +// AnimationDelegate implementation that deletes the animation in ended. +class DeletingAnimationDelegate : public AnimationDelegate { + public: + void AnimationEnded(const Animation* animation) override { + delete animation; + base::RunLoop::QuitCurrentWhenIdleDeprecated(); + } +}; + +} // namespace + +/////////////////////////////////////////////////////////////////////////////// +// LinearCase + +TEST_F(AnimationTest, RunCase) { + TestAnimationDelegate ad; + RunAnimation a1(150, &ad); + a1.SetDuration(base::Seconds(2)); + a1.Start(); + base::RunLoop().Run(); + + EXPECT_TRUE(ad.finished()); + EXPECT_FALSE(ad.canceled()); +} + +TEST_F(AnimationTest, CancelCase) { + TestAnimationDelegate ad; + CancelAnimation a2(base::Seconds(2), 150, &ad); + a2.Start(); + base::RunLoop().Run(); + + EXPECT_TRUE(ad.finished()); + EXPECT_TRUE(ad.canceled()); +} + +// Lets an animation run, invoking End part way through and make sure we get the +// right delegate methods invoked. +TEST_F(AnimationTest, EndCase) { + TestAnimationDelegate ad; + EndAnimation a2(base::Seconds(2), 150, &ad); + a2.Start(); + base::RunLoop().Run(); + + EXPECT_TRUE(ad.finished()); + EXPECT_FALSE(ad.canceled()); +} + +// Runs an animation with a delegate that deletes the animation in end. +TEST_F(AnimationTest, DeleteFromEnd) { + DeletingAnimationDelegate delegate; + RunAnimation* animation = new RunAnimation(150, &delegate); + animation->Start(); + base::RunLoop().Run(); + // delegate should have deleted animation. +} + +TEST_F(AnimationTest, ShouldRenderRichAnimation) { +#if defined(OS_WIN) + BOOL result; + ASSERT_NE(0, + ::SystemParametersInfo(SPI_GETCLIENTAREAANIMATION, 0, &result, 0)); + // ShouldRenderRichAnimation() should check the SPI_GETCLIENTAREAANIMATION + // value on Vista. + EXPECT_EQ(!!result, Animation::ShouldRenderRichAnimation()); +#else + EXPECT_TRUE(Animation::ShouldRenderRichAnimation()); +#endif +} + +// Test that current value is always 0 after Start() is called. +TEST_F(AnimationTest, StartState) { + LinearAnimation animation(base::Milliseconds(100), 60, NULL); + EXPECT_EQ(0.0, animation.GetCurrentValue()); + animation.Start(); + EXPECT_EQ(0.0, animation.GetCurrentValue()); + animation.End(); + EXPECT_EQ(1.0, animation.GetCurrentValue()); + animation.Start(); + EXPECT_EQ(0.0, animation.GetCurrentValue()); +} + +/////////////////////////////////////////////////////////////////////////////// +// PrefersReducedMotion tests + +TEST_F(AnimationTest, PrefersReducedMotionRespectsOverrideFlag) { + base::CommandLine::ForCurrentProcess()->AppendSwitchASCII( + switches::kForcePrefersReducedMotion, "1"); + EXPECT_TRUE(Animation::PrefersReducedMotion()); + + // It doesn't matter what the system setting says; the flag should continue to + // override it. + Animation::SetPrefersReducedMotionForTesting(false); + EXPECT_TRUE(Animation::PrefersReducedMotion()); +} + +} // namespace gfx diff --git a/animation/animation_win.cc b/animation/animation_win.cc new file mode 100644 index 000000000000..c8fdc6d9a893 --- /dev/null +++ b/animation/animation_win.cc @@ -0,0 +1,40 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/animation/animation.h" + +#include + +#include "base/win/win_util.h" + +namespace gfx { + +// static +bool Animation::ShouldRenderRichAnimationImpl() { + BOOL result; + // Get "Turn off all unnecessary animations" value. + if (::SystemParametersInfo(SPI_GETCLIENTAREAANIMATION, 0, &result, 0)) { + return !!result; + } + return !base::win::IsCurrentSessionRemote(); +} + +// static +bool Animation::ScrollAnimationsEnabledBySystem() { + return ShouldRenderRichAnimation(); +} + +// static +void Animation::UpdatePrefersReducedMotion() { + // prefers_reduced_motion_ should only be modified on the UI thread. + // TODO(crbug.com/927163): DCHECK this assertion once tests are well-behaved. + + // We default to assuming that animations are enabled, to avoid impacting the + // experience for users on systems that don't have SPI_GETCLIENTAREAANIMATION. + BOOL win_anim_enabled = true; + SystemParametersInfo(SPI_GETCLIENTAREAANIMATION, 0, &win_anim_enabled, 0); + prefers_reduced_motion_ = !win_anim_enabled; +} + +} // namespace gfx diff --git a/animation/keyframe/BUILD.gn b/animation/keyframe/BUILD.gn new file mode 100644 index 000000000000..85e2bef645c8 --- /dev/null +++ b/animation/keyframe/BUILD.gn @@ -0,0 +1,51 @@ +# Copyright 2021 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//build/config/ui.gni") + +keyframe_animation_remove_configs = [] +keyframe_animation_add_configs = [ + "//build/config:precompiled_headers", + "//build/config/compiler:noshadowing", + "//build/config/compiler:wexit_time_destructors", +] + +if (!is_debug) { + keyframe_animation_remove_configs += + [ "//build/config/compiler:default_optimization" ] + keyframe_animation_add_configs += [ "//build/config/compiler:optimize_max" ] +} + +component("keyframe") { + sources = [ + "animation_curve.cc", + "animation_curve.h", + "keyframe_animation_export.h", + "keyframe_effect.cc", + "keyframe_effect.h", + "keyframe_model.cc", + "keyframe_model.h", + "keyframed_animation_curve-inl.h", + "keyframed_animation_curve.cc", + "keyframed_animation_curve.h", + "target_property.h", + "timing_function.cc", + "timing_function.h", + "transition.cc", + "transition.h", + ] + + defines = [ "GFX_KEYFRAME_ANIMATION_IMPLEMENTATION=1" ] + + deps = [ + "//base", + "//skia", + "//ui/gfx/animation", + "//ui/gfx/geometry", + "//ui/gfx/geometry:geometry_skia", + ] + + configs += keyframe_animation_add_configs + configs -= keyframe_animation_remove_configs +} diff --git a/animation/keyframe/animation_curve.cc b/animation/keyframe/animation_curve.cc new file mode 100644 index 000000000000..17fcc23cf2e3 --- /dev/null +++ b/animation/keyframe/animation_curve.cc @@ -0,0 +1,49 @@ +// Copyright 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/animation/keyframe/animation_curve.h" + +#include "base/check.h" + +namespace gfx { + +bool AnimationCurve::PreservesAxisAlignment() const { + return true; +} + +bool AnimationCurve::MaximumScale(float* max_scale) const { + return false; +} + +base::TimeDelta AnimationCurve::TickInterval() const { + return base::TimeDelta(); +} + +#define DEFINE_ANIMATION_CURVE(Name, CurveType) \ + void Name##AnimationCurve::Tick(base::TimeDelta t, int property_id, \ + KeyframeModel* keyframe_model) const { \ + if (target_) { \ + target_->On##Name##Animated(GetValue(t), property_id, keyframe_model); \ + } \ + } \ + int Name##AnimationCurve::Type() const { return AnimationCurve::CurveType; } \ + const char* Name##AnimationCurve::TypeName() const { return #Name; } \ + const Name##AnimationCurve* Name##AnimationCurve::To##Name##AnimationCurve( \ + const AnimationCurve* c) { \ + DCHECK_EQ(AnimationCurve::CurveType, c->Type()); \ + return static_cast(c); \ + } \ + Name##AnimationCurve* Name##AnimationCurve::To##Name##AnimationCurve( \ + AnimationCurve* c) { \ + DCHECK_EQ(AnimationCurve::CurveType, c->Type()); \ + return static_cast(c); \ + } + +DEFINE_ANIMATION_CURVE(Transform, TRANSFORM) +DEFINE_ANIMATION_CURVE(Float, FLOAT) +DEFINE_ANIMATION_CURVE(Size, SIZE) +DEFINE_ANIMATION_CURVE(Color, COLOR) +DEFINE_ANIMATION_CURVE(Rect, RECT) + +} // namespace gfx diff --git a/animation/keyframe/animation_curve.h b/animation/keyframe/animation_curve.h new file mode 100644 index 000000000000..105c53e7d86a --- /dev/null +++ b/animation/keyframe/animation_curve.h @@ -0,0 +1,115 @@ +// Copyright 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_ANIMATION_KEYFRAME_ANIMATION_CURVE_H_ +#define UI_GFX_ANIMATION_KEYFRAME_ANIMATION_CURVE_H_ + +#include + +#include "base/time/time.h" +#include "third_party/skia/include/core/SkColor.h" +#include "ui/gfx/animation/keyframe/keyframe_animation_export.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/size_f.h" +#include "ui/gfx/geometry/transform.h" +#include "ui/gfx/geometry/transform_operations.h" + +namespace gfx { +class TransformOperations; +class KeyframeModel; + +// An animation curve is a function that returns a value given a time. +class GFX_KEYFRAME_ANIMATION_EXPORT AnimationCurve { + public: + // TODO(crbug.com/1176334): we shouldn't need the curve type, long term. + // + // In the meanime, external clients of the animation machinery will have + // other curve types and should be added to this enum to ensure uniqueness + // (eg, there are serveral cc-specific types here, presently). + enum CurveType { + COLOR = 0, + FLOAT, + TRANSFORM, + SIZE, + RECT, + + // cc:: curve types. + FILTER, + SCROLL_OFFSET, + }; + + virtual ~AnimationCurve() = default; + + virtual base::TimeDelta Duration() const = 0; + virtual int Type() const = 0; + virtual const char* TypeName() const = 0; + virtual std::unique_ptr Clone() const = 0; + virtual void Tick(base::TimeDelta t, + int property_id, + KeyframeModel* keyframe_model) const = 0; + + // Returns true if this animation preserves axis alignment. + virtual bool PreservesAxisAlignment() const; + + // Set |max_scale| to the maximum scale along any dimension during the + // animation, of all steps (keyframes) with calculatable scale. Returns + // false if none of the steps can calculate a scale. + virtual bool MaximumScale(float* max_scale) const; + + // Returns step interval if it's step animation. Returns 0 otherwise. + virtual base::TimeDelta TickInterval() const; +}; + +#define DECLARE_ANIMATION_CURVE_BODY(T, Name) \ + public: \ + static const Name##AnimationCurve* To##Name##AnimationCurve( \ + const AnimationCurve* c); \ + static Name##AnimationCurve* To##Name##AnimationCurve(AnimationCurve* c); \ + class Target { \ + public: \ + virtual ~Target() = default; \ + virtual void On##Name##Animated(const T& value, \ + int target_property_id, \ + gfx::KeyframeModel* keyframe_model) = 0; \ + }; \ + ~Name##AnimationCurve() override = default; \ + virtual T GetValue(base::TimeDelta t) const = 0; \ + void Tick(base::TimeDelta t, int property_id, \ + gfx::KeyframeModel* keyframe_model) const override; \ + void set_target(Target* target) { target_ = target; } \ + int Type() const override; \ + const char* TypeName() const override; \ + \ + protected: \ + Target* target() const { return target_; } \ + \ + private: \ + Target* target_ = nullptr; + +class GFX_KEYFRAME_ANIMATION_EXPORT ColorAnimationCurve + : public AnimationCurve { + DECLARE_ANIMATION_CURVE_BODY(SkColor, Color) +}; + +class GFX_KEYFRAME_ANIMATION_EXPORT FloatAnimationCurve + : public AnimationCurve { + DECLARE_ANIMATION_CURVE_BODY(float, Float) +}; + +class GFX_KEYFRAME_ANIMATION_EXPORT SizeAnimationCurve : public AnimationCurve { + DECLARE_ANIMATION_CURVE_BODY(gfx::SizeF, Size) +}; + +class GFX_KEYFRAME_ANIMATION_EXPORT TransformAnimationCurve + : public AnimationCurve { + DECLARE_ANIMATION_CURVE_BODY(gfx::TransformOperations, Transform) +}; + +class GFX_KEYFRAME_ANIMATION_EXPORT RectAnimationCurve : public AnimationCurve { + DECLARE_ANIMATION_CURVE_BODY(gfx::Rect, Rect) +}; + +} // namespace gfx + +#endif // UI_GFX_ANIMATION_KEYFRAME_ANIMATION_CURVE_H_ diff --git a/animation/keyframe/keyframe_animation_export.h b/animation/keyframe/keyframe_animation_export.h new file mode 100644 index 000000000000..db0f21ec1ab2 --- /dev/null +++ b/animation/keyframe/keyframe_animation_export.h @@ -0,0 +1,29 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_ANIMATION_KEYFRAME_KEYFRAME_ANIMATION_EXPORT_H_ +#define UI_GFX_ANIMATION_KEYFRAME_KEYFRAME_ANIMATION_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(GFX_KEYFRAME_ANIMATION_IMPLEMENTATION) +#define GFX_KEYFRAME_ANIMATION_EXPORT __declspec(dllexport) +#else +#define GFX_KEYFRAME_ANIMATION_EXPORT __declspec(dllimport) +#endif // defined(CC_ANIMATION_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(GFX_KEYFRAME_ANIMATION_IMPLEMENTATION) +#define GFX_KEYFRAME_ANIMATION_EXPORT __attribute__((visibility("default"))) +#else +#define GFX_KEYFRAME_ANIMATION_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define GFX_KEYFRAME_ANIMATION_EXPORT +#endif + +#endif // UI_GFX_ANIMATION_KEYFRAME_KEYFRAME_ANIMATION_EXPORT_H_ diff --git a/animation/keyframe/keyframe_animation_unittest.cc b/animation/keyframe/keyframe_animation_unittest.cc new file mode 100644 index 000000000000..90158df3c37b --- /dev/null +++ b/animation/keyframe/keyframe_animation_unittest.cc @@ -0,0 +1,922 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/animation/keyframe/keyframe_effect.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkColor.h" +#include "ui/gfx/animation/keyframe/animation_curve.h" +#include "ui/gfx/animation/keyframe/keyframed_animation_curve.h" +#include "ui/gfx/animation/keyframe/test/animation_utils.h" +#include "ui/gfx/geometry/test/size_test_util.h" +#include "ui/gfx/test/gfx_util.h" + +namespace gfx { + +static constexpr float kNoise = 1e-6f; +static constexpr float kEpsilon = 1e-5f; + +// Tests client-specific property ids. +static constexpr int kLayoutOffsetPropertyId = 19; +static constexpr int kBackgroundColorPropertyId = 20; +static constexpr int kOpacityPropertyId = 21; +static constexpr int kBoundsPropertyId = 22; +static constexpr int kTransformPropertyId = 23; +static constexpr int kRectPropertyId = 24; + +class TestAnimationTarget : public SizeAnimationCurve::Target, + public TransformAnimationCurve::Target, + public FloatAnimationCurve::Target, + public ColorAnimationCurve::Target, + public RectAnimationCurve::Target { + public: + TestAnimationTarget() { + layout_offset_.AppendTranslate(0, 0, 0); + operations_.AppendTranslate(0, 0, 0); + operations_.AppendRotate(1, 0, 0, 0); + operations_.AppendScale(1, 1, 1); + } + + const SizeF& size() const { return size_; } + const TransformOperations& operations() const { return operations_; } + const TransformOperations& layout_offset() const { return layout_offset_; } + float opacity() const { return opacity_; } + SkColor background_color() const { return background_color_; } + Rect rect() const { return rect_; } + + void OnSizeAnimated(const SizeF& size, + int target_property_id, + KeyframeModel* keyframe_model) override { + size_ = size; + } + + void OnTransformAnimated(const TransformOperations& operations, + int target_property_id, + KeyframeModel* keyframe_model) override { + if (target_property_id == kLayoutOffsetPropertyId) { + layout_offset_ = operations; + } else { + operations_ = operations; + } + } + + void OnFloatAnimated(const float& opacity, + int target_property_id, + KeyframeModel* keyframe_model) override { + opacity_ = opacity; + } + + void OnColorAnimated(const SkColor& color, + int target_property_id, + KeyframeModel* keyframe_model) override { + background_color_ = color; + } + + void OnRectAnimated(const Rect& rect, + int target_property_id, + KeyframeModel* keyframe_model) override { + rect_ = rect; + } + + private: + TransformOperations layout_offset_; + TransformOperations operations_; + SizeF size_ = {10.0f, 10.0f}; + float opacity_ = 1.0f; + SkColor background_color_ = SK_ColorRED; + Rect rect_; +}; + +TEST(KeyframeAnimationTest, AddRemoveKeyframeModels) { + KeyframeEffect animator; + EXPECT_TRUE(animator.keyframe_models().empty()); + TestAnimationTarget target; + + animator.AddKeyframeModel(CreateSizeAnimation(&target, 1, kBoundsPropertyId, + SizeF(10, 100), SizeF(20, 200), + MicrosecondsToDelta(10000))); + EXPECT_EQ(1ul, animator.keyframe_models().size()); + EXPECT_EQ(kBoundsPropertyId, animator.keyframe_models()[0]->TargetProperty()); + + TransformOperations from_operations; + from_operations.AppendTranslate(10, 100, 1000); + TransformOperations to_operations; + to_operations.AppendTranslate(20, 200, 2000); + animator.AddKeyframeModel(CreateTransformAnimation( + &target, 2, kTransformPropertyId, from_operations, to_operations, + MicrosecondsToDelta(10000))); + + EXPECT_EQ(2ul, animator.keyframe_models().size()); + EXPECT_EQ(kTransformPropertyId, + animator.keyframe_models()[1]->TargetProperty()); + + animator.AddKeyframeModel(CreateTransformAnimation( + &target, 3, kTransformPropertyId, from_operations, to_operations, + MicrosecondsToDelta(10000))); + EXPECT_EQ(3ul, animator.keyframe_models().size()); + EXPECT_EQ(kTransformPropertyId, + animator.keyframe_models()[2]->TargetProperty()); + + animator.RemoveKeyframeModels(kTransformPropertyId); + EXPECT_EQ(1ul, animator.keyframe_models().size()); + EXPECT_EQ(kBoundsPropertyId, animator.keyframe_models()[0]->TargetProperty()); + + animator.RemoveKeyframeModel(animator.keyframe_models()[0]->id()); + EXPECT_TRUE(animator.keyframe_models().empty()); +} + +TEST(KeyframeAnimationTest, AnimationLifecycle) { + TestAnimationTarget target; + KeyframeEffect animator; + + animator.AddKeyframeModel(CreateSizeAnimation(&target, 1, kBoundsPropertyId, + SizeF(10, 100), SizeF(20, 200), + MicrosecondsToDelta(10000))); + EXPECT_EQ(1ul, animator.keyframe_models().size()); + EXPECT_EQ(kBoundsPropertyId, animator.keyframe_models()[0]->TargetProperty()); + EXPECT_EQ(KeyframeModel::WAITING_FOR_TARGET_AVAILABILITY, + animator.keyframe_models()[0]->run_state()); + + base::TimeTicks start_time = MicrosecondsToTicks(1); + animator.Tick(start_time); + EXPECT_EQ(KeyframeModel::RUNNING, animator.keyframe_models()[0]->run_state()); + + EXPECT_SIZEF_EQ(SizeF(10, 100), target.size()); + + // Tick beyond the animation + animator.Tick(start_time + MicrosecondsToDelta(20000)); + + EXPECT_TRUE(animator.keyframe_models().empty()); + + // Should have assumed the final value. + EXPECT_SIZEF_EQ(SizeF(20, 200), target.size()); +} + +TEST(KeyframeAnimationTest, AnimationQueue) { + TestAnimationTarget target; + KeyframeEffect animator; + + animator.AddKeyframeModel(CreateSizeAnimation(&target, 1, kBoundsPropertyId, + SizeF(10, 100), SizeF(20, 200), + MicrosecondsToDelta(10000))); + EXPECT_EQ(1ul, animator.keyframe_models().size()); + EXPECT_EQ(kBoundsPropertyId, animator.keyframe_models()[0]->TargetProperty()); + EXPECT_EQ(KeyframeModel::WAITING_FOR_TARGET_AVAILABILITY, + animator.keyframe_models()[0]->run_state()); + + base::TimeTicks start_time = MicrosecondsToTicks(1); + animator.Tick(start_time); + EXPECT_EQ(KeyframeModel::RUNNING, animator.keyframe_models()[0]->run_state()); + EXPECT_SIZEF_EQ(SizeF(10, 100), target.size()); + + animator.AddKeyframeModel(CreateSizeAnimation(&target, 2, kBoundsPropertyId, + SizeF(10, 100), SizeF(20, 200), + MicrosecondsToDelta(10000))); + + TransformOperations from_operations; + from_operations.AppendTranslate(10, 100, 1000); + TransformOperations to_operations; + to_operations.AppendTranslate(20, 200, 2000); + animator.AddKeyframeModel(CreateTransformAnimation( + &target, 3, kTransformPropertyId, from_operations, to_operations, + MicrosecondsToDelta(10000))); + + EXPECT_EQ(3ul, animator.keyframe_models().size()); + EXPECT_EQ(kBoundsPropertyId, animator.keyframe_models()[1]->TargetProperty()); + EXPECT_EQ(kTransformPropertyId, + animator.keyframe_models()[2]->TargetProperty()); + int id1 = animator.keyframe_models()[1]->id(); + + animator.Tick(start_time + MicrosecondsToDelta(1)); + + // Only the transform animation should have started (since there's no + // conflicting animation). + EXPECT_EQ(KeyframeModel::WAITING_FOR_TARGET_AVAILABILITY, + animator.keyframe_models()[1]->run_state()); + EXPECT_EQ(KeyframeModel::RUNNING, animator.keyframe_models()[2]->run_state()); + + // Tick beyond the first animator. This should cause it (and the transform + // animation) to get removed and for the second bounds animation to start. + animator.Tick(start_time + MicrosecondsToDelta(15000)); + + EXPECT_EQ(1ul, animator.keyframe_models().size()); + EXPECT_EQ(KeyframeModel::RUNNING, animator.keyframe_models()[0]->run_state()); + EXPECT_EQ(id1, animator.keyframe_models()[0]->id()); + + // Tick beyond all animations. There should be none remaining. + animator.Tick(start_time + MicrosecondsToDelta(30000)); + EXPECT_TRUE(animator.keyframe_models().empty()); +} + +TEST(KeyframeAnimationTest, FinishedTransition) { + TestAnimationTarget target; + KeyframeEffect animator; + Transition transition; + transition.target_properties = {kOpacityPropertyId}; + transition.duration = MsToDelta(10); + animator.set_transition(transition); + + base::TimeTicks start_time = MsToTicks(1000); + animator.Tick(start_time); + + float from = 1.0f; + float to = 0.0f; + animator.TransitionFloatTo(&target, start_time, kOpacityPropertyId, from, to); + + animator.Tick(start_time); + EXPECT_EQ(from, target.opacity()); + + // We now simulate a long pause where the element hasn't been ticked (eg, it + // may have been hidden). If this happens, the unticked transition must still + // be treated as having finished. + animator.TransitionFloatTo(&target, start_time + MsToDelta(1000), + kOpacityPropertyId, target.opacity(), 1.0f); + + animator.Tick(start_time + MsToDelta(1000)); + EXPECT_EQ(to, target.opacity()); +} + +TEST(KeyframeAnimationTest, OpacityTransitions) { + TestAnimationTarget target; + KeyframeEffect animator; + Transition transition; + transition.target_properties = {kOpacityPropertyId}; + transition.duration = MicrosecondsToDelta(10000); + animator.set_transition(transition); + + base::TimeTicks start_time = MicrosecondsToTicks(1000000); + animator.Tick(start_time); + + float from = 1.0f; + float to = 0.5f; + animator.TransitionFloatTo(&target, start_time, kOpacityPropertyId, from, to); + + EXPECT_EQ(from, target.opacity()); + animator.Tick(start_time); + + // Scheduling a redundant, approximately equal transition should be ignored. + int keyframe_model_id = animator.keyframe_models().front()->id(); + float nearby = to + kNoise; + animator.TransitionFloatTo(&target, start_time, kOpacityPropertyId, from, + nearby); + EXPECT_EQ(keyframe_model_id, animator.keyframe_models().front()->id()); + + animator.Tick(start_time + MicrosecondsToDelta(5000)); + EXPECT_GT(from, target.opacity()); + EXPECT_LT(to, target.opacity()); + + animator.Tick(start_time + MicrosecondsToDelta(10000)); + EXPECT_EQ(to, target.opacity()); +} + +TEST(KeyframeAnimationTest, ReversedOpacityTransitions) { + TestAnimationTarget target; + KeyframeEffect animator; + Transition transition; + transition.target_properties = {kOpacityPropertyId}; + transition.duration = MicrosecondsToDelta(10000); + animator.set_transition(transition); + + base::TimeTicks start_time = MicrosecondsToTicks(1000000); + animator.Tick(start_time); + + float from = 1.0f; + float to = 0.5f; + animator.TransitionFloatTo(&target, start_time, kOpacityPropertyId, from, to); + + EXPECT_EQ(from, target.opacity()); + animator.Tick(start_time); + + animator.Tick(start_time + MicrosecondsToDelta(1000)); + float value_before_reversing = target.opacity(); + EXPECT_GT(from, value_before_reversing); + EXPECT_LT(to, value_before_reversing); + + animator.TransitionFloatTo(&target, start_time + MicrosecondsToDelta(1000), + kOpacityPropertyId, target.opacity(), from); + animator.Tick(start_time + MicrosecondsToDelta(1000)); + EXPECT_FLOAT_EQ(value_before_reversing, target.opacity()); + + animator.Tick(start_time + MicrosecondsToDelta(2000)); + EXPECT_EQ(from, target.opacity()); +} + +TEST(KeyframeAnimationTest, RetargetOpacityTransition) { + TestAnimationTarget target; + KeyframeEffect animator; + + std::unique_ptr curve( + gfx::KeyframedFloatAnimationCurve::Create()); + curve->AddKeyframe(FloatKeyframe::Create(base::TimeDelta(), 1.0f, nullptr)); + curve->AddKeyframe( + FloatKeyframe::Create(MicrosecondsToDelta(10000), 0.0f, nullptr)); + curve->set_target(&target); + animator.AddKeyframeModel(KeyframeModel::Create( + std::move(curve), KeyframeEffect::GetNextKeyframeModelId(), + kOpacityPropertyId)); + + base::TimeTicks start_time = MicrosecondsToTicks(1000000); + animator.Tick(start_time); + EXPECT_EQ(1.f, target.opacity()); + animator.Tick(start_time + MicrosecondsToDelta(5000)); + EXPECT_FLOAT_EQ(0.5f, target.opacity()); + + animator.GetKeyframeModel(kOpacityPropertyId) + ->Retarget(start_time + MicrosecondsToDelta(5000), kOpacityPropertyId, + 1.f); + animator.Tick(start_time + MicrosecondsToDelta(5000)); + EXPECT_FLOAT_EQ(0.5f, target.opacity()); + + animator.Tick(start_time + MicrosecondsToDelta(7500)); + EXPECT_FLOAT_EQ(0.75f, target.opacity()); +} + +TEST(KeyframeAnimationTest, RetargetTransitionBeforeLastKeyframe) { + TestAnimationTarget target; + KeyframeEffect animator; + + std::unique_ptr curve( + gfx::KeyframedFloatAnimationCurve::Create()); + curve->AddKeyframe(FloatKeyframe::Create(base::TimeDelta(), 1.0f, nullptr)); + curve->AddKeyframe( + FloatKeyframe::Create(MicrosecondsToDelta(5000), 0.5f, nullptr)); + curve->AddKeyframe( + FloatKeyframe::Create(MicrosecondsToDelta(10000), 0.0f, nullptr)); + curve->set_target(&target); + animator.AddKeyframeModel(KeyframeModel::Create( + std::move(curve), KeyframeEffect::GetNextKeyframeModelId(), + kOpacityPropertyId)); + + base::TimeTicks start_time = MicrosecondsToTicks(1000000); + animator.Tick(start_time); + EXPECT_EQ(1.f, target.opacity()); + + animator.GetKeyframeModel(kOpacityPropertyId) + ->Retarget(start_time + MicrosecondsToDelta(4000), kOpacityPropertyId, + 0.1f); + animator.Tick(start_time + MicrosecondsToDelta(5000)); + EXPECT_FLOAT_EQ(0.5f, target.opacity()); + + animator.Tick(start_time + MicrosecondsToDelta(7500)); + EXPECT_FLOAT_EQ(0.3f, target.opacity()); +} + +TEST(KeyframeAnimationTest, LayoutOffsetTransitions) { + // In this test, we do expect exact equality. + float tolerance = 0.0f; + TestAnimationTarget target; + KeyframeEffect animator; + Transition transition; + transition.target_properties = {kLayoutOffsetPropertyId}; + transition.duration = MicrosecondsToDelta(10000); + animator.set_transition(transition); + base::TimeTicks start_time = MicrosecondsToTicks(1000000); + animator.Tick(start_time); + + TransformOperations from = target.layout_offset(); + + TransformOperations to; + to.AppendTranslate(8, 0, 0); + + animator.TransitionTransformOperationsTo(&target, start_time, + kLayoutOffsetPropertyId, from, to); + + EXPECT_TRUE(from.ApproximatelyEqual(target.layout_offset(), tolerance)); + animator.Tick(start_time); + + // Scheduling a redundant, approximately equal transition should be ignored. + int keyframe_model_id = animator.keyframe_models().front()->id(); + TransformOperations nearby = to; + nearby.at(0).translate.x += kNoise; + animator.TransitionTransformOperationsTo( + &target, start_time, kLayoutOffsetPropertyId, from, nearby); + EXPECT_EQ(keyframe_model_id, animator.keyframe_models().front()->id()); + + animator.Tick(start_time + MicrosecondsToDelta(5000)); + EXPECT_LT(from.at(0).translate.x, target.layout_offset().at(0).translate.x); + EXPECT_GT(to.at(0).translate.x, target.layout_offset().at(0).translate.x); + + animator.Tick(start_time + MicrosecondsToDelta(10000)); + EXPECT_TRUE(to.ApproximatelyEqual(target.layout_offset(), tolerance)); +} + +TEST(KeyframeAnimationTest, TransformTransitions) { + // In this test, we do expect exact equality. + float tolerance = 0.0f; + TestAnimationTarget target; + KeyframeEffect animator; + Transition transition; + transition.target_properties = {kTransformPropertyId}; + transition.duration = MicrosecondsToDelta(10000); + animator.set_transition(transition); + base::TimeTicks start_time = MicrosecondsToTicks(1000000); + animator.Tick(start_time); + + TransformOperations from = target.operations(); + + TransformOperations to; + to.AppendTranslate(8, 0, 0); + to.AppendRotate(1, 0, 0, 0); + to.AppendScale(1, 1, 1); + + animator.TransitionTransformOperationsTo(&target, start_time, + kTransformPropertyId, from, to); + + EXPECT_TRUE(from.ApproximatelyEqual(target.operations(), tolerance)); + animator.Tick(start_time); + + // Scheduling a redundant, approximately equal transition should be ignored. + int keyframe_model_id = animator.keyframe_models().front()->id(); + TransformOperations nearby = to; + nearby.at(0).translate.x += kNoise; + animator.TransitionTransformOperationsTo(&target, start_time, + kTransformPropertyId, from, nearby); + EXPECT_EQ(keyframe_model_id, animator.keyframe_models().front()->id()); + + animator.Tick(start_time + MicrosecondsToDelta(5000)); + EXPECT_LT(from.at(0).translate.x, target.operations().at(0).translate.x); + EXPECT_GT(to.at(0).translate.x, target.operations().at(0).translate.x); + + animator.Tick(start_time + MicrosecondsToDelta(10000)); + EXPECT_TRUE(to.ApproximatelyEqual(target.operations(), tolerance)); +} + +TEST(KeyframeAnimationTest, ReversedTransformTransitions) { + // In this test, we do expect exact equality. + float tolerance = 0.0f; + TestAnimationTarget target; + KeyframeEffect animator; + Transition transition; + transition.target_properties = {kTransformPropertyId}; + transition.duration = MicrosecondsToDelta(10000); + animator.set_transition(transition); + base::TimeTicks start_time = MicrosecondsToTicks(1000000); + animator.Tick(start_time); + + TransformOperations from = target.operations(); + + TransformOperations to; + to.AppendTranslate(8, 0, 0); + to.AppendRotate(1, 0, 0, 0); + to.AppendScale(1, 1, 1); + + animator.TransitionTransformOperationsTo(&target, start_time, + kTransformPropertyId, from, to); + + EXPECT_TRUE(from.ApproximatelyEqual(target.operations(), tolerance)); + animator.Tick(start_time); + + animator.Tick(start_time + MicrosecondsToDelta(1000)); + TransformOperations value_before_reversing = target.operations(); + EXPECT_LT(from.at(0).translate.x, target.operations().at(0).translate.x); + EXPECT_GT(to.at(0).translate.x, target.operations().at(0).translate.x); + + animator.TransitionTransformOperationsTo( + &target, start_time + MicrosecondsToDelta(1000), kTransformPropertyId, + target.operations(), from); + animator.Tick(start_time + MicrosecondsToDelta(1000)); + EXPECT_TRUE(value_before_reversing.ApproximatelyEqual(target.operations(), + tolerance)); + + animator.Tick(start_time + MicrosecondsToDelta(2000)); + EXPECT_TRUE(from.ApproximatelyEqual(target.operations(), tolerance)); +} + +TEST(KeyframeAnimationTest, RetargetTransformTransition) { + float tolerance = 0.0f; + TestAnimationTarget target; + KeyframeEffect animator; + + TransformOperations from; + from.AppendScale(1, 1, 1); + from.AppendTranslate(0, 0, 0); + TransformOperations to; + to.AppendScale(11, 11, 11); + to.AppendTranslate(-10, -10, -10); + + std::unique_ptr curve( + gfx::KeyframedTransformAnimationCurve::Create()); + curve->AddKeyframe( + TransformKeyframe::Create(base::TimeDelta(), from, nullptr)); + curve->AddKeyframe( + TransformKeyframe::Create(MicrosecondsToDelta(10000), to, nullptr)); + curve->set_target(&target); + animator.AddKeyframeModel(KeyframeModel::Create( + std::move(curve), KeyframeEffect::GetNextKeyframeModelId(), + kTransformPropertyId)); + + base::TimeTicks start_time = MicrosecondsToTicks(1000000); + animator.Tick(start_time); + EXPECT_TRUE(from.ApproximatelyEqual(target.operations(), tolerance)); + animator.Tick(start_time + MicrosecondsToDelta(5000)); + + EXPECT_FLOAT_EQ(6.f, target.operations().at(0).scale.x); + EXPECT_FLOAT_EQ(-5.f, target.operations().at(1).translate.x); + + TransformOperations new_to; + new_to.AppendScale(110, 110, 110); + new_to.AppendTranslate(-101, -101, -101); + + animator.GetKeyframeModel(kTransformPropertyId) + ->Retarget(start_time + MicrosecondsToDelta(5000), kTransformPropertyId, + new_to); + animator.Tick(start_time + MicrosecondsToDelta(5000)); + EXPECT_FLOAT_EQ(6.f, target.operations().at(0).scale.x); + EXPECT_FLOAT_EQ(-5.f, target.operations().at(1).translate.x); + + animator.Tick(start_time + MicrosecondsToDelta(7500)); + EXPECT_FLOAT_EQ(58.f, target.operations().at(0).scale.x); + EXPECT_FLOAT_EQ(-53.f, target.operations().at(1).translate.x); +} + +TEST(KeyframeAnimationTest, BoundsTransitions) { + TestAnimationTarget target; + KeyframeEffect animator; + Transition transition; + transition.target_properties = {kBoundsPropertyId}; + transition.duration = MicrosecondsToDelta(10000); + animator.set_transition(transition); + base::TimeTicks start_time = MicrosecondsToTicks(1000000); + animator.Tick(start_time); + + SizeF from = target.size(); + SizeF to(20.0f, 20.0f); + + animator.TransitionSizeTo(&target, start_time, kBoundsPropertyId, from, to); + + EXPECT_FLOAT_SIZE_EQ(from, target.size()); + animator.Tick(start_time); + + // Scheduling a redundant, approximately equal transition should be ignored. + int keyframe_model_id = animator.keyframe_models().front()->id(); + SizeF nearby = to; + nearby.set_width(to.width() + kNoise); + animator.TransitionSizeTo(&target, start_time, kBoundsPropertyId, from, + nearby); + EXPECT_EQ(keyframe_model_id, animator.keyframe_models().front()->id()); + + animator.Tick(start_time + MicrosecondsToDelta(5000)); + EXPECT_LT(from.width(), target.size().width()); + EXPECT_GT(to.width(), target.size().width()); + EXPECT_LT(from.height(), target.size().height()); + EXPECT_GT(to.height(), target.size().height()); + + animator.Tick(start_time + MicrosecondsToDelta(10000)); + EXPECT_FLOAT_SIZE_EQ(to, target.size()); +} + +TEST(KeyframeAnimationTest, RetargetSizeTransition) { + TestAnimationTarget target; + KeyframeEffect animator; + + SizeF from(1, 2); + SizeF to(11, 22); + + std::unique_ptr curve( + gfx::KeyframedSizeAnimationCurve::Create()); + curve->AddKeyframe(SizeKeyframe::Create(base::TimeDelta(), from, nullptr)); + curve->AddKeyframe( + SizeKeyframe::Create(MicrosecondsToDelta(10000), to, nullptr)); + curve->set_target(&target); + animator.AddKeyframeModel(KeyframeModel::Create( + std::move(curve), KeyframeEffect::GetNextKeyframeModelId(), + kBoundsPropertyId)); + + base::TimeTicks start_time = MicrosecondsToTicks(1000000); + animator.Tick(start_time); + EXPECT_EQ(from, target.size()); + animator.Tick(start_time + MicrosecondsToDelta(5000)); + + EXPECT_FLOAT_SIZE_EQ(SizeF(6, 12), target.size()); + + SizeF new_to(600, 1200); + + animator.GetKeyframeModel(kBoundsPropertyId) + ->Retarget(start_time + MicrosecondsToDelta(5000), kRectPropertyId, + new_to); + animator.Tick(start_time + MicrosecondsToDelta(5000)); + EXPECT_FLOAT_SIZE_EQ(SizeF(6, 12), target.size()); + + animator.Tick(start_time + MicrosecondsToDelta(7500)); + EXPECT_FLOAT_SIZE_EQ(SizeF(303, 606), target.size()); +} + +TEST(KeyframeAnimationTest, ReversedBoundsTransitions) { + TestAnimationTarget target; + KeyframeEffect animator; + Transition transition; + transition.target_properties = {kBoundsPropertyId}; + transition.duration = MicrosecondsToDelta(10000); + animator.set_transition(transition); + base::TimeTicks start_time = MicrosecondsToTicks(1000000); + animator.Tick(start_time); + + SizeF from = target.size(); + SizeF to(20.0f, 20.0f); + + animator.TransitionSizeTo(&target, start_time, kBoundsPropertyId, from, to); + + EXPECT_FLOAT_SIZE_EQ(from, target.size()); + animator.Tick(start_time); + + animator.Tick(start_time + MicrosecondsToDelta(1000)); + SizeF value_before_reversing = target.size(); + EXPECT_LT(from.width(), target.size().width()); + EXPECT_GT(to.width(), target.size().width()); + EXPECT_LT(from.height(), target.size().height()); + EXPECT_GT(to.height(), target.size().height()); + + animator.TransitionSizeTo(&target, start_time + MicrosecondsToDelta(1000), + kBoundsPropertyId, target.size(), from); + animator.Tick(start_time + MicrosecondsToDelta(1000)); + EXPECT_FLOAT_SIZE_EQ(value_before_reversing, target.size()); + + animator.Tick(start_time + MicrosecondsToDelta(2000)); + EXPECT_FLOAT_SIZE_EQ(from, target.size()); +} + +TEST(KeyframeAnimationTest, BackgroundColorTransitions) { + TestAnimationTarget target; + KeyframeEffect animator; + Transition transition; + transition.target_properties = {kBackgroundColorPropertyId}; + transition.duration = MicrosecondsToDelta(10000); + animator.set_transition(transition); + base::TimeTicks start_time = MicrosecondsToTicks(1000000); + animator.Tick(start_time); + + SkColor from = SK_ColorRED; + SkColor to = SK_ColorGREEN; + + animator.TransitionColorTo(&target, start_time, kBackgroundColorPropertyId, + from, to); + + EXPECT_EQ(from, target.background_color()); + animator.Tick(start_time); + + animator.Tick(start_time + MicrosecondsToDelta(5000)); + EXPECT_GT(SkColorGetR(from), SkColorGetR(target.background_color())); + EXPECT_LT(SkColorGetR(to), SkColorGetR(target.background_color())); + EXPECT_LT(SkColorGetG(from), SkColorGetG(target.background_color())); + EXPECT_GT(SkColorGetG(to), SkColorGetG(target.background_color())); + EXPECT_EQ(0u, SkColorGetB(target.background_color())); + EXPECT_EQ(255u, SkColorGetA(target.background_color())); + + animator.Tick(start_time + MicrosecondsToDelta(10000)); + EXPECT_EQ(to, target.background_color()); +} + +TEST(KeyframeAnimationTest, ReversedBackgroundColorTransitions) { + TestAnimationTarget target; + KeyframeEffect animator; + Transition transition; + transition.target_properties = {kBackgroundColorPropertyId}; + transition.duration = MicrosecondsToDelta(10000); + animator.set_transition(transition); + base::TimeTicks start_time = MicrosecondsToTicks(1000000); + animator.Tick(start_time); + + SkColor from = SK_ColorRED; + SkColor to = SK_ColorGREEN; + + animator.TransitionColorTo(&target, start_time, kBackgroundColorPropertyId, + from, to); + + EXPECT_EQ(from, target.background_color()); + animator.Tick(start_time); + + animator.Tick(start_time + MicrosecondsToDelta(1000)); + SkColor value_before_reversing = target.background_color(); + EXPECT_GT(SkColorGetR(from), SkColorGetR(target.background_color())); + EXPECT_LT(SkColorGetR(to), SkColorGetR(target.background_color())); + EXPECT_LT(SkColorGetG(from), SkColorGetG(target.background_color())); + EXPECT_GT(SkColorGetG(to), SkColorGetG(target.background_color())); + EXPECT_EQ(0u, SkColorGetB(target.background_color())); + EXPECT_EQ(255u, SkColorGetA(target.background_color())); + + animator.TransitionColorTo(&target, start_time + MicrosecondsToDelta(1000), + kBackgroundColorPropertyId, + target.background_color(), from); + animator.Tick(start_time + MicrosecondsToDelta(1000)); + EXPECT_EQ(value_before_reversing, target.background_color()); + + animator.Tick(start_time + MicrosecondsToDelta(2000)); + EXPECT_EQ(from, target.background_color()); +} + +TEST(KeyframeAnimationTest, RetargetColorTransition) { + TestAnimationTarget target; + KeyframeEffect animator; + + SkColor from = SkColorSetRGB(0, 0, 0); + SkColor to = SkColorSetRGB(10, 10, 10); + + std::unique_ptr curve( + gfx::KeyframedColorAnimationCurve::Create()); + curve->AddKeyframe(ColorKeyframe::Create(base::TimeDelta(), from, nullptr)); + curve->AddKeyframe( + ColorKeyframe::Create(MicrosecondsToDelta(10000), to, nullptr)); + curve->set_target(&target); + animator.AddKeyframeModel(KeyframeModel::Create( + std::move(curve), KeyframeEffect::GetNextKeyframeModelId(), + kBackgroundColorPropertyId)); + + base::TimeTicks start_time = MicrosecondsToTicks(1000000); + animator.Tick(start_time); + EXPECT_EQ(from, target.background_color()); + animator.Tick(start_time + MicrosecondsToDelta(5000)); + + EXPECT_EQ(5u, SkColorGetR(target.background_color())); + + SkColor new_to = SkColorSetRGB(101, 101, 101); + + animator.GetKeyframeModel(kBackgroundColorPropertyId) + ->Retarget(start_time + MicrosecondsToDelta(5000), kRectPropertyId, + new_to); + animator.Tick(start_time + MicrosecondsToDelta(5000)); + EXPECT_EQ(5u, SkColorGetR(target.background_color())); + + animator.Tick(start_time + MicrosecondsToDelta(7500)); + EXPECT_EQ(53u, SkColorGetR(target.background_color())); +} + +TEST(KeyframeAnimationTest, DoubleReversedTransitions) { + TestAnimationTarget target; + KeyframeEffect animator; + Transition transition; + transition.target_properties = {kOpacityPropertyId}; + transition.duration = MicrosecondsToDelta(10000); + animator.set_transition(transition); + + base::TimeTicks start_time = MicrosecondsToTicks(1000000); + animator.Tick(start_time); + + float from = 1.0f; + float to = 0.5f; + animator.TransitionFloatTo(&target, start_time, kOpacityPropertyId, from, to); + + EXPECT_EQ(from, target.opacity()); + animator.Tick(start_time); + + animator.Tick(start_time + MicrosecondsToDelta(1000)); + float value_before_reversing = target.opacity(); + EXPECT_GT(from, value_before_reversing); + EXPECT_LT(to, value_before_reversing); + + animator.TransitionFloatTo(&target, start_time + MicrosecondsToDelta(1000), + kOpacityPropertyId, target.opacity(), from); + animator.Tick(start_time + MicrosecondsToDelta(1000)); + EXPECT_FLOAT_EQ(value_before_reversing, target.opacity()); + + animator.Tick(start_time + MicrosecondsToDelta(1500)); + value_before_reversing = target.opacity(); + // If the code for reversing transitions does not account for an existing time + // offset, then reversing a second time will give incorrect values. + animator.TransitionFloatTo(&target, start_time + MicrosecondsToDelta(1500), + kOpacityPropertyId, target.opacity(), to); + animator.Tick(start_time + MicrosecondsToDelta(1500)); + EXPECT_FLOAT_EQ(value_before_reversing, target.opacity()); +} + +TEST(KeyframeAnimationTest, RedundantTransition) { + TestAnimationTarget target; + KeyframeEffect animator; + Transition transition; + transition.target_properties = {kOpacityPropertyId}; + transition.duration = MicrosecondsToDelta(10000); + animator.set_transition(transition); + + base::TimeTicks start_time = MicrosecondsToTicks(1000000); + animator.Tick(start_time); + + float from = 1.0f; + float to = 0.5f; + animator.TransitionFloatTo(&target, start_time, kOpacityPropertyId, from, to); + + EXPECT_EQ(from, target.opacity()); + animator.Tick(start_time); + + animator.Tick(start_time + MicrosecondsToDelta(1000)); + float value_before_redundant_transition = target.opacity(); + + // While an existing transition is in progress to the same value, we should + // not start a new transition. + animator.TransitionFloatTo(&target, start_time, kOpacityPropertyId, + target.opacity(), to); + + EXPECT_EQ(1lu, animator.keyframe_models().size()); + EXPECT_EQ(value_before_redundant_transition, target.opacity()); +} + +TEST(KeyframeAnimationTest, TransitionToSameValue) { + TestAnimationTarget target; + KeyframeEffect animator; + Transition transition; + transition.target_properties = {kOpacityPropertyId}; + transition.duration = MicrosecondsToDelta(10000); + animator.set_transition(transition); + + base::TimeTicks start_time = MicrosecondsToTicks(1000000); + animator.Tick(start_time); + + // Transitioning to the same value should be a no-op. + float from = 1.0f; + float to = 1.0f; + animator.TransitionFloatTo(&target, start_time, kOpacityPropertyId, from, to); + EXPECT_EQ(from, target.opacity()); + EXPECT_TRUE(animator.keyframe_models().empty()); +} + +TEST(KeyframeAnimationTest, CorrectTargetValue) { + TestAnimationTarget target; + KeyframeEffect animator; + base::TimeDelta duration = MicrosecondsToDelta(10000); + + float from_opacity = 1.0f; + float to_opacity = 0.5f; + SizeF from_bounds = SizeF(10, 200); + SizeF to_bounds = SizeF(20, 200); + SkColor from_color = SK_ColorRED; + SkColor to_color = SK_ColorGREEN; + TransformOperations from_transform; + from_transform.AppendTranslate(10, 100, 1000); + TransformOperations to_transform; + to_transform.AppendTranslate(20, 200, 2000); + + // Verify the default value is returned if there's no running animations. + EXPECT_EQ(from_opacity, + animator.GetTargetFloatValue(kOpacityPropertyId, from_opacity)); + EXPECT_SIZEF_EQ(from_bounds, + animator.GetTargetSizeValue(kBoundsPropertyId, from_bounds)); + EXPECT_EQ(from_color, animator.GetTargetColorValue(kBackgroundColorPropertyId, + from_color)); + EXPECT_TRUE(from_transform.ApproximatelyEqual( + animator.GetTargetTransformOperationsValue(kTransformPropertyId, + from_transform), + kEpsilon)); + + // Add keyframe_models. + animator.AddKeyframeModel(CreateFloatAnimation( + &target, 2, kOpacityPropertyId, from_opacity, to_opacity, duration)); + animator.AddKeyframeModel(CreateSizeAnimation( + &target, 1, kBoundsPropertyId, from_bounds, to_bounds, duration)); + animator.AddKeyframeModel(CreateColorAnimation( + &target, 3, kBackgroundColorPropertyId, from_color, to_color, duration)); + animator.AddKeyframeModel( + CreateTransformAnimation(&target, 4, kTransformPropertyId, from_transform, + to_transform, duration)); + + base::TimeTicks start_time = MicrosecondsToTicks(1000000); + animator.Tick(start_time); + + // Verify target value. + EXPECT_EQ(to_opacity, + animator.GetTargetFloatValue(kOpacityPropertyId, from_opacity)); + EXPECT_SIZEF_EQ(to_bounds, + animator.GetTargetSizeValue(kBoundsPropertyId, from_bounds)); + EXPECT_EQ(to_color, animator.GetTargetColorValue(kBackgroundColorPropertyId, + from_color)); + EXPECT_TRUE(to_transform.ApproximatelyEqual( + animator.GetTargetTransformOperationsValue(kTransformPropertyId, + from_transform), + kEpsilon)); +} + +TEST(KeyframeAnimationTest, RetargetRectTransition) { + TestAnimationTarget target; + KeyframeEffect animator; + + Rect from(1, 2, 3, 4); + Rect to(11, 22, 33, 44); + + std::unique_ptr curve( + gfx::KeyframedRectAnimationCurve::Create()); + curve->AddKeyframe(RectKeyframe::Create(base::TimeDelta(), from, nullptr)); + curve->AddKeyframe( + RectKeyframe::Create(MicrosecondsToDelta(10000), to, nullptr)); + curve->set_target(&target); + animator.AddKeyframeModel(KeyframeModel::Create( + std::move(curve), KeyframeEffect::GetNextKeyframeModelId(), + kRectPropertyId)); + + base::TimeTicks start_time = MicrosecondsToTicks(1000000); + animator.Tick(start_time); + EXPECT_EQ(from, target.rect()); + animator.Tick(start_time + MicrosecondsToDelta(5000)); + + EXPECT_EQ(Rect(6, 12, 18, 24), target.rect()); + + Rect new_to(600, 1200, 1800, 2400); + + animator.GetKeyframeModel(kRectPropertyId) + ->Retarget(start_time + MicrosecondsToDelta(5000), kRectPropertyId, + new_to); + animator.Tick(start_time + MicrosecondsToDelta(5000)); + EXPECT_EQ(Rect(6, 12, 18, 24), target.rect()); + + animator.Tick(start_time + MicrosecondsToDelta(7500)); + EXPECT_EQ(Rect(303, 606, 909, 1212), target.rect()); +} + +} // namespace gfx diff --git a/animation/keyframe/keyframe_effect.cc b/animation/keyframe/keyframe_effect.cc new file mode 100644 index 000000000000..46304b16e212 --- /dev/null +++ b/animation/keyframe/keyframe_effect.cc @@ -0,0 +1,397 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/animation/keyframe/keyframe_effect.h" + +#include + +#include "base/containers/cxx20_erase.h" +#include "ui/gfx/animation/keyframe/animation_curve.h" +#include "ui/gfx/animation/keyframe/keyframed_animation_curve.h" + +namespace gfx { + +namespace { + +static int s_next_keyframe_model_id = 1; +static int s_next_group_id = 1; + +void ReverseKeyframeModel(base::TimeTicks monotonic_time, + KeyframeModel* keyframe_model) { + keyframe_model->set_direction(keyframe_model->direction() == + KeyframeModel::Direction::NORMAL + ? KeyframeModel::Direction::REVERSE + : KeyframeModel::Direction::NORMAL); + // Our goal here is to reverse the given keyframe_model. That is, if + // we're 20% of the way through the keyframe_model in the forward direction, + // we'd like to be 80% of the way of the reversed keyframe model (so it will + // end quickly). + // + // We can modify our "progress" through an animation by modifying the "time + // offset", a value added to the current time by the animation system before + // applying any other adjustments. + // + // Let our start time be s, our current time be t, and our final time (or + // duration) be d. After reversing the keyframe_model, we would like to start + // sampling from d - t as depicted below. + // + // Forward: + // s t d + // |----|-------------------------| + // + // Reversed: + // s t d + // |----|--------------------|----| + // -----time-offset-----> + // + // Now, if we let o represent our desired offset, we need to ensure that + // t = d - (o + t) + // + // That is, sampling at the current time in either the forward or reverse + // curves must result in the same value, otherwise we'll get jank. + // + // This implies that, + // 0 = d - o - 2t + // o = d - 2t + // + // Now if there was a previous offset, we must adjust d by that offset before + // performing this computation, so it becomes d - o_old - 2t: + keyframe_model->set_time_offset( + keyframe_model->curve()->Duration() - keyframe_model->time_offset() - + (2 * (monotonic_time - keyframe_model->start_time()))); +} + +std::unique_ptr CreateTransitionTimingFunction() { + return CubicBezierTimingFunction::CreatePreset( + CubicBezierTimingFunction::EaseType::EASE); +} + +base::TimeDelta GetStartTime(KeyframeModel* keyframe_model) { + if (keyframe_model->direction() == KeyframeModel::Direction::NORMAL) { + return base::TimeDelta(); + } + return keyframe_model->curve()->Duration(); +} + +base::TimeDelta GetEndTime(KeyframeModel* keyframe_model) { + if (keyframe_model->direction() == KeyframeModel::Direction::REVERSE) { + return base::TimeDelta(); + } + return keyframe_model->curve()->Duration(); +} + +template +void TransitionValueTo(KeyframeEffect* animator, + typename AnimationTraits::TargetType* target, + base::TimeTicks monotonic_time, + int target_property, + const ValueType& from, + const ValueType& to) { + DCHECK(target); + + if (animator->transition().target_properties.find(target_property) == + animator->transition().target_properties.end()) { + AnimationTraits::OnValueAnimated(target, to, target_property); + return; + } + + KeyframeModel* running_keyframe_model = + animator->GetRunningKeyframeModelForProperty(target_property); + + ValueType effective_current = from; + + if (running_keyframe_model) { + const auto* curve = AnimationTraits::ToDerivedCurve( + running_keyframe_model->curve()); + + if (running_keyframe_model->IsFinishedAt(monotonic_time)) { + effective_current = curve->GetValue(GetEndTime(running_keyframe_model)); + } else { + if (SufficientlyEqual( + to, curve->GetValue(GetEndTime(running_keyframe_model)))) { + return; + } + if (SufficientlyEqual( + to, curve->GetValue(GetStartTime(running_keyframe_model)))) { + ReverseKeyframeModel(monotonic_time, running_keyframe_model); + return; + } + } + } else if (SufficientlyEqual(to, from)) { + return; + } + + animator->RemoveKeyframeModels(target_property); + + std::unique_ptr::KeyframedCurveType> + curve(AnimationTraits::KeyframedCurveType::Create()); + + curve->AddKeyframe(AnimationTraits::KeyframeType::Create( + base::TimeDelta(), effective_current, CreateTransitionTimingFunction())); + + curve->AddKeyframe(AnimationTraits::KeyframeType::Create( + animator->transition().duration, to, CreateTransitionTimingFunction())); + + curve->set_target(target); + + animator->AddKeyframeModel(KeyframeModel::Create( + std::move(curve), KeyframeEffect::GetNextKeyframeModelId(), + target_property)); +} + +} // namespace + +int KeyframeEffect::GetNextKeyframeModelId() { + return s_next_keyframe_model_id++; +} + +int KeyframeEffect::GetNextGroupId() { + return s_next_group_id++; +} + +KeyframeEffect::KeyframeEffect() = default; +KeyframeEffect::KeyframeEffect(KeyframeEffect&&) = default; +KeyframeEffect::~KeyframeEffect() = default; + +void KeyframeEffect::AddKeyframeModel( + std::unique_ptr keyframe_model) { + keyframe_models_.push_back(std::move(keyframe_model)); +} + +void KeyframeEffect::RemoveKeyframeModel(int keyframe_model_id) { + // Since we want to use the KeyframeModels that we're going to remove, we + // need to use a stable_partition here instead of remove_if. remove_if leaves + // the removed items in an unspecified state. + auto keyframe_models_to_remove = std::stable_partition( + keyframe_models_.begin(), keyframe_models_.end(), + [keyframe_model_id]( + const std::unique_ptr& keyframe_model) { + return keyframe_model->id() != keyframe_model_id; + }); + + RemoveKeyframeModelRange(keyframe_models_to_remove, keyframe_models_.end()); +} + +void KeyframeEffect::RemoveKeyframeModels(int target_property) { + auto keyframe_models_to_remove = std::stable_partition( + keyframe_models_.begin(), keyframe_models_.end(), + [target_property]( + const std::unique_ptr& keyframe_model) { + return keyframe_model->TargetProperty() != target_property; + }); + RemoveKeyframeModelRange(keyframe_models_to_remove, keyframe_models_.end()); +} + +void KeyframeEffect::RemoveAllKeyframeModels() { + RemoveKeyframeModelRange(keyframe_models_.begin(), keyframe_models_.end()); +} + +void KeyframeEffect::Tick(base::TimeTicks monotonic_time) { + TickInternal(monotonic_time, true); +} + +void KeyframeEffect::RemoveKeyframeModelRange( + typename KeyframeModels::iterator to_remove_begin, + typename KeyframeModels::iterator to_remove_end) { + keyframe_models_.erase(to_remove_begin, to_remove_end); +} + +void KeyframeEffect::TickKeyframeModel(base::TimeTicks monotonic_time, + KeyframeModel* keyframe_model) { + if ((keyframe_model->run_state() != KeyframeModel::STARTING && + keyframe_model->run_state() != KeyframeModel::RUNNING && + keyframe_model->run_state() != KeyframeModel::PAUSED) || + !keyframe_model->HasActiveTime(monotonic_time)) { + return; + } + + AnimationCurve* curve = keyframe_model->curve(); + base::TimeDelta trimmed = + keyframe_model->TrimTimeToCurrentIteration(monotonic_time); + curve->Tick(trimmed, keyframe_model->TargetProperty(), keyframe_model); +} + +void KeyframeEffect::TickInternal(base::TimeTicks monotonic_time, + bool include_infinite_animations) { + StartKeyframeModels(monotonic_time, include_infinite_animations); + + for (auto& keyframe_model : keyframe_models_) { + if (!include_infinite_animations && + keyframe_model->iterations() == std::numeric_limits::infinity()) + continue; + TickKeyframeModel(monotonic_time, keyframe_model.get()); + } + + // Remove finished keyframe_models. + base::EraseIf( + keyframe_models_, + [monotonic_time](const std::unique_ptr& keyframe_model) { + return !keyframe_model->is_finished() && + keyframe_model->IsFinishedAt(monotonic_time); + }); + + StartKeyframeModels(monotonic_time, include_infinite_animations); +} + +void KeyframeEffect::FinishAll() { + base::TimeTicks now = base::TimeTicks::Now(); + const bool include_infinite_animations = false; + TickInternal(now, include_infinite_animations); + TickInternal(base::TimeTicks::Max(), include_infinite_animations); +#ifndef NDEBUG + for (auto& keyframe_model : keyframe_models_) { + DCHECK_EQ(std::numeric_limits::infinity(), + keyframe_model->iterations()); + } +#endif +} + +void KeyframeEffect::SetTransitionedProperties( + const std::set& properties) { + transition_.target_properties = properties; +} + +void KeyframeEffect::SetTransitionDuration(base::TimeDelta delta) { + transition_.duration = delta; +} + +void KeyframeEffect::TransitionFloatTo(FloatAnimationCurve::Target* target, + base::TimeTicks monotonic_time, + int target_property, + float from, + float to) { + TransitionValueTo(this, target, monotonic_time, target_property, from, + to); +} + +void KeyframeEffect::TransitionTransformOperationsTo( + TransformAnimationCurve::Target* target, + base::TimeTicks monotonic_time, + int target_property, + const gfx::TransformOperations& from, + const gfx::TransformOperations& to) { + TransitionValueTo(this, target, monotonic_time, + target_property, from, to); +} + +void KeyframeEffect::TransitionSizeTo(SizeAnimationCurve::Target* target, + base::TimeTicks monotonic_time, + int target_property, + const gfx::SizeF& from, + const gfx::SizeF& to) { + TransitionValueTo(this, target, monotonic_time, target_property, + from, to); +} + +void KeyframeEffect::TransitionColorTo(ColorAnimationCurve::Target* target, + base::TimeTicks monotonic_time, + int target_property, + SkColor from, + SkColor to) { + TransitionValueTo(this, target, monotonic_time, target_property, + from, to); +} + +bool KeyframeEffect::IsAnimatingProperty(int property) const { + for (auto& keyframe_model : keyframe_models_) { + if (keyframe_model->TargetProperty() == property) + return true; + } + return false; +} + +bool KeyframeEffect::IsAnimating() const { + return !keyframe_models_.empty(); +} + +float KeyframeEffect::GetTargetFloatValue(int target_property, + float default_value) const { + return GetTargetValue(target_property, default_value); +} + +gfx::TransformOperations KeyframeEffect::GetTargetTransformOperationsValue( + int target_property, + const gfx::TransformOperations& default_value) const { + return GetTargetValue(target_property, + default_value); +} + +gfx::SizeF KeyframeEffect::GetTargetSizeValue( + int target_property, + const gfx::SizeF& default_value) const { + return GetTargetValue(target_property, default_value); +} + +SkColor KeyframeEffect::GetTargetColorValue(int target_property, + SkColor default_value) const { + return GetTargetValue(target_property, default_value); +} + +void KeyframeEffect::StartKeyframeModels(base::TimeTicks monotonic_time, + bool include_infinite_animations) { + TargetProperties animated_properties; + for (auto& keyframe_model : keyframe_models_) { + if (!include_infinite_animations && + keyframe_model->iterations() == std::numeric_limits::infinity()) + continue; + if (keyframe_model->run_state() == KeyframeModel::RUNNING || + keyframe_model->run_state() == KeyframeModel::PAUSED) { + animated_properties[keyframe_model->TargetProperty()] = true; + } + } + for (auto& keyframe_model : keyframe_models_) { + if (!include_infinite_animations && + keyframe_model->iterations() == std::numeric_limits::infinity()) + continue; + if (!animated_properties[keyframe_model->TargetProperty()] && + keyframe_model->run_state() == + KeyframeModel::WAITING_FOR_TARGET_AVAILABILITY) { + animated_properties[keyframe_model->TargetProperty()] = true; + keyframe_model->SetRunState(KeyframeModel::RUNNING, monotonic_time); + keyframe_model->set_start_time(monotonic_time); + } + } +} + +KeyframeModel* KeyframeEffect::GetRunningKeyframeModelForProperty( + int target_property) const { + for (auto& keyframe_model : keyframe_models_) { + if ((keyframe_model->run_state() == KeyframeModel::RUNNING || + keyframe_model->run_state() == KeyframeModel::PAUSED) && + keyframe_model->TargetProperty() == target_property) { + return keyframe_model.get(); + } + } + return nullptr; +} + +KeyframeModel* KeyframeEffect::GetKeyframeModel(int target_property) const { + for (size_t i = 0; i < keyframe_models().size(); ++i) { + size_t index = keyframe_models().size() - i - 1; + if (keyframe_models_[index]->TargetProperty() == target_property) + return keyframe_models_[index].get(); + } + return nullptr; +} + +KeyframeModel* KeyframeEffect::GetKeyframeModelById(int id) const { + for (auto& keyframe_model : keyframe_models()) + if (keyframe_model->id() == id) + return keyframe_model.get(); + return nullptr; +} + +template +ValueType KeyframeEffect::GetTargetValue(int target_property, + const ValueType& default_value) const { + KeyframeModel* running_keyframe_model = GetKeyframeModel(target_property); + if (!running_keyframe_model) { + return default_value; + } + const auto* curve = AnimationTraits::ToDerivedCurve( + running_keyframe_model->curve()); + return curve->GetValue(GetEndTime(running_keyframe_model)); +} + +} // namespace gfx diff --git a/animation/keyframe/keyframe_effect.h b/animation/keyframe/keyframe_effect.h new file mode 100644 index 000000000000..818dce77b1e9 --- /dev/null +++ b/animation/keyframe/keyframe_effect.h @@ -0,0 +1,134 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_ANIMATION_KEYFRAME_KEYFRAME_EFFECT_H_ +#define UI_GFX_ANIMATION_KEYFRAME_KEYFRAME_EFFECT_H_ + +#include +#include +#include + +#include "base/macros.h" +#include "third_party/skia/include/core/SkColor.h" +#include "ui/gfx/animation/keyframe/animation_curve.h" +#include "ui/gfx/animation/keyframe/keyframe_animation_export.h" +#include "ui/gfx/animation/keyframe/keyframe_model.h" +#include "ui/gfx/animation/keyframe/transition.h" + +namespace gfx { +class SizeF; +class TransformOperations; + +static constexpr size_t kMaxTargetPropertyId = 32u; +using TargetProperties = std::bitset; + +// This is a simplified version of cc::KeyframeEffect. Its sole purpose is the +// management of its collection of KeyframeModels. Ticking them, updating their +// state, and deleting them as required. +// +// For background on the name of this class, please refer to the WebAnimations +// spec: https://www.w3.org/TR/web-animations-1/#the-keyframeeffect-interface +// +// TODO(crbug.com/747185): Make cc::KeyframeEffect a subclass of KeyframeEffect +// and share common code. +class GFX_KEYFRAME_ANIMATION_EXPORT KeyframeEffect { + public: + static int GetNextKeyframeModelId(); + static int GetNextGroupId(); + + KeyframeEffect(); + ~KeyframeEffect(); + + KeyframeEffect(KeyframeEffect&&); + KeyframeEffect(const KeyframeEffect&) = delete; + + KeyframeEffect& operator=(KeyframeEffect&&) = default; + KeyframeEffect& operator=(const KeyframeEffect&) = delete; + + virtual void AddKeyframeModel(std::unique_ptr keyframe_model); + void RemoveAllKeyframeModels(); + void RemoveKeyframeModel(int keyframe_model_id); + void RemoveKeyframeModels(int target_property); + + virtual void Tick(base::TimeTicks monotonic_time); + + // This ticks all keyframe models until they are complete. + void FinishAll(); + + using KeyframeModels = std::vector>; + const KeyframeModels& keyframe_models() const { return keyframe_models_; } + KeyframeModels& keyframe_models() { return keyframe_models_; } + + // The transition is analogous to CSS transitions. When configured, the + // transition object will cause subsequent calls the corresponding + // TransitionXXXTo functions to induce transition animations. + const Transition& transition() const { return transition_; } + void set_transition(const Transition& transition) { + transition_ = transition; + } + + void SetTransitionedProperties(const std::set& properties); + void SetTransitionDuration(base::TimeDelta delta); + + void TransitionFloatTo(FloatAnimationCurve::Target* target, + base::TimeTicks monotonic_time, + int target_property, + float from, + float to); + void TransitionTransformOperationsTo(TransformAnimationCurve::Target* target, + base::TimeTicks monotonic_time, + int target_property, + const gfx::TransformOperations& from, + const gfx::TransformOperations& to); + void TransitionSizeTo(SizeAnimationCurve::Target* target, + base::TimeTicks monotonic_time, + int target_property, + const gfx::SizeF& from, + const gfx::SizeF& to); + void TransitionColorTo(ColorAnimationCurve::Target* target, + base::TimeTicks monotonic_time, + int target_property, + SkColor from, + SkColor to); + + bool IsAnimatingProperty(int property) const; + bool IsAnimating() const; + + float GetTargetFloatValue(int target_property, float default_value) const; + gfx::TransformOperations GetTargetTransformOperationsValue( + int target_property, + const gfx::TransformOperations& default_value) const; + gfx::SizeF GetTargetSizeValue(int target_property, + const gfx::SizeF& default_value) const; + SkColor GetTargetColorValue(int target_property, SkColor default_value) const; + KeyframeModel* GetRunningKeyframeModelForProperty(int target_property) const; + KeyframeModel* GetKeyframeModel(int target_property) const; + KeyframeModel* GetKeyframeModelById(int id) const; + + protected: + // Removes all keyframe models in the range provided. This is virtual so that + // subclasses that need to do extra bookkeeping upon removals may do so. As a + // consequence, please ensure that all removals happen via this method. + virtual void RemoveKeyframeModelRange( + typename KeyframeModels::iterator to_remove_begin, + typename KeyframeModels::iterator to_remove_end); + void TickKeyframeModel(base::TimeTicks monotonic_time, + KeyframeModel* keyframe_model); + + private: + void TickInternal(base::TimeTicks monotonic_time, + bool include_infinite_animations); + void StartKeyframeModels(base::TimeTicks monotonic_time, + bool include_infinite_animations); + template + ValueType GetTargetValue(int target_property, + const ValueType& default_value) const; + + KeyframeModels keyframe_models_; + Transition transition_; +}; + +} // namespace gfx + +#endif // UI_GFX_ANIMATION_KEYFRAME_KEYFRAME_EFFECT_H_ diff --git a/animation/keyframe/keyframe_model.cc b/animation/keyframe/keyframe_model.cc new file mode 100644 index 000000000000..eaa8a86ae102 --- /dev/null +++ b/animation/keyframe/keyframe_model.cc @@ -0,0 +1,257 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/animation/keyframe/keyframe_model.h" + +#include "base/cxx17_backports.h" +#include "base/memory/ptr_util.h" +#include "base/notreached.h" + +namespace gfx { +namespace { + +// This should match the RunState enum. +static const char* const s_runStateNames[] = {"WAITING_FOR_TARGET_AVAILABILITY", + "WAITING_FOR_DELETION", + "STARTING", + "RUNNING", + "PAUSED", + "FINISHED", + "ABORTED", + "ABORTED_BUT_NEEDS_COMPLETION"}; + +static_assert(static_cast(KeyframeModel::LAST_RUN_STATE) + 1 == + base::size(s_runStateNames), + "RunStateEnumSize should equal the number of elements in " + "s_runStateNames"); + +} // namespace + +std::string KeyframeModel::ToString(RunState state) { + return s_runStateNames[state]; +} + +std::unique_ptr KeyframeModel::Create( + std::unique_ptr curve, + int keyframe_model_id, + int target_property_id) { + return base::WrapUnique(new KeyframeModel(std::move(curve), keyframe_model_id, + target_property_id)); +} + +KeyframeModel::KeyframeModel(std::unique_ptr curve, + int keyframe_model_id, + int target_property_id) + : curve_(std::move(curve)), + id_(keyframe_model_id), + target_property_(target_property_id), + run_state_(WAITING_FOR_TARGET_AVAILABILITY), + iterations_(1), + iteration_start_(0), + direction_(Direction::NORMAL), + playback_rate_(1), + fill_mode_(FillMode::BOTH) {} + +KeyframeModel::~KeyframeModel() { + if (run_state() == RUNNING || run_state() == PAUSED) + SetRunState(ABORTED, base::TimeTicks()); +} + +int KeyframeModel::TargetProperty() const { + return target_property_; +} + +void KeyframeModel::SetRunState(RunState run_state, + base::TimeTicks monotonic_time) { + if (run_state == RUNNING && run_state_ == PAUSED) + total_paused_duration_ += (monotonic_time - pause_time_); + else if (run_state == PAUSED) + pause_time_ = monotonic_time; + run_state_ = run_state; +} + +void KeyframeModel::Pause(base::TimeDelta pause_offset) { + // Convert pause offset which is in local time to monotonic time. + // TODO(crbug.com/912407): This should be scaled by playbackrate. + base::TimeTicks monotonic_time = + pause_offset + start_time_ + total_paused_duration_; + SetRunState(PAUSED, monotonic_time); +} + +KeyframeModel::Phase KeyframeModel::CalculatePhaseForTesting( + base::TimeDelta local_time) const { + return CalculatePhase(local_time); +} + +KeyframeModel::Phase KeyframeModel::CalculatePhase( + base::TimeDelta local_time) const { + base::TimeDelta opposite_time_offset = time_offset_ == base::TimeDelta::Min() + ? base::TimeDelta::Max() + : -time_offset_; + base::TimeDelta before_active_boundary_time = + std::max(opposite_time_offset, base::TimeDelta()); + if (local_time < before_active_boundary_time || + (local_time == before_active_boundary_time && playback_rate_ < 0)) { + return KeyframeModel::Phase::BEFORE; + } + // TODO(crbug.com/909794): By spec end time = max(start delay + duration + + // end delay, 0). The logic should be updated once "end delay" is supported. + base::TimeDelta active_after_boundary_time = base::TimeDelta::Max(); + if (std::isfinite(iterations_)) { + // Scaling the duration is against spec but needed to comply with the cc + // implementation. By spec (in blink) the playback rate is an Animation + // level concept but in cc it's per KeyframeModel. We grab the active time + // calculated here and later scale it with the playback rate in order to get + // a proper progress. Therefore we need to un-scale it here. This can be + // fixed once we scale the local time by playback rate. See + // https://crbug.com/912407. + base::TimeDelta active_duration = + curve_->Duration() * iterations_ / std::abs(playback_rate_); + active_after_boundary_time = + std::max(opposite_time_offset + active_duration, base::TimeDelta()); + } + if (local_time > active_after_boundary_time || + (local_time == active_after_boundary_time && playback_rate_ > 0)) { + return KeyframeModel::Phase::AFTER; + } + return KeyframeModel::Phase::ACTIVE; +} + +absl::optional KeyframeModel::CalculateActiveTime( + base::TimeTicks monotonic_time) const { + base::TimeDelta local_time = ConvertMonotonicTimeToLocalTime(monotonic_time); + KeyframeModel::Phase phase = CalculatePhase(local_time); + DCHECK(playback_rate_); + switch (phase) { + case KeyframeModel::Phase::BEFORE: + if (fill_mode_ == FillMode::BACKWARDS || fill_mode_ == FillMode::BOTH) + return std::max(local_time + time_offset_, base::TimeDelta()); + return absl::nullopt; + case KeyframeModel::Phase::ACTIVE: + return local_time + time_offset_; + case KeyframeModel::Phase::AFTER: + if (fill_mode_ == FillMode::FORWARDS || fill_mode_ == FillMode::BOTH) { + DCHECK_NE(iterations_, std::numeric_limits::infinity()); + base::TimeDelta active_duration = + curve_->Duration() * iterations_ / std::abs(playback_rate_); + return std::max(std::min(local_time + time_offset_, active_duration), + base::TimeDelta()); + } + return absl::nullopt; + default: + NOTREACHED(); + return absl::nullopt; + } +} + +bool KeyframeModel::IsFinishedAt(base::TimeTicks monotonic_time) const { + if (is_finished()) + return true; + + if (StartShouldBeDeferred()) + return false; + + if (playback_rate_ == 0) + return false; + + return run_state_ == RUNNING && std::isfinite(iterations_) && + (curve_->Duration() * (iterations_ / std::abs(playback_rate_))) <= + (ConvertMonotonicTimeToLocalTime(monotonic_time) + time_offset_); +} + +bool KeyframeModel::HasActiveTime(base::TimeTicks monotonic_time) const { + return CalculateActiveTime(monotonic_time).has_value(); +} + +bool KeyframeModel::StartShouldBeDeferred() const { + return false; +} + +base::TimeDelta KeyframeModel::TrimTimeToCurrentIteration( + base::TimeTicks monotonic_time) const { + DCHECK(playback_rate_); + DCHECK_GE(iteration_start_, 0); + + DCHECK(HasActiveTime(monotonic_time)); + base::TimeDelta active_time = CalculateActiveTime(monotonic_time).value(); + base::TimeDelta start_offset = curve_->Duration() * iteration_start_; + + // Return start offset if we are before the start of the keyframe model + if (active_time < base::TimeDelta()) + return start_offset; + // Always return zero if we have no iterations. + if (!iterations_) + return base::TimeDelta(); + + // Don't attempt to trim if we have no duration. + if (curve_->Duration() <= base::TimeDelta()) + return base::TimeDelta(); + + base::TimeDelta repeated_duration = std::isfinite(iterations_) + ? (curve_->Duration() * iterations_) + : base::TimeDelta::Max(); + + // Calculate the scaled active time + base::TimeDelta scaled_active_time; + if (playback_rate_ < 0) { + DCHECK(std::isfinite(iterations_)); + base::TimeDelta active_duration = + repeated_duration / std::abs(playback_rate_); + scaled_active_time = + ((active_time - active_duration) * playback_rate_) + start_offset; + } else { + scaled_active_time = (active_time * playback_rate_) + start_offset; + } + + // Calculate the iteration time + base::TimeDelta iteration_time; + bool has_defined_time_delta = + (start_offset != scaled_active_time) || + !(start_offset.is_max() || start_offset.is_min()); + if (has_defined_time_delta && + scaled_active_time - start_offset == repeated_duration && + fmod(iterations_ + iteration_start_, 1) == 0) + iteration_time = curve_->Duration(); + else + iteration_time = scaled_active_time % curve_->Duration(); + + // Calculate the current iteration + int iteration; + if (scaled_active_time <= base::TimeDelta()) + iteration = 0; + else if (iteration_time == curve_->Duration()) + iteration = ceil(iteration_start_ + iterations_ - 1); + else + iteration = base::ClampFloor(scaled_active_time / curve_->Duration()); + + // Check if we are running the keyframe model in reverse direction for the + // current iteration + bool reverse = + (direction_ == Direction::REVERSE) || + (direction_ == Direction::ALTERNATE_NORMAL && iteration % 2 == 1) || + (direction_ == Direction::ALTERNATE_REVERSE && iteration % 2 == 0); + + // If we are running the keyframe model in reverse direction, reverse the + // result + if (reverse) + iteration_time = curve_->Duration() - iteration_time; + + return iteration_time; +} + +// TODO(crbug.com/912407): Local time should be scaled by playback rate by spec. +base::TimeDelta KeyframeModel::ConvertMonotonicTimeToLocalTime( + base::TimeTicks monotonic_time) const { + // When waiting on receiving a start time, then our global clock is 'stuck' at + // the initial state. + if ((run_state_ == STARTING && !has_set_start_time()) || + StartShouldBeDeferred()) + return base::TimeDelta(); + + // If we're paused, time is 'stuck' at the pause time. + base::TimeTicks time = (run_state_ == PAUSED) ? pause_time_ : monotonic_time; + return time - start_time_ - total_paused_duration_; +} + +} // namespace gfx diff --git a/animation/keyframe/keyframe_model.h b/animation/keyframe/keyframe_model.h new file mode 100644 index 000000000000..3cb053b81368 --- /dev/null +++ b/animation/keyframe/keyframe_model.h @@ -0,0 +1,223 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_ANIMATION_KEYFRAME_KEYFRAME_MODEL_H_ +#define UI_GFX_ANIMATION_KEYFRAME_KEYFRAME_MODEL_H_ + +#include + +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/gfx/animation/keyframe/animation_curve.h" +#include "ui/gfx/animation/keyframe/keyframe_animation_export.h" +#include "ui/gfx/animation/keyframe/keyframed_animation_curve.h" + +namespace gfx { + +class GFX_KEYFRAME_ANIMATION_EXPORT KeyframeModel { + public: + // KeyframeModels begin in the 'WAITING_FOR_TARGET_AVAILABILITY' state. A + // KeyframeModel waiting for target availability will run as soon as its + // target property is free (and all the KeyframeModels animating with it are + // also able to run). When this time arrives, the controller will move the + // keyframe model into the STARTING state, and then into the RUNNING state. + // RUNNING KeyframeModels may toggle between RUNNING and PAUSED, and may be + // stopped by moving into either the ABORTED or FINISHED states. A FINISHED + // keyframe model was allowed to run to completion, but an ABORTED keyframe + // model was not. An animation in the state ABORTED_BUT_NEEDS_COMPLETION is a + // keyframe model that was aborted for some reason, but needs to be finished. + // Currently this is for impl-only scroll offset KeyframeModels that need to + // be completed on the main thread. + enum RunState { + WAITING_FOR_TARGET_AVAILABILITY = 0, + WAITING_FOR_DELETION, + STARTING, + RUNNING, + PAUSED, + FINISHED, + ABORTED, + ABORTED_BUT_NEEDS_COMPLETION, + // This sentinel must be last. + LAST_RUN_STATE = ABORTED_BUT_NEEDS_COMPLETION + }; + static std::string ToString(RunState); + + enum class Direction { NORMAL, REVERSE, ALTERNATE_NORMAL, ALTERNATE_REVERSE }; + + enum class FillMode { NONE, FORWARDS, BACKWARDS, BOTH, AUTO }; + + enum class Phase { BEFORE, ACTIVE, AFTER }; + + static std::unique_ptr Create( + std::unique_ptr curve, + int keyframe_model_id, + int target_property_id); + + KeyframeModel(const KeyframeModel&) = delete; + virtual ~KeyframeModel(); + + KeyframeModel& operator=(const KeyframeModel&) = delete; + + int id() const { return id_; } + + virtual int TargetProperty() const; + + RunState run_state() const { return run_state_; } + virtual void SetRunState(RunState run_state, base::TimeTicks monotonic_time); + + // Pause the keyframe effect at local time |pause_offset|. + void Pause(base::TimeDelta pause_offset); + + base::TimeTicks start_time() const { return start_time_; } + + void set_start_time(base::TimeTicks monotonic_time) { + start_time_ = monotonic_time; + } + bool has_set_start_time() const { return !start_time_.is_null(); } + + base::TimeDelta time_offset() const { return time_offset_; } + void set_time_offset(base::TimeDelta monotonic_time) { + time_offset_ = monotonic_time; + } + + Direction direction() const { return direction_; } + void set_direction(Direction direction) { direction_ = direction; } + + FillMode fill_mode() const { return fill_mode_; } + void set_fill_mode(FillMode fill_mode) { fill_mode_ = fill_mode; } + + double playback_rate() const { return playback_rate_; } + void set_playback_rate(double playback_rate) { + playback_rate_ = playback_rate; + } + + base::TimeTicks pause_time() const { return pause_time_; } + void set_pause_time(base::TimeTicks pause_time) { pause_time_ = pause_time; } + + base::TimeDelta total_paused_duration() const { + return total_paused_duration_; + } + void set_total_paused_duration(base::TimeDelta total_paused_duration) { + total_paused_duration_ = total_paused_duration; + } + + // This is the number of times that the keyframe model will play. If this + // value is zero the keyframe model will not play. If it is negative, then + // the keyframe model will loop indefinitely. + double iterations() const { return iterations_; } + void set_iterations(double n) { iterations_ = n; } + + double iteration_start() const { return iteration_start_; } + void set_iteration_start(double iteration_start) { + iteration_start_ = iteration_start; + } + + AnimationCurve* curve() { return curve_.get(); } + const AnimationCurve* curve() const { return curve_.get(); } + + bool IsFinishedAt(base::TimeTicks monotonic_time) const; + bool is_finished() const { + return run_state_ == FINISHED || run_state_ == ABORTED || + run_state_ == WAITING_FOR_DELETION; + } + + bool HasActiveTime(base::TimeTicks monotonic_time) const; + + template + void Retarget(base::TimeTicks now, + int property_id, + const T& new_target_value) { + if (!curve_) + return; + base::TimeDelta now_delta = TrimTimeToCurrentIteration(now); + + DCHECK_EQ(CalculatePhase(now_delta), KeyframeModel::Phase::ACTIVE); + auto* keyframed_curve = AnimationTraits::ToKeyframedCurve(curve_.get()); + DCHECK(keyframed_curve); + if (auto new_curve = keyframed_curve->Retarget(now_delta, new_target_value)) + curve_ = std::move(new_curve); + } + + // Some clients may run threaded animations and may need to defer starting + // until the animation on the other thread has been started. + virtual bool StartShouldBeDeferred() const; + + // Takes the given absolute time, and using the start time and the number + // of iterations, returns the relative time in the current iteration. + base::TimeDelta TrimTimeToCurrentIteration( + base::TimeTicks monotonic_time) const; + + KeyframeModel::Phase CalculatePhaseForTesting( + base::TimeDelta local_time) const; + + protected: + KeyframeModel(std::unique_ptr curve, + int keyframe_model_id, + int target_property_id); + + void ForceRunState(RunState run_state) { run_state_ = run_state; } + absl::optional CalculateActiveTime( + base::TimeTicks monotonic_time) const; + + private: + KeyframeModel::Phase CalculatePhase(base::TimeDelta local_time) const; + + // Return local time for this keyframe model given the absolute monotonic + // time. + // + // Local time represents the time value that is used to tick this keyframe + // model and is relative to its start time. It is closely related to the local + // time concept in web animations [1]. It is: + // - for playing animation : wall time - start time - paused duration + // - for paused animation : paused time + // - otherwise : zero + // + // Here is small diagram that shows how active, local, and monotonic times + // relate to each other and to the run state. + // + // run state Starting (R)unning Paused (R) Paused (R) Finished + // ^ ^ + // | | + // monotonic time -------------------------------------------------> + // | | + // local time +-----------------+ +---+ +---------> + // | | + // active time + +------+ +---+ +------+ + // (-offset) + // + // [1] https://drafts.csswg.org/web-animations/#local-time-section + base::TimeDelta ConvertMonotonicTimeToLocalTime( + base::TimeTicks monotonic_time) const; + + std::unique_ptr curve_; + + // IDs must be unique. + int id_; + + int target_property_ = 0; + RunState run_state_; + double iterations_; + double iteration_start_; + Direction direction_; + double playback_rate_; + FillMode fill_mode_; + + base::TimeTicks start_time_; + + // The time offset effectively pushes the start of the keyframe model back in + // time. This is used for resuming paused KeyframeModels -- an animation is + // added with a non-zero time offset, causing the keyframe model to skip ahead + // to the desired point in time. + base::TimeDelta time_offset_; + + // These are used when converting monotonic to local time to account for time + // spent while paused. This is not included in AnimationState since it + // there is absolutely no need for clients of this controller to know + // about these values. + base::TimeTicks pause_time_; + base::TimeDelta total_paused_duration_; +}; + +} // namespace gfx + +#endif // UI_GFX_ANIMATION_KEYFRAME_KEYFRAME_MODEL_H_ diff --git a/animation/keyframe/keyframed_animation_curve-inl.h b/animation/keyframe/keyframed_animation_curve-inl.h new file mode 100644 index 000000000000..ac226f6c7f54 --- /dev/null +++ b/animation/keyframe/keyframed_animation_curve-inl.h @@ -0,0 +1,148 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_ANIMATION_KEYFRAME_KEYFRAMED_ANIMATION_CURVE_INL_H_ +#define UI_GFX_ANIMATION_KEYFRAME_KEYFRAMED_ANIMATION_CURVE_INL_H_ + +namespace { + +template +void InsertKeyframe(std::unique_ptr keyframe, + std::vector>* keyframes) { + // Usually, the keyframes will be added in order, so this loop would be + // unnecessary and we should skip it if possible. + if (!keyframes->empty() && keyframe->Time() < keyframes->back()->Time()) { + for (size_t i = 0; i < keyframes->size(); ++i) { + if (keyframe->Time() < keyframes->at(i)->Time()) { + keyframes->insert(keyframes->begin() + i, std::move(keyframe)); + return; + } + } + } + + keyframes->push_back(std::move(keyframe)); +} + +struct TimeValues { + base::TimeDelta start_time; + base::TimeDelta duration; + double progress; +}; + +template +TimeValues GetTimeValues(const KeyframeType& start_frame, + const KeyframeType& end_frame, + double scaled_duration, + base::TimeDelta time) { + TimeValues values; + values.start_time = start_frame.Time() * scaled_duration; + values.duration = (end_frame.Time() * scaled_duration) - values.start_time; + const base::TimeDelta elapsed = time - values.start_time; + values.progress = (elapsed.is_inf() || values.duration.is_zero()) + ? 1.0 + : (elapsed / values.duration); + return values; +} + +template +base::TimeDelta TransformedAnimationTime( + const std::vector>& keyframes, + const std::unique_ptr& timing_function, + double scaled_duration, + base::TimeDelta time) { + if (timing_function) { + const auto values = GetTimeValues(*keyframes.front(), *keyframes.back(), + scaled_duration, time); + time = (values.duration * timing_function->GetValue(values.progress)) + + values.start_time; + } + + return time; +} + +template +size_t GetActiveKeyframe( + const std::vector>& keyframes, + double scaled_duration, + base::TimeDelta time) { + DCHECK_GE(keyframes.size(), 2ul); + size_t i = 0; + while ((i < keyframes.size() - 2) && // Last keyframe is never active. + (time >= (keyframes[i + 1]->Time() * scaled_duration))) + ++i; + + return i; +} + +template +double TransformedKeyframeProgress( + const std::vector>& keyframes, + double scaled_duration, + base::TimeDelta time, + size_t i) { + const double progress = + GetTimeValues(*keyframes[i], *keyframes[i + 1], scaled_duration, time) + .progress; + return keyframes[i]->timing_function() + ? keyframes[i]->timing_function()->GetValue(progress) + : progress; +} + +int GetTimingFunctionSteps(const gfx::TimingFunction* timing_function) { + DCHECK(timing_function && + timing_function->GetType() == gfx::TimingFunction::Type::STEPS); + const gfx::StepsTimingFunction* steps_timing_function = + reinterpret_cast(timing_function); + DCHECK(steps_timing_function); + return steps_timing_function->steps(); +} + +template +base::TimeDelta ComputeTickInterval( + const std::unique_ptr& timing_function, + double scaled_duration, + const std::vector>& keyframes) { + // TODO(crbug.com/1140603): include animation progress in order to pinpoint + // which keyframe's timing function is in effect at any point in time. + DCHECK_LT(0u, keyframes.size()); + gfx::TimingFunction::Type timing_function_type = + timing_function ? timing_function->GetType() + : gfx::TimingFunction::Type::LINEAR; + // Even if the keyframe's have step timing functions, a non-linear + // animation-wide timing function results in unevenly timed steps. + switch (timing_function_type) { + case gfx::TimingFunction::Type::LINEAR: { + base::TimeDelta min_interval = base::TimeDelta::Max(); + // If any keyframe uses non-step "easing", return 0, except for the last + // keyframe, whose "easing" is never used. + for (size_t ii = 0; ii < keyframes.size() - 1; ++ii) { + KeyframeType* keyframe = keyframes[ii].get(); + if (!keyframe->timing_function() || + keyframe->timing_function()->GetType() != + gfx::TimingFunction::Type::STEPS) { + return base::TimeDelta(); + } + KeyframeType* next_keyframe = keyframes[ii + 1].get(); + int steps = GetTimingFunctionSteps(keyframe->timing_function()); + DCHECK_LT(0, steps); + base::TimeDelta interval = (next_keyframe->Time() - keyframe->Time()) * + scaled_duration / steps; + if (interval < min_interval) + min_interval = interval; + } + return min_interval; + } + case gfx::TimingFunction::Type::STEPS: { + return (keyframes.back()->Time() - keyframes.front()->Time()) * + scaled_duration / GetTimingFunctionSteps(timing_function.get()); + } + case gfx::TimingFunction::Type::CUBIC_BEZIER: + break; + } + return base::TimeDelta(); +} + +} // namespace + +#endif // UI_GFX_ANIMATION_KEYFRAME_KEYFRAMED_ANIMATION_CURVE_INL_H_ diff --git a/animation/keyframe/keyframed_animation_curve.cc b/animation/keyframe/keyframed_animation_curve.cc new file mode 100644 index 000000000000..3aba6cf916d2 --- /dev/null +++ b/animation/keyframe/keyframed_animation_curve.cc @@ -0,0 +1,586 @@ +// Copyright 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/animation/keyframe/keyframed_animation_curve.h" + +#include + +#include +#include +#include + +#include "base/memory/ptr_util.h" +#include "base/numerics/ranges.h" +#include "base/time/time.h" +#include "ui/gfx/animation/keyframe/keyframed_animation_curve-inl.h" +#include "ui/gfx/animation/tween.h" +#include "ui/gfx/geometry/box_f.h" + +namespace gfx { +namespace { + +static constexpr float kTolerance = 1e-5f; + +template +std::unique_ptr RetargettedCurve( + std::vector>& keyframes, + base::TimeDelta t, + const ValueType& value_at_t, + const ValueType& new_target_value, + double scaled_duration, + TargetType* target, + const TimingFunction* timing_function) { + if (SufficientlyEqual(keyframes.back()->Value(), new_target_value)) + return nullptr; + + DCHECK_GE(keyframes.size(), 2u); + DCHECK_GT(scaled_duration, 0.f); + + // If we haven't progressed to animating between the last 2 keyframes, simply + // clobber the value for the last keyframe. + const bool at_last_keyframe = + (keyframes[keyframes.size() - 2]->Time() * scaled_duration) <= t; + if (!at_last_keyframe) { + auto& last_keyframe = keyframes.back(); + auto* keyframe_timing_function = last_keyframe->timing_function(); + last_keyframe = KeyframeType::Create( + last_keyframe->Time(), new_target_value, + keyframe_timing_function ? keyframe_timing_function->Clone() : nullptr); + return nullptr; + } + + // Ensure that `t` happens between the last two keyframes. + DCHECK_GE(keyframes[keyframes.size() - 1]->Time() * scaled_duration, t); + + // TODO(crbug.com/1198305): This can be changed to a different / special + // interpolation curve type to maintain c2 continuity. + auto curve = AnimationTraits::KeyframedCurveType::Create(); + curve->set_scaled_duration(scaled_duration); + curve->set_target(target); + + auto generate_timing_function = + [timing_function]() -> std::unique_ptr { + if (timing_function) + return timing_function->Clone(); + return nullptr; + }; + + // Keep the curve duration the same by adding the same first frame. + curve->AddKeyframe(KeyframeType::Create(keyframes.front()->Time(), + keyframes.front()->Value(), + generate_timing_function())); + + // Snap the current value at `t` so that the current value stays the same. + curve->AddKeyframe(KeyframeType::Create(t / scaled_duration, value_at_t, + generate_timing_function())); + + // Add a new target at the same time as the last frame. + curve->AddKeyframe(KeyframeType::Create( + keyframes.back()->Time(), new_target_value, generate_timing_function())); + + return curve; +} + +} // namespace + +Keyframe::Keyframe(base::TimeDelta time, + std::unique_ptr timing_function) + : time_(time), timing_function_(std::move(timing_function)) {} + +Keyframe::~Keyframe() = default; + +base::TimeDelta Keyframe::Time() const { + return time_; +} + +std::unique_ptr ColorKeyframe::Create( + base::TimeDelta time, + SkColor value, + std::unique_ptr timing_function) { + return base::WrapUnique( + new ColorKeyframe(time, value, std::move(timing_function))); +} + +ColorKeyframe::ColorKeyframe(base::TimeDelta time, + SkColor value, + std::unique_ptr timing_function) + : Keyframe(time, std::move(timing_function)), value_(value) {} + +ColorKeyframe::~ColorKeyframe() = default; + +SkColor ColorKeyframe::Value() const { + return value_; +} + +std::unique_ptr ColorKeyframe::Clone() const { + std::unique_ptr func; + if (timing_function()) + func = timing_function()->Clone(); + return ColorKeyframe::Create(Time(), Value(), std::move(func)); +} + +std::unique_ptr FloatKeyframe::Create( + base::TimeDelta time, + float value, + std::unique_ptr timing_function) { + return base::WrapUnique( + new FloatKeyframe(time, value, std::move(timing_function))); +} + +FloatKeyframe::FloatKeyframe(base::TimeDelta time, + float value, + std::unique_ptr timing_function) + : Keyframe(time, std::move(timing_function)), value_(value) {} + +FloatKeyframe::~FloatKeyframe() = default; + +float FloatKeyframe::Value() const { + return value_; +} + +std::unique_ptr FloatKeyframe::Clone() const { + std::unique_ptr func; + if (timing_function()) + func = timing_function()->Clone(); + return FloatKeyframe::Create(Time(), Value(), std::move(func)); +} + +std::unique_ptr TransformKeyframe::Create( + base::TimeDelta time, + const gfx::TransformOperations& value, + std::unique_ptr timing_function) { + return base::WrapUnique( + new TransformKeyframe(time, value, std::move(timing_function))); +} + +TransformKeyframe::TransformKeyframe( + base::TimeDelta time, + const gfx::TransformOperations& value, + std::unique_ptr timing_function) + : Keyframe(time, std::move(timing_function)), value_(value) {} + +TransformKeyframe::~TransformKeyframe() = default; + +const gfx::TransformOperations& TransformKeyframe::Value() const { + return value_; +} + +std::unique_ptr TransformKeyframe::Clone() const { + std::unique_ptr func; + if (timing_function()) + func = timing_function()->Clone(); + return TransformKeyframe::Create(Time(), Value(), std::move(func)); +} + +std::unique_ptr SizeKeyframe::Create( + base::TimeDelta time, + const gfx::SizeF& value, + std::unique_ptr timing_function) { + return base::WrapUnique( + new SizeKeyframe(time, value, std::move(timing_function))); +} + +SizeKeyframe::SizeKeyframe(base::TimeDelta time, + const gfx::SizeF& value, + std::unique_ptr timing_function) + : Keyframe(time, std::move(timing_function)), value_(value) {} + +SizeKeyframe::~SizeKeyframe() = default; + +const gfx::SizeF& SizeKeyframe::Value() const { + return value_; +} + +std::unique_ptr SizeKeyframe::Clone() const { + std::unique_ptr func; + if (timing_function()) + func = timing_function()->Clone(); + return SizeKeyframe::Create(Time(), Value(), std::move(func)); +} + +std::unique_ptr RectKeyframe::Create( + base::TimeDelta time, + const gfx::Rect& value, + std::unique_ptr timing_function) { + return base::WrapUnique( + new RectKeyframe(time, value, std::move(timing_function))); +} + +RectKeyframe::RectKeyframe(base::TimeDelta time, + const gfx::Rect& value, + std::unique_ptr timing_function) + : Keyframe(time, std::move(timing_function)), value_(value) {} + +RectKeyframe::~RectKeyframe() = default; + +const gfx::Rect& RectKeyframe::Value() const { + return value_; +} + +std::unique_ptr RectKeyframe::Clone() const { + std::unique_ptr func; + if (timing_function()) + func = timing_function()->Clone(); + return RectKeyframe::Create(Time(), Value(), std::move(func)); +} + +std::unique_ptr +KeyframedColorAnimationCurve::Create() { + return base::WrapUnique(new KeyframedColorAnimationCurve); +} + +KeyframedColorAnimationCurve::KeyframedColorAnimationCurve() + : scaled_duration_(1.0) {} + +KeyframedColorAnimationCurve::~KeyframedColorAnimationCurve() = default; + +void KeyframedColorAnimationCurve::AddKeyframe( + std::unique_ptr keyframe) { + InsertKeyframe(std::move(keyframe), &keyframes_); +} + +base::TimeDelta KeyframedColorAnimationCurve::Duration() const { + return (keyframes_.back()->Time() - keyframes_.front()->Time()) * + scaled_duration(); +} + +base::TimeDelta KeyframedColorAnimationCurve::TickInterval() const { + return ComputeTickInterval(timing_function_, scaled_duration(), keyframes_); +} + +std::unique_ptr KeyframedColorAnimationCurve::Clone() const { + std::unique_ptr to_return = + KeyframedColorAnimationCurve::Create(); + for (const auto& keyframe : keyframes_) + to_return->AddKeyframe(keyframe->Clone()); + + if (timing_function_) + to_return->SetTimingFunction(timing_function_->Clone()); + + to_return->set_scaled_duration(scaled_duration()); + + return std::move(to_return); +} + +SkColor KeyframedColorAnimationCurve::GetValue(base::TimeDelta t) const { + if (t <= (keyframes_.front()->Time() * scaled_duration())) + return keyframes_.front()->Value(); + + if (t >= (keyframes_.back()->Time() * scaled_duration())) + return keyframes_.back()->Value(); + + t = TransformedAnimationTime(keyframes_, timing_function_, scaled_duration(), + t); + size_t i = GetActiveKeyframe(keyframes_, scaled_duration(), t); + double progress = + TransformedKeyframeProgress(keyframes_, scaled_duration(), t, i); + + return gfx::Tween::ColorValueBetween(progress, keyframes_[i]->Value(), + keyframes_[i + 1]->Value()); +} + +std::unique_ptr KeyframedColorAnimationCurve::Retarget( + base::TimeDelta t, + SkColor new_target) { + DCHECK(!keyframes_.empty()); + return RetargettedCurve(keyframes_, t, GetValue(t), new_target, + scaled_duration(), target(), timing_function_.get()); +} + +std::unique_ptr +KeyframedFloatAnimationCurve::Create() { + return base::WrapUnique(new KeyframedFloatAnimationCurve); +} + +KeyframedFloatAnimationCurve::KeyframedFloatAnimationCurve() + : scaled_duration_(1.0) {} + +KeyframedFloatAnimationCurve::~KeyframedFloatAnimationCurve() = default; + +void KeyframedFloatAnimationCurve::AddKeyframe( + std::unique_ptr keyframe) { + InsertKeyframe(std::move(keyframe), &keyframes_); +} + +base::TimeDelta KeyframedFloatAnimationCurve::Duration() const { + return (keyframes_.back()->Time() - keyframes_.front()->Time()) * + scaled_duration(); +} + +base::TimeDelta KeyframedFloatAnimationCurve::TickInterval() const { + return ComputeTickInterval(timing_function_, scaled_duration(), keyframes_); +} + +std::unique_ptr KeyframedFloatAnimationCurve::Clone() const { + std::unique_ptr to_return = + KeyframedFloatAnimationCurve::Create(); + for (const auto& keyframe : keyframes_) + to_return->AddKeyframe(keyframe->Clone()); + + if (timing_function_) + to_return->SetTimingFunction(timing_function_->Clone()); + + to_return->set_scaled_duration(scaled_duration()); + + return std::move(to_return); +} + +std::unique_ptr KeyframedFloatAnimationCurve::Retarget( + base::TimeDelta t, + float new_target) { + DCHECK(!keyframes_.empty()); + return RetargettedCurve(keyframes_, t, GetValue(t), new_target, + scaled_duration(), target(), timing_function_.get()); +} + +float KeyframedFloatAnimationCurve::GetValue(base::TimeDelta t) const { + if (t <= (keyframes_.front()->Time() * scaled_duration())) + return keyframes_.front()->Value(); + + if (t >= (keyframes_.back()->Time() * scaled_duration())) + return keyframes_.back()->Value(); + + t = TransformedAnimationTime(keyframes_, timing_function_, scaled_duration(), + t); + size_t i = GetActiveKeyframe(keyframes_, scaled_duration(), t); + double progress = + TransformedKeyframeProgress(keyframes_, scaled_duration(), t, i); + + return keyframes_[i]->Value() + + (keyframes_[i + 1]->Value() - keyframes_[i]->Value()) * progress; +} + +std::unique_ptr +KeyframedTransformAnimationCurve::Create() { + return base::WrapUnique(new KeyframedTransformAnimationCurve); +} + +KeyframedTransformAnimationCurve::KeyframedTransformAnimationCurve() + : scaled_duration_(1.0) {} + +KeyframedTransformAnimationCurve::~KeyframedTransformAnimationCurve() = default; + +void KeyframedTransformAnimationCurve::AddKeyframe( + std::unique_ptr keyframe) { + InsertKeyframe(std::move(keyframe), &keyframes_); +} + +base::TimeDelta KeyframedTransformAnimationCurve::Duration() const { + return (keyframes_.back()->Time() - keyframes_.front()->Time()) * + scaled_duration(); +} + +base::TimeDelta KeyframedTransformAnimationCurve::TickInterval() const { + return ComputeTickInterval(timing_function_, scaled_duration(), keyframes_); +} + +std::unique_ptr KeyframedTransformAnimationCurve::Clone() + const { + std::unique_ptr to_return = + KeyframedTransformAnimationCurve::Create(); + for (const auto& keyframe : keyframes_) + to_return->AddKeyframe(keyframe->Clone()); + + if (timing_function_) + to_return->SetTimingFunction(timing_function_->Clone()); + + to_return->set_scaled_duration(scaled_duration()); + + return std::move(to_return); +} + +gfx::TransformOperations KeyframedTransformAnimationCurve::GetValue( + base::TimeDelta t) const { + if (t <= (keyframes_.front()->Time() * scaled_duration())) + return keyframes_.front()->Value(); + + if (t >= (keyframes_.back()->Time() * scaled_duration())) + return keyframes_.back()->Value(); + + t = TransformedAnimationTime(keyframes_, timing_function_, scaled_duration(), + t); + size_t i = GetActiveKeyframe(keyframes_, scaled_duration(), t); + double progress = + TransformedKeyframeProgress(keyframes_, scaled_duration(), t, i); + + return keyframes_[i + 1]->Value().Blend(keyframes_[i]->Value(), progress); +} + +bool KeyframedTransformAnimationCurve::PreservesAxisAlignment() const { + for (const auto& keyframe : keyframes_) { + if (!keyframe->Value().PreservesAxisAlignment()) + return false; + } + return true; +} + +bool KeyframedTransformAnimationCurve::MaximumScale(float* max_scale) const { + DCHECK_GE(keyframes_.size(), 2ul); + *max_scale = 0.f; + for (auto& keyframe : keyframes_) { + float keyframe_scale = 0.f; + if (!keyframe->Value().ScaleComponent(&keyframe_scale)) + continue; + *max_scale = std::max(*max_scale, keyframe_scale); + } + return *max_scale > 0.f; +} + +std::unique_ptr KeyframedTransformAnimationCurve::Retarget( + base::TimeDelta t, + const gfx::TransformOperations& new_target) { + DCHECK(!keyframes_.empty()); + return RetargettedCurve(keyframes_, t, GetValue(t), new_target, + scaled_duration(), target(), timing_function_.get()); +} + +std::unique_ptr +KeyframedSizeAnimationCurve::Create() { + return base::WrapUnique(new KeyframedSizeAnimationCurve); +} + +KeyframedSizeAnimationCurve::KeyframedSizeAnimationCurve() + : scaled_duration_(1.0) {} + +KeyframedSizeAnimationCurve::~KeyframedSizeAnimationCurve() = default; + +void KeyframedSizeAnimationCurve::AddKeyframe( + std::unique_ptr keyframe) { + InsertKeyframe(std::move(keyframe), &keyframes_); +} + +base::TimeDelta KeyframedSizeAnimationCurve::Duration() const { + return (keyframes_.back()->Time() - keyframes_.front()->Time()) * + scaled_duration(); +} + +base::TimeDelta KeyframedSizeAnimationCurve::TickInterval() const { + return ComputeTickInterval(timing_function_, scaled_duration(), keyframes_); +} + +std::unique_ptr KeyframedSizeAnimationCurve::Clone() const { + std::unique_ptr to_return = + KeyframedSizeAnimationCurve::Create(); + for (const auto& keyframe : keyframes_) + to_return->AddKeyframe(keyframe->Clone()); + + if (timing_function_) + to_return->SetTimingFunction(timing_function_->Clone()); + + to_return->set_scaled_duration(scaled_duration()); + + return std::move(to_return); +} + +gfx::SizeF KeyframedSizeAnimationCurve::GetValue(base::TimeDelta t) const { + if (t <= (keyframes_.front()->Time() * scaled_duration())) + return keyframes_.front()->Value(); + + if (t >= (keyframes_.back()->Time() * scaled_duration())) + return keyframes_.back()->Value(); + + t = TransformedAnimationTime(keyframes_, timing_function_, scaled_duration(), + t); + size_t i = GetActiveKeyframe(keyframes_, scaled_duration(), t); + double progress = + TransformedKeyframeProgress(keyframes_, scaled_duration(), t, i); + + return gfx::Tween::SizeFValueBetween(progress, keyframes_[i]->Value(), + keyframes_[i + 1]->Value()); +} + +std::unique_ptr KeyframedSizeAnimationCurve::Retarget( + base::TimeDelta t, + const gfx::SizeF& new_target) { + DCHECK(!keyframes_.empty()); + return RetargettedCurve(keyframes_, t, GetValue(t), new_target, + scaled_duration(), target(), timing_function_.get()); +} + +std::unique_ptr +KeyframedRectAnimationCurve::Create() { + return base::WrapUnique(new KeyframedRectAnimationCurve); +} + +KeyframedRectAnimationCurve::KeyframedRectAnimationCurve() + : scaled_duration_(1.0) {} + +KeyframedRectAnimationCurve::~KeyframedRectAnimationCurve() = default; + +void KeyframedRectAnimationCurve::AddKeyframe( + std::unique_ptr keyframe) { + InsertKeyframe(std::move(keyframe), &keyframes_); +} + +base::TimeDelta KeyframedRectAnimationCurve::Duration() const { + return (keyframes_.back()->Time() - keyframes_.front()->Time()) * + scaled_duration(); +} + +base::TimeDelta KeyframedRectAnimationCurve::TickInterval() const { + return ComputeTickInterval(timing_function_, scaled_duration(), keyframes_); +} + +std::unique_ptr KeyframedRectAnimationCurve::Clone() const { + std::unique_ptr to_return = + KeyframedRectAnimationCurve::Create(); + for (const auto& keyframe : keyframes_) + to_return->AddKeyframe(keyframe->Clone()); + + if (timing_function_) + to_return->SetTimingFunction(timing_function_->Clone()); + + to_return->set_scaled_duration(scaled_duration()); + + return std::move(to_return); +} + +gfx::Rect KeyframedRectAnimationCurve::GetValue(base::TimeDelta t) const { + if (t <= (keyframes_.front()->Time() * scaled_duration())) + return keyframes_.front()->Value(); + + if (t >= (keyframes_.back()->Time() * scaled_duration())) + return keyframes_.back()->Value(); + + t = TransformedAnimationTime(keyframes_, timing_function_, scaled_duration(), + t); + size_t i = GetActiveKeyframe(keyframes_, scaled_duration(), t); + double progress = + TransformedKeyframeProgress(keyframes_, scaled_duration(), t, i); + + return gfx::Tween::RectValueBetween(progress, keyframes_[i]->Value(), + keyframes_[i + 1]->Value()); +} + +std::unique_ptr KeyframedRectAnimationCurve::Retarget( + base::TimeDelta t, + const gfx::Rect& new_target) { + DCHECK(!keyframes_.empty()); + return RetargettedCurve(keyframes_, t, GetValue(t), new_target, + scaled_duration(), target(), timing_function_.get()); +} + +bool SufficientlyEqual(float lhs, float rhs) { + return base::IsApproximatelyEqual(lhs, rhs, kTolerance); +} + +bool SufficientlyEqual(const TransformOperations& lhs, + const TransformOperations& rhs) { + return lhs.ApproximatelyEqual(rhs, kTolerance); +} + +bool SufficientlyEqual(const SizeF& lhs, const SizeF& rhs) { + return base::IsApproximatelyEqual(lhs.width(), rhs.width(), kTolerance) && + base::IsApproximatelyEqual(lhs.height(), rhs.height(), kTolerance); +} + +bool SufficientlyEqual(SkColor lhs, SkColor rhs) { + return lhs == rhs; +} + +bool SufficientlyEqual(const Rect& lhs, const Rect& rhs) { + return lhs == rhs; +} + +} // namespace gfx diff --git a/animation/keyframe/keyframed_animation_curve.h b/animation/keyframe/keyframed_animation_curve.h new file mode 100644 index 000000000000..9a292b59793b --- /dev/null +++ b/animation/keyframe/keyframed_animation_curve.h @@ -0,0 +1,413 @@ +// Copyright 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_ANIMATION_KEYFRAME_KEYFRAMED_ANIMATION_CURVE_H_ +#define UI_GFX_ANIMATION_KEYFRAME_KEYFRAMED_ANIMATION_CURVE_H_ + +#include +#include +#include + +#include "base/time/time.h" +#include "ui/gfx/animation/keyframe/animation_curve.h" +#include "ui/gfx/animation/keyframe/keyframe_animation_export.h" +#include "ui/gfx/animation/keyframe/timing_function.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/size_f.h" +#include "ui/gfx/geometry/transform_operations.h" + +namespace gfx { + +class GFX_KEYFRAME_ANIMATION_EXPORT Keyframe { + public: + Keyframe(const Keyframe&) = delete; + Keyframe& operator=(const Keyframe&) = delete; + + base::TimeDelta Time() const; + const TimingFunction* timing_function() const { + return timing_function_.get(); + } + + protected: + Keyframe(base::TimeDelta time, + std::unique_ptr timing_function); + virtual ~Keyframe(); + + private: + base::TimeDelta time_; + std::unique_ptr timing_function_; +}; + +class GFX_KEYFRAME_ANIMATION_EXPORT ColorKeyframe : public Keyframe { + public: + static std::unique_ptr Create( + base::TimeDelta time, + SkColor value, + std::unique_ptr timing_function); + ~ColorKeyframe() override; + + SkColor Value() const; + + std::unique_ptr Clone() const; + + private: + ColorKeyframe(base::TimeDelta time, + SkColor value, + std::unique_ptr timing_function); + + SkColor value_; +}; + +class GFX_KEYFRAME_ANIMATION_EXPORT FloatKeyframe : public Keyframe { + public: + static std::unique_ptr Create( + base::TimeDelta time, + float value, + std::unique_ptr timing_function); + ~FloatKeyframe() override; + + float Value() const; + + std::unique_ptr Clone() const; + + private: + FloatKeyframe(base::TimeDelta time, + float value, + std::unique_ptr timing_function); + + float value_; +}; + +class GFX_KEYFRAME_ANIMATION_EXPORT TransformKeyframe : public Keyframe { + public: + static std::unique_ptr Create( + base::TimeDelta time, + const gfx::TransformOperations& value, + std::unique_ptr timing_function); + ~TransformKeyframe() override; + + const gfx::TransformOperations& Value() const; + + std::unique_ptr Clone() const; + + private: + TransformKeyframe(base::TimeDelta time, + const gfx::TransformOperations& value, + std::unique_ptr timing_function); + + gfx::TransformOperations value_; +}; + +class GFX_KEYFRAME_ANIMATION_EXPORT SizeKeyframe : public Keyframe { + public: + static std::unique_ptr Create( + base::TimeDelta time, + const gfx::SizeF& bounds, + std::unique_ptr timing_function); + ~SizeKeyframe() override; + + const gfx::SizeF& Value() const; + + std::unique_ptr Clone() const; + + private: + SizeKeyframe(base::TimeDelta time, + const gfx::SizeF& value, + std::unique_ptr timing_function); + + gfx::SizeF value_; +}; + +class GFX_KEYFRAME_ANIMATION_EXPORT RectKeyframe : public Keyframe { + public: + static std::unique_ptr Create( + base::TimeDelta time, + const gfx::Rect& value, + std::unique_ptr timing_function); + ~RectKeyframe() override; + + const gfx::Rect& Value() const; + + std::unique_ptr Clone() const; + + private: + RectKeyframe(base::TimeDelta time, + const gfx::Rect& value, + std::unique_ptr timing_function); + + gfx::Rect value_; +}; + +class GFX_KEYFRAME_ANIMATION_EXPORT KeyframedColorAnimationCurve + : public ColorAnimationCurve { + public: + // It is required that the keyframes be sorted by time. + static std::unique_ptr Create(); + + KeyframedColorAnimationCurve(const KeyframedColorAnimationCurve&) = delete; + ~KeyframedColorAnimationCurve() override; + + KeyframedColorAnimationCurve& operator=(const KeyframedColorAnimationCurve&) = + delete; + + void AddKeyframe(std::unique_ptr keyframe); + void SetTimingFunction(std::unique_ptr timing_function) { + timing_function_ = std::move(timing_function); + } + double scaled_duration() const { return scaled_duration_; } + void set_scaled_duration(double scaled_duration) { + scaled_duration_ = scaled_duration; + } + + // AnimationCurve implementation + base::TimeDelta Duration() const override; + std::unique_ptr Clone() const override; + base::TimeDelta TickInterval() const override; + + // BackgrounColorAnimationCurve implementation + SkColor GetValue(base::TimeDelta t) const override; + + std::unique_ptr Retarget(base::TimeDelta t, + SkColor new_target); + + using Keyframes = std::vector>; + const Keyframes& keyframes_for_testing() const { return keyframes_; } + + private: + KeyframedColorAnimationCurve(); + + // Always sorted in order of increasing time. No two keyframes have the + // same time. + Keyframes keyframes_; + std::unique_ptr timing_function_; + double scaled_duration_; +}; + +class GFX_KEYFRAME_ANIMATION_EXPORT KeyframedFloatAnimationCurve + : public FloatAnimationCurve { + public: + // It is required that the keyframes be sorted by time. + static std::unique_ptr Create(); + + KeyframedFloatAnimationCurve(const KeyframedFloatAnimationCurve&) = delete; + ~KeyframedFloatAnimationCurve() override; + + KeyframedFloatAnimationCurve& operator=(const KeyframedFloatAnimationCurve&) = + delete; + + void AddKeyframe(std::unique_ptr keyframe); + + void SetTimingFunction(std::unique_ptr timing_function) { + timing_function_ = std::move(timing_function); + } + TimingFunction* timing_function_for_testing() const { + return timing_function_.get(); + } + double scaled_duration() const { return scaled_duration_; } + void set_scaled_duration(double scaled_duration) { + scaled_duration_ = scaled_duration; + } + + // AnimationCurve implementation + base::TimeDelta Duration() const override; + std::unique_ptr Clone() const override; + base::TimeDelta TickInterval() const override; + + // FloatAnimationCurve implementation + float GetValue(base::TimeDelta t) const override; + + std::unique_ptr Retarget(base::TimeDelta t, float new_target); + + using Keyframes = std::vector>; + const Keyframes& keyframes_for_testing() const { return keyframes_; } + + private: + KeyframedFloatAnimationCurve(); + + // Always sorted in order of increasing time. No two keyframes have the + // same time. + Keyframes keyframes_; + std::unique_ptr timing_function_; + double scaled_duration_; +}; + +class GFX_KEYFRAME_ANIMATION_EXPORT KeyframedTransformAnimationCurve + : public TransformAnimationCurve { + public: + // It is required that the keyframes be sorted by time. + static std::unique_ptr Create(); + + KeyframedTransformAnimationCurve(const KeyframedTransformAnimationCurve&) = + delete; + ~KeyframedTransformAnimationCurve() override; + + KeyframedTransformAnimationCurve& operator=( + const KeyframedTransformAnimationCurve&) = delete; + + void AddKeyframe(std::unique_ptr keyframe); + void SetTimingFunction(std::unique_ptr timing_function) { + timing_function_ = std::move(timing_function); + } + double scaled_duration() const { return scaled_duration_; } + void set_scaled_duration(double scaled_duration) { + scaled_duration_ = scaled_duration; + } + + // AnimationCurve implementation + base::TimeDelta Duration() const override; + std::unique_ptr Clone() const override; + base::TimeDelta TickInterval() const override; + + // TransformAnimationCurve implementation + gfx::TransformOperations GetValue(base::TimeDelta t) const override; + bool PreservesAxisAlignment() const override; + bool MaximumScale(float* max_scale) const override; + + std::unique_ptr Retarget( + base::TimeDelta t, + const gfx::TransformOperations& new_target); + + private: + KeyframedTransformAnimationCurve(); + + // Always sorted in order of increasing time. No two keyframes have the + // same time. + std::vector> keyframes_; + std::unique_ptr timing_function_; + double scaled_duration_; +}; + +class GFX_KEYFRAME_ANIMATION_EXPORT KeyframedSizeAnimationCurve + : public SizeAnimationCurve { + public: + // It is required that the keyframes be sorted by time. + static std::unique_ptr Create(); + + KeyframedSizeAnimationCurve(const KeyframedSizeAnimationCurve&) = delete; + ~KeyframedSizeAnimationCurve() override; + + KeyframedSizeAnimationCurve& operator=(const KeyframedSizeAnimationCurve&) = + delete; + + void AddKeyframe(std::unique_ptr keyframe); + void SetTimingFunction(std::unique_ptr timing_function) { + timing_function_ = std::move(timing_function); + } + double scaled_duration() const { return scaled_duration_; } + void set_scaled_duration(double scaled_duration) { + scaled_duration_ = scaled_duration; + } + + // AnimationCurve implementation + base::TimeDelta Duration() const override; + std::unique_ptr Clone() const override; + base::TimeDelta TickInterval() const override; + + // SizeAnimationCurve implementation + gfx::SizeF GetValue(base::TimeDelta t) const override; + + std::unique_ptr Retarget(base::TimeDelta t, + const gfx::SizeF& new_target); + + private: + KeyframedSizeAnimationCurve(); + + // Always sorted in order of increasing time. No two keyframes have the + // same time. + std::vector> keyframes_; + std::unique_ptr timing_function_; + double scaled_duration_; +}; + +class GFX_KEYFRAME_ANIMATION_EXPORT KeyframedRectAnimationCurve + : public RectAnimationCurve { + public: + // It is required that the keyframes be sorted by time. + static std::unique_ptr Create(); + + KeyframedRectAnimationCurve(const KeyframedRectAnimationCurve&) = delete; + ~KeyframedRectAnimationCurve() override; + + KeyframedRectAnimationCurve& operator=(const KeyframedRectAnimationCurve&) = + delete; + + void AddKeyframe(std::unique_ptr keyframe); + void SetTimingFunction(std::unique_ptr timing_function) { + timing_function_ = std::move(timing_function); + } + double scaled_duration() const { return scaled_duration_; } + void set_scaled_duration(double scaled_duration) { + scaled_duration_ = scaled_duration; + } + + // AnimationCurve implementation + base::TimeDelta Duration() const override; + std::unique_ptr Clone() const override; + base::TimeDelta TickInterval() const override; + + // RectAnimationCurve implementation + gfx::Rect GetValue(base::TimeDelta t) const override; + + std::unique_ptr Retarget(base::TimeDelta t, + const gfx::Rect& new_target); + + private: + KeyframedRectAnimationCurve(); + + // Always sorted in order of increasing time. No two keyframes have the + // same time. + std::vector> keyframes_; + std::unique_ptr timing_function_; + double scaled_duration_ = 0.; +}; + +template +struct AnimationTraits {}; + +#define DEFINE_ANIMATION_TRAITS(value_type, name) \ + template <> \ + struct AnimationTraits { \ + typedef value_type ValueType; \ + typedef name##AnimationCurve::Target TargetType; \ + typedef name##AnimationCurve CurveType; \ + typedef Keyframed##name##AnimationCurve KeyframedCurveType; \ + typedef name##Keyframe KeyframeType; \ + static const CurveType* ToDerivedCurve(const AnimationCurve* curve) { \ + return name##AnimationCurve::To##name##AnimationCurve(curve); \ + } \ + static CurveType* ToDerivedCurve(AnimationCurve* curve) { \ + return name##AnimationCurve::To##name##AnimationCurve(curve); \ + } \ + static const KeyframedCurveType* ToKeyframedCurve( \ + const AnimationCurve* curve) { \ + return static_cast(ToDerivedCurve(curve)); \ + } \ + static KeyframedCurveType* ToKeyframedCurve(AnimationCurve* curve) { \ + return static_cast(ToDerivedCurve(curve)); \ + } \ + static void OnValueAnimated(name##AnimationCurve::Target* target, \ + const ValueType& target_value, \ + int target_property) { \ + target->On##name##Animated(target_value, target_property, nullptr); \ + } \ + } + +DEFINE_ANIMATION_TRAITS(float, Float); +DEFINE_ANIMATION_TRAITS(TransformOperations, Transform); +DEFINE_ANIMATION_TRAITS(SizeF, Size); +DEFINE_ANIMATION_TRAITS(SkColor, Color); +DEFINE_ANIMATION_TRAITS(Rect, Rect); + +#undef DEFINE_ANIMATION_TRAITS + +bool SufficientlyEqual(float lhs, float rhs); +bool SufficientlyEqual(const TransformOperations& lhs, + const TransformOperations& rhs); +bool SufficientlyEqual(const SizeF& lhs, const SizeF& rhs); +bool SufficientlyEqual(SkColor lhs, SkColor rhs); +bool SufficientlyEqual(const Rect& lhs, const Rect& rhs); + +} // namespace gfx + +#endif // UI_GFX_ANIMATION_KEYFRAME_KEYFRAMED_ANIMATION_CURVE_H_ diff --git a/animation/keyframe/keyframed_animation_curve_unittest.cc b/animation/keyframe/keyframed_animation_curve_unittest.cc new file mode 100644 index 000000000000..215316335037 --- /dev/null +++ b/animation/keyframe/keyframed_animation_curve_unittest.cc @@ -0,0 +1,991 @@ +// Copyright 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/animation/keyframe/keyframed_animation_curve.h" + +#include + +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/animation/tween.h" +#include "ui/gfx/geometry/box_f.h" +#include "ui/gfx/geometry/test/transform_test_util.h" +#include "ui/gfx/geometry/transform_operations.h" +#include "ui/gfx/test/gfx_util.h" + +namespace gfx { +namespace { + +void ExpectTranslateX(SkScalar translate_x, + const gfx::TransformOperations& operations) { + EXPECT_FLOAT_EQ(translate_x, operations.Apply().matrix().get(0, 3)); +} + +// Tests that a color animation with one keyframe works as expected. +TEST(KeyframedAnimationCurveTest, OneColorKeyFrame) { + SkColor color = SkColorSetARGB(255, 255, 255, 255); + std::unique_ptr curve( + KeyframedColorAnimationCurve::Create()); + curve->AddKeyframe(ColorKeyframe::Create(base::TimeDelta(), color, nullptr)); + + EXPECT_SKCOLOR_EQ(color, curve->GetValue(base::Seconds(-1.f))); + EXPECT_SKCOLOR_EQ(color, curve->GetValue(base::Seconds(0.f))); + EXPECT_SKCOLOR_EQ(color, curve->GetValue(base::Seconds(0.5f))); + EXPECT_SKCOLOR_EQ(color, curve->GetValue(base::Seconds(1.f))); + EXPECT_SKCOLOR_EQ(color, curve->GetValue(base::Seconds(2.f))); +} + +// Tests that a color animation with two keyframes works as expected. +TEST(KeyframedAnimationCurveTest, TwoColorKeyFrame) { + SkColor color_a = SkColorSetARGB(255, 255, 0, 0); + SkColor color_b = SkColorSetARGB(255, 0, 255, 0); + SkColor color_midpoint = gfx::Tween::ColorValueBetween(0.5, color_a, color_b); + std::unique_ptr curve( + KeyframedColorAnimationCurve::Create()); + curve->AddKeyframe( + ColorKeyframe::Create(base::TimeDelta(), color_a, nullptr)); + curve->AddKeyframe( + ColorKeyframe::Create(base::Seconds(1.0), color_b, nullptr)); + + EXPECT_SKCOLOR_EQ(color_a, curve->GetValue(base::Seconds(-1.f))); + EXPECT_SKCOLOR_EQ(color_a, curve->GetValue(base::Seconds(0.f))); + EXPECT_SKCOLOR_EQ(color_midpoint, curve->GetValue(base::Seconds(0.5f))); + EXPECT_SKCOLOR_EQ(color_b, curve->GetValue(base::Seconds(1.f))); + EXPECT_SKCOLOR_EQ(color_b, curve->GetValue(base::Seconds(2.f))); +} + +// Tests that a color animation with three keyframes works as expected. +TEST(KeyframedAnimationCurveTest, ThreeColorKeyFrame) { + SkColor color_a = SkColorSetARGB(255, 255, 0, 0); + SkColor color_b = SkColorSetARGB(255, 0, 255, 0); + SkColor color_c = SkColorSetARGB(255, 0, 0, 255); + SkColor color_midpoint1 = + gfx::Tween::ColorValueBetween(0.5, color_a, color_b); + SkColor color_midpoint2 = + gfx::Tween::ColorValueBetween(0.5, color_b, color_c); + std::unique_ptr curve( + KeyframedColorAnimationCurve::Create()); + curve->AddKeyframe( + ColorKeyframe::Create(base::TimeDelta(), color_a, nullptr)); + curve->AddKeyframe( + ColorKeyframe::Create(base::Seconds(1.0), color_b, nullptr)); + curve->AddKeyframe( + ColorKeyframe::Create(base::Seconds(2.0), color_c, nullptr)); + + EXPECT_SKCOLOR_EQ(color_a, curve->GetValue(base::Seconds(-1.f))); + EXPECT_SKCOLOR_EQ(color_a, curve->GetValue(base::Seconds(0.f))); + EXPECT_SKCOLOR_EQ(color_midpoint1, curve->GetValue(base::Seconds(0.5f))); + EXPECT_SKCOLOR_EQ(color_b, curve->GetValue(base::Seconds(1.f))); + EXPECT_SKCOLOR_EQ(color_midpoint2, curve->GetValue(base::Seconds(1.5f))); + EXPECT_SKCOLOR_EQ(color_c, curve->GetValue(base::Seconds(2.f))); + EXPECT_SKCOLOR_EQ(color_c, curve->GetValue(base::Seconds(3.f))); +} + +// Tests that a color animation with multiple keys at a given time works sanely. +TEST(KeyframedAnimationCurveTest, RepeatedColorKeyFrame) { + SkColor color_a = SkColorSetARGB(255, 64, 0, 0); + SkColor color_b = SkColorSetARGB(255, 192, 0, 0); + + std::unique_ptr curve( + KeyframedColorAnimationCurve::Create()); + curve->AddKeyframe( + ColorKeyframe::Create(base::TimeDelta(), color_a, nullptr)); + curve->AddKeyframe( + ColorKeyframe::Create(base::Seconds(1.0), color_a, nullptr)); + curve->AddKeyframe( + ColorKeyframe::Create(base::Seconds(1.0), color_b, nullptr)); + curve->AddKeyframe( + ColorKeyframe::Create(base::Seconds(2.0), color_b, nullptr)); + + EXPECT_SKCOLOR_EQ(color_a, curve->GetValue(base::Seconds(-1.f))); + EXPECT_SKCOLOR_EQ(color_a, curve->GetValue(base::Seconds(0.f))); + EXPECT_SKCOLOR_EQ(color_a, curve->GetValue(base::Seconds(0.5f))); + + SkColor value = curve->GetValue(base::Seconds(1.0f)); + EXPECT_EQ(255u, SkColorGetA(value)); + int red_value = SkColorGetR(value); + EXPECT_LE(64, red_value); + EXPECT_GE(192, red_value); + + EXPECT_SKCOLOR_EQ(color_b, curve->GetValue(base::Seconds(1.5f))); + EXPECT_SKCOLOR_EQ(color_b, curve->GetValue(base::Seconds(2.f))); + EXPECT_SKCOLOR_EQ(color_b, curve->GetValue(base::Seconds(3.f))); +} + +// Tests that a float animation with one keyframe works as expected. +TEST(KeyframedAnimationCurveTest, OneFloatKeyframe) { + std::unique_ptr curve( + KeyframedFloatAnimationCurve::Create()); + curve->AddKeyframe(FloatKeyframe::Create(base::TimeDelta(), 2.f, nullptr)); + EXPECT_FLOAT_EQ(2.f, curve->GetValue(base::Seconds(-1.f))); + EXPECT_FLOAT_EQ(2.f, curve->GetValue(base::Seconds(0.f))); + EXPECT_FLOAT_EQ(2.f, curve->GetValue(base::Seconds(0.5f))); + EXPECT_FLOAT_EQ(2.f, curve->GetValue(base::Seconds(1.f))); + EXPECT_FLOAT_EQ(2.f, curve->GetValue(base::Seconds(2.f))); +} + +// Tests that a float animation with two keyframes works as expected. +TEST(KeyframedAnimationCurveTest, TwoFloatKeyframe) { + std::unique_ptr curve( + KeyframedFloatAnimationCurve::Create()); + curve->AddKeyframe(FloatKeyframe::Create(base::TimeDelta(), 2.f, nullptr)); + curve->AddKeyframe(FloatKeyframe::Create(base::Seconds(1.0), 4.f, nullptr)); + EXPECT_FLOAT_EQ(2.f, curve->GetValue(base::Seconds(-1.f))); + EXPECT_FLOAT_EQ(2.f, curve->GetValue(base::Seconds(0.f))); + EXPECT_FLOAT_EQ(3.f, curve->GetValue(base::Seconds(0.5f))); + EXPECT_FLOAT_EQ(4.f, curve->GetValue(base::Seconds(1.f))); + EXPECT_FLOAT_EQ(4.f, curve->GetValue(base::Seconds(2.f))); +} + +// Tests that a float animation with three keyframes works as expected. +TEST(KeyframedAnimationCurveTest, ThreeFloatKeyframe) { + std::unique_ptr curve( + KeyframedFloatAnimationCurve::Create()); + curve->AddKeyframe(FloatKeyframe::Create(base::TimeDelta(), 2.f, nullptr)); + curve->AddKeyframe(FloatKeyframe::Create(base::Seconds(1.0), 4.f, nullptr)); + curve->AddKeyframe(FloatKeyframe::Create(base::Seconds(2.0), 8.f, nullptr)); + EXPECT_FLOAT_EQ(2.f, curve->GetValue(base::Seconds(-1.f))); + EXPECT_FLOAT_EQ(2.f, curve->GetValue(base::Seconds(0.f))); + EXPECT_FLOAT_EQ(3.f, curve->GetValue(base::Seconds(0.5f))); + EXPECT_FLOAT_EQ(4.f, curve->GetValue(base::Seconds(1.f))); + EXPECT_FLOAT_EQ(6.f, curve->GetValue(base::Seconds(1.5f))); + EXPECT_FLOAT_EQ(8.f, curve->GetValue(base::Seconds(2.f))); + EXPECT_FLOAT_EQ(8.f, curve->GetValue(base::Seconds(3.f))); +} + +// Tests that a float animation with multiple keys at a given time works sanely. +TEST(KeyframedAnimationCurveTest, RepeatedFloatKeyTimes) { + std::unique_ptr curve( + KeyframedFloatAnimationCurve::Create()); + curve->AddKeyframe(FloatKeyframe::Create(base::TimeDelta(), 4.f, nullptr)); + curve->AddKeyframe(FloatKeyframe::Create(base::Seconds(1.0), 4.f, nullptr)); + curve->AddKeyframe(FloatKeyframe::Create(base::Seconds(1.0), 6.f, nullptr)); + curve->AddKeyframe(FloatKeyframe::Create(base::Seconds(2.0), 6.f, nullptr)); + + EXPECT_FLOAT_EQ(4.f, curve->GetValue(base::Seconds(-1.f))); + EXPECT_FLOAT_EQ(4.f, curve->GetValue(base::Seconds(0.f))); + EXPECT_FLOAT_EQ(4.f, curve->GetValue(base::Seconds(0.5f))); + + // There is a discontinuity at 1. Any value between 4 and 6 is valid. + float value = curve->GetValue(base::Seconds(1.f)); + EXPECT_TRUE(value >= 4 && value <= 6); + + EXPECT_FLOAT_EQ(6.f, curve->GetValue(base::Seconds(1.5f))); + EXPECT_FLOAT_EQ(6.f, curve->GetValue(base::Seconds(2.f))); + EXPECT_FLOAT_EQ(6.f, curve->GetValue(base::Seconds(3.f))); +} + +// Tests that a transform animation with one keyframe works as expected. +TEST(KeyframedAnimationCurveTest, OneTransformKeyframe) { + std::unique_ptr curve( + KeyframedTransformAnimationCurve::Create()); + gfx::TransformOperations operations; + operations.AppendTranslate(2.f, 0.f, 0.f); + curve->AddKeyframe( + TransformKeyframe::Create(base::TimeDelta(), operations, nullptr)); + + ExpectTranslateX(2.f, curve->GetValue(base::Seconds(-1.f))); + ExpectTranslateX(2.f, curve->GetValue(base::Seconds(0.f))); + ExpectTranslateX(2.f, curve->GetValue(base::Seconds(0.5f))); + ExpectTranslateX(2.f, curve->GetValue(base::Seconds(1.f))); + ExpectTranslateX(2.f, curve->GetValue(base::Seconds(2.f))); +} + +// Tests that a transform animation with two keyframes works as expected. +TEST(KeyframedAnimationCurveTest, TwoTransformKeyframe) { + std::unique_ptr curve( + KeyframedTransformAnimationCurve::Create()); + gfx::TransformOperations operations1; + operations1.AppendTranslate(2.f, 0.f, 0.f); + gfx::TransformOperations operations2; + operations2.AppendTranslate(4.f, 0.f, 0.f); + + curve->AddKeyframe( + TransformKeyframe::Create(base::TimeDelta(), operations1, nullptr)); + curve->AddKeyframe( + TransformKeyframe::Create(base::Seconds(1.0), operations2, nullptr)); + ExpectTranslateX(2.f, curve->GetValue(base::Seconds(-1.f))); + ExpectTranslateX(2.f, curve->GetValue(base::Seconds(0.f))); + ExpectTranslateX(3.f, curve->GetValue(base::Seconds(0.5f))); + ExpectTranslateX(4.f, curve->GetValue(base::Seconds(1.f))); + ExpectTranslateX(4.f, curve->GetValue(base::Seconds(2.f))); +} + +// Tests that a transform animation with three keyframes works as expected. +TEST(KeyframedAnimationCurveTest, ThreeTransformKeyframe) { + std::unique_ptr curve( + KeyframedTransformAnimationCurve::Create()); + gfx::TransformOperations operations1; + operations1.AppendTranslate(2.f, 0.f, 0.f); + gfx::TransformOperations operations2; + operations2.AppendTranslate(4.f, 0.f, 0.f); + gfx::TransformOperations operations3; + operations3.AppendTranslate(8.f, 0.f, 0.f); + curve->AddKeyframe( + TransformKeyframe::Create(base::TimeDelta(), operations1, nullptr)); + curve->AddKeyframe( + TransformKeyframe::Create(base::Seconds(1.0), operations2, nullptr)); + curve->AddKeyframe( + TransformKeyframe::Create(base::Seconds(2.0), operations3, nullptr)); + ExpectTranslateX(2.f, curve->GetValue(base::Seconds(-1.f))); + ExpectTranslateX(2.f, curve->GetValue(base::Seconds(0.f))); + ExpectTranslateX(3.f, curve->GetValue(base::Seconds(0.5f))); + ExpectTranslateX(4.f, curve->GetValue(base::Seconds(1.f))); + ExpectTranslateX(6.f, curve->GetValue(base::Seconds(1.5f))); + ExpectTranslateX(8.f, curve->GetValue(base::Seconds(2.f))); + ExpectTranslateX(8.f, curve->GetValue(base::Seconds(3.f))); +} + +// Tests that a transform animation with multiple keys at a given time works +// sanely. +TEST(KeyframedAnimationCurveTest, RepeatedTransformKeyTimes) { + std::unique_ptr curve( + KeyframedTransformAnimationCurve::Create()); + // A step function. + gfx::TransformOperations operations1; + operations1.AppendTranslate(4.f, 0.f, 0.f); + gfx::TransformOperations operations2; + operations2.AppendTranslate(4.f, 0.f, 0.f); + gfx::TransformOperations operations3; + operations3.AppendTranslate(6.f, 0.f, 0.f); + gfx::TransformOperations operations4; + operations4.AppendTranslate(6.f, 0.f, 0.f); + curve->AddKeyframe( + TransformKeyframe::Create(base::TimeDelta(), operations1, nullptr)); + curve->AddKeyframe( + TransformKeyframe::Create(base::Seconds(1.0), operations2, nullptr)); + curve->AddKeyframe( + TransformKeyframe::Create(base::Seconds(1.0), operations3, nullptr)); + curve->AddKeyframe( + TransformKeyframe::Create(base::Seconds(2.0), operations4, nullptr)); + + ExpectTranslateX(4.f, curve->GetValue(base::Seconds(-1.f))); + ExpectTranslateX(4.f, curve->GetValue(base::Seconds(0.f))); + ExpectTranslateX(4.f, curve->GetValue(base::Seconds(0.5f))); + + // There is a discontinuity at 1. Any value between 4 and 6 is valid. + gfx::Transform value = curve->GetValue(base::Seconds(1.f)).Apply(); + EXPECT_GE(value.matrix().get(0, 3), 4.f); + EXPECT_LE(value.matrix().get(0, 3), 6.f); + + ExpectTranslateX(6.f, curve->GetValue(base::Seconds(1.5f))); + ExpectTranslateX(6.f, curve->GetValue(base::Seconds(2.f))); + ExpectTranslateX(6.f, curve->GetValue(base::Seconds(3.f))); +} + +// Tests that a discrete transform animation (e.g. where one or more keyframes +// is a non-invertible matrix) works as expected. +TEST(KeyframedAnimationCurveTest, DiscreteLinearTransformAnimation) { + gfx::Transform non_invertible_matrix(0, 0, 0, 0, 0, 0); + gfx::Transform identity_matrix; + + std::unique_ptr curve( + KeyframedTransformAnimationCurve::Create()); + gfx::TransformOperations operations1; + operations1.AppendMatrix(non_invertible_matrix); + gfx::TransformOperations operations2; + operations2.AppendMatrix(identity_matrix); + gfx::TransformOperations operations3; + operations3.AppendMatrix(non_invertible_matrix); + + curve->AddKeyframe( + TransformKeyframe::Create(base::TimeDelta(), operations1, nullptr)); + curve->AddKeyframe( + TransformKeyframe::Create(base::Seconds(1.0), operations2, nullptr)); + curve->AddKeyframe( + TransformKeyframe::Create(base::Seconds(2.0), operations3, nullptr)); + + gfx::TransformOperations result; + + // Between 0 and 0.5 seconds, the first keyframe should be returned. + result = curve->GetValue(base::Seconds(0.01f)); + ExpectTransformationMatrixEq(non_invertible_matrix, result.Apply()); + + result = curve->GetValue(base::Seconds(0.49f)); + ExpectTransformationMatrixEq(non_invertible_matrix, result.Apply()); + + // Between 0.5 and 1.5 seconds, the middle keyframe should be returned. + result = curve->GetValue(base::Seconds(0.5f)); + ExpectTransformationMatrixEq(identity_matrix, result.Apply()); + + result = curve->GetValue(base::Seconds(1.49f)); + ExpectTransformationMatrixEq(identity_matrix, result.Apply()); + + // Between 1.5 and 2.0 seconds, the last keyframe should be returned. + result = curve->GetValue(base::Seconds(1.5f)); + ExpectTransformationMatrixEq(non_invertible_matrix, result.Apply()); + + result = curve->GetValue(base::Seconds(2.0f)); + ExpectTransformationMatrixEq(non_invertible_matrix, result.Apply()); +} + +TEST(KeyframedAnimationCurveTest, DiscreteCubicBezierTransformAnimation) { + gfx::Transform non_invertible_matrix(0, 0, 0, 0, 0, 0); + gfx::Transform identity_matrix; + + std::unique_ptr curve( + KeyframedTransformAnimationCurve::Create()); + gfx::TransformOperations operations1; + operations1.AppendMatrix(non_invertible_matrix); + gfx::TransformOperations operations2; + operations2.AppendMatrix(identity_matrix); + gfx::TransformOperations operations3; + operations3.AppendMatrix(non_invertible_matrix); + + // The cubic-bezier here is a nice fairly strong ease-in curve, where 50% + // progression is at approximately 85% of the time. + curve->AddKeyframe(TransformKeyframe::Create( + base::TimeDelta(), operations1, + CubicBezierTimingFunction::Create(0.75f, 0.25f, 0.9f, 0.4f))); + curve->AddKeyframe(TransformKeyframe::Create( + base::Seconds(1.0), operations2, + CubicBezierTimingFunction::Create(0.75f, 0.25f, 0.9f, 0.4f))); + curve->AddKeyframe(TransformKeyframe::Create( + base::Seconds(2.0), operations3, + CubicBezierTimingFunction::Create(0.75f, 0.25f, 0.9f, 0.4f))); + + gfx::TransformOperations result; + + // Due to the cubic-bezier, the first keyframe is returned almost all the way + // to 1 second. + result = curve->GetValue(base::Seconds(0.01f)); + ExpectTransformationMatrixEq(non_invertible_matrix, result.Apply()); + + result = curve->GetValue(base::Seconds(0.8f)); + ExpectTransformationMatrixEq(non_invertible_matrix, result.Apply()); + + // Between ~0.85 and ~1.85 seconds, the middle keyframe should be returned. + result = curve->GetValue(base::Seconds(0.85f)); + ExpectTransformationMatrixEq(identity_matrix, result.Apply()); + + result = curve->GetValue(base::Seconds(1.8f)); + ExpectTransformationMatrixEq(identity_matrix, result.Apply()); + + // Finally the last keyframe only takes effect after ~1.85 seconds. + result = curve->GetValue(base::Seconds(1.85f)); + ExpectTransformationMatrixEq(non_invertible_matrix, result.Apply()); + + result = curve->GetValue(base::Seconds(2.0f)); + ExpectTransformationMatrixEq(non_invertible_matrix, result.Apply()); +} + +// Tests that the keyframes may be added out of order. +TEST(KeyframedAnimationCurveTest, UnsortedKeyframes) { + std::unique_ptr curve( + KeyframedFloatAnimationCurve::Create()); + curve->AddKeyframe(FloatKeyframe::Create(base::Seconds(2.f), 8.f, nullptr)); + curve->AddKeyframe(FloatKeyframe::Create(base::TimeDelta(), 2.f, nullptr)); + curve->AddKeyframe(FloatKeyframe::Create(base::Seconds(1.f), 4.f, nullptr)); + EXPECT_FLOAT_EQ(2.f, curve->GetValue(base::Seconds(-1.f))); + EXPECT_FLOAT_EQ(2.f, curve->GetValue(base::Seconds(0.f))); + EXPECT_FLOAT_EQ(3.f, curve->GetValue(base::Seconds(0.5f))); + EXPECT_FLOAT_EQ(4.f, curve->GetValue(base::Seconds(1.f))); + EXPECT_FLOAT_EQ(6.f, curve->GetValue(base::Seconds(1.5f))); + EXPECT_FLOAT_EQ(8.f, curve->GetValue(base::Seconds(2.f))); + EXPECT_FLOAT_EQ(8.f, curve->GetValue(base::Seconds(3.f))); +} + +// Tests that a linear timing function works as expected. +TEST(KeyframedAnimationCurveTest, LinearTimingFunction) { + std::unique_ptr curve( + KeyframedFloatAnimationCurve::Create()); + curve->AddKeyframe(FloatKeyframe::Create(base::TimeDelta(), 0.f, + LinearTimingFunction::Create())); + curve->AddKeyframe(FloatKeyframe::Create(base::Seconds(1.0), 1.f, nullptr)); + + EXPECT_FLOAT_EQ(0.f, curve->GetValue(base::Seconds(0.f))); + EXPECT_FLOAT_EQ(0.75f, curve->GetValue(base::Seconds(0.75f))); +} + +// Tests that a cubic bezier timing function works as expected. +TEST(KeyframedAnimationCurveTest, CubicBezierTimingFunction) { + std::unique_ptr curve( + KeyframedFloatAnimationCurve::Create()); + curve->AddKeyframe(FloatKeyframe::Create( + base::TimeDelta(), 0.f, + CubicBezierTimingFunction::Create(0.25f, 0.f, 0.75f, 1.f))); + curve->AddKeyframe(FloatKeyframe::Create(base::Seconds(1.0), 1.f, nullptr)); + + EXPECT_FLOAT_EQ(0.f, curve->GetValue(base::Seconds(0.f))); + EXPECT_LT(0.f, curve->GetValue(base::Seconds(0.25f))); + EXPECT_GT(0.25f, curve->GetValue(base::Seconds(0.25f))); + EXPECT_NEAR(curve->GetValue(base::Seconds(0.5f)), 0.5f, 0.00015f); + EXPECT_LT(0.75f, curve->GetValue(base::Seconds(0.75f))); + EXPECT_GT(1.f, curve->GetValue(base::Seconds(0.75f))); + EXPECT_FLOAT_EQ(1.f, curve->GetValue(base::Seconds(1.f))); +} + +// Tests a step timing function if the change of values occur at the start. +TEST(KeyframedAnimationCurveTest, StepsTimingFunctionStepAtStart) { + std::unique_ptr curve( + KeyframedFloatAnimationCurve::Create()); + const int num_steps = 36; + curve->AddKeyframe(FloatKeyframe::Create( + base::TimeDelta(), 0.f, + StepsTimingFunction::Create(num_steps, + StepsTimingFunction::StepPosition::START))); + curve->AddKeyframe( + FloatKeyframe::Create(base::Seconds(1.0), num_steps, nullptr)); + + const float time_threshold = 0.0001f; + + for (float i = 0.f; i < num_steps; i += 1.f) { + const base::TimeDelta time1 = base::Seconds(i / num_steps - time_threshold); + const base::TimeDelta time2 = base::Seconds(i / num_steps + time_threshold); + EXPECT_FLOAT_EQ(std::ceil(i), curve->GetValue(time1)); + EXPECT_FLOAT_EQ(std::ceil(i) + 1.f, curve->GetValue(time2)); + } + EXPECT_FLOAT_EQ(num_steps, curve->GetValue(base::Seconds(1.0))); + + for (float i = 0.5f; i <= num_steps; i += 1.0f) { + const base::TimeDelta time = base::Seconds(i / num_steps); + EXPECT_FLOAT_EQ(std::ceil(i), curve->GetValue(time)); + } +} + +// Tests a step timing function if the change of values occur at the end. +TEST(KeyframedAnimationCurveTest, StepsTimingFunctionStepAtEnd) { + std::unique_ptr curve( + KeyframedFloatAnimationCurve::Create()); + const int num_steps = 36; + curve->AddKeyframe(FloatKeyframe::Create( + base::TimeDelta(), 0.f, + StepsTimingFunction::Create(num_steps, + StepsTimingFunction::StepPosition::END))); + curve->AddKeyframe( + FloatKeyframe::Create(base::Seconds(1.0), num_steps, nullptr)); + + const float time_threshold = 0.0001f; + + EXPECT_FLOAT_EQ(0.f, curve->GetValue(base::TimeDelta())); + for (float i = 1.f; i <= num_steps; i += 1.f) { + const base::TimeDelta time1 = base::Seconds(i / num_steps - time_threshold); + const base::TimeDelta time2 = base::Seconds(i / num_steps + time_threshold); + EXPECT_FLOAT_EQ(std::floor(i) - 1.f, curve->GetValue(time1)); + EXPECT_FLOAT_EQ(std::floor(i), curve->GetValue(time2)); + } + EXPECT_FLOAT_EQ(num_steps, curve->GetValue(base::Seconds(1.0))); + + for (float i = 0.5f; i <= num_steps; i += 1.0f) { + const base::TimeDelta time = base::Seconds(i / num_steps); + EXPECT_FLOAT_EQ(std::floor(i), curve->GetValue(time)); + } +} + +// Tests that maximum animation scale is computed as expected. +TEST(KeyframedAnimationCurveTest, MaximumScale) { + std::unique_ptr curve( + KeyframedTransformAnimationCurve::Create()); + + gfx::TransformOperations operations1; + curve->AddKeyframe( + TransformKeyframe::Create(base::TimeDelta(), operations1, nullptr)); + operations1.AppendScale(2.f, -3.f, 1.f); + curve->AddKeyframe(TransformKeyframe::Create( + base::Seconds(1.f), operations1, + CubicBezierTimingFunction::CreatePreset( + CubicBezierTimingFunction::EaseType::EASE))); + + constexpr float kArbitraryScale = 12345.f; + float maximum_scale = kArbitraryScale; + EXPECT_TRUE(curve->MaximumScale(&maximum_scale)); + EXPECT_EQ(3.f, maximum_scale); + + gfx::TransformOperations operations2; + operations2.AppendScale(6.f, 3.f, 2.f); + curve->AddKeyframe(TransformKeyframe::Create( + base::Seconds(2.f), operations2, + CubicBezierTimingFunction::CreatePreset( + CubicBezierTimingFunction::EaseType::EASE))); + + maximum_scale = kArbitraryScale; + EXPECT_TRUE(curve->MaximumScale(&maximum_scale)); + EXPECT_EQ(6.f, maximum_scale); + + gfx::TransformOperations operations3; + operations3.AppendRotate(1.f, 0.f, 0.f, 90.f); + curve->AddKeyframe(TransformKeyframe::Create( + base::Seconds(3.f), operations3, + CubicBezierTimingFunction::CreatePreset( + CubicBezierTimingFunction::EaseType::EASE))); + + maximum_scale = kArbitraryScale; + EXPECT_TRUE(curve->MaximumScale(&maximum_scale)); + EXPECT_EQ(6.f, maximum_scale); + + // All scales are used in computing the max. + std::unique_ptr curve2( + KeyframedTransformAnimationCurve::Create()); + + gfx::TransformOperations operations5; + operations5.AppendScale(0.4f, 0.2f, 0.6f); + curve2->AddKeyframe(TransformKeyframe::Create( + base::TimeDelta(), operations5, + CubicBezierTimingFunction::CreatePreset( + CubicBezierTimingFunction::EaseType::EASE))); + gfx::TransformOperations operations6; + operations6.AppendScale(0.5f, 0.3f, -0.8f); + curve2->AddKeyframe(TransformKeyframe::Create( + base::Seconds(1.f), operations6, + CubicBezierTimingFunction::CreatePreset( + CubicBezierTimingFunction::EaseType::EASE))); + + maximum_scale = kArbitraryScale; + EXPECT_TRUE(curve2->MaximumScale(&maximum_scale)); + EXPECT_EQ(0.8f, maximum_scale); +} + +TEST(KeyframeAnimationCurveTest, NonCalculatableMaximumScale) { + auto curve = KeyframedTransformAnimationCurve::Create(); + gfx::TransformOperations operations4; + operations4.AppendPerspective(3.f); + curve->AddKeyframe(TransformKeyframe::Create( + base::Seconds(1.f), operations4, + CubicBezierTimingFunction::CreatePreset( + CubicBezierTimingFunction::EaseType::EASE))); + curve->AddKeyframe(TransformKeyframe::Create( + base::Seconds(1.f), operations4, + CubicBezierTimingFunction::CreatePreset( + CubicBezierTimingFunction::EaseType::EASE))); + + constexpr float kArbitraryScale = 12345.f; + float maximum_scale = kArbitraryScale; + EXPECT_FALSE(curve->MaximumScale(&maximum_scale)); + + // If the scale of any keyframe can be calculated, the keyframes with + // non-calculatable scale will be ignored. + gfx::TransformOperations operations; + operations.AppendScale(0.4f, 0.2f, 0.6f); + curve->AddKeyframe(TransformKeyframe::Create( + base::TimeDelta(), operations, + CubicBezierTimingFunction::CreatePreset( + CubicBezierTimingFunction::EaseType::EASE))); + + maximum_scale = kArbitraryScale; + EXPECT_TRUE(curve->MaximumScale(&maximum_scale)); + EXPECT_EQ(0.6f, maximum_scale); +} + +// Tests that an animation with a curve timing function works as expected. +TEST(KeyframedAnimationCurveTest, CurveTiming) { + std::unique_ptr curve( + KeyframedFloatAnimationCurve::Create()); + curve->AddKeyframe(FloatKeyframe::Create(base::TimeDelta(), 0.f, nullptr)); + curve->AddKeyframe(FloatKeyframe::Create(base::Seconds(1.f), 1.f, nullptr)); + curve->SetTimingFunction( + CubicBezierTimingFunction::Create(0.75f, 0.f, 0.25f, 1.f)); + EXPECT_FLOAT_EQ(0.f, curve->GetValue(base::Seconds(-1.f))); + EXPECT_FLOAT_EQ(0.f, curve->GetValue(base::Seconds(0.f))); + EXPECT_NEAR(0.05f, curve->GetValue(base::Seconds(0.25f)), 0.005f); + EXPECT_FLOAT_EQ(0.5f, curve->GetValue(base::Seconds(0.5f))); + EXPECT_NEAR(0.95f, curve->GetValue(base::Seconds(0.75f)), 0.005f); + EXPECT_FLOAT_EQ(1.f, curve->GetValue(base::Seconds(1.f))); + EXPECT_FLOAT_EQ(1.f, curve->GetValue(base::Seconds(2.f))); +} + +// Tests that an animation with a curve and keyframe timing function works as +// expected. +TEST(KeyframedAnimationCurveTest, CurveAndKeyframeTiming) { + std::unique_ptr curve( + KeyframedFloatAnimationCurve::Create()); + curve->AddKeyframe(FloatKeyframe::Create( + base::TimeDelta(), 0.f, + CubicBezierTimingFunction::Create(0.35f, 0.f, 0.65f, 1.f))); + curve->AddKeyframe(FloatKeyframe::Create(base::Seconds(1.f), 1.f, nullptr)); + // Curve timing function producing outputs outside of range [0,1]. + curve->SetTimingFunction( + CubicBezierTimingFunction::Create(0.5f, -0.5f, 0.5f, 1.5f)); + EXPECT_FLOAT_EQ(0.f, curve->GetValue(base::Seconds(-1.f))); + EXPECT_FLOAT_EQ(0.f, curve->GetValue(base::Seconds(0.f))); + EXPECT_FLOAT_EQ( + 0.f, curve->GetValue(base::Seconds(0.25f))); // Clamped. c(.25) < 0 + EXPECT_NEAR(0.17f, curve->GetValue(base::Seconds(0.42f)), + 0.005f); // c(.42)=.27, k(.27)=.17 + EXPECT_FLOAT_EQ(0.5f, curve->GetValue(base::Seconds(0.5f))); + EXPECT_NEAR(0.83f, curve->GetValue(base::Seconds(0.58f)), + 0.005f); // c(.58)=.73, k(.73)=.83 + EXPECT_FLOAT_EQ( + 1.f, curve->GetValue(base::Seconds(0.75f))); // Clamped. c(.75) > 1 + EXPECT_FLOAT_EQ(1.f, curve->GetValue(base::Seconds(1.f))); + EXPECT_FLOAT_EQ(1.f, curve->GetValue(base::Seconds(2.f))); +} + +// Tests that a linear timing function works as expected for inputs outside of +// range [0,1] +TEST(KeyframedAnimationCurveTest, LinearTimingInputsOutsideZeroOneRange) { + std::unique_ptr curve( + KeyframedFloatAnimationCurve::Create()); + curve->AddKeyframe(FloatKeyframe::Create(base::TimeDelta(), 0.f, nullptr)); + curve->AddKeyframe(FloatKeyframe::Create(base::Seconds(1.0), 2.f, nullptr)); + // Curve timing function producing timing outputs outside of range [0,1]. + curve->SetTimingFunction( + CubicBezierTimingFunction::Create(0.5f, -0.5f, 0.5f, 1.5f)); + + EXPECT_NEAR(-0.076f, curve->GetValue(base::Seconds(0.25f)), 0.001f); + EXPECT_NEAR(2.076f, curve->GetValue(base::Seconds(0.75f)), 0.001f); +} + +// If a curve cubic-bezier timing function produces timing outputs outside +// the range [0, 1] then a keyframe cubic-bezier timing function +// should consume that input properly (using end-point gradients). +TEST(KeyframedAnimationCurveTest, CurveTimingInputsOutsideZeroOneRange) { + std::unique_ptr curve( + KeyframedFloatAnimationCurve::Create()); + // Keyframe timing function with 0.5 gradients at each end. + curve->AddKeyframe(FloatKeyframe::Create( + base::TimeDelta(), 0.f, + CubicBezierTimingFunction::Create(0.5f, 0.25f, 0.5f, 0.75f))); + curve->AddKeyframe(FloatKeyframe::Create(base::Seconds(1.f), 1.f, nullptr)); + // Curve timing function producing timing outputs outside of range [0,1]. + curve->SetTimingFunction( + CubicBezierTimingFunction::Create(0.5f, -0.5f, 0.5f, 1.5f)); + + EXPECT_NEAR(-0.02f, curve->GetValue(base::Seconds(0.25f)), + 0.002f); // c(.25)=-.04, -.04*0.5=-0.02 + EXPECT_NEAR(0.33f, curve->GetValue(base::Seconds(0.46f)), + 0.002f); // c(.46)=.38, k(.38)=.33 + + EXPECT_NEAR(0.67f, curve->GetValue(base::Seconds(0.54f)), + 0.002f); // c(.54)=.62, k(.62)=.67 + EXPECT_NEAR(1.02f, curve->GetValue(base::Seconds(0.75f)), + 0.002f); // c(.75)=1.04 1+.04*0.5=1.02 +} + +// Tests that a step timing function works as expected for inputs outside of +// range [0,1] +TEST(KeyframedAnimationCurveTest, StepsTimingStartInputsOutsideZeroOneRange) { + std::unique_ptr curve( + KeyframedFloatAnimationCurve::Create()); + curve->AddKeyframe( + FloatKeyframe::Create(base::TimeDelta(), 0.f, + StepsTimingFunction::Create( + 4, StepsTimingFunction::StepPosition::START))); + curve->AddKeyframe(FloatKeyframe::Create(base::Seconds(1.0), 2.f, nullptr)); + // Curve timing function producing timing outputs outside of range [0,1]. + curve->SetTimingFunction( + CubicBezierTimingFunction::Create(0.5f, -0.5f, 0.5f, 1.5f)); + + EXPECT_FLOAT_EQ(0.f, curve->GetValue(base::Seconds(0.25f))); + EXPECT_FLOAT_EQ(2.5f, curve->GetValue(base::Seconds(0.75f))); +} + +TEST(KeyframedAnimationCurveTest, StepsTimingEndInputsOutsideZeroOneRange) { + std::unique_ptr curve( + KeyframedFloatAnimationCurve::Create()); + curve->AddKeyframe(FloatKeyframe::Create( + base::TimeDelta(), 0.f, + StepsTimingFunction::Create(4, StepsTimingFunction::StepPosition::END))); + curve->AddKeyframe(FloatKeyframe::Create(base::Seconds(1.0), 2.f, nullptr)); + // Curve timing function producing timing outputs outside of range [0,1]. + curve->SetTimingFunction( + CubicBezierTimingFunction::Create(0.5f, -0.5f, 0.5f, 1.5f)); + + EXPECT_FLOAT_EQ(-0.5f, curve->GetValue(base::Seconds(0.25f))); + EXPECT_FLOAT_EQ(2.f, curve->GetValue(base::Seconds(0.75f))); +} + +// Tests that an animation with a curve timing function and multiple keyframes +// works as expected. +TEST(KeyframedAnimationCurveTest, CurveTimingMultipleKeyframes) { + std::unique_ptr curve( + KeyframedFloatAnimationCurve::Create()); + curve->AddKeyframe(FloatKeyframe::Create(base::TimeDelta(), 0.f, nullptr)); + curve->AddKeyframe(FloatKeyframe::Create(base::Seconds(1.f), 1.f, nullptr)); + curve->AddKeyframe(FloatKeyframe::Create(base::Seconds(2.f), 3.f, nullptr)); + curve->AddKeyframe(FloatKeyframe::Create(base::Seconds(3.f), 6.f, nullptr)); + curve->AddKeyframe(FloatKeyframe::Create(base::Seconds(4.f), 9.f, nullptr)); + curve->SetTimingFunction( + CubicBezierTimingFunction::Create(0.5f, 0.f, 0.5f, 1.f)); + EXPECT_FLOAT_EQ(0.f, curve->GetValue(base::Seconds(-1.f))); + EXPECT_FLOAT_EQ(0.f, curve->GetValue(base::Seconds(0.f))); + EXPECT_NEAR(0.42f, curve->GetValue(base::Seconds(1.f)), 0.005f); + EXPECT_NEAR(1.f, curve->GetValue(base::Seconds(1.455f)), 0.005f); + EXPECT_FLOAT_EQ(3.f, curve->GetValue(base::Seconds(2.f))); + EXPECT_NEAR(8.72f, curve->GetValue(base::Seconds(3.5f)), 0.01f); + EXPECT_FLOAT_EQ(9.f, curve->GetValue(base::Seconds(4.f))); + EXPECT_FLOAT_EQ(9.f, curve->GetValue(base::Seconds(5.f))); +} + +// Tests that an animation with a curve timing function that overshoots works as +// expected. +TEST(KeyframedAnimationCurveTest, CurveTimingOvershootMultipeKeyframes) { + std::unique_ptr curve( + KeyframedFloatAnimationCurve::Create()); + curve->AddKeyframe(FloatKeyframe::Create(base::TimeDelta(), 0.f, nullptr)); + curve->AddKeyframe(FloatKeyframe::Create(base::Seconds(1.0), 1.f, nullptr)); + curve->AddKeyframe(FloatKeyframe::Create(base::Seconds(2.0), 3.f, nullptr)); + curve->AddKeyframe(FloatKeyframe::Create(base::Seconds(3.0), 6.f, nullptr)); + curve->AddKeyframe(FloatKeyframe::Create(base::Seconds(4.0), 9.f, nullptr)); + // Curve timing function producing outputs outside of range [0,1]. + curve->SetTimingFunction( + CubicBezierTimingFunction::Create(0.5f, -0.5f, 0.5f, 1.5f)); + EXPECT_LE(curve->GetValue(base::Seconds(1.f)), + 0.f); // c(.25) < 0 + EXPECT_GE(curve->GetValue(base::Seconds(3.f)), + 9.f); // c(.75) > 1 +} + +// Tests that a float animation with multiple keys works with scaled duration. +TEST(KeyframedAnimationCurveTest, ScaledDuration) { + std::unique_ptr curve( + KeyframedFloatAnimationCurve::Create()); + curve->AddKeyframe(FloatKeyframe::Create(base::TimeDelta(), 0.f, nullptr)); + curve->AddKeyframe(FloatKeyframe::Create(base::Seconds(1.f), 1.f, nullptr)); + curve->AddKeyframe(FloatKeyframe::Create(base::Seconds(2.f), 3.f, nullptr)); + curve->AddKeyframe(FloatKeyframe::Create(base::Seconds(3.f), 6.f, nullptr)); + curve->AddKeyframe(FloatKeyframe::Create(base::Seconds(4.f), 9.f, nullptr)); + curve->SetTimingFunction( + CubicBezierTimingFunction::Create(0.5f, 0.f, 0.5f, 1.f)); + + const double scale = 1000.0; + curve->set_scaled_duration(scale); + + EXPECT_DOUBLE_EQ(scale * 4, curve->Duration().InSecondsF()); + + EXPECT_FLOAT_EQ(0.f, curve->GetValue(base::Seconds(scale * -1.f))); + EXPECT_FLOAT_EQ(0.f, curve->GetValue(base::Seconds(scale * 0.f))); + EXPECT_NEAR(0.42f, curve->GetValue(base::Seconds(scale * 1.f)), 0.005f); + EXPECT_NEAR(1.f, curve->GetValue(base::Seconds(scale * 1.455f)), 0.005f); + EXPECT_FLOAT_EQ(3.f, curve->GetValue(base::Seconds(scale * 2.f))); + EXPECT_NEAR(8.72f, curve->GetValue(base::Seconds(scale * 3.5f)), 0.01f); + EXPECT_FLOAT_EQ(9.f, curve->GetValue(base::Seconds(scale * 4.f))); + EXPECT_FLOAT_EQ(9.f, curve->GetValue(base::Seconds(scale * 5.f))); +} + +// Tests that a size animation with one keyframe works as expected. +TEST(KeyframedAnimationCurveTest, OneSizeKeyFrame) { + gfx::SizeF size = gfx::SizeF(100, 100); + std::unique_ptr curve( + KeyframedSizeAnimationCurve::Create()); + curve->AddKeyframe(SizeKeyframe::Create(base::TimeDelta(), size, nullptr)); + + EXPECT_SIZEF_EQ(size, curve->GetValue(base::Seconds(-1.f))); + EXPECT_SIZEF_EQ(size, curve->GetValue(base::Seconds(0.f))); + EXPECT_SIZEF_EQ(size, curve->GetValue(base::Seconds(0.5f))); + EXPECT_SIZEF_EQ(size, curve->GetValue(base::Seconds(1.f))); + EXPECT_SIZEF_EQ(size, curve->GetValue(base::Seconds(2.f))); +} + +// Tests that a size animation with two keyframes works as expected. +TEST(KeyframedAnimationCurveTest, TwoSizeKeyFrame) { + gfx::SizeF size_a = gfx::SizeF(100, 100); + gfx::SizeF size_b = gfx::SizeF(100, 0); + gfx::SizeF size_midpoint = gfx::Tween::SizeFValueBetween(0.5, size_a, size_b); + std::unique_ptr curve( + KeyframedSizeAnimationCurve::Create()); + curve->AddKeyframe(SizeKeyframe::Create(base::TimeDelta(), size_a, nullptr)); + curve->AddKeyframe(SizeKeyframe::Create(base::Seconds(1.0), size_b, nullptr)); + + EXPECT_SIZEF_EQ(size_a, curve->GetValue(base::Seconds(-1.f))); + EXPECT_SIZEF_EQ(size_a, curve->GetValue(base::Seconds(0.f))); + EXPECT_SIZEF_EQ(size_midpoint, curve->GetValue(base::Seconds(0.5f))); + EXPECT_SIZEF_EQ(size_b, curve->GetValue(base::Seconds(1.f))); + EXPECT_SIZEF_EQ(size_b, curve->GetValue(base::Seconds(2.f))); +} + +// Tests that a size animation with three keyframes works as expected. +TEST(KeyframedAnimationCurveTest, ThreeSizeKeyFrame) { + gfx::SizeF size_a = gfx::SizeF(100, 100); + gfx::SizeF size_b = gfx::SizeF(100, 0); + gfx::SizeF size_c = gfx::SizeF(200, 0); + gfx::SizeF size_midpoint1 = + gfx::Tween::SizeFValueBetween(0.5, size_a, size_b); + gfx::SizeF size_midpoint2 = + gfx::Tween::SizeFValueBetween(0.5, size_b, size_c); + std::unique_ptr curve( + KeyframedSizeAnimationCurve::Create()); + curve->AddKeyframe(SizeKeyframe::Create(base::TimeDelta(), size_a, nullptr)); + curve->AddKeyframe(SizeKeyframe::Create(base::Seconds(1.0), size_b, nullptr)); + curve->AddKeyframe(SizeKeyframe::Create(base::Seconds(2.0), size_c, nullptr)); + + EXPECT_SIZEF_EQ(size_a, curve->GetValue(base::Seconds(-1.f))); + EXPECT_SIZEF_EQ(size_a, curve->GetValue(base::Seconds(0.f))); + EXPECT_SIZEF_EQ(size_midpoint1, curve->GetValue(base::Seconds(0.5f))); + EXPECT_SIZEF_EQ(size_b, curve->GetValue(base::Seconds(1.f))); + EXPECT_SIZEF_EQ(size_midpoint2, curve->GetValue(base::Seconds(1.5f))); + EXPECT_SIZEF_EQ(size_c, curve->GetValue(base::Seconds(2.f))); + EXPECT_SIZEF_EQ(size_c, curve->GetValue(base::Seconds(3.f))); +} + +// Tests that a size animation with multiple keys at a given time works sanely. +TEST(KeyframedAnimationCurveTest, RepeatedSizeKeyFrame) { + gfx::SizeF size_a = gfx::SizeF(100, 64); + gfx::SizeF size_b = gfx::SizeF(100, 192); + + std::unique_ptr curve( + KeyframedSizeAnimationCurve::Create()); + curve->AddKeyframe(SizeKeyframe::Create(base::TimeDelta(), size_a, nullptr)); + curve->AddKeyframe(SizeKeyframe::Create(base::Seconds(1.0), size_a, nullptr)); + curve->AddKeyframe(SizeKeyframe::Create(base::Seconds(1.0), size_b, nullptr)); + curve->AddKeyframe(SizeKeyframe::Create(base::Seconds(2.0), size_b, nullptr)); + + EXPECT_SIZEF_EQ(size_a, curve->GetValue(base::Seconds(-1.f))); + EXPECT_SIZEF_EQ(size_a, curve->GetValue(base::Seconds(0.f))); + EXPECT_SIZEF_EQ(size_a, curve->GetValue(base::Seconds(0.5f))); + + gfx::SizeF value = curve->GetValue(base::Seconds(1.0f)); + EXPECT_FLOAT_EQ(100.0f, value.width()); + EXPECT_LE(64.0f, value.height()); + EXPECT_GE(192.0f, value.height()); + + EXPECT_SIZEF_EQ(size_b, curve->GetValue(base::Seconds(1.5f))); + EXPECT_SIZEF_EQ(size_b, curve->GetValue(base::Seconds(2.f))); + EXPECT_SIZEF_EQ(size_b, curve->GetValue(base::Seconds(3.f))); +} + +// Tests that a rect animation with one keyframe works as expected. +TEST(KeyframedAnimationCurveTest, OneRectKeyFrame) { + gfx::Rect rect = gfx::Rect(1, 2, 101, 102); + std::unique_ptr curve( + KeyframedRectAnimationCurve::Create()); + curve->AddKeyframe(RectKeyframe::Create(base::TimeDelta(), rect, nullptr)); + + EXPECT_EQ(rect, curve->GetValue(base::Seconds(-1.f))); + EXPECT_EQ(rect, curve->GetValue(base::Seconds(0.f))); + EXPECT_EQ(rect, curve->GetValue(base::Seconds(0.5f))); + EXPECT_EQ(rect, curve->GetValue(base::Seconds(1.f))); + EXPECT_EQ(rect, curve->GetValue(base::Seconds(2.f))); +} + +// Tests that a rect animation with two keyframes works as expected. +TEST(KeyframedAnimationCurveTest, TwoRectKeyFrame) { + gfx::Rect rect_a = gfx::Rect(1, 2, 100, 100); + gfx::Rect rect_b = gfx::Rect(11, 12, 100, 0); + gfx::Rect rect_midpoint = gfx::Tween::RectValueBetween(0.5, rect_a, rect_b); + std::unique_ptr curve( + KeyframedRectAnimationCurve::Create()); + curve->AddKeyframe(RectKeyframe::Create(base::TimeDelta(), rect_a, nullptr)); + curve->AddKeyframe(RectKeyframe::Create(base::Seconds(1.0), rect_b, nullptr)); + + EXPECT_EQ(rect_a, curve->GetValue(base::Seconds(-1.f))); + EXPECT_EQ(rect_a, curve->GetValue(base::Seconds(0.f))); + EXPECT_EQ(rect_midpoint, curve->GetValue(base::Seconds(0.5f))); + EXPECT_EQ(rect_b, curve->GetValue(base::Seconds(1.f))); + EXPECT_EQ(rect_b, curve->GetValue(base::Seconds(2.f))); +} + +// Tests that a rect animation with three keyframes works as expected. +TEST(KeyframedAnimationCurveTest, ThreeRectKeyFrame) { + gfx::Rect rect_a = gfx::Rect(1, 2, 100, 100); + gfx::Rect rect_b = gfx::Rect(11, 12, 100, 0); + gfx::Rect rect_c = gfx::Rect(101, 102, 200, 0); + gfx::Rect rect_midpoint1 = gfx::Tween::RectValueBetween(0.5, rect_a, rect_b); + gfx::Rect rect_midpoint2 = gfx::Tween::RectValueBetween(0.5, rect_b, rect_c); + std::unique_ptr curve( + KeyframedRectAnimationCurve::Create()); + curve->AddKeyframe(RectKeyframe::Create(base::TimeDelta(), rect_a, nullptr)); + curve->AddKeyframe(RectKeyframe::Create(base::Seconds(1.0), rect_b, nullptr)); + curve->AddKeyframe(RectKeyframe::Create(base::Seconds(2.0), rect_c, nullptr)); + + EXPECT_EQ(rect_a, curve->GetValue(base::Seconds(-1.f))); + EXPECT_EQ(rect_a, curve->GetValue(base::Seconds(0.f))); + EXPECT_EQ(rect_midpoint1, curve->GetValue(base::Seconds(0.5f))); + EXPECT_EQ(rect_b, curve->GetValue(base::Seconds(1.f))); + EXPECT_EQ(rect_midpoint2, curve->GetValue(base::Seconds(1.5f))); + EXPECT_EQ(rect_c, curve->GetValue(base::Seconds(2.f))); + EXPECT_EQ(rect_c, curve->GetValue(base::Seconds(3.f))); +} + +// Tests that a rect animation with multiple keys at a given time works sanely. +TEST(KeyframedAnimationCurveTest, RepeatedRectKeyFrame) { + gfx::Rect rect_a = gfx::Rect(10, 20, 100, 64); + gfx::Rect rect_b = gfx::Rect(30, 40, 100, 192); + + std::unique_ptr curve( + KeyframedRectAnimationCurve::Create()); + curve->AddKeyframe(RectKeyframe::Create(base::TimeDelta(), rect_a, nullptr)); + curve->AddKeyframe(RectKeyframe::Create(base::Seconds(1.0), rect_a, nullptr)); + curve->AddKeyframe(RectKeyframe::Create(base::Seconds(1.0), rect_b, nullptr)); + curve->AddKeyframe(RectKeyframe::Create(base::Seconds(2.0), rect_b, nullptr)); + + EXPECT_EQ(rect_a, curve->GetValue(base::Seconds(-1.f))); + EXPECT_EQ(rect_a, curve->GetValue(base::Seconds(0.f))); + EXPECT_EQ(rect_a, curve->GetValue(base::Seconds(0.5f))); + + gfx::Rect value = curve->GetValue(base::Seconds(1.0f)); + EXPECT_EQ(100, value.width()); + EXPECT_LE(64, value.height()); + EXPECT_GE(192, value.height()); + EXPECT_LE(10, value.x()); + EXPECT_GE(30, value.x()); + EXPECT_LE(20, value.y()); + EXPECT_GE(40, value.y()); + + EXPECT_EQ(rect_b, curve->GetValue(base::Seconds(1.5f))); + EXPECT_EQ(rect_b, curve->GetValue(base::Seconds(2.f))); + EXPECT_EQ(rect_b, curve->GetValue(base::Seconds(3.f))); +} + +// Tests that the computing of tick interval for STEPS TimingFunction works +// correctly. +TEST(KeyFrameAnimationCurveTest, TickIntervalForStepsTimingFunction) { + double kDuration = 1.0; + int kNumSteps = 10; + std::unique_ptr curve( + KeyframedFloatAnimationCurve::Create()); + curve->AddKeyframe(FloatKeyframe::Create(base::TimeDelta(), 2.0, nullptr)); + curve->AddKeyframe( + FloatKeyframe::Create(base::Seconds(kDuration), 4.0, nullptr)); + curve->SetTimingFunction(StepsTimingFunction::Create( + kNumSteps, StepsTimingFunction::StepPosition::START)); + EXPECT_FLOAT_EQ(kDuration / kNumSteps, curve->TickInterval().InSecondsF()); +} + +// Tests that the computing of tick interval for CUBIC_BEZIER TimingFunction +// works correctly. +TEST(KeyFrameAnimationCurveTest, TickIntervalForCubicBezierTimingFunction) { + SkColor color_a = SkColorSetARGB(255, 255, 0, 0); + SkColor color_b = SkColorSetARGB(255, 0, 255, 0); + double kDuration = 1.0; + std::unique_ptr curve( + KeyframedColorAnimationCurve::Create()); + curve->AddKeyframe( + ColorKeyframe::Create(base::TimeDelta(), color_a, nullptr)); + curve->AddKeyframe( + ColorKeyframe::Create(base::Seconds(kDuration), color_b, nullptr)); + curve->SetTimingFunction( + CubicBezierTimingFunction::Create(0.75f, 0.25f, 0.9f, 0.4f)); + EXPECT_FLOAT_EQ(0, curve->TickInterval().InSecondsF()); +} + +// Tests that the computing of tick interval for LINEAR TimingFunction works +// correctly. +TEST(KeyFrameAnimationCurveTest, TickIntervalForLinearTimingFunction) { + gfx::SizeF size_a = gfx::SizeF(100, 64); + gfx::SizeF size_b = gfx::SizeF(100, 192); + gfx::SizeF size_c = gfx::SizeF(100, 218); + gfx::SizeF size_d = gfx::SizeF(100, 321); + double kDurationAB = 1.0; + double kDurationBC = 2.0; + double kDurationCD = 1.0; + int kNumStepsAB = 10; + int kNumStepsBC = 100; + std::unique_ptr curve( + KeyframedSizeAnimationCurve::Create()); + curve->AddKeyframe(SizeKeyframe::Create( + base::TimeDelta(), size_a, + StepsTimingFunction::Create(kNumStepsAB, + StepsTimingFunction::StepPosition::START))); + curve->AddKeyframe(SizeKeyframe::Create( + base::Seconds(kDurationAB), size_b, + StepsTimingFunction::Create(kNumStepsBC, + StepsTimingFunction::StepPosition::START))); + curve->AddKeyframe(SizeKeyframe::Create( + base::Seconds(kDurationAB + kDurationBC), size_c, nullptr)); + + // Without explicitly setting a timing function, the default is linear. + EXPECT_FLOAT_EQ(kDurationBC / kNumStepsBC, + curve->TickInterval().InSecondsF()); + curve->SetTimingFunction(LinearTimingFunction::Create()); + EXPECT_FLOAT_EQ(kDurationBC / kNumStepsBC, + curve->TickInterval().InSecondsF()); + + // Add a 4th keyframe. + // Now the 3rd keyframe's "easing" into the 4th isn't STEPS. + curve->AddKeyframe(SizeKeyframe::Create( + base::Seconds(kDurationAB + kDurationBC + kDurationCD), size_d, nullptr)); + EXPECT_FLOAT_EQ(0, curve->TickInterval().InSecondsF()); +} + +} // namespace +} // namespace gfx diff --git a/animation/keyframe/target_property.h b/animation/keyframe/target_property.h new file mode 100644 index 000000000000..1c76ab41ed3c --- /dev/null +++ b/animation/keyframe/target_property.h @@ -0,0 +1,19 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_ANIMATION_KEYFRAME_TARGET_PROPERTY_H_ +#define UI_GFX_ANIMATION_KEYFRAME_TARGET_PROPERTY_H_ + +#include + +namespace gfx { + +static constexpr size_t kMaxTargetPropertyIndex = 32u; + +// A set of target properties. +using TargetProperties = std::bitset; + +} // namespace gfx + +#endif // UI_GFX_ANIMATION_KEYFRAME_TARGET_PROPERTY_H_ diff --git a/animation/keyframe/test/animation_utils.cc b/animation/keyframe/test/animation_utils.cc new file mode 100644 index 000000000000..d0b709835d4a --- /dev/null +++ b/animation/keyframe/test/animation_utils.cc @@ -0,0 +1,97 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/animation/keyframe/test/animation_utils.h" + +#include "ui/gfx/animation/keyframe/keyframed_animation_curve.h" + +namespace gfx { + +std::unique_ptr CreateTransformAnimation( + TransformAnimationCurve::Target* target, + int id, + int property_id, + const TransformOperations& from, + const TransformOperations& to, + base::TimeDelta duration) { + std::unique_ptr curve( + KeyframedTransformAnimationCurve::Create()); + curve->AddKeyframe( + TransformKeyframe::Create(base::TimeDelta(), from, nullptr)); + curve->AddKeyframe(TransformKeyframe::Create(duration, to, nullptr)); + curve->set_target(target); + std::unique_ptr keyframe_model( + KeyframeModel::Create(std::move(curve), id, property_id)); + return keyframe_model; +} + +std::unique_ptr CreateSizeAnimation( + SizeAnimationCurve::Target* target, + int id, + int property_id, + const SizeF& from, + const SizeF& to, + base::TimeDelta duration) { + std::unique_ptr curve( + KeyframedSizeAnimationCurve::Create()); + curve->AddKeyframe(SizeKeyframe::Create(base::TimeDelta(), from, nullptr)); + curve->AddKeyframe(SizeKeyframe::Create(duration, to, nullptr)); + curve->set_target(target); + std::unique_ptr keyframe_model( + KeyframeModel::Create(std::move(curve), id, property_id)); + return keyframe_model; +} + +std::unique_ptr CreateFloatAnimation( + FloatAnimationCurve::Target* target, + int id, + int property_id, + float from, + float to, + base::TimeDelta duration) { + std::unique_ptr curve( + KeyframedFloatAnimationCurve::Create()); + curve->AddKeyframe(FloatKeyframe::Create(base::TimeDelta(), from, nullptr)); + curve->AddKeyframe(FloatKeyframe::Create(duration, to, nullptr)); + curve->set_target(target); + std::unique_ptr keyframe_model( + KeyframeModel::Create(std::move(curve), id, property_id)); + return keyframe_model; +} + +std::unique_ptr CreateColorAnimation( + ColorAnimationCurve::Target* target, + int id, + int property_id, + SkColor from, + SkColor to, + base::TimeDelta duration) { + std::unique_ptr curve( + KeyframedColorAnimationCurve::Create()); + curve->AddKeyframe(ColorKeyframe::Create(base::TimeDelta(), from, nullptr)); + curve->AddKeyframe(ColorKeyframe::Create(duration, to, nullptr)); + curve->set_target(target); + std::unique_ptr keyframe_model( + KeyframeModel::Create(std::move(curve), id, property_id)); + return keyframe_model; +} + +base::TimeTicks MicrosecondsToTicks(uint64_t us) { + base::TimeTicks to_return; + return base::Microseconds(us) + to_return; +} + +base::TimeDelta MicrosecondsToDelta(uint64_t us) { + return base::Microseconds(us); +} + +base::TimeTicks MsToTicks(uint64_t ms) { + return MicrosecondsToTicks(1000 * ms); +} + +base::TimeDelta MsToDelta(uint64_t ms) { + return MicrosecondsToDelta(1000 * ms); +} + +} // namespace gfx diff --git a/animation/keyframe/test/animation_utils.h b/animation/keyframe/test/animation_utils.h new file mode 100644 index 000000000000..355c3edab8b0 --- /dev/null +++ b/animation/keyframe/test/animation_utils.h @@ -0,0 +1,56 @@ + +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_ANIMATION_KEYFRAME_TEST_ANIMATION_UTILS_H_ +#define UI_GFX_ANIMATION_KEYFRAME_TEST_ANIMATION_UTILS_H_ + +#include + +#include "ui/gfx/animation/keyframe/keyframe_model.h" +#include "ui/gfx/animation/keyframe/keyframed_animation_curve.h" + +namespace gfx { + +std::unique_ptr CreateTransformAnimation( + TransformAnimationCurve::Target* target, + int id, + int property_id, + const TransformOperations& from, + const TransformOperations& to, + base::TimeDelta duration); + +std::unique_ptr CreateSizeAnimation( + SizeAnimationCurve::Target* target, + int id, + int property_id, + const SizeF& from, + const SizeF& to, + base::TimeDelta duration); + +std::unique_ptr CreateFloatAnimation( + FloatAnimationCurve::Target* target, + int id, + int property_id, + float from, + float to, + base::TimeDelta duration); + +std::unique_ptr CreateColorAnimation( + ColorAnimationCurve::Target* target, + int id, + int property_id, + SkColor from, + SkColor to, + base::TimeDelta duration); + +base::TimeTicks MicrosecondsToTicks(uint64_t us); +base::TimeDelta MicrosecondsToDelta(uint64_t us); + +base::TimeTicks MsToTicks(uint64_t us); +base::TimeDelta MsToDelta(uint64_t us); + +} // namespace gfx + +#endif // UI_GFX_ANIMATION_KEYFRAME_TEST_ANIMATION_UTILS_H_ diff --git a/animation/keyframe/timing_function.cc b/animation/keyframe/timing_function.cc new file mode 100644 index 000000000000..9650a501bd68 --- /dev/null +++ b/animation/keyframe/timing_function.cc @@ -0,0 +1,182 @@ +// Copyright 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/animation/keyframe/timing_function.h" + +#include +#include + +#include "base/check_op.h" +#include "base/memory/ptr_util.h" +#include "base/notreached.h" + +namespace gfx { + +TimingFunction::TimingFunction() = default; + +TimingFunction::~TimingFunction() = default; + +std::unique_ptr +CubicBezierTimingFunction::CreatePreset(EaseType ease_type) { + // These numbers come from + // http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag. + switch (ease_type) { + case EaseType::EASE: + return base::WrapUnique( + new CubicBezierTimingFunction(ease_type, 0.25, 0.1, 0.25, 1.0)); + case EaseType::EASE_IN: + return base::WrapUnique( + new CubicBezierTimingFunction(ease_type, 0.42, 0.0, 1.0, 1.0)); + case EaseType::EASE_OUT: + return base::WrapUnique( + new CubicBezierTimingFunction(ease_type, 0.0, 0.0, 0.58, 1.0)); + case EaseType::EASE_IN_OUT: + return base::WrapUnique( + new CubicBezierTimingFunction(ease_type, 0.42, 0.0, 0.58, 1)); + default: + NOTREACHED(); + return nullptr; + } +} +std::unique_ptr +CubicBezierTimingFunction::Create(double x1, double y1, double x2, double y2) { + return base::WrapUnique( + new CubicBezierTimingFunction(EaseType::CUSTOM, x1, y1, x2, y2)); +} + +CubicBezierTimingFunction::CubicBezierTimingFunction(EaseType ease_type, + double x1, + double y1, + double x2, + double y2) + : bezier_(x1, y1, x2, y2), ease_type_(ease_type) {} + +CubicBezierTimingFunction::~CubicBezierTimingFunction() = default; + +TimingFunction::Type CubicBezierTimingFunction::GetType() const { + return Type::CUBIC_BEZIER; +} + +double CubicBezierTimingFunction::GetValue(double x) const { + return bezier_.Solve(x); +} + +double CubicBezierTimingFunction::Velocity(double x) const { + return bezier_.Slope(x); +} + +std::unique_ptr CubicBezierTimingFunction::Clone() const { + return base::WrapUnique(new CubicBezierTimingFunction(*this)); +} + +std::unique_ptr StepsTimingFunction::Create( + int steps, + StepPosition step_position) { + return base::WrapUnique(new StepsTimingFunction(steps, step_position)); +} + +StepsTimingFunction::StepsTimingFunction(int steps, StepPosition step_position) + : steps_(steps), step_position_(step_position) {} + +StepsTimingFunction::~StepsTimingFunction() = default; + +TimingFunction::Type StepsTimingFunction::GetType() const { + return Type::STEPS; +} + +double StepsTimingFunction::GetValue(double t) const { + return GetPreciseValue(t, TimingFunction::LimitDirection::RIGHT); +} + +std::unique_ptr StepsTimingFunction::Clone() const { + return base::WrapUnique(new StepsTimingFunction(*this)); +} + +double StepsTimingFunction::Velocity(double x) const { + return 0; +} + +double StepsTimingFunction::GetPreciseValue(double t, + LimitDirection direction) const { + const double steps = static_cast(steps_); + double current_step = std::floor((steps * t) + GetStepsStartOffset()); + // Adjust step if using a left limit at a discontinuous step boundary. + if (direction == LimitDirection::LEFT && + steps * t - std::floor(steps * t) == 0) { + current_step -= 1; + } + // Jumps may differ from steps based on the number of end-point + // discontinuities, which may be 0, 1 or 2. + int jumps = NumberOfJumps(); + if (t >= 0 && current_step < 0) + current_step = 0; + if (t <= 1 && current_step > jumps) + current_step = jumps; + return current_step / jumps; +} + +int StepsTimingFunction::NumberOfJumps() const { + switch (step_position_) { + case StepPosition::END: + case StepPosition::START: + case StepPosition::JUMP_END: + case StepPosition::JUMP_START: + return steps_; + + case StepPosition::JUMP_BOTH: + return steps_ + 1; + + case StepPosition::JUMP_NONE: + DCHECK_GT(steps_, 1); + return steps_ - 1; + + default: + NOTREACHED(); + return steps_; + } +} + +float StepsTimingFunction::GetStepsStartOffset() const { + switch (step_position_) { + case StepPosition::JUMP_BOTH: + case StepPosition::JUMP_START: + case StepPosition::START: + return 1; + + case StepPosition::JUMP_END: + case StepPosition::JUMP_NONE: + case StepPosition::END: + return 0; + + default: + NOTREACHED(); + return 1; + } +} + +std::unique_ptr LinearTimingFunction::Create() { + return base::WrapUnique(new LinearTimingFunction()); +} + +LinearTimingFunction::LinearTimingFunction() = default; + +LinearTimingFunction::~LinearTimingFunction() = default; + +TimingFunction::Type LinearTimingFunction::GetType() const { + return Type::LINEAR; +} + +std::unique_ptr LinearTimingFunction::Clone() const { + return base::WrapUnique(new LinearTimingFunction(*this)); +} + +double LinearTimingFunction::Velocity(double x) const { + return 0; +} + +double LinearTimingFunction::GetValue(double t) const { + return t; +} + +} // namespace gfx diff --git a/animation/keyframe/timing_function.h b/animation/keyframe/timing_function.h new file mode 100644 index 000000000000..7ce71331dfe7 --- /dev/null +++ b/animation/keyframe/timing_function.h @@ -0,0 +1,143 @@ +// Copyright 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_ANIMATION_KEYFRAME_TIMING_FUNCTION_H_ +#define UI_GFX_ANIMATION_KEYFRAME_TIMING_FUNCTION_H_ + +#include + +#include "ui/gfx/animation/keyframe/keyframe_animation_export.h" +#include "ui/gfx/geometry/cubic_bezier.h" + +namespace gfx { + +// See http://www.w3.org/TR/css3-transitions/. +class GFX_KEYFRAME_ANIMATION_EXPORT TimingFunction { + public: + virtual ~TimingFunction(); + + TimingFunction& operator=(const TimingFunction&) = delete; + + // Note that LINEAR is a nullptr TimingFunction (for now). + enum class Type { LINEAR, CUBIC_BEZIER, STEPS }; + + // Which limit to apply at a discontinuous boundary. + enum class LimitDirection { LEFT, RIGHT }; + + virtual Type GetType() const = 0; + virtual double GetValue(double t) const = 0; + virtual double Velocity(double time) const = 0; + virtual std::unique_ptr Clone() const = 0; + + protected: + TimingFunction(); +}; + +class GFX_KEYFRAME_ANIMATION_EXPORT CubicBezierTimingFunction + : public TimingFunction { + public: + enum class EaseType { EASE, EASE_IN, EASE_OUT, EASE_IN_OUT, CUSTOM }; + + static std::unique_ptr CreatePreset( + EaseType ease_type); + static std::unique_ptr Create(double x1, + double y1, + double x2, + double y2); + ~CubicBezierTimingFunction() override; + + CubicBezierTimingFunction& operator=(const CubicBezierTimingFunction&) = + delete; + + // TimingFunction implementation. + Type GetType() const override; + double GetValue(double time) const override; + double Velocity(double time) const override; + std::unique_ptr Clone() const override; + + EaseType ease_type() const { return ease_type_; } + const gfx::CubicBezier& bezier() const { return bezier_; } + + private: + CubicBezierTimingFunction(EaseType ease_type, + double x1, + double y1, + double x2, + double y2); + + gfx::CubicBezier bezier_; + EaseType ease_type_; +}; + +class GFX_KEYFRAME_ANIMATION_EXPORT StepsTimingFunction + : public TimingFunction { + public: + // step-timing-function values + // https://drafts.csswg.org/css-easing-1/#typedef-step-timing-function + enum class StepPosition { + START, // Discontinuity at progress = 0. + // Alias for jump-start. Maintaining a separate enumerated value + // for serialization. + END, // Discontinuity at progress = 1. + // Alias for jump-end. Maintaining a separate enumerated value + // for serialization. + JUMP_BOTH, // Discontinuities at progress = 0 and 1. + JUMP_END, // Discontinuity at progress = 1. + JUMP_NONE, // Continuous at progress = 0 and 1. + JUMP_START // Discontinuity at progress = 0. + }; + + static std::unique_ptr Create( + int steps, + StepPosition step_position); + ~StepsTimingFunction() override; + + StepsTimingFunction& operator=(const StepsTimingFunction&) = delete; + + // TimingFunction implementation. + Type GetType() const override; + double GetValue(double t) const override; + std::unique_ptr Clone() const override; + double Velocity(double time) const override; + + int steps() const { return steps_; } + StepPosition step_position() const { return step_position_; } + double GetPreciseValue(double t, LimitDirection limit_direction) const; + + private: + StepsTimingFunction(int steps, StepPosition step_position); + + // The number of jumps is the number of discontinuities in the timing + // function. There is a subtle distinction between the number of steps and + // jumps. The number of steps is the number of intervals in the timing + // function. The number of jumps differs from the number of steps when either + // both or neither end point has a discontinuity. + // https://drafts.csswg.org/css-easing-1/#step-easing-functions + int NumberOfJumps() const; + + float GetStepsStartOffset() const; + + int steps_; + StepPosition step_position_; +}; + +class GFX_KEYFRAME_ANIMATION_EXPORT LinearTimingFunction + : public TimingFunction { + public: + static std::unique_ptr Create(); + ~LinearTimingFunction() override; + + // TimingFunction implementation. + Type GetType() const override; + double GetValue(double t) const override; + std::unique_ptr Clone() const override; + double Velocity(double time) const override; + + private: + LinearTimingFunction(); +}; + +} // namespace gfx + +#endif // UI_GFX_ANIMATION_KEYFRAME_TIMING_FUNCTION_H_ diff --git a/animation/keyframe/transition.cc b/animation/keyframe/transition.cc new file mode 100644 index 000000000000..f2305f05c1b2 --- /dev/null +++ b/animation/keyframe/transition.cc @@ -0,0 +1,20 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/animation/keyframe/transition.h" + +namespace gfx { + +namespace { +static constexpr int kDefaultTransitionDurationMs = 225; +} // namespace + +Transition::Transition() + : duration(base::Milliseconds(kDefaultTransitionDurationMs)) {} + +Transition::Transition(const Transition&) = default; +Transition::Transition(Transition&&) = default; +Transition::~Transition() = default; + +} // namespace gfx diff --git a/animation/keyframe/transition.h b/animation/keyframe/transition.h new file mode 100644 index 000000000000..d902f918cee1 --- /dev/null +++ b/animation/keyframe/transition.h @@ -0,0 +1,30 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_ANIMATION_KEYFRAME_TRANSITION_H_ +#define UI_GFX_ANIMATION_KEYFRAME_TRANSITION_H_ + +#include + +#include "base/time/time.h" +#include "ui/gfx/animation/keyframe/keyframe_animation_export.h" + +namespace gfx { + +struct GFX_KEYFRAME_ANIMATION_EXPORT Transition { + Transition(); + Transition(const Transition&); + Transition(Transition&&); + ~Transition(); + + Transition& operator=(const Transition&) = default; + Transition& operator=(Transition&&) = default; + + base::TimeDelta duration; + std::set target_properties; +}; + +} // namespace gfx + +#endif // UI_GFX_ANIMATION_KEYFRAME_TRANSITION_H_ diff --git a/animation/linear_animation.cc b/animation/linear_animation.cc new file mode 100644 index 000000000000..39f367169ff4 --- /dev/null +++ b/animation/linear_animation.cc @@ -0,0 +1,109 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/animation/linear_animation.h" + +#include +#include + +#include + +#include "base/command_line.h" +#include "base/cxx17_backports.h" +#include "base/strings/string_number_conversions.h" +#include "ui/gfx/animation/animation_container.h" +#include "ui/gfx/animation/animation_delegate.h" +#include "ui/gfx/switches.h" + +namespace gfx { + +static base::TimeDelta CalculateInterval(int frame_rate) { + int timer_interval = 1000000 / frame_rate; + if (timer_interval < 10000) + timer_interval = 10000; + return base::Microseconds(timer_interval); +} + +const int LinearAnimation::kDefaultFrameRate = 60; + +LinearAnimation::LinearAnimation(AnimationDelegate* delegate, int frame_rate) + : LinearAnimation({}, frame_rate, delegate) {} + +LinearAnimation::LinearAnimation(base::TimeDelta duration, + int frame_rate, + AnimationDelegate* delegate) + : Animation(CalculateInterval(frame_rate)), state_(0.0), in_end_(false) { + set_delegate(delegate); + SetDuration(duration); +} + +double LinearAnimation::GetCurrentValue() const { + // Default is linear relationship, subclass to adapt. + return state_; +} + +void LinearAnimation::SetCurrentValue(double new_value) { + new_value = base::clamp(new_value, 0.0, 1.0); + const base::TimeDelta time_delta = duration_ * (new_value - state_); + SetStartTime(start_time() - time_delta); + state_ = new_value; +} + +void LinearAnimation::End() { + if (!is_animating()) + return; + + // NOTE: We don't use AutoReset here as Stop may end up deleting us (by way + // of the delegate). + in_end_ = true; + Stop(); +} + +void LinearAnimation::SetDuration(base::TimeDelta duration) { + static const double duration_scale_factor = []() { + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); + double animation_duration_scale; + return (base::StringToDouble(command_line->GetSwitchValueASCII( + switches::kAnimationDurationScale), + &animation_duration_scale) && + (animation_duration_scale >= 0.0)) + ? animation_duration_scale + : 1.0; + }(); + duration_ = std::max(duration * duration_scale_factor, timer_interval()); + if (is_animating()) + SetStartTime(container()->last_tick_time()); +} + +void LinearAnimation::Step(base::TimeTicks time_now) { + state_ = std::min((time_now - start_time()) / duration_, 1.0); + + AnimateToState(state_); + + if (delegate()) + delegate()->AnimationProgressed(this); + + if (state_ == 1.0) + Stop(); +} + +void LinearAnimation::AnimationStarted() { + state_ = 0.0; +} + +void LinearAnimation::AnimationStopped() { + if (!in_end_) + return; + + in_end_ = false; + // Set state_ to ensure we send ended to delegate and not canceled. + state_ = 1; + AnimateToState(1.0); +} + +bool LinearAnimation::ShouldSendCanceledFromStop() { + return state_ != 1; +} + +} // namespace gfx diff --git a/animation/linear_animation.h b/animation/linear_animation.h new file mode 100644 index 000000000000..d313ecd78a51 --- /dev/null +++ b/animation/linear_animation.h @@ -0,0 +1,90 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_ANIMATION_LINEAR_ANIMATION_H_ +#define UI_GFX_ANIMATION_LINEAR_ANIMATION_H_ + +#include "base/macros.h" +#include "base/time/time.h" +#include "ui/gfx/animation/animation.h" + +namespace gfx { + +class AnimationDelegate; + +// Linear time bounded animation. As the animation progresses AnimateToState is +// invoked. +class ANIMATION_EXPORT LinearAnimation : public Animation { + public: + // Default frame rate (hz). + static const int kDefaultFrameRate; + + // Initializes everything except the duration. + // + // Caller must make sure to call SetDuration() if they use this + // constructor; it is preferable to use the full one, but sometimes + // duration can change between calls to Start() and we need to + // expose this interface. + explicit LinearAnimation(AnimationDelegate* delegate, + int frame_rate = kDefaultFrameRate); + + // Initializes all fields. + LinearAnimation(base::TimeDelta duration, + int frame_rate, + AnimationDelegate* delegate); + + LinearAnimation(const LinearAnimation&) = delete; + LinearAnimation& operator=(const LinearAnimation&) = delete; + + // Gets the value for the current state, according to the animation curve in + // use. This class provides only for a linear relationship, however subclasses + // can override this to provide others. + double GetCurrentValue() const override; + + // Change the current state of the animation to |new_value|. + void SetCurrentValue(double new_value); + + // Skip to the end of the current animation. + void End(); + + // Changes the length of the animation. This resets the current + // state of the animation to the beginning. This value will be multiplied by + // the currently set scale factor. + void SetDuration(base::TimeDelta duration); + + protected: + // Called when the animation progresses. Subclasses override this to + // efficiently update their state. + virtual void AnimateToState(double state) {} + + // Invoked by the AnimationContainer when the animation is running to advance + // the animation. Use |time_now| rather than Time::Now to avoid multiple + // animations running at the same time diverging. + void Step(base::TimeTicks time_now) override; + + // Overriden to initialize state. + void AnimationStarted() override; + + // Overriden to advance to the end (if End was invoked). + void AnimationStopped() override; + + // Overriden to return true if state is not 1. + bool ShouldSendCanceledFromStop() override; + + base::TimeDelta duration() const { return duration_; } + + private: + base::TimeDelta duration_; + + // Current state, on a scale from 0.0 to 1.0. + double state_; + + // If true, we're in end. This is used to determine if the animation should + // be advanced to the end from AnimationStopped. + bool in_end_; +}; + +} // namespace gfx + +#endif // UI_GFX_ANIMATION_LINEAR_ANIMATION_H_ diff --git a/animation/multi_animation.cc b/animation/multi_animation.cc new file mode 100644 index 000000000000..09b4423a3fa8 --- /dev/null +++ b/animation/multi_animation.cc @@ -0,0 +1,88 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/animation/multi_animation.h" + +#include + +#include "base/check_op.h" +#include "base/notreached.h" +#include "ui/gfx/animation/animation_delegate.h" + +namespace gfx { + +static base::TimeDelta TotalTime(const MultiAnimation::Parts& parts) { + return std::accumulate(parts.cbegin(), parts.cend(), base::TimeDelta(), + [](base::TimeDelta total, const auto& part) { + return total + part.length; + }); +} + +// static +constexpr base::TimeDelta MultiAnimation::kDefaultTimerInterval; + +MultiAnimation::MultiAnimation(const Parts& parts, + base::TimeDelta timer_interval) + : Animation(timer_interval), parts_(parts), cycle_time_(TotalTime(parts)) { + DCHECK(!parts_.empty()); +} + +MultiAnimation::~MultiAnimation() = default; + +double MultiAnimation::GetCurrentValue() const { + const Part& current_part = parts_[current_part_index_]; + return Tween::DoubleValueBetween( + Tween::CalculateValue(current_part.type, current_part_state_), + current_part.start_value, current_part.end_value); +} + +void MultiAnimation::Step(base::TimeTicks time_now) { + double last_value = GetCurrentValue(); + size_t last_index = current_part_index_; + + base::TimeDelta delta = time_now - start_time(); + bool should_stop = delta >= cycle_time_ && !continuous_; + if (should_stop) { + current_part_index_ = parts_.size() - 1; + current_part_state_ = 1.0; + } else { + delta %= cycle_time_; + const Part& part = GetPart(&delta, ¤t_part_index_); + current_part_state_ = delta / part.length; + DCHECK_LE(current_part_state_, 1); + } + + if ((GetCurrentValue() != last_value || current_part_index_ != last_index) && + delegate()) { + // Run AnimationProgressed() even if the animation will be stopped, so that + // the animation runs its final frame. + delegate()->AnimationProgressed(this); + } + if (should_stop) + Stop(); +} + +void MultiAnimation::SetStartTime(base::TimeTicks start_time) { + Animation::SetStartTime(start_time); + current_part_state_ = 0.0; + current_part_index_ = 0; +} + +const MultiAnimation::Part& MultiAnimation::GetPart(base::TimeDelta* time, + size_t* part_index) { + DCHECK_LT(*time, cycle_time_); + + for (size_t i = 0; i < parts_.size(); ++i) { + if (*time < parts_[i].length) { + *part_index = i; + return parts_[i]; + } + + *time -= parts_[i].length; + } + NOTREACHED(); + return parts_[0]; +} + +} // namespace gfx diff --git a/animation/multi_animation.h b/animation/multi_animation.h new file mode 100644 index 000000000000..870e92f5cdf8 --- /dev/null +++ b/animation/multi_animation.h @@ -0,0 +1,112 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_ANIMATION_MULTI_ANIMATION_H_ +#define UI_GFX_ANIMATION_MULTI_ANIMATION_H_ + +#include + +#include + +#include "base/macros.h" +#include "base/time/time.h" +#include "ui/gfx/animation/animation.h" +#include "ui/gfx/animation/tween.h" + +namespace gfx { + +// MultiAnimation is an animation that consists of a number of sub animations. +// To create a MultiAnimation pass in the parts, invoke Start() and the delegate +// is notified as the animation progresses. By default MultiAnimation runs until +// Stop is invoked, see |set_continuous()| for details. +class ANIMATION_EXPORT MultiAnimation : public Animation { + public: + // Defines part of the animation. Each part consists of the following: + // + // part_length: the length of time the part runs. + // part_start: the amount of time to offset this part by when calculating the + // initial percentage. + // total_length: the total length used to calculate the percentange completed. + // start_value: the animation value at the beginning of this part of the + // animation. Defaults to 0. + // end_value: the animation value at the end of this part of the animation. + // Defaults to 1. + // + // In most cases |part_start| is empty and |total_length| = |part_length|. But + // you can adjust the start/total for different effects. For example, to run a + // part for 200ms with a % between .25 and .75 use the following three values: + // part_length = 200, part_start = 100, total_length = 400. + // + // |start_value| and |end_value| can be used to chain multiple animations into + // a single function. A common use case is a MultiAnimation that consists of + // these parts: 0->1 (fade-in), 1->1 (hold) and 1->0 (fade out). + struct Part { + Part(base::TimeDelta length, + Tween::Type type, + double start_value = 0.0, + double end_value = 1.0) + : length(length), + type(type), + start_value(start_value), + end_value(end_value) {} + + base::TimeDelta length; + Tween::Type type; + double start_value; + double end_value; + }; + using Parts = std::vector; + + static constexpr auto kDefaultTimerInterval = base::Milliseconds(20); + + explicit MultiAnimation( + const Parts& parts, + base::TimeDelta timer_interval = kDefaultTimerInterval); + + MultiAnimation(const MultiAnimation&) = delete; + MultiAnimation& operator=(const MultiAnimation&) = delete; + + ~MultiAnimation() override; + + // Sets whether the animation continues after it reaches the end. If true, the + // animation runs until explicitly stopped. The default is true. + void set_continuous(bool continuous) { continuous_ = continuous; } + + // Returns the current value. The current value for a MultiAnimation is + // determined from the tween type of the current part. + double GetCurrentValue() const override; + + // Returns the index of the current part. + size_t current_part_index() const { return current_part_index_; } + + protected: + // Animation overrides. + void Step(base::TimeTicks time_now) override; + void SetStartTime(base::TimeTicks start_time) override; + + private: + // Returns the part containing the specified time. |time| is reset to be + // relative to the part containing the time and |part_index| the index of the + // part. + const Part& GetPart(base::TimeDelta* time, size_t* part_index); + + // The parts that make up the animation. + const Parts parts_; + + // Total time of all the parts. + const base::TimeDelta cycle_time_; + + // Animation state for the current part. + double current_part_state_ = 0.0; + + // Index of the current part. + size_t current_part_index_ = 0; + + // See description above setter. + bool continuous_ = true; +}; + +} // namespace gfx + +#endif // UI_GFX_ANIMATION_MULTI_ANIMATION_H_ diff --git a/animation/multi_animation_unittest.cc b/animation/multi_animation_unittest.cc new file mode 100644 index 000000000000..dec64d98ffea --- /dev/null +++ b/animation/multi_animation_unittest.cc @@ -0,0 +1,135 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/animation/multi_animation.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/animation/animation_container_element.h" +#include "ui/gfx/animation/animation_delegate.h" + +namespace gfx { + +TEST(MultiAnimationTest, Basic) { + // Create a MultiAnimation with two parts. + MultiAnimation::Parts parts; + parts.push_back(MultiAnimation::Part(base::Milliseconds(100), Tween::LINEAR)); + parts.push_back( + MultiAnimation::Part(base::Milliseconds(100), Tween::EASE_OUT)); + + MultiAnimation animation(parts); + AnimationContainerElement* as_element = + static_cast(&animation); + as_element->SetStartTime(base::TimeTicks()); + + // Step to 50, which is half way through the first part. + as_element->Step(base::TimeTicks() + base::Milliseconds(50)); + EXPECT_EQ(.5, animation.GetCurrentValue()); + + // Step to 120, which is 20% through the second part. + as_element->Step(base::TimeTicks() + base::Milliseconds(120)); + EXPECT_DOUBLE_EQ(Tween::CalculateValue(Tween::EASE_OUT, .2), + animation.GetCurrentValue()); + + // Step to 320, which is 20% through the second part. + as_element->Step(base::TimeTicks() + base::Milliseconds(320)); + EXPECT_DOUBLE_EQ(Tween::CalculateValue(Tween::EASE_OUT, .2), + animation.GetCurrentValue()); +} + +// Makes sure multi-animation stops if cycles is false. +TEST(MultiAnimationTest, DontCycle) { + MultiAnimation::Parts parts; + parts.push_back(MultiAnimation::Part(base::Milliseconds(200), Tween::LINEAR)); + MultiAnimation animation(parts); + AnimationContainerElement* as_element = + static_cast(&animation); + as_element->SetStartTime(base::TimeTicks()); + animation.set_continuous(false); + + // Step to 300, which is greater than the cycle time. + as_element->Step(base::TimeTicks() + base::Milliseconds(300)); + EXPECT_EQ(1.0, animation.GetCurrentValue()); + EXPECT_FALSE(animation.is_animating()); +} + +class CurrentValueDelegate : public AnimationDelegate { + public: + CurrentValueDelegate() = default; + + double latest_current_value() { return latest_current_value_; } + + // AnimationDelegate overrides: + void AnimationProgressed(const Animation* animation) override { + latest_current_value_ = animation->GetCurrentValue(); + } + + private: + double latest_current_value_ = 0.0; +}; + +// Makes sure multi-animation runs the final frame when exceeding the cycle time +// and not running continuously. +TEST(MultiAnimationTest, ExceedCycleNonContinuous) { + MultiAnimation::Parts parts; + parts.push_back(MultiAnimation::Part(base::Milliseconds(200), Tween::LINEAR)); + MultiAnimation animation(parts); + CurrentValueDelegate delegate; + animation.set_delegate(&delegate); + animation.set_continuous(false); + AnimationContainerElement* as_element = + static_cast(&animation); + as_element->SetStartTime(base::TimeTicks()); + + // Step to 300, which is greater than the cycle time. + as_element->Step(base::TimeTicks() + base::Milliseconds(300)); + EXPECT_EQ(1.0, delegate.latest_current_value()); +} + +// Makes sure multi-animation cycles correctly. +TEST(MultiAnimationTest, Cycle) { + MultiAnimation::Parts parts; + parts.push_back(MultiAnimation::Part(base::Milliseconds(200), Tween::LINEAR)); + MultiAnimation animation(parts); + AnimationContainerElement* as_element = + static_cast(&animation); + as_element->SetStartTime(base::TimeTicks()); + + // Step to 300, which is greater than the cycle time. + as_element->Step(base::TimeTicks() + base::Milliseconds(300)); + EXPECT_EQ(.5, animation.GetCurrentValue()); +} + +// Make sure MultiAnimation::GetCurrentValue is derived from the start and end +// of the current MultiAnimation::Part. +TEST(MultiAnimationTest, GetCurrentValueDerivedFromStartAndEndOfCurrentPart) { + // Create a MultiAnimation with two parts. The second part goes from 0.8 to + // 0.4 instead of the default 0 -> 1. + constexpr double kSecondPartStart = 0.8; + constexpr double kSecondPartEnd = 0.4; + MultiAnimation::Parts parts; + parts.push_back(MultiAnimation::Part(base::Milliseconds(100), Tween::LINEAR)); + parts.push_back(MultiAnimation::Part(base::Milliseconds(100), Tween::EASE_OUT, + kSecondPartStart, kSecondPartEnd)); + + MultiAnimation animation(parts); + animation.set_continuous(false); + AnimationContainerElement* as_element = + static_cast(&animation); + as_element->SetStartTime(base::TimeTicks()); + + // Step to 150, which is half way through the second part. + as_element->Step(base::TimeTicks() + base::Milliseconds(150)); + const double current_animation_value = + Tween::CalculateValue(Tween::EASE_OUT, .5); + EXPECT_DOUBLE_EQ(Tween::DoubleValueBetween(current_animation_value, + kSecondPartStart, kSecondPartEnd), + animation.GetCurrentValue()); + + // Step to 200 which is at the end. The final value should now be kPartEnd as + // the animation is not continuous. + as_element->Step(base::TimeTicks() + base::Milliseconds(200)); + EXPECT_DOUBLE_EQ(kSecondPartEnd, animation.GetCurrentValue()); +} + +} // namespace gfx diff --git a/animation/slide_animation.cc b/animation/slide_animation.cc new file mode 100644 index 000000000000..d525fbb1ed1b --- /dev/null +++ b/animation/slide_animation.cc @@ -0,0 +1,89 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/animation/slide_animation.h" + +#include + +#include "base/cxx17_backports.h" +#include "ui/gfx/animation/animation_delegate.h" + +namespace gfx { + +SlideAnimation::SlideAnimation(AnimationDelegate* target) + : LinearAnimation(target), target_(target) {} + +SlideAnimation::~SlideAnimation() = default; + +void SlideAnimation::Reset(double value) { + direction_ = absl::nullopt; + value_current_ = value; + Stop(); +} + +void SlideAnimation::Show() { + BeginAnimating(Direction::kShowing); +} + +void SlideAnimation::Hide() { + BeginAnimating(Direction::kHiding); +} + +void SlideAnimation::SetSlideDuration(base::TimeDelta duration) { + slide_duration_ = duration; +} + +void SlideAnimation::SetDampeningValue(double dampening_value) { + dampening_value_ = dampening_value; +} + +double SlideAnimation::GetCurrentValue() const { + return value_current_; +} + +base::TimeDelta SlideAnimation::GetDuration() { + const double current_progress = + direction_ == Direction::kShowing ? value_current_ : 1.0 - value_current_; + + return slide_duration_ * (1 - pow(current_progress, dampening_value_)); +} + +void SlideAnimation::BeginAnimating(Direction direction) { + if (direction_ == direction) + return; + + direction_ = direction; + value_start_ = value_current_; + value_end_ = (direction_ == Direction::kShowing) ? 1.0 : 0.0; + + // Make sure we actually have something to do. + if (slide_duration_.is_zero()) { + AnimateToState(1.0); // Skip to the end of the animation. + if (delegate()) { + delegate()->AnimationProgressed(this); + delegate()->AnimationEnded(this); + } + } else if (value_current_ != value_end_) { + // This will also reset the currently-occurring animation. + SetDuration(GetDuration()); + Start(); + } +} + +void SlideAnimation::AnimateToState(double state) { + state = Tween::CalculateValue(tween_type_, base::clamp(state, 0.0, 1.0)); + if (state == 1.0) + direction_ = absl::nullopt; + + value_current_ = value_start_ + (value_end_ - value_start_) * state; + + // Correct for any overshoot (while state may be capped at 1.0, let's not + // take any rounding error chances. + if ((value_end_ >= value_start_) ? (value_current_ > value_end_) + : (value_current_ < value_end_)) { + value_current_ = value_end_; + } +} + +} // namespace gfx diff --git a/animation/slide_animation.h b/animation/slide_animation.h new file mode 100644 index 000000000000..35e86f6205e2 --- /dev/null +++ b/animation/slide_animation.h @@ -0,0 +1,134 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_ANIMATION_SLIDE_ANIMATION_H_ +#define UI_GFX_ANIMATION_SLIDE_ANIMATION_H_ + +#include "base/macros.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/gfx/animation/linear_animation.h" +#include "ui/gfx/animation/tween.h" + +namespace gfx { + +// Slide Animation +// +// Used for reversible animations and as a general helper class. Typical usage: +// +// #include "ui/gfx/animation/slide_animation.h" +// +// class MyClass : public AnimationDelegate { +// public: +// MyClass() { +// animation_ = std::make_unique(this); +// animation_->SetSlideDuration(base::Milliseconds(500)); +// } +// void OnMouseOver() { +// animation_->Show(); +// } +// void OnMouseOut() { +// animation_->Hide(); +// } +// void AnimationProgressed(const Animation* animation) { +// if (animation == animation_.get()) { +// Layout(); +// SchedulePaint(); +// } else if (animation == other_animation_.get()) { +// ... +// } +// } +// void Layout() { +// if (animation_->is_animating()) { +// hover_image_.SetOpacity(animation_->GetCurrentValue()); +// } +// } +// private: +// std::unique_ptr animation_; +// } +class ANIMATION_EXPORT SlideAnimation : public LinearAnimation { + public: + explicit SlideAnimation(AnimationDelegate* target); + + SlideAnimation(const SlideAnimation&) = delete; + SlideAnimation& operator=(const SlideAnimation&) = delete; + + ~SlideAnimation() override; + + // Set the animation to some state. + virtual void Reset(double value = 0); + + // Begin a showing animation or reverse a hiding animation in progress. + // Animates GetCurrentValue() towards 1. + virtual void Show(); + + // Begin a hiding animation or reverse a showing animation in progress. + // Animates GetCurrentValue() towards 0. + virtual void Hide(); + + // Sets the time a slide will take. Note that this isn't actually + // the amount of time an animation will take as the current value of + // the slide is considered. + virtual void SetSlideDuration(base::TimeDelta duration); + base::TimeDelta GetSlideDuration() const { return slide_duration_; } + void SetTweenType(Tween::Type tween_type) { tween_type_ = tween_type; } + + // Dampens the reduction in duration for an animation which starts partway. + // The default value of 1 has no effect. + void SetDampeningValue(double dampening_value); + + double GetCurrentValue() const override; + // TODO(bruthig): Fix IsShowing() and IsClosing() to be consistent. e.g. + // IsShowing() will currently return true after the 'show' animation has been + // completed however IsClosing() will return false after the 'hide' animation + // has been completed. + bool IsShowing() const { + return direction_ == Direction::kShowing || + (!direction_ && value_current_ == 1); + } + bool IsClosing() const { + return direction_ == Direction::kHiding && value_end_ < value_current_; + } + + class TestApi; + + private: + // Gets the duration based on the dampening factor and whether the animation + // is showing or hiding. + base::TimeDelta GetDuration(); + + enum class Direction { + kShowing, + kHiding, + }; + + // Implementation of Show() and Hide(). + void BeginAnimating(Direction direction); + + // Overridden from Animation. + void AnimateToState(double state) override; + + AnimationDelegate* target_; + + Tween::Type tween_type_ = Tween::EASE_OUT; + + // Current animation direction, or nullopt if not animating. + absl::optional direction_; + + // Animation values. These are a layer on top of Animation::state_ to + // provide the reversability. + double value_start_ = 0; + double value_end_ = 0; + double value_current_ = 0; + + // How long a hover in/out animation will last for. This can be overridden + // with SetSlideDuration(). + base::TimeDelta slide_duration_ = base::Milliseconds(120); + + // Dampens the reduction in duration for animations which start partway. + double dampening_value_ = 1.0; +}; + +} // namespace gfx + +#endif // UI_GFX_ANIMATION_SLIDE_ANIMATION_H_ diff --git a/animation/slide_animation_unittest.cc b/animation/slide_animation_unittest.cc new file mode 100644 index 000000000000..9a53c8679eb6 --- /dev/null +++ b/animation/slide_animation_unittest.cc @@ -0,0 +1,242 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/animation/slide_animation.h" + +#include + +#include "base/macros.h" +#include "base/test/task_environment.h" +#include "base/time/time.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/animation/animation_test_api.h" +#include "ui/gfx/animation/test_animation_delegate.h" + +namespace gfx { + +//////////////////////////////////////////////////////////////////////////////// +// SlideAnimationTest +class SlideAnimationTest : public testing::Test { + public: + void RunAnimationFor(base::TimeDelta duration) { + base::TimeTicks now = base::TimeTicks::Now(); + animation_api_->SetStartTime(now); + animation_api_->Step(now + duration); + } + + std::unique_ptr animation_api_; + std::unique_ptr slide_animation_; + + protected: + SlideAnimationTest() + : task_environment_( + base::test::SingleThreadTaskEnvironment::MainThreadType::UI) { + slide_animation_ = std::make_unique(nullptr); + animation_api_ = std::make_unique(slide_animation_.get()); + } + + private: + base::test::SingleThreadTaskEnvironment task_environment_; +}; + +// Tests animation construction. +TEST_F(SlideAnimationTest, InitialState) { + // By default, slide animations are 60 Hz, so the timer interval should be + // 1/60th of a second. + EXPECT_EQ(1000 / 60, slide_animation_->timer_interval().InMilliseconds()); + // Duration defaults to 120 ms. + EXPECT_EQ(120, slide_animation_->GetSlideDuration().InMilliseconds()); + // Slide is neither showing nor closing. + EXPECT_FALSE(slide_animation_->IsShowing()); + EXPECT_FALSE(slide_animation_->IsClosing()); + // Starts at 0. + EXPECT_EQ(0.0, slide_animation_->GetCurrentValue()); +} + +TEST_F(SlideAnimationTest, Basics) { + // Use linear tweening to make the math easier below. + slide_animation_->SetTweenType(Tween::LINEAR); + + // Duration can be set after construction. + slide_animation_->SetSlideDuration(base::Milliseconds(100)); + EXPECT_EQ(100, slide_animation_->GetSlideDuration().InMilliseconds()); + + // Show toggles the appropriate state. + slide_animation_->Show(); + EXPECT_TRUE(slide_animation_->IsShowing()); + EXPECT_FALSE(slide_animation_->IsClosing()); + + // Simulate running the animation. + RunAnimationFor(base::Milliseconds(50)); + EXPECT_EQ(0.5, slide_animation_->GetCurrentValue()); + + // We can start hiding mid-way through the animation. + slide_animation_->Hide(); + EXPECT_FALSE(slide_animation_->IsShowing()); + EXPECT_TRUE(slide_animation_->IsClosing()); + + // Reset stops the animation. + slide_animation_->Reset(); + EXPECT_EQ(0.0, slide_animation_->GetCurrentValue()); + EXPECT_FALSE(slide_animation_->IsShowing()); + EXPECT_FALSE(slide_animation_->IsClosing()); +} + +// Tests that delegate is not notified when animation is running and is deleted. +// (Such a scenario would cause problems for BoundsAnimator). +TEST_F(SlideAnimationTest, DontNotifyOnDelete) { + TestAnimationDelegate delegate; + std::unique_ptr animation(new SlideAnimation(&delegate)); + + // Start the animation. + animation->Show(); + + // Delete the animation. + animation.reset(); + + // Make sure the delegate wasn't notified. + EXPECT_FALSE(delegate.finished()); + EXPECT_FALSE(delegate.canceled()); +} + +// Tests that animations which are started partway and have a dampening factor +// of 1 progress linearly. +TEST_F(SlideAnimationTest, + AnimationWithPartialProgressAndDefaultDampeningFactor) { + slide_animation_->SetTweenType(Tween::LINEAR); + slide_animation_->SetSlideDuration(base::Milliseconds(100)); + slide_animation_->Show(); + EXPECT_EQ(slide_animation_->GetCurrentValue(), 0.0); + + // Advance the animation to halfway done. + RunAnimationFor(base::Milliseconds(50)); + EXPECT_EQ(0.5, slide_animation_->GetCurrentValue()); + + // Reverse the animation and run it for half of the remaining duration. + slide_animation_->Hide(); + RunAnimationFor(base::Milliseconds(25)); + EXPECT_EQ(0.25, slide_animation_->GetCurrentValue()); + + // Reverse the animation again and run it for half of the remaining duration. + slide_animation_->Show(); + RunAnimationFor(base::Milliseconds(37.5)); + EXPECT_EQ(0.625, slide_animation_->GetCurrentValue()); +} + +// Tests that animations which are started partway and have a dampening factor +// of >1 progress sub-leanearly. +TEST_F(SlideAnimationTest, + AnimationWithPartialProgressAndNonDefaultDampeningFactor) { + slide_animation_->SetTweenType(Tween::LINEAR); + slide_animation_->SetDampeningValue(2.0); + slide_animation_->SetSlideDuration(base::Milliseconds(100)); + slide_animation_->Show(); + // Advance the animation to halfway done. + RunAnimationFor(base::Milliseconds(50)); + EXPECT_EQ(0.5, slide_animation_->GetCurrentValue()); + + // Reverse the animation and run it for the same duration, it should be + // sub-linear with dampening. + slide_animation_->Hide(); + RunAnimationFor(base::Milliseconds(50)); + EXPECT_GT(slide_animation_->GetCurrentValue(), 0); +} + +// Tests that a mostly complete dampened animation takes a sub-linear +// amount of time to complete. +TEST_F(SlideAnimationTest, DampenedAnimationMostlyComplete) { + slide_animation_->SetTweenType(Tween::LINEAR); + slide_animation_->SetDampeningValue(2.0); + slide_animation_->SetSlideDuration(base::Milliseconds(100)); + slide_animation_->Show(); + // Advance the animation to 1/10th of the way done. + RunAnimationFor(base::Milliseconds(10)); + EXPECT_EQ(0.1, slide_animation_->GetCurrentValue()); + + // Reverse the animation and run it for 1/10th of the duration, it should not + // be complete. + slide_animation_->Hide(); + RunAnimationFor(base::Milliseconds(10)); + EXPECT_GT(slide_animation_->GetCurrentValue(), 0); + + // Finish the animation and set up the test for a mostly complete show + // animation. + RunAnimationFor(base::Milliseconds(100)); + EXPECT_EQ(0, slide_animation_->GetCurrentValue()); + slide_animation_->Show(); + // Advance the animation to 9/10th of the way done. + RunAnimationFor(base::Milliseconds(90)); + EXPECT_EQ(0.9, slide_animation_->GetCurrentValue()); + + // Hide and then Show the animation to force the duration to be recalculated, + // then show for 1/10th of the duration and test that the animation is not + // complete. + slide_animation_->Hide(); + slide_animation_->Show(); + RunAnimationFor(base::Milliseconds(10)); + EXPECT_LT(slide_animation_->GetCurrentValue(), 1); + + RunAnimationFor(base::Milliseconds(40)); + EXPECT_EQ(1, slide_animation_->GetCurrentValue()); +} + +// Tests that a mostly incomplete dampened animation takes a sub-linear amount +// of time to complete. +TEST_F(SlideAnimationTest, DampenedAnimationMostlyIncomplete) { + slide_animation_->SetTweenType(Tween::LINEAR); + slide_animation_->SetDampeningValue(2.0); + slide_animation_->SetSlideDuration(base::Milliseconds(100)); + slide_animation_->Show(); + // Advance the animation to 1/10th of the way done. + RunAnimationFor(base::Milliseconds(10)); + EXPECT_EQ(0.1, slide_animation_->GetCurrentValue()); + + // Hide and then Show the animation to force the duration to be recalculated, + // then show for 9/10th of the duration and test that the animation is not + // complete. + slide_animation_->Hide(); + slide_animation_->Show(); + RunAnimationFor(base::Milliseconds(90)); + EXPECT_LT(slide_animation_->GetCurrentValue(), 1); + + // Finish the animation and set up the test for a mostly incomplete hide + // animation. + RunAnimationFor(base::Milliseconds(100)); + EXPECT_EQ(1, slide_animation_->GetCurrentValue()); + slide_animation_->Hide(); + RunAnimationFor(base::Milliseconds(10)); + EXPECT_EQ(0.9, slide_animation_->GetCurrentValue()); + + // Show and then hide the animation to recompute the duration, then run the + // animation for 9/10ths of the duration and test that the animation is not + // complete. + slide_animation_->Show(); + slide_animation_->Hide(); + RunAnimationFor(base::Milliseconds(90)); + EXPECT_GT(slide_animation_->GetCurrentValue(), 0); + + RunAnimationFor(base::Milliseconds(100)); + EXPECT_EQ(0, slide_animation_->GetCurrentValue()); +} + +TEST_F(SlideAnimationTest, HideFromHalfway) { + auto container = base::MakeRefCounted(); + AnimationContainerTestApi test_api(container.get()); + + slide_animation_->SetContainer(container.get()); + slide_animation_->SetTweenType(Tween::LINEAR); + slide_animation_->SetSlideDuration(base::Milliseconds(100)); + + slide_animation_->Reset(0.5); + EXPECT_FALSE(slide_animation_->is_animating()); + EXPECT_EQ(0.5, slide_animation_->GetCurrentValue()); + + slide_animation_->Hide(); + ASSERT_TRUE(slide_animation_->is_animating()); + + test_api.IncrementTime(base::Milliseconds(100)); + EXPECT_EQ(0.0, slide_animation_->GetCurrentValue()); +} + +} // namespace gfx diff --git a/animation/test_animation_delegate.h b/animation/test_animation_delegate.h new file mode 100644 index 000000000000..99da55621487 --- /dev/null +++ b/animation/test_animation_delegate.h @@ -0,0 +1,44 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_ANIMATION_TEST_ANIMATION_DELEGATE_H_ +#define UI_GFX_ANIMATION_TEST_ANIMATION_DELEGATE_H_ + +#include "base/macros.h" +#include "base/run_loop.h" +#include "ui/gfx/animation/animation_delegate.h" + +namespace gfx { + +// Trivial AnimationDelegate implementation. AnimationEnded/Canceled quit the +// message loop. +class TestAnimationDelegate : public AnimationDelegate { + public: + TestAnimationDelegate() = default; + + TestAnimationDelegate(const TestAnimationDelegate&) = delete; + TestAnimationDelegate& operator=(const TestAnimationDelegate&) = delete; + + virtual void AnimationEnded(const Animation* animation) { + finished_ = true; + if (base::RunLoop::IsRunningOnCurrentThread()) + base::RunLoop::QuitCurrentWhenIdleDeprecated(); + } + + virtual void AnimationCanceled(const Animation* animation) { + canceled_ = true; + AnimationEnded(animation); + } + + bool finished() const { return finished_; } + bool canceled() const { return canceled_; } + + private: + bool canceled_ = false; + bool finished_ = false; +}; + +} // namespace gfx + +#endif // UI_GFX_ANIMATION_TEST_ANIMATION_DELEGATE_H_ diff --git a/animation/throb_animation.cc b/animation/throb_animation.cc new file mode 100644 index 000000000000..2c2f90c8ed41 --- /dev/null +++ b/animation/throb_animation.cc @@ -0,0 +1,73 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/animation/throb_animation.h" + +#include + +namespace gfx { + +ThrobAnimation::ThrobAnimation(AnimationDelegate* target) + : SlideAnimation(target) {} + +void ThrobAnimation::StartThrobbing(int cycles_til_stop) { + if (cycles_til_stop < 0) + cycles_til_stop = std::numeric_limits::max(); + cycles_remaining_ = cycles_til_stop; + throbbing_ = true; + SlideAnimation::SetSlideDuration(throb_duration_); + if (is_animating()) + return; // We're already running, we'll cycle when current loop finishes. + + if (IsShowing()) + SlideAnimation::Hide(); + else + SlideAnimation::Show(); +} + +void ThrobAnimation::Reset(double value) { + StopThrobbing(); + SlideAnimation::Reset(value); +} + +void ThrobAnimation::Show() { + StopThrobbing(); + SlideAnimation::Show(); +} + +void ThrobAnimation::Hide() { + StopThrobbing(); + SlideAnimation::Hide(); +} + +void ThrobAnimation::SetSlideDuration(base::TimeDelta duration) { + slide_duration_ = duration; +} + +void ThrobAnimation::Step(base::TimeTicks time_now) { + LinearAnimation::Step(time_now); + + if (!is_animating() && throbbing_) { + // Were throbbing a finished a cycle. Start the next cycle unless we're at + // the end of the cycles, in which case we stop. + cycles_remaining_--; + if (IsShowing()) { + // We want to stop hidden, hence this doesn't check cycles_remaining_. + SlideAnimation::Hide(); + } else if (cycles_remaining_ > 0) { + SlideAnimation::Show(); + } else { + // We're done throbbing. + throbbing_ = false; + } + } +} + +void ThrobAnimation::StopThrobbing() { + SlideAnimation::SetSlideDuration(slide_duration_); + cycles_remaining_ = 0; + throbbing_ = false; +} + +} // namespace gfx diff --git a/animation/throb_animation.h b/animation/throb_animation.h new file mode 100644 index 000000000000..6e9d0940a146 --- /dev/null +++ b/animation/throb_animation.h @@ -0,0 +1,73 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_ANIMATION_THROB_ANIMATION_H_ +#define UI_GFX_ANIMATION_THROB_ANIMATION_H_ + +#include "base/macros.h" +#include "ui/gfx/animation/slide_animation.h" + +namespace gfx { + +// A subclass of SlideAnimation that can continually slide. All of the Animation +// methods behave like that of SlideAnimation: transition to the next state. +// The StartThrobbing method causes the ThrobAnimation to cycle between hidden +// and shown for a set number of cycles. +// +// A ThrobAnimation has two durations: the duration used when behavior like +// a SlideAnimation, and the duration used when throbbing. +class ANIMATION_EXPORT ThrobAnimation : public SlideAnimation { + public: + explicit ThrobAnimation(AnimationDelegate* target); + + ThrobAnimation(const ThrobAnimation&) = delete; + ThrobAnimation& operator=(const ThrobAnimation&) = delete; + + ~ThrobAnimation() override {} + + // Starts throbbing. cycles_til_stop gives the number of cycles to do before + // stopping. A negative value means "throb indefinitely". + void StartThrobbing(int cycles_til_stop); + + // Sets the duration of the slide animation when throbbing. + void SetThrobDuration(base::TimeDelta duration) { + throb_duration_ = duration; + } + + // Overridden to reset to the slide duration. + void Reset(double value = 0) override; + void Show() override; + void Hide() override; + + // Overridden to maintain the slide duration. + void SetSlideDuration(base::TimeDelta duration) override; + + // The number of cycles remaining until the animation stops. + void set_cycles_remaining(int value) { cycles_remaining_ = value; } + int cycles_remaining() const { return cycles_remaining_; } + + protected: + // Overriden to continually throb (assuming we're throbbing). + void Step(base::TimeTicks time_now) override; + + private: + // Stops throbbing; as a result this will behave like a SlideAnimation. + void StopThrobbing(); + + // Duration of the slide animation. + base::TimeDelta slide_duration_ = GetSlideDuration(); + + // Duration of the slide animation when throbbing. + base::TimeDelta throb_duration_ = base::Milliseconds(400); + + // If throbbing, this is the number of cycles left. + int cycles_remaining_ = 0; + + // Are we throbbing? + bool throbbing_ = false; +}; + +} // namespace gfx + +#endif // UI_GFX_ANIMATION_THROB_ANIMATION_H_ diff --git a/animation/tween.cc b/animation/tween.cc new file mode 100644 index 000000000000..928f5dd1040a --- /dev/null +++ b/animation/tween.cc @@ -0,0 +1,260 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/animation/tween.h" + +#include +#include + +#include + +#include "base/check_op.h" +#include "base/notreached.h" +#include "base/numerics/safe_conversions.h" +#include "base/time/time.h" +#include "build/build_config.h" +#include "ui/gfx/geometry/cubic_bezier.h" + +#if defined(OS_WIN) +#include +#endif + +namespace gfx { + +// static +double Tween::CalculateValue(Tween::Type type, double state) { + DCHECK_GE(state, 0); + DCHECK_LE(state, 1); + + switch (type) { + case EASE_IN: + return pow(state, 2); + + case EASE_IN_2: + return pow(state, 4); + + case EASE_IN_OUT: + if (state < 0.5) + return pow(state * 2, 2) / 2.0; + return 1.0 - (pow((state - 1.0) * 2, 2) / 2.0); + + case EASE_IN_OUT_2: + return gfx::CubicBezier(0.33, 0, 0.67, 1).Solve(state); + + case EASE_OUT_3: + return gfx::CubicBezier(0.6, 0, 0, 1).Solve(state); + + case EASE_OUT_4: + return gfx::CubicBezier(1, 0, 0.8, 1).Solve(state); + + case LINEAR: + return state; + + case EASE_OUT: + return 1.0 - pow(1.0 - state, 2); + + case EASE_OUT_2: + return gfx::CubicBezier(0.4, 0, 0, 1).Solve(state); + + case SMOOTH_IN_OUT: + return sin(state); + + case FAST_OUT_SLOW_IN: + return gfx::CubicBezier(0.4, 0, 0.2, 1).Solve(state); + + case FAST_OUT_SLOW_IN_2: + return gfx::CubicBezier(0.2, 0, 0.2, 1).Solve(state); + + case FAST_OUT_SLOW_IN_3: + return gfx::CubicBezier(0.2, 0, 0, 1).Solve(state); + + case LINEAR_OUT_SLOW_IN: + return gfx::CubicBezier(0, 0, .2, 1).Solve(state); + + case SLOW_OUT_LINEAR_IN: + return gfx::CubicBezier(0, 0, 1, .2).Solve(state); + + case FAST_OUT_LINEAR_IN: + return gfx::CubicBezier(0.4, 0, 1, 1).Solve(state); + + case ZERO: + return 0; + + case ACCEL_LIN_DECEL_60: + return gfx::CubicBezier(0, 0, 0.4, 1).Solve(state); + + case ACCEL_LIN_DECEL_100: + return gfx::CubicBezier(0, 0, 0, 1).Solve(state); + + case ACCEL_20_DECEL_60: + return gfx::CubicBezier(0.2, 0, 0.4, 1).Solve(state); + + case ACCEL_80_DECEL_20: + return gfx::CubicBezier(0.8, 0, 0.8, 1).Solve(state); + + case ACCEL_0_40_DECEL_100: + return gfx::CubicBezier(0, 0.4, 0, 1).Solve(state); + + case ACCEL_0_80_DECEL_80: + return gfx::CubicBezier(0, 0.8, 0.2, 1).Solve(state); + } + + NOTREACHED(); + return state; +} + +namespace { + +uint8_t FloatToColorByte(float f) { + return base::ClampRound(f * 255.0f); +} + +uint8_t BlendColorComponents(uint8_t start, + uint8_t target, + float start_alpha, + float target_alpha, + float blended_alpha, + double progress) { + // Since progress can be outside [0, 1], blending can produce a value outside + // [0, 255]. + float blended_premultiplied = Tween::FloatValueBetween( + progress, start / 255.f * start_alpha, target / 255.f * target_alpha); + return FloatToColorByte(blended_premultiplied / blended_alpha); +} + +} // namespace + +// static +SkColor Tween::ColorValueBetween(double value, SkColor start, SkColor target) { + float start_a = SkColorGetA(start) / 255.f; + float target_a = SkColorGetA(target) / 255.f; + float blended_a = FloatValueBetween(value, start_a, target_a); + if (blended_a <= 0.f) + return SK_ColorTRANSPARENT; + blended_a = std::min(blended_a, 1.f); + + uint8_t blended_r = + BlendColorComponents(SkColorGetR(start), SkColorGetR(target), start_a, + target_a, blended_a, value); + uint8_t blended_g = + BlendColorComponents(SkColorGetG(start), SkColorGetG(target), start_a, + target_a, blended_a, value); + uint8_t blended_b = + BlendColorComponents(SkColorGetB(start), SkColorGetB(target), start_a, + target_a, blended_a, value); + + return SkColorSetARGB( + FloatToColorByte(blended_a), blended_r, blended_g, blended_b); +} + +// static +double Tween::DoubleValueBetween(double value, double start, double target) { + return start + (target - start) * value; +} + +// static +float Tween::FloatValueBetween(double value, float start, float target) { + return static_cast(start + (target - start) * value); +} + +// static +float Tween::ClampedFloatValueBetween(const base::TimeTicks& time, + const base::TimeTicks& start_time, + float start, + const base::TimeTicks& target_time, + float target) { + if (time <= start_time) + return start; + if (time >= target_time) + return target; + + const double progress = (time - start_time) / (target_time - start_time); + return FloatValueBetween(progress, start, target); +} + +// static +int Tween::IntValueBetween(double value, int start, int target) { + if (start == target) + return start; + double delta = static_cast(target - start); + if (delta < 0) + delta--; + else + delta++; +#if defined(OS_WIN) + return start + static_cast(value * _nextafter(delta, 0)); +#else + return start + static_cast(value * nextafter(delta, 0)); +#endif +} + +// static +int Tween::LinearIntValueBetween(double value, int start, int target) { + // NOTE: Do not use base::ClampRound()! See comments on function declaration. + return base::ClampFloor(0.5 + DoubleValueBetween(value, start, target)); +} + +// static +gfx::Rect Tween::RectValueBetween(double value, + const gfx::Rect& start, + const gfx::Rect& target) { + const int x = LinearIntValueBetween(value, start.x(), target.x()); + const int y = LinearIntValueBetween(value, start.y(), target.y()); + const int right = LinearIntValueBetween(value, start.right(), target.right()); + const int bottom = + LinearIntValueBetween(value, start.bottom(), target.bottom()); + return gfx::Rect(x, y, right - x, bottom - y); +} + +// static +gfx::RectF Tween::RectFValueBetween(double value, + const gfx::RectF& start, + const gfx::RectF& target) { + const float x = FloatValueBetween(value, start.x(), target.x()); + const float y = FloatValueBetween(value, start.y(), target.y()); + const float right = FloatValueBetween(value, start.right(), target.right()); + const float bottom = + FloatValueBetween(value, start.bottom(), target.bottom()); + return gfx::RectF(x, y, right - x, bottom - y); +} + +// static +gfx::Transform Tween::TransformValueBetween(double value, + const gfx::Transform& start, + const gfx::Transform& target) { + if (value >= 1.0) + return target; + if (value <= 0.0) + return start; + + gfx::Transform to_return = target; + to_return.Blend(start, value); + return to_return; +} + +// static +gfx::TransformOperations Tween::TransformOperationsValueBetween( + double value, + const gfx::TransformOperations& start, + const gfx::TransformOperations& target) { + return target.Blend(start, value); +} + +gfx::Size Tween::SizeValueBetween(double value, + const gfx::Size& start, + const gfx::Size& target) { + return gfx::Size( + Tween::LinearIntValueBetween(value, start.width(), target.width()), + Tween::LinearIntValueBetween(value, start.height(), target.height())); +} + +gfx::SizeF Tween::SizeFValueBetween(double value, + const gfx::SizeF& start, + const gfx::SizeF& target) { + return gfx::SizeF( + Tween::FloatValueBetween(value, start.width(), target.width()), + Tween::FloatValueBetween(value, start.height(), target.height())); +} + +} // namespace gfx diff --git a/animation/tween.h b/animation/tween.h new file mode 100644 index 000000000000..da3d12d21bca --- /dev/null +++ b/animation/tween.h @@ -0,0 +1,144 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_ANIMATION_TWEEN_H_ +#define UI_GFX_ANIMATION_TWEEN_H_ + +#include "base/macros.h" +#include "third_party/skia/include/core/SkColor.h" +#include "ui/gfx/animation/animation_export.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/rect_f.h" +#include "ui/gfx/geometry/size_f.h" +#include "ui/gfx/geometry/transform.h" +#include "ui/gfx/geometry/transform_operations.h" + +namespace base { +class TimeTicks; +} + +namespace gfx { + +class ANIMATION_EXPORT Tween { + public: + enum Type { + LINEAR, // Linear. + EASE_OUT, // Fast in, slow out (default). + EASE_OUT_2, // Variant of EASE_OUT that ends slower than EASE_OUT. + EASE_OUT_3, // Variant of EASE_OUT that ends slower than EASE_OUT_2. + EASE_OUT_4, // Variant of EASE_OUT that start slower than EASE_OUT_3, + // and ends faster. Best used to lead into a bounce + // animation. + EASE_IN, // Slow in, fast out. + EASE_IN_2, // Variant of EASE_IN that starts out slower than + // EASE_IN. + EASE_IN_OUT, // Slow in and out, fast in the middle. + EASE_IN_OUT_2, // Variant of EASE_IN_OUT that starts and ends slower + // than EASE_IN_OUT. + SMOOTH_IN_OUT, // Smooth, consistent speeds in and out (sine wave). + FAST_OUT_SLOW_IN, // Variant of EASE_IN_OUT which should be used in most + // cases. + FAST_OUT_SLOW_IN_2, // Variant of FAST_OUT_SLOW_IN that starts out quicker. + FAST_OUT_SLOW_IN_3, // Variant of FAST_OUT_SLOW_IN that starts out quicker + // than FAST_OUT_SLOW_IN_2. Best used for rebound in + // bounce animation. + LINEAR_OUT_SLOW_IN, // Variant of EASE_OUT which should be used for + // fading in from 0% or motion when entering a scene. + SLOW_OUT_LINEAR_IN, // Reverse of LINEAR_OUT_SLOW_IN which should be used + // in reverse animation to create a rubberband effect. + FAST_OUT_LINEAR_IN, // Variant of EASE_IN which should should be used for + // fading out to 0% or motion when exiting a scene. + ZERO, // Returns a value of 0 always. + + // TODO(zxdan): New animation curve name convention will be used to resolve + // the confusion caused by "IN" and "OUT". + + // The new name convention is below: + // ACCEL_<1>_DECEL_<2> where <1> and <2> are used to express the + // acceleration and deceleration speeds. The corresponding cubic bezier + // curve parameters would be ( 0.01 * <1>, 0, 1 - 0.01 * <2>, 1 ). Note that + // LIN means the speed is 0. For example, + // ACCEL_20_DECEL_20 = (0.2, 0, 0.8, 1): https://cubic-bezier.com/#.2,0,.8,1 + // ACCEL_100_DECEL_100 = (1, 0, 0, 1): https://cubic-bezier.com/#1,0,0,1 + // ACCEL_LIN_DECEL_LIN = (0, 0, 1, 1): https://cubic-bezier.com/#0,0,1,1 + // ACCEL_40_DECEL_80 = (0.4, 0, 0.2, 1): https://cubic-bezier.com/#.4,0,.2,1 + ACCEL_LIN_DECEL_60, // Pulling a small to medium element into a place. + ACCEL_LIN_DECEL_100, // Pulling a small to medium element into a place that + // has very fast deceleration. + ACCEL_20_DECEL_60, // Moving a small, low emphasis or responsive elements. + ACCEL_80_DECEL_20, // Slow in and fast out with ease. + + // ACCEL_0_<1>_DECEL_<2> where <1> and <2> are used to express the + // acceleration and deceleration speeds. The corresponding cubic bezier + // curve parameters would be ( 0, 0.01 * <1>, 1 - 0.01 * <2>, 1 ). + ACCEL_0_40_DECEL_100, // Specialized curve with an emphasized deceleration + // drift. + ACCEL_0_80_DECEL_80, // Variant of ACCEL_0_40_DECEL_100 which drops in + // value faster, but flattens out into the drift + // sooner. + }; + + Tween(const Tween&) = delete; + Tween& operator=(const Tween&) = delete; + + // Returns the value based on the tween type. |state| is from 0-1. + static double CalculateValue(Type type, double state); + + // Conveniences for getting a value between a start and end point. + static SkColor ColorValueBetween(double value, SkColor start, SkColor target); + static double DoubleValueBetween(double value, double start, double target); + static float FloatValueBetween(double value, float start, float target); + static float ClampedFloatValueBetween(const base::TimeTicks& time, + const base::TimeTicks& start_time, + float start, + const base::TimeTicks& target_time, + float target); + + // Interpolated between start and target, with every integer in this range + // given equal weight. + static int IntValueBetween(double value, int start, int target); + + // Interpolates between start and target as real numbers, and rounds the + // result to the nearest integer, with ties broken by rounding towards + // positive infinity. This gives start and target half the weight of the + // other integers in the range. This is the integer interpolation approach + // specified by www.w3.org/TR/css3-transitions. + static int LinearIntValueBetween(double value, int start, int target); + + // Interpolates between |start| and |target| rects, animating the rect corners + // (as opposed to animating the rect origin and size) to minimize rounding + // error accumulation at intermediate stages. + static gfx::Rect RectValueBetween(double value, + const gfx::Rect& start, + const gfx::Rect& target); + + static gfx::RectF RectFValueBetween(double value, + const gfx::RectF& start, + const gfx::RectF& target); + + static gfx::Transform TransformValueBetween(double value, + const gfx::Transform& start, + const gfx::Transform& target); + + static gfx::TransformOperations TransformOperationsValueBetween( + double value, + const gfx::TransformOperations& start, + const gfx::TransformOperations& target); + + static gfx::Size SizeValueBetween(double value, + const gfx::Size& start, + const gfx::Size& target); + + static gfx::SizeF SizeFValueBetween(double value, + const gfx::SizeF& start, + const gfx::SizeF& target); + + private: + Tween(); + ~Tween(); +}; + +} // namespace gfx + +#endif // UI_GFX_ANIMATION_TWEEN_H_ diff --git a/animation/tween_unittest.cc b/animation/tween_unittest.cc new file mode 100644 index 000000000000..19ff9eb1b12f --- /dev/null +++ b/animation/tween_unittest.cc @@ -0,0 +1,233 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/animation/tween.h" + +#include + +#include "base/time/time.h" +#include "build/build_config.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/test/gfx_util.h" + +#if defined(OS_WIN) +#include +#endif + +namespace gfx { +namespace { + +double next_double(double d) { +#if defined(OS_WIN) + return _nextafter(d, d + 1); +#else + // Step two units of least precision towards positive infinity. On some 32 + // bit x86 compilers a single step was not enough due to loss of precision in + // optimized code. + return nextafter(nextafter(d, d + 1), d + 1); +#endif +} + +// Validates that the same interpolations are made as in Blink. +TEST(TweenTest, ColorValueBetween) { + // From blink's AnimatableColorTest. + EXPECT_SKCOLOR_EQ(0xFF00FF00, + Tween::ColorValueBetween(-10.0, 0xFF00FF00, 0xFF00FF00)); + EXPECT_SKCOLOR_EQ(0xFF00FF00, + Tween::ColorValueBetween(-10.0, 0xFF00FF00, 0xFFFF00FF)); + EXPECT_SKCOLOR_EQ(0xFF00FF00, + Tween::ColorValueBetween(0.0, 0xFF00FF00, 0xFFFF00FF)); + EXPECT_SKCOLOR_EQ(0xFF01FE01, + Tween::ColorValueBetween(1.0 / 255, 0xFF00FF00, 0xFFFF00FF)); + EXPECT_SKCOLOR_EQ(0xFF808080, + Tween::ColorValueBetween(0.5, 0xFF00FF00, 0xFFFF00FF)); + EXPECT_SKCOLOR_EQ( + 0xFFFE01FE, + Tween::ColorValueBetween(254.0 / 255.0, 0xFF00FF00, 0xFFFF00FF)); + EXPECT_SKCOLOR_EQ(0xFFFF00FF, + Tween::ColorValueBetween(1.0, 0xFF00FF00, 0xFFFF00FF)); + EXPECT_SKCOLOR_EQ(0xFFFF00FF, + Tween::ColorValueBetween(10.0, 0xFF00FF00, 0xFFFF00FF)); + EXPECT_SKCOLOR_EQ(0xFF0C253E, + Tween::ColorValueBetween(3.0 / 16.0, 0xFF001020, 0xFF4080C0)); + EXPECT_SKCOLOR_EQ(0x80FF00FF, + Tween::ColorValueBetween(0.5, 0x0000FF00, 0xFFFF00FF)); + EXPECT_SKCOLOR_EQ(0x60AA55AA, + Tween::ColorValueBetween(0.5, 0x4000FF00, 0x80FF00FF)); + EXPECT_SKCOLOR_EQ(0x60FFAAFF, + Tween::ColorValueBetween(0.5, 0x40FF00FF, 0x80FFFFFF)); + EXPECT_SKCOLOR_EQ(0x103060A0, + Tween::ColorValueBetween(0.5, 0x10204080, 0x104080C0)); +} + +// Ensures that each of the 3 integers in [0, 1, 2] ae selected with equal +// weight. +TEST(TweenTest, IntValueBetween) { + EXPECT_EQ(0, Tween::IntValueBetween(0.0, 0, 2)); + EXPECT_EQ(0, Tween::IntValueBetween(0.5 / 3.0, 0, 2)); + EXPECT_EQ(0, Tween::IntValueBetween(1.0 / 3.0, 0, 2)); + + EXPECT_EQ(1, Tween::IntValueBetween(next_double(1.0 / 3.0), 0, 2)); + EXPECT_EQ(1, Tween::IntValueBetween(1.5 / 3.0, 0, 2)); + EXPECT_EQ(1, Tween::IntValueBetween(2.0 / 3.0, 0, 2)); + + EXPECT_EQ(2, Tween::IntValueBetween(next_double(2.0 / 3.0), 0, 2)); + EXPECT_EQ(2, Tween::IntValueBetween(2.5 / 3.0, 0, 2)); + EXPECT_EQ(2, Tween::IntValueBetween(3.0 / 3.0, 0, 2)); +} + +TEST(TweenTest, IntValueBetweenNegative) { + EXPECT_EQ(-2, Tween::IntValueBetween(0.0, -2, 0)); + EXPECT_EQ(-2, Tween::IntValueBetween(0.5 / 3.0, -2, 0)); + EXPECT_EQ(-2, Tween::IntValueBetween(1.0 / 3.0, -2, 0)); + + EXPECT_EQ(-1, Tween::IntValueBetween(next_double(1.0 / 3.0), -2, 0)); + EXPECT_EQ(-1, Tween::IntValueBetween(1.5 / 3.0, -2, 0)); + EXPECT_EQ(-1, Tween::IntValueBetween(2.0 / 3.0, -2, 0)); + + EXPECT_EQ(0, Tween::IntValueBetween(next_double(2.0 / 3.0), -2, 0)); + EXPECT_EQ(0, Tween::IntValueBetween(2.5 / 3.0, -2, 0)); + EXPECT_EQ(0, Tween::IntValueBetween(3.0 / 3.0, -2, 0)); +} + +TEST(TweenTest, IntValueBetweenReverse) { + EXPECT_EQ(2, Tween::IntValueBetween(0.0, 2, 0)); + EXPECT_EQ(2, Tween::IntValueBetween(0.5 / 3.0, 2, 0)); + EXPECT_EQ(2, Tween::IntValueBetween(1.0 / 3.0, 2, 0)); + + EXPECT_EQ(1, Tween::IntValueBetween(next_double(1.0 / 3.0), 2, 0)); + EXPECT_EQ(1, Tween::IntValueBetween(1.5 / 3.0, 2, 0)); + EXPECT_EQ(1, Tween::IntValueBetween(2.0 / 3.0, 2, 0)); + + EXPECT_EQ(0, Tween::IntValueBetween(next_double(2.0 / 3.0), 2, 0)); + EXPECT_EQ(0, Tween::IntValueBetween(2.5 / 3.0, 2, 0)); + EXPECT_EQ(0, Tween::IntValueBetween(3.0 / 3.0, 2, 0)); +} + +TEST(TweenTest, LinearIntValueBetween) { + EXPECT_EQ(0, Tween::LinearIntValueBetween(0.0, 0, 2)); + EXPECT_EQ(0, Tween::LinearIntValueBetween(0.5 / 4.0, 0, 2)); + EXPECT_EQ(0, Tween::LinearIntValueBetween(0.99 / 4.0, 0, 2)); + + EXPECT_EQ(1, Tween::LinearIntValueBetween(1.0 / 4.0, 0, 2)); + EXPECT_EQ(1, Tween::LinearIntValueBetween(1.5 / 4.0, 0, 2)); + EXPECT_EQ(1, Tween::LinearIntValueBetween(2.0 / 4.0, 0, 2)); + EXPECT_EQ(1, Tween::LinearIntValueBetween(2.5 / 4.0, 0, 2)); + EXPECT_EQ(1, Tween::LinearIntValueBetween(2.99 / 4.0, 0, 2)); + + EXPECT_EQ(2, Tween::LinearIntValueBetween(3.0 / 4.0, 0, 2)); + EXPECT_EQ(2, Tween::LinearIntValueBetween(3.5 / 4.0, 0, 2)); + EXPECT_EQ(2, Tween::LinearIntValueBetween(4.0 / 4.0, 0, 2)); +} + +TEST(TweenTest, LinearIntValueBetweenNegative) { + EXPECT_EQ(-2, Tween::LinearIntValueBetween(0.0, -2, 0)); + EXPECT_EQ(-2, Tween::LinearIntValueBetween(0.5 / 4.0, -2, 0)); + EXPECT_EQ(-2, Tween::LinearIntValueBetween(0.99 / 4.0, -2, 0)); + + EXPECT_EQ(-1, Tween::LinearIntValueBetween(1.0 / 4.0, -2, 0)); + EXPECT_EQ(-1, Tween::LinearIntValueBetween(1.5 / 4.0, -2, 0)); + EXPECT_EQ(-1, Tween::LinearIntValueBetween(2.0 / 4.0, -2, 0)); + EXPECT_EQ(-1, Tween::LinearIntValueBetween(2.5 / 4.0, -2, 0)); + EXPECT_EQ(-1, Tween::LinearIntValueBetween(2.99 / 4.0, -2, 0)); + + EXPECT_EQ(0, Tween::LinearIntValueBetween(3.0 / 4.0, -2, 0)); + EXPECT_EQ(0, Tween::LinearIntValueBetween(3.5 / 4.0, -2, 0)); + EXPECT_EQ(0, Tween::LinearIntValueBetween(4.0 / 4.0, -2, 0)); +} + +TEST(TweenTest, ClampedFloatValueBetweenTimeTicks) { + const float v1 = 10.0f; + const float v2 = 20.0f; + + const auto t0 = base::TimeTicks(); + + base::TimeTicks from = t0 + base::Seconds(1); + base::TimeTicks to = t0 + base::Seconds(2); + + base::TimeTicks t_before = t0 + base::Seconds(0.9); + base::TimeTicks t_between = t0 + base::Seconds(1.6); + base::TimeTicks t_after = t0 + base::Seconds(2.2); + + EXPECT_EQ(v1, Tween::ClampedFloatValueBetween(t_before, from, v1, to, v2)); + EXPECT_EQ(16.0, Tween::ClampedFloatValueBetween(t_between, from, v1, to, v2)); + EXPECT_EQ(v2, Tween::ClampedFloatValueBetween(t_after, from, v1, to, v2)); +} + +// Verifies the corners of the rect are animated, rather than the origin/size +// (which would result in different rounding). +TEST(TweenTest, RectValueBetween) { + constexpr gfx::Rect r1(0, 0, 10, 10); + constexpr gfx::Rect r2(10, 10, 30, 30); + + // TODO(pkasting): Move the geometry test helpers from + // cc/test/geometry_test_utils.h to ui/gfx/test/gfx_util.h or similar and use + // a rect-comparison function here. + const gfx::Rect tweened = Tween::RectValueBetween(0.08, r1, r2); + EXPECT_EQ(11, tweened.width()); + EXPECT_EQ(11, tweened.height()); +} + +TEST(TweenTest, SizeValueBetween) { + constexpr gfx::Size kSize1(12, 24); + constexpr gfx::Size kSize2(36, 48); + + constexpr double kBefore = -0.125; + constexpr double kFrom = 0.0; + constexpr double kBetween = 0.5; + constexpr double kTo = 1.0; + constexpr double kAfter = 1.125; + + EXPECT_EQ(gfx::Size(9, 21), Tween::SizeValueBetween(kBefore, kSize1, kSize2)); + EXPECT_EQ(kSize1, Tween::SizeValueBetween(kFrom, kSize1, kSize2)); + EXPECT_EQ(gfx::Size(24, 36), + Tween::SizeValueBetween(kBetween, kSize1, kSize2)); + EXPECT_EQ(kSize2, Tween::SizeValueBetween(kTo, kSize1, kSize2)); + EXPECT_EQ(gfx::Size(39, 51), Tween::SizeValueBetween(kAfter, kSize1, kSize2)); +} + +TEST(TweenTest, SizeValueBetweenClampedExtrapolation) { + constexpr gfx::Size kSize1(0, 0); + constexpr gfx::Size kSize2(36, 48); + + constexpr double kBefore = -1.0f; + + // We should not extrapolate in this case as it would result in a negative and + // invalid size. + EXPECT_EQ(kSize1, Tween::SizeValueBetween(kBefore, kSize1, kSize2)); +} + +TEST(TweenTest, SizeFValueBetween) { + const gfx::SizeF s1(12.0f, 24.0f); + const gfx::SizeF s2(36.0f, 48.0f); + + constexpr double kBefore = -0.125; + constexpr double kFrom = 0.0; + constexpr double kBetween = 0.5; + constexpr double kTo = 1.0; + constexpr double kAfter = 1.125; + + EXPECT_SIZEF_EQ(gfx::SizeF(9.0f, 21.0f), + Tween::SizeFValueBetween(kBefore, s1, s2)); + EXPECT_SIZEF_EQ(s1, Tween::SizeFValueBetween(kFrom, s1, s2)); + EXPECT_SIZEF_EQ(gfx::SizeF(24.0f, 36.0f), + Tween::SizeFValueBetween(kBetween, s1, s2)); + EXPECT_SIZEF_EQ(s2, Tween::SizeFValueBetween(kTo, s1, s2)); + EXPECT_SIZEF_EQ(gfx::SizeF(39.0f, 51.0f), + Tween::SizeFValueBetween(kAfter, s1, s2)); +} + +TEST(TweenTest, SizeFValueBetweenClampedExtrapolation) { + const gfx::SizeF s1(0.0f, 0.0f); + const gfx::SizeF s2(36.0f, 48.0f); + + constexpr double kBefore = -1.0f; + + // We should not extrapolate in this case as it would result in a negative and + // invalid size. + EXPECT_SIZEF_EQ(s1, Tween::SizeFValueBetween(kBefore, s1, s2)); +} + +} // namespace +} // namespace gfx diff --git a/bidi_line_iterator.cc b/bidi_line_iterator.cc new file mode 100644 index 000000000000..cda076ac74ff --- /dev/null +++ b/bidi_line_iterator.cc @@ -0,0 +1,75 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/bidi_line_iterator.h" + +#include "base/check.h" +#include "base/notreached.h" + +namespace ui { +namespace gfx { + +namespace { + +UBiDiLevel GetParagraphLevelForDirection(base::i18n::TextDirection direction) { + switch (direction) { + case base::i18n::UNKNOWN_DIRECTION: + return UBIDI_DEFAULT_LTR; + case base::i18n::RIGHT_TO_LEFT: + return 1; // Highest RTL level. + case base::i18n::LEFT_TO_RIGHT: + return 0; // Highest LTR level. + default: + NOTREACHED(); + return 0; + } +} + +} // namespace + +BiDiLineIterator::BiDiLineIterator() : bidi_(nullptr) {} + +BiDiLineIterator::~BiDiLineIterator() { + if (bidi_) { + ubidi_close(bidi_); + bidi_ = nullptr; + } +} + +bool BiDiLineIterator::Open(const std::u16string& text, + base::i18n::TextDirection direction) { + DCHECK(!bidi_); + UErrorCode error = U_ZERO_ERROR; + bidi_ = ubidi_openSized(static_cast(text.length()), 0, &error); + if (U_FAILURE(error)) + return false; + + ubidi_setPara(bidi_, text.data(), static_cast(text.length()), + GetParagraphLevelForDirection(direction), nullptr, &error); + return (U_SUCCESS(error)); +} + +int BiDiLineIterator::CountRuns() const { + DCHECK(bidi_ != nullptr); + UErrorCode error = U_ZERO_ERROR; + const int runs = ubidi_countRuns(bidi_, &error); + return U_SUCCESS(error) ? runs : 0; +} + +UBiDiDirection BiDiLineIterator::GetVisualRun(int index, + int* start, + int* length) const { + DCHECK(bidi_ != nullptr); + return ubidi_getVisualRun(bidi_, index, start, length); +} + +void BiDiLineIterator::GetLogicalRun(int start, + int* end, + UBiDiLevel* level) const { + DCHECK(bidi_ != nullptr); + ubidi_getLogicalRun(bidi_, start, end, level); +} + +} // namespace gfx +} // namespace ui diff --git a/bidi_line_iterator.h b/bidi_line_iterator.h new file mode 100644 index 000000000000..cf181170124b --- /dev/null +++ b/bidi_line_iterator.h @@ -0,0 +1,51 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_BIDI_LINE_ITERATOR_H_ +#define UI_GFX_BIDI_LINE_ITERATOR_H_ + +#include + +#include "base/i18n/rtl.h" +#include "base/macros.h" +#include "third_party/icu/source/common/unicode/ubidi.h" +#include "third_party/icu/source/common/unicode/uchar.h" +#include "ui/gfx/gfx_export.h" + +namespace ui { +namespace gfx { + +// A simple wrapper class for the bidirectional iterator of ICU. +// This class uses the bidirectional iterator of ICU to split a line of +// bidirectional texts into visual runs in its display order. +class GFX_EXPORT BiDiLineIterator { + public: + BiDiLineIterator(); + + BiDiLineIterator(const BiDiLineIterator&) = delete; + BiDiLineIterator& operator=(const BiDiLineIterator&) = delete; + + ~BiDiLineIterator(); + + // Initializes the bidirectional iterator with the specified text. Returns + // whether initialization succeeded. + bool Open(const std::u16string& text, base::i18n::TextDirection direction); + + // Returns the number of visual runs in the text, or zero on error. + int CountRuns() const; + + // Gets the logical offset, length, and direction of the specified visual run. + UBiDiDirection GetVisualRun(int index, int* start, int* length) const; + + // Given a start position, figure out where the run ends (and the BiDiLevel). + void GetLogicalRun(int start, int* end, UBiDiLevel* level) const; + + private: + UBiDi* bidi_; +}; + +} // namespace gfx +} // namespace ui + +#endif // UI_GFX_BIDI_LINE_ITERATOR_H_ diff --git a/bidi_line_iterator_unittest.cc b/bidi_line_iterator_unittest.cc new file mode 100644 index 000000000000..8a8f3925989f --- /dev/null +++ b/bidi_line_iterator_unittest.cc @@ -0,0 +1,152 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/bidi_line_iterator.h" + +#include "base/strings/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace ui { +namespace gfx { +namespace { + +class BiDiLineIteratorTest + : public testing::TestWithParam { + public: + BiDiLineIteratorTest() = default; + + BiDiLineIteratorTest(const BiDiLineIteratorTest&) = delete; + BiDiLineIteratorTest& operator=(const BiDiLineIteratorTest&) = delete; + + BiDiLineIterator* iterator() { return &iterator_; } + + private: + BiDiLineIterator iterator_; +}; + +TEST_P(BiDiLineIteratorTest, OnlyLTR) { + iterator()->Open(u"abc 😁 测试", GetParam()); + ASSERT_EQ(1, iterator()->CountRuns()); + + int start, length; + EXPECT_EQ(UBIDI_LTR, iterator()->GetVisualRun(0, &start, &length)); + EXPECT_EQ(0, start); + EXPECT_EQ(9, length); + + int end; + UBiDiLevel level; + iterator()->GetLogicalRun(0, &end, &level); + EXPECT_EQ(9, end); + if (GetParam() == base::i18n::TextDirection::RIGHT_TO_LEFT) + EXPECT_EQ(2, level); + else + EXPECT_EQ(0, level); +} + +TEST_P(BiDiLineIteratorTest, OnlyRTL) { + iterator()->Open(u"מה השעה", GetParam()); + ASSERT_EQ(1, iterator()->CountRuns()); + + int start, length; + EXPECT_EQ(UBIDI_RTL, iterator()->GetVisualRun(0, &start, &length)); + EXPECT_EQ(0, start); + EXPECT_EQ(7, length); + + int end; + UBiDiLevel level; + iterator()->GetLogicalRun(0, &end, &level); + EXPECT_EQ(7, end); + EXPECT_EQ(1, level); +} + +TEST_P(BiDiLineIteratorTest, Mixed) { + iterator()->Open(u"אני משתמש ב- Chrome כדפדפן האינטרנט שלי", GetParam()); + ASSERT_EQ(3, iterator()->CountRuns()); + + // We'll get completely different results depending on the top-level paragraph + // direction. + if (GetParam() == base::i18n::TextDirection::RIGHT_TO_LEFT) { + // If para direction is RTL, expect the LTR substring "Chrome" to be nested + // within the surrounding RTL text. + int start, length; + EXPECT_EQ(UBIDI_RTL, iterator()->GetVisualRun(0, &start, &length)); + EXPECT_EQ(19, start); + EXPECT_EQ(20, length); + EXPECT_EQ(UBIDI_LTR, iterator()->GetVisualRun(1, &start, &length)); + EXPECT_EQ(13, start); + EXPECT_EQ(6, length); + EXPECT_EQ(UBIDI_RTL, iterator()->GetVisualRun(2, &start, &length)); + EXPECT_EQ(0, start); + EXPECT_EQ(13, length); + + int end; + UBiDiLevel level; + iterator()->GetLogicalRun(0, &end, &level); + EXPECT_EQ(13, end); + EXPECT_EQ(1, level); + iterator()->GetLogicalRun(13, &end, &level); + EXPECT_EQ(19, end); + EXPECT_EQ(2, level); + iterator()->GetLogicalRun(19, &end, &level); + EXPECT_EQ(39, end); + EXPECT_EQ(1, level); + } else { + // If the para direction is LTR, expect the LTR substring "- Chrome " to be + // at the top level, with two nested RTL runs on either side. + int start, length; + EXPECT_EQ(UBIDI_RTL, iterator()->GetVisualRun(0, &start, &length)); + EXPECT_EQ(0, start); + EXPECT_EQ(11, length); + EXPECT_EQ(UBIDI_LTR, iterator()->GetVisualRun(1, &start, &length)); + EXPECT_EQ(11, start); + EXPECT_EQ(9, length); + EXPECT_EQ(UBIDI_RTL, iterator()->GetVisualRun(2, &start, &length)); + EXPECT_EQ(20, start); + EXPECT_EQ(19, length); + + int end; + UBiDiLevel level; + iterator()->GetLogicalRun(0, &end, &level); + EXPECT_EQ(11, end); + EXPECT_EQ(1, level); + iterator()->GetLogicalRun(11, &end, &level); + EXPECT_EQ(20, end); + EXPECT_EQ(0, level); + iterator()->GetLogicalRun(20, &end, &level); + EXPECT_EQ(39, end); + EXPECT_EQ(1, level); + } +} + +TEST_P(BiDiLineIteratorTest, RTLPunctuationNoCustomBehavior) { + // This string features Hebrew characters interleaved with ASCII punctuation. + iterator()->Open( + u"א!ב\"ג#ד$ה%ו&ז'ח(ט)י*ך+כ,ל-ם.מ/" + u"ן:נ;ס<ע=ף>פ?ץ@צ[ק\\ר]ש^ת_א`ב{ג|ד}ה~ו", + GetParam()); + + // Expect a single RTL run. + ASSERT_EQ(1, iterator()->CountRuns()); + + int start, length; + EXPECT_EQ(UBIDI_RTL, iterator()->GetVisualRun(0, &start, &length)); + EXPECT_EQ(0, start); + EXPECT_EQ(65, length); + + int end; + UBiDiLevel level; + iterator()->GetLogicalRun(0, &end, &level); + EXPECT_EQ(65, end); + EXPECT_EQ(1, level); +} + +INSTANTIATE_TEST_SUITE_P( + All, + BiDiLineIteratorTest, + ::testing::Values(base::i18n::TextDirection::LEFT_TO_RIGHT, + base::i18n::TextDirection::RIGHT_TO_LEFT)); + +} // namespace +} // namespace gfx +} // namespace ui diff --git a/blit.cc b/blit.cc new file mode 100644 index 000000000000..d7d74a5e52fc --- /dev/null +++ b/blit.cc @@ -0,0 +1,99 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/blit.h" + +#include + +#include "base/check.h" +#include "build/build_config.h" +#include "skia/ext/platform_canvas.h" +#include "third_party/skia/include/core/SkPixmap.h" +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/vector2d.h" + +namespace gfx { + +namespace { + +// Returns true if the given canvas has any part of itself clipped out or +// any non-identity tranform. +bool HasClipOrTransform(SkCanvas& canvas) { + if (!canvas.getTotalMatrix().isIdentity()) + return true; + + if (!canvas.isClipRect()) + return true; + + // Now we know the clip is a regular rectangle, make sure it covers the + // entire canvas. + SkIRect clip_bounds = canvas.getDeviceClipBounds(); + + SkImageInfo info; + size_t row_bytes; + void* pixels = canvas.accessTopLayerPixels(&info, &row_bytes); + DCHECK(pixels); + if (!pixels) + return true; + + if (clip_bounds.fLeft != 0 || clip_bounds.fTop != 0 || + clip_bounds.fRight != info.width() || + clip_bounds.fBottom != info.height()) + return true; + + return false; +} + +} // namespace + +void ScrollCanvas(SkCanvas* canvas, + const gfx::Rect& in_clip, + const gfx::Vector2d& offset) { + DCHECK(!HasClipOrTransform(*canvas)); // Don't support special stuff. + + SkPixmap pixmap; + bool success = skia::GetWritablePixels(canvas, &pixmap); + DCHECK(success); + + // We expect all coords to be inside the canvas, so clip here. + gfx::Rect clip = gfx::IntersectRects( + in_clip, gfx::Rect(0, 0, pixmap.width(), pixmap.height())); + + // Compute the set of pixels we'll actually end up painting. + gfx::Rect dest_rect = gfx::IntersectRects(clip + offset, clip); + if (dest_rect.size().IsEmpty()) + return; // Nothing to do. + + // Compute the source pixels that will map to the dest_rect + gfx::Rect src_rect = dest_rect - offset; + + size_t row_bytes = dest_rect.width() * 4; + if (offset.y() > 0) { + // Data is moving down, copy from the bottom up. + for (int y = dest_rect.height() - 1; y >= 0; y--) { + memcpy(pixmap.writable_addr32(dest_rect.x(), dest_rect.y() + y), + pixmap.addr32(src_rect.x(), src_rect.y() + y), + row_bytes); + } + } else if (offset.y() < 0) { + // Data is moving up, copy from the top down. + for (int y = 0; y < dest_rect.height(); y++) { + memcpy(pixmap.writable_addr32(dest_rect.x(), dest_rect.y() + y), + pixmap.addr32(src_rect.x(), src_rect.y() + y), + row_bytes); + } + } else if (offset.x() != 0) { + // Horizontal-only scroll. We can do it in either top-to-bottom or bottom- + // to-top, but have to be careful about the order for copying each row. + // Fortunately, memmove already handles this for us. + for (int y = 0; y < dest_rect.height(); y++) { + memmove(pixmap.writable_addr32(dest_rect.x(), dest_rect.y() + y), + pixmap.addr32(src_rect.x(), src_rect.y() + y), + row_bytes); + } + } +} + +} // namespace gfx diff --git a/blit.h b/blit.h new file mode 100644 index 000000000000..b4a76ae13b69 --- /dev/null +++ b/blit.h @@ -0,0 +1,27 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_BLIT_H_ +#define UI_GFX_BLIT_H_ + +#include "ui/gfx/gfx_export.h" +#include "ui/gfx/native_widget_types.h" + +class SkCanvas; + +namespace gfx { + +class Rect; +class Vector2d; + +// Scrolls the given subset of the given canvas by the given offset. +// The canvas should not have a clip or a transform applied, since platforms +// may implement those operations differently. +GFX_EXPORT void ScrollCanvas(SkCanvas* canvas, + const Rect& clip, + const Vector2d& offset); + +} // namespace gfx + +#endif // UI_GFX_BLIT_H_ diff --git a/blit_unittest.cc b/blit_unittest.cc new file mode 100644 index 000000000000..1d70915a0b45 --- /dev/null +++ b/blit_unittest.cc @@ -0,0 +1,176 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/memory/platform_shared_memory_region.h" +#include "build/build_config.h" +#include "skia/ext/platform_canvas.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/blit.h" +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/rect.h" + +namespace { + +// Fills the given canvas with the values by duplicating the values into each +// color channel for the corresponding pixel. +// +// Example values = {{0x0, 0x01}, {0x12, 0xFF}} would give a canvas with: +// 0x00000000 0x01010101 +// 0x12121212 0xFFFFFFFF +template +void SetToCanvas(SkCanvas* canvas, uint8_t values[h][w]) { + ASSERT_EQ(w, canvas->imageInfo().width()); + ASSERT_EQ(h, canvas->imageInfo().height()); + + // This wouldn't be necessary if we extended the values in the inputs, but + // the uint8_t values are a little bit easier to read and maintain. + uint32_t extendedValues[w*h]; + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + uint8_t value = values[y][x]; + extendedValues[y*w+x] = + (value << 24) | (value << 16) | (value << 8) | value; + } + } + + SkImageInfo info = SkImageInfo::MakeN32Premul(w, h); + canvas->writePixels(info, extendedValues, w*4, 0, 0); +} + +// Checks each pixel in the given canvas and see if it is made up of the given +// values, where each value has been duplicated into each channel of the given +// bitmap (see SetToCanvas above). +template +void VerifyCanvasValues(SkCanvas* canvas, uint8_t values[h][w]) { + SkBitmap bitmap = skia::ReadPixels(canvas); + ASSERT_EQ(w, bitmap.width()); + ASSERT_EQ(h, bitmap.height()); + + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + uint8_t value = values[y][x]; + uint32_t expected = (value << 24) | (value << 16) | (value << 8) | value; + ASSERT_EQ(expected, *bitmap.getAddr32(x, y)); + } + } +} + +} // namespace + +TEST(Blit, ScrollCanvas) { + static const int kCanvasWidth = 5; + static const int kCanvasHeight = 5; + std::unique_ptr canvas = + skia::CreatePlatformCanvas(kCanvasWidth, kCanvasHeight, false); + uint8_t initial_values[kCanvasHeight][kCanvasWidth] = { + {0x00, 0x01, 0x02, 0x03, 0x04}, + {0x10, 0x11, 0x12, 0x13, 0x14}, + {0x20, 0x21, 0x22, 0x23, 0x24}, + {0x30, 0x31, 0x32, 0x33, 0x34}, + {0x40, 0x41, 0x42, 0x43, 0x44}}; + SetToCanvas<5, 5>(canvas.get(), initial_values); + + // Sanity check on input. + VerifyCanvasValues<5, 5>(canvas.get(), initial_values); + + // Scroll none and make sure it's a NOP. + gfx::ScrollCanvas(canvas.get(), + gfx::Rect(0, 0, kCanvasWidth, kCanvasHeight), + gfx::Vector2d(0, 0)); + VerifyCanvasValues<5, 5>(canvas.get(), initial_values); + + // Scroll with a empty clip and make sure it's a NOP. + gfx::Rect empty_clip(1, 1, 0, 0); + gfx::ScrollCanvas(canvas.get(), empty_clip, gfx::Vector2d(0, 1)); + VerifyCanvasValues<5, 5>(canvas.get(), initial_values); + + // Scroll the center 3 pixels up one. + gfx::Rect center_three(1, 1, 3, 3); + gfx::ScrollCanvas(canvas.get(), center_three, gfx::Vector2d(0, -1)); + uint8_t scroll_up_expected[kCanvasHeight][kCanvasWidth] = { + {0x00, 0x01, 0x02, 0x03, 0x04}, + {0x10, 0x21, 0x22, 0x23, 0x14}, + {0x20, 0x31, 0x32, 0x33, 0x24}, + {0x30, 0x31, 0x32, 0x33, 0x34}, + {0x40, 0x41, 0x42, 0x43, 0x44}}; + VerifyCanvasValues<5, 5>(canvas.get(), scroll_up_expected); + + // Reset and scroll the center 3 pixels down one. + SetToCanvas<5, 5>(canvas.get(), initial_values); + gfx::ScrollCanvas(canvas.get(), center_three, gfx::Vector2d(0, 1)); + uint8_t scroll_down_expected[kCanvasHeight][kCanvasWidth] = { + {0x00, 0x01, 0x02, 0x03, 0x04}, + {0x10, 0x11, 0x12, 0x13, 0x14}, + {0x20, 0x11, 0x12, 0x13, 0x24}, + {0x30, 0x21, 0x22, 0x23, 0x34}, + {0x40, 0x41, 0x42, 0x43, 0x44}}; + VerifyCanvasValues<5, 5>(canvas.get(), scroll_down_expected); + + // Reset and scroll the center 3 pixels right one. + SetToCanvas<5, 5>(canvas.get(), initial_values); + gfx::ScrollCanvas(canvas.get(), center_three, gfx::Vector2d(1, 0)); + uint8_t scroll_right_expected[kCanvasHeight][kCanvasWidth] = { + {0x00, 0x01, 0x02, 0x03, 0x04}, + {0x10, 0x11, 0x11, 0x12, 0x14}, + {0x20, 0x21, 0x21, 0x22, 0x24}, + {0x30, 0x31, 0x31, 0x32, 0x34}, + {0x40, 0x41, 0x42, 0x43, 0x44}}; + VerifyCanvasValues<5, 5>(canvas.get(), scroll_right_expected); + + // Reset and scroll the center 3 pixels left one. + SetToCanvas<5, 5>(canvas.get(), initial_values); + gfx::ScrollCanvas(canvas.get(), center_three, gfx::Vector2d(-1, 0)); + uint8_t scroll_left_expected[kCanvasHeight][kCanvasWidth] = { + {0x00, 0x01, 0x02, 0x03, 0x04}, + {0x10, 0x12, 0x13, 0x13, 0x14}, + {0x20, 0x22, 0x23, 0x23, 0x24}, + {0x30, 0x32, 0x33, 0x33, 0x34}, + {0x40, 0x41, 0x42, 0x43, 0x44}}; + VerifyCanvasValues<5, 5>(canvas.get(), scroll_left_expected); + + // Diagonal scroll. + SetToCanvas<5, 5>(canvas.get(), initial_values); + gfx::ScrollCanvas(canvas.get(), center_three, gfx::Vector2d(2, 2)); + uint8_t scroll_diagonal_expected[kCanvasHeight][kCanvasWidth] = { + {0x00, 0x01, 0x02, 0x03, 0x04}, + {0x10, 0x11, 0x12, 0x13, 0x14}, + {0x20, 0x21, 0x22, 0x23, 0x24}, + {0x30, 0x31, 0x32, 0x11, 0x34}, + {0x40, 0x41, 0x42, 0x43, 0x44}}; + VerifyCanvasValues<5, 5>(canvas.get(), scroll_diagonal_expected); +} + +#if defined(OS_WIN) + +TEST(Blit, WithSharedMemory) { + const int kCanvasWidth = 5; + const int kCanvasHeight = 5; + base::subtle::PlatformSharedMemoryRegion section = + base::subtle::PlatformSharedMemoryRegion::CreateWritable(kCanvasWidth * + kCanvasHeight); + ASSERT_TRUE(section.IsValid()); + std::unique_ptr canvas = + skia::CreatePlatformCanvasWithSharedSection( + kCanvasWidth, kCanvasHeight, false, section.GetPlatformHandle(), + skia::RETURN_NULL_ON_FAILURE); + ASSERT_TRUE(canvas); + // Closes a HANDLE associated with |section|, |canvas| must remain valid. + section = base::subtle::PlatformSharedMemoryRegion(); + + uint8_t initial_values[kCanvasHeight][kCanvasWidth] = { + {0x00, 0x01, 0x02, 0x03, 0x04}, + {0x10, 0x11, 0x12, 0x13, 0x14}, + {0x20, 0x21, 0x22, 0x23, 0x24}, + {0x30, 0x31, 0x32, 0x33, 0x34}, + {0x40, 0x41, 0x42, 0x43, 0x44}}; + SetToCanvas<5, 5>(canvas.get(), initial_values); + + // Sanity check on input. + VerifyCanvasValues<5, 5>(canvas.get(), initial_values); +} + +#endif + diff --git a/break_list.h b/break_list.h new file mode 100644 index 000000000000..19c947f5afae --- /dev/null +++ b/break_list.h @@ -0,0 +1,176 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_BREAK_LIST_H_ +#define UI_GFX_BREAK_LIST_H_ + +#include + +#include +#include + +#include "base/check_op.h" +#include "ui/gfx/range/range.h" + +namespace gfx { + +// BreakLists manage ordered, non-overlapping, and non-repeating ranged values. +// These may be used to apply ranged colors and styles to text, for an example. +// +// Each break stores the start position and value of its associated range. +// A solitary break at position 0 applies to the entire space [0, max_). +// |max_| is initially 0 and should be set to match the available ranged space. +// The first break always has position 0, to ensure all positions have a value. +// The value of the terminal break applies to the range [break.first, max_). +// The value of other breaks apply to the range [break.first, (break+1).first). +template +class BreakList { + public: + // The break type and const iterator, typedef'ed for convenience. + typedef std::pair Break; + typedef typename std::vector::const_iterator const_iterator; + + // Initialize a break at position 0 with the default or supplied |value|. + BreakList(); + explicit BreakList(T value); + + const std::vector& breaks() const { return breaks_; } + + // Clear the breaks and set a break at position 0 with the supplied |value|. + void SetValue(T value); + + // Adjust the breaks to apply |value| over the supplied |range|. + void ApplyValue(T value, const Range& range); + + // Set the max position and trim any breaks at or beyond that position. + void SetMax(size_t max); + size_t max() const { return max_; } + + // Get the break applicable to |position| (at or preceeding |position|). + typename std::vector::iterator GetBreak(size_t position); + typename std::vector::const_iterator GetBreak(size_t position) const; + + // Get the range of the supplied break; returns the break's start position and + // the next break's start position (or |max_| for the terminal break). + Range GetRange(const typename BreakList::const_iterator& i) const; + + // Comparison functions for testing purposes. + bool EqualsValueForTesting(T value) const; + bool EqualsForTesting(const std::vector& breaks) const; + + private: +#ifndef NDEBUG + // Check for ordered breaks [0, |max_|) with no adjacent equivalent values. + void CheckBreaks(); +#endif + + std::vector breaks_; + size_t max_; +}; + +template +BreakList::BreakList() : breaks_(1, Break(0, T())), max_(0) { +} + +template +BreakList::BreakList(T value) : breaks_(1, Break(0, value)), max_(0) { +} + +template +void BreakList::SetValue(T value) { + breaks_.clear(); + breaks_.push_back(Break(0, value)); +} + +template +void BreakList::ApplyValue(T value, const Range& range) { + if (!range.IsValid() || range.is_empty()) + return; + DCHECK(!breaks_.empty()); + DCHECK(!range.is_reversed()); + DCHECK(Range(0, static_cast(max_)).Contains(range)); + + // Erase any breaks in |range|, then add start and end breaks as needed. + typename std::vector::iterator start = GetBreak(range.start()); + start += start->first < range.start() ? 1 : 0; + typename std::vector::iterator end = GetBreak(range.end()); + T trailing_value = end->second; + typename std::vector::iterator i = + start == breaks_.end() ? start : breaks_.erase(start, end + 1); + if (range.start() == 0 || (i - 1)->second != value) + i = breaks_.insert(i, Break(range.start(), value)) + 1; + if (trailing_value != value && range.end() != max_) + breaks_.insert(i, Break(range.end(), trailing_value)); + +#ifndef NDEBUG + CheckBreaks(); +#endif +} + +template +void BreakList::SetMax(size_t max) { + typename std::vector::iterator i = GetBreak(max); + i += (i == breaks_.begin() || i->first < max) ? 1 : 0; + breaks_.erase(i, breaks_.end()); + max_ = max; + +#ifndef NDEBUG + CheckBreaks(); +#endif +} + +template +typename std::vector >::iterator BreakList::GetBreak( + size_t position) { + typename std::vector::iterator i = breaks_.end() - 1; + for (; i != breaks_.begin() && i->first > position; --i); + return i; +} + +template +typename std::vector >::const_iterator + BreakList::GetBreak(size_t position) const { + typename std::vector::const_iterator i = breaks_.end() - 1; + for (; i != breaks_.begin() && i->first > position; --i); + return i; +} + +template +Range BreakList::GetRange( + const typename BreakList::const_iterator& i) const { + const typename BreakList::const_iterator next = i + 1; + return Range(i->first, next == breaks_.end() ? max_ : next->first); +} + +template +bool BreakList::EqualsValueForTesting(T value) const { + return breaks_.size() == 1 && breaks_[0] == Break(0, value); +} + +template +bool BreakList::EqualsForTesting(const std::vector& breaks) const { + if (breaks_.size() != breaks.size()) + return false; + for (size_t i = 0; i < breaks.size(); ++i) + if (breaks_[i] != breaks[i]) + return false; + return true; +} + +#ifndef NDEBUG +template +void BreakList::CheckBreaks() { + DCHECK_EQ(breaks_[0].first, 0U) << "The first break must be at position 0."; + for (size_t i = 0; i < breaks_.size() - 1; ++i) { + DCHECK_LT(breaks_[i].first, breaks_[i + 1].first) << "Break out of order."; + DCHECK_NE(breaks_[i].second, breaks_[i + 1].second) << "Redundant break."; + } + if (max_ > 0) + DCHECK_LT(breaks_.back().first, max_) << "Break beyond max position."; +} +#endif + +} // namespace gfx + +#endif // UI_GFX_BREAK_LIST_H_ diff --git a/break_list_unittest.cc b/break_list_unittest.cc new file mode 100644 index 000000000000..81a662106e15 --- /dev/null +++ b/break_list_unittest.cc @@ -0,0 +1,168 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/break_list.h" + +#include + +#include "base/cxx17_backports.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkColor.h" +#include "ui/gfx/range/range.h" + +namespace gfx { + +class BreakListTest : public testing::Test {}; + +TEST_F(BreakListTest, SetValue) { + // Check the default values applied to new instances. + BreakList style_breaks(false); + EXPECT_TRUE(style_breaks.EqualsValueForTesting(false)); + style_breaks.SetValue(true); + EXPECT_TRUE(style_breaks.EqualsValueForTesting(true)); + + // Ensure that setting values works correctly. + BreakList color_breaks(SK_ColorRED); + EXPECT_TRUE(color_breaks.EqualsValueForTesting(SK_ColorRED)); + color_breaks.SetValue(SK_ColorBLACK); + EXPECT_TRUE(color_breaks.EqualsValueForTesting(SK_ColorBLACK)); +} + +TEST_F(BreakListTest, ApplyValue) { + BreakList breaks(false); + const size_t max = 99; + breaks.SetMax(max); + + // Ensure ApplyValue is a no-op on invalid and empty ranges. + breaks.ApplyValue(true, Range::InvalidRange()); + EXPECT_TRUE(breaks.EqualsValueForTesting(false)); + for (size_t i = 0; i < 3; ++i) { + breaks.ApplyValue(true, Range(i, i)); + EXPECT_TRUE(breaks.EqualsValueForTesting(false)); + } + + // Apply a value to a valid range, check breaks; repeating should be no-op. + std::vector > expected; + expected.push_back(std::pair(0, false)); + expected.push_back(std::pair(2, true)); + expected.push_back(std::pair(3, false)); + for (size_t i = 0; i < 2; ++i) { + breaks.ApplyValue(true, Range(2, 3)); + EXPECT_TRUE(breaks.EqualsForTesting(expected)); + } + + // Ensure setting a value overrides the ranged value. + breaks.SetValue(true); + EXPECT_TRUE(breaks.EqualsValueForTesting(true)); + + // Ensure applying a value over [0, |max|) is the same as setting a value. + breaks.ApplyValue(false, Range(0, max)); + EXPECT_TRUE(breaks.EqualsValueForTesting(false)); + + // Ensure applying a value that is already applied has no effect. + breaks.ApplyValue(false, Range(0, 2)); + breaks.ApplyValue(false, Range(3, 6)); + breaks.ApplyValue(false, Range(7, max)); + EXPECT_TRUE(breaks.EqualsValueForTesting(false)); + + // Ensure applying an identical neighboring value merges the ranges. + breaks.ApplyValue(true, Range(0, 3)); + breaks.ApplyValue(true, Range(3, 6)); + breaks.ApplyValue(true, Range(6, max)); + EXPECT_TRUE(breaks.EqualsValueForTesting(true)); + + // Ensure applying a value with the same range overrides the ranged value. + breaks.ApplyValue(false, Range(2, 3)); + breaks.ApplyValue(true, Range(2, 3)); + EXPECT_TRUE(breaks.EqualsValueForTesting(true)); + + // Ensure applying a value with a containing range overrides contained values. + breaks.ApplyValue(false, Range(0, 1)); + breaks.ApplyValue(false, Range(2, 3)); + breaks.ApplyValue(true, Range(0, 3)); + EXPECT_TRUE(breaks.EqualsValueForTesting(true)); + breaks.ApplyValue(false, Range(4, 5)); + breaks.ApplyValue(false, Range(6, 7)); + breaks.ApplyValue(false, Range(8, 9)); + breaks.ApplyValue(true, Range(4, 9)); + EXPECT_TRUE(breaks.EqualsValueForTesting(true)); + + // Ensure applying various overlapping values yields the intended results. + breaks.ApplyValue(false, Range(1, 4)); + breaks.ApplyValue(false, Range(5, 8)); + breaks.ApplyValue(true, Range(0, 2)); + breaks.ApplyValue(true, Range(3, 6)); + breaks.ApplyValue(true, Range(7, max)); + std::vector > overlap; + overlap.push_back(std::pair(0, true)); + overlap.push_back(std::pair(2, false)); + overlap.push_back(std::pair(3, true)); + overlap.push_back(std::pair(6, false)); + overlap.push_back(std::pair(7, true)); + EXPECT_TRUE(breaks.EqualsForTesting(overlap)); +} + +TEST_F(BreakListTest, SetMax) { + // Ensure values adjust to accommodate max position changes. + BreakList breaks(false); + breaks.SetMax(9); + breaks.ApplyValue(true, Range(0, 2)); + breaks.ApplyValue(true, Range(3, 6)); + breaks.ApplyValue(true, Range(7, 9)); + + std::vector > expected; + expected.push_back(std::pair(0, true)); + expected.push_back(std::pair(2, false)); + expected.push_back(std::pair(3, true)); + expected.push_back(std::pair(6, false)); + expected.push_back(std::pair(7, true)); + EXPECT_TRUE(breaks.EqualsForTesting(expected)); + + // Setting a smaller max should remove any corresponding breaks. + breaks.SetMax(7); + expected.resize(4); + EXPECT_TRUE(breaks.EqualsForTesting(expected)); + breaks.SetMax(4); + expected.resize(3); + EXPECT_TRUE(breaks.EqualsForTesting(expected)); + breaks.SetMax(4); + EXPECT_TRUE(breaks.EqualsForTesting(expected)); + + // Setting a larger max should not change any breaks. + breaks.SetMax(50); + EXPECT_TRUE(breaks.EqualsForTesting(expected)); +} + +TEST_F(BreakListTest, GetBreakAndRange) { + BreakList breaks(false); + breaks.SetMax(8); + breaks.ApplyValue(true, Range(1, 2)); + breaks.ApplyValue(true, Range(4, 6)); + + struct { + size_t position; + size_t break_index; + Range range; + } cases[] = { + { 0, 0, Range(0, 1) }, + { 1, 1, Range(1, 2) }, + { 2, 2, Range(2, 4) }, + { 3, 2, Range(2, 4) }, + { 4, 3, Range(4, 6) }, + { 5, 3, Range(4, 6) }, + { 6, 4, Range(6, 8) }, + { 7, 4, Range(6, 8) }, + // Positions at or beyond the max simply return the last break and range. + { 8, 4, Range(6, 8) }, + { 9, 4, Range(6, 8) }, + }; + + for (size_t i = 0; i < base::size(cases); ++i) { + BreakList::const_iterator it = breaks.GetBreak(cases[i].position); + EXPECT_EQ(breaks.breaks()[cases[i].break_index], *it); + EXPECT_EQ(breaks.GetRange(it), cases[i].range); + } +} + +} // namespace gfx diff --git a/buffer_format_util.cc b/buffer_format_util.cc new file mode 100644 index 000000000000..0ca68b9e1631 --- /dev/null +++ b/buffer_format_util.cc @@ -0,0 +1,322 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/buffer_format_util.h" + +#include "base/check_op.h" +#include "base/cxx17_backports.h" +#include "base/notreached.h" +#include "base/numerics/safe_math.h" +#include "ui/gfx/switches.h" + +namespace gfx { +namespace { + +const BufferFormat kBufferFormats[] = {BufferFormat::R_8, + BufferFormat::R_16, + BufferFormat::RG_88, + BufferFormat::BGR_565, + BufferFormat::RGBA_4444, + BufferFormat::RGBX_8888, + BufferFormat::RGBA_8888, + BufferFormat::BGRX_8888, + BufferFormat::BGRA_1010102, + BufferFormat::RGBA_1010102, + BufferFormat::BGRA_8888, + BufferFormat::RGBA_F16, + BufferFormat::YUV_420_BIPLANAR, + BufferFormat::YVU_420, + BufferFormat::P010}; + +static_assert(base::size(kBufferFormats) == + (static_cast(BufferFormat::LAST) + 1), + "BufferFormat::LAST must be last value of kBufferFormats"); + +} // namespace + +std::vector GetBufferFormatsForTesting() { + return std::vector(kBufferFormats, + kBufferFormats + base::size(kBufferFormats)); +} + +size_t AlphaBitsForBufferFormat(BufferFormat format) { + switch (format) { + case BufferFormat::RGBA_4444: + return 4; + case BufferFormat::RGBA_8888: + return 8; + case BufferFormat::BGRA_1010102: + case BufferFormat::RGBA_1010102: + return 2; + case BufferFormat::BGRA_8888: + return 8; + case BufferFormat::RGBA_F16: + return 16; + case BufferFormat::R_8: + case BufferFormat::R_16: + case BufferFormat::RG_88: + case BufferFormat::BGR_565: + case BufferFormat::RGBX_8888: + case BufferFormat::BGRX_8888: + case BufferFormat::YVU_420: + case BufferFormat::YUV_420_BIPLANAR: + case BufferFormat::P010: + return 0; + } + NOTREACHED(); + return 0; +} + +size_t NumberOfPlanesForLinearBufferFormat(BufferFormat format) { + switch (format) { + case BufferFormat::R_8: + case BufferFormat::R_16: + case BufferFormat::RG_88: + case BufferFormat::BGR_565: + case BufferFormat::RGBA_4444: + case BufferFormat::RGBX_8888: + case BufferFormat::RGBA_8888: + case BufferFormat::BGRX_8888: + case BufferFormat::BGRA_1010102: + case BufferFormat::RGBA_1010102: + case BufferFormat::BGRA_8888: + case BufferFormat::RGBA_F16: + return 1; + case BufferFormat::YUV_420_BIPLANAR: + case BufferFormat::P010: + return 2; + case BufferFormat::YVU_420: + return 3; + } + NOTREACHED(); + return 0; +} + +size_t SubsamplingFactorForBufferFormat(BufferFormat format, size_t plane) { + switch (format) { + case BufferFormat::R_8: + case BufferFormat::R_16: + case BufferFormat::RG_88: + case BufferFormat::BGR_565: + case BufferFormat::RGBA_4444: + case BufferFormat::RGBX_8888: + case BufferFormat::RGBA_8888: + case BufferFormat::BGRX_8888: + case BufferFormat::BGRA_1010102: + case BufferFormat::RGBA_1010102: + case BufferFormat::BGRA_8888: + case BufferFormat::RGBA_F16: + return 1; + case BufferFormat::YVU_420: { + static size_t factor[] = {1, 2, 2}; + DCHECK_LT(static_cast(plane), base::size(factor)); + return factor[plane]; + } + case BufferFormat::YUV_420_BIPLANAR: + case BufferFormat::P010: { + static size_t factor[] = {1, 2}; + DCHECK_LT(static_cast(plane), base::size(factor)); + return factor[plane]; + } + } + NOTREACHED(); + return 0; +} + +size_t RowSizeForBufferFormat(size_t width, BufferFormat format, size_t plane) { + size_t row_size = 0; + bool valid = RowSizeForBufferFormatChecked(width, format, plane, &row_size); + DCHECK(valid); + return row_size; +} + +bool RowSizeForBufferFormatChecked(size_t width, + BufferFormat format, + size_t plane, + size_t* size_in_bytes) { + base::CheckedNumeric checked_size = width; + switch (format) { + case BufferFormat::R_8: + checked_size += 3; + if (!checked_size.IsValid()) + return false; + *size_in_bytes = (checked_size & ~0x3).ValueOrDie(); + return true; + case BufferFormat::R_16: + case BufferFormat::RG_88: + case BufferFormat::BGR_565: + case BufferFormat::RGBA_4444: + checked_size *= 2; + checked_size += 3; + if (!checked_size.IsValid()) + return false; + *size_in_bytes = (checked_size & ~0x3).ValueOrDie(); + return true; + case BufferFormat::BGRX_8888: + case BufferFormat::BGRA_1010102: + case BufferFormat::RGBA_1010102: + case BufferFormat::RGBX_8888: + case BufferFormat::RGBA_8888: + case BufferFormat::BGRA_8888: + checked_size *= 4; + if (!checked_size.IsValid()) + return false; + *size_in_bytes = checked_size.ValueOrDie(); + return true; + case BufferFormat::RGBA_F16: + checked_size *= 8; + if (!checked_size.IsValid()) + return false; + *size_in_bytes = checked_size.ValueOrDie(); + return true; + case BufferFormat::YVU_420: + DCHECK_EQ(width % 2, 0u); + *size_in_bytes = width / SubsamplingFactorForBufferFormat(format, plane); + return true; + case BufferFormat::YUV_420_BIPLANAR: + DCHECK_EQ(width % 2, 0u); + *size_in_bytes = width; + return true; + case BufferFormat::P010: + DCHECK_EQ(width % 2, 0u); + *size_in_bytes = 2 * width; + return true; + } + NOTREACHED(); + return false; +} + +size_t BufferSizeForBufferFormat(const Size& size, BufferFormat format) { + size_t buffer_size = 0; + bool valid = BufferSizeForBufferFormatChecked(size, format, &buffer_size); + DCHECK(valid); + return buffer_size; +} + +bool BufferSizeForBufferFormatChecked(const Size& size, + BufferFormat format, + size_t* size_in_bytes) { + base::CheckedNumeric checked_size = 0; + size_t num_planes = NumberOfPlanesForLinearBufferFormat(format); + for (size_t i = 0; i < num_planes; ++i) { + size_t row_size = 0; + if (!RowSizeForBufferFormatChecked(size.width(), format, i, &row_size)) + return false; + base::CheckedNumeric checked_plane_size = row_size; + checked_plane_size *= size.height() / + SubsamplingFactorForBufferFormat(format, i); + if (!checked_plane_size.IsValid()) + return false; + checked_size += checked_plane_size.ValueOrDie(); + if (!checked_size.IsValid()) + return false; + } + *size_in_bytes = checked_size.ValueOrDie(); + return true; +} + +size_t BufferOffsetForBufferFormat(const Size& size, + BufferFormat format, + size_t plane) { + DCHECK_LT(plane, gfx::NumberOfPlanesForLinearBufferFormat(format)); + switch (format) { + case BufferFormat::R_8: + case BufferFormat::R_16: + case BufferFormat::RG_88: + case BufferFormat::BGR_565: + case BufferFormat::RGBA_4444: + case BufferFormat::RGBX_8888: + case BufferFormat::RGBA_8888: + case BufferFormat::BGRX_8888: + case BufferFormat::BGRA_1010102: + case BufferFormat::RGBA_1010102: + case BufferFormat::BGRA_8888: + case BufferFormat::RGBA_F16: + return 0; + case BufferFormat::YVU_420: { + static size_t offset_in_2x2_sub_sampling_sizes[] = {0, 4, 5}; + DCHECK_LT(plane, base::size(offset_in_2x2_sub_sampling_sizes)); + return offset_in_2x2_sub_sampling_sizes[plane] * (size.width() / 2) * + (size.height() / 2); + } + case gfx::BufferFormat::YUV_420_BIPLANAR: { + static size_t offset_in_2x2_sub_sampling_sizes[] = {0, 4}; + DCHECK_LT(plane, base::size(offset_in_2x2_sub_sampling_sizes)); + return offset_in_2x2_sub_sampling_sizes[plane] * (size.width() / 2) * + (size.height() / 2); + } + case BufferFormat::P010: { + static size_t offset_in_2x2_sub_sampling_sizes[] = {0, 4}; + DCHECK_LT(plane, base::size(offset_in_2x2_sub_sampling_sizes)); + return 2 * offset_in_2x2_sub_sampling_sizes[plane] * + (size.width() / 2 + size.height() / 2); + } + } + NOTREACHED(); + return 0; +} + +const char* BufferFormatToString(BufferFormat format) { + switch (format) { + case BufferFormat::R_8: + return "R_8"; + case BufferFormat::R_16: + return "R_16"; + case BufferFormat::RG_88: + return "RG_88"; + case BufferFormat::BGR_565: + return "BGR_565"; + case BufferFormat::RGBA_4444: + return "RGBA_4444"; + case BufferFormat::RGBX_8888: + return "RGBX_8888"; + case BufferFormat::RGBA_8888: + return "RGBA_8888"; + case BufferFormat::BGRX_8888: + return "BGRX_8888"; + case BufferFormat::BGRA_1010102: + return "BGRA_1010102"; + case BufferFormat::RGBA_1010102: + return "RGBA_1010102"; + case BufferFormat::BGRA_8888: + return "BGRA_8888"; + case BufferFormat::RGBA_F16: + return "RGBA_F16"; + case BufferFormat::YVU_420: + return "YVU_420"; + case BufferFormat::YUV_420_BIPLANAR: + return "YUV_420_BIPLANAR"; + case BufferFormat::P010: + return "P010"; + } + NOTREACHED() + << "Invalid BufferFormat: " + << static_cast::type>(format); + return "Invalid Format"; +} + +const char* BufferPlaneToString(BufferPlane format) { + switch (format) { + case BufferPlane::DEFAULT: + return "DEFAULT"; + case BufferPlane::Y: + return "Y"; + case BufferPlane::UV: + return "UV"; + case BufferPlane::U: + return "U"; + case BufferPlane::V: + return "V"; + } + NOTREACHED() << "Invalid BufferPlane: " + << static_cast::type>( + format); + return "Invalid Plane"; +} + +bool AllowOddHeightMultiPlanarBuffers() { + return base::FeatureList::IsEnabled(features::kOddHeightMultiPlanarBuffers); +} + +} // namespace gfx diff --git a/buffer_format_util.h b/buffer_format_util.h new file mode 100644 index 000000000000..a73b264b2fbf --- /dev/null +++ b/buffer_format_util.h @@ -0,0 +1,69 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_BUFFER_FORMAT_UTIL_H_ +#define UI_GFX_BUFFER_FORMAT_UTIL_H_ + +#include + +#include + +#include "ui/gfx/buffer_types.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +// Returns a vector containing all buffer formats. +GFX_EXPORT std::vector GetBufferFormatsForTesting(); + +// Returns the number of bits of alpha for the specified format. +GFX_EXPORT size_t AlphaBitsForBufferFormat(BufferFormat format); + +// Returns the number of planes for |format|. +GFX_EXPORT size_t NumberOfPlanesForLinearBufferFormat(BufferFormat format); + +// Returns the subsampling factor applied to the given zero-indexed |plane| of +// |format| both horizontally and vertically. +GFX_EXPORT size_t SubsamplingFactorForBufferFormat(BufferFormat format, + size_t plane); + +// Returns the number of bytes used to store a row of the given zero-indexed +// |plane| of |format|. +GFX_EXPORT size_t RowSizeForBufferFormat(size_t width, + BufferFormat format, + size_t plane); +GFX_EXPORT bool RowSizeForBufferFormatChecked(size_t width, + BufferFormat format, + size_t plane, + size_t* size_in_bytes) + WARN_UNUSED_RESULT; + +// Returns the number of bytes used to store all the planes of a given |format|. +GFX_EXPORT size_t BufferSizeForBufferFormat(const Size& size, + BufferFormat format); +GFX_EXPORT bool BufferSizeForBufferFormatChecked(const Size& size, + BufferFormat format, + size_t* size_in_bytes) + WARN_UNUSED_RESULT; + +GFX_EXPORT size_t BufferOffsetForBufferFormat(const Size& size, + BufferFormat format, + size_t plane); + +// Returns the name of |format| as a string. +GFX_EXPORT const char* BufferFormatToString(BufferFormat format); + +// Returns the name of |plane| as a string. +GFX_EXPORT const char* BufferPlaneToString(BufferPlane plane); + +// Multiplanar buffer formats (e.g, YUV_420_BIPLANAR, YVU_420, P010) can be +// tricky when the size of the primary plane is odd, because the subsampled +// planes will have a size that is not a divisor of the primary plane's size. +// This indicates that odd height multiplanar formats are supported. +GFX_EXPORT bool AllowOddHeightMultiPlanarBuffers(); + +} // namespace gfx + +#endif // UI_GFX_BUFFER_FORMAT_UTIL_H_ diff --git a/buffer_types.h b/buffer_types.h new file mode 100644 index 000000000000..1ae77fc757bd --- /dev/null +++ b/buffer_types.h @@ -0,0 +1,99 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_BUFFER_TYPES_H_ +#define UI_GFX_BUFFER_TYPES_H_ + +#include + +#include + +namespace gfx { + +// The format needs to be taken into account when mapping a buffer into the +// client's address space. +enum class BufferFormat { + R_8, + R_16, + RG_88, + BGR_565, + RGBA_4444, + RGBX_8888, + RGBA_8888, + BGRX_8888, + BGRA_1010102, + RGBA_1010102, + BGRA_8888, + RGBA_F16, + YVU_420, + YUV_420_BIPLANAR, + P010, + + LAST = P010 +}; + +// The usage mode affects how a buffer can be used. Only buffers created with +// *_CPU_READ_WRITE_* can be mapped into the client's address space and accessed +// by the CPU. SCANOUT implies GPU_READ_WRITE. +// *_VDA_WRITE is for cases where a video decode accellerator writes into +// the buffers. +// PROTECTED_* are for HW protected buffers that cannot be read by the CPU and +// can only be read in protected GPU contexts or scanned out to overlays. + +// TODO(reveman): Add GPU_READ_WRITE for use-cases where SCANOUT is not +// required. +enum class BufferUsage { + GPU_READ, + SCANOUT, + // SCANOUT_CAMERA_READ_WRITE implies CPU_READ_WRITE. + SCANOUT_CAMERA_READ_WRITE, + CAMERA_AND_CPU_READ_WRITE, + SCANOUT_CPU_READ_WRITE, + SCANOUT_VDA_WRITE, + PROTECTED_SCANOUT_VDA_WRITE, + GPU_READ_CPU_READ_WRITE, + SCANOUT_VEA_CPU_READ, + SCANOUT_FRONT_RENDERING, + VEA_READ_CAMERA_AND_CPU_READ_WRITE, + + LAST = VEA_READ_CAMERA_AND_CPU_READ_WRITE +}; + +struct BufferUsageAndFormat { + BufferUsageAndFormat() + : usage(BufferUsage::GPU_READ), format(BufferFormat::RGBA_8888) {} + BufferUsageAndFormat(BufferUsage usage, BufferFormat format) + : usage(usage), format(format) {} + + bool operator==(const BufferUsageAndFormat& other) const { + return std::tie(usage, format) == std::tie(other.usage, other.format); + } + + BufferUsage usage; + BufferFormat format; +}; + +// Used to identify the plane of a GpuMemoryBuffer to use when creating a +// SharedImage. +enum class BufferPlane { + // For single-plane GpuMemoryBuffer, this refers to that single plane. For + // YUV_420, YUV_420_BIPLANAR, and P010 GpuMemoryBuffers, this refers to an + // RGB representation of the planes (either bound directly as a texture or + // created through an extra copy). + DEFAULT, + // The Y plane for YUV_420, YUV_420_BIPLANAR, and P010. + Y, + // The UV plane for YUV_420_BIPLANAR and P010. + UV, + // The U plane for YUV_420. + U, + // The V plane for YUV_420. + V, + + LAST = V +}; + +} // namespace gfx + +#endif // UI_GFX_BUFFER_TYPES_H_ diff --git a/buffer_usage_util.cc b/buffer_usage_util.cc new file mode 100644 index 000000000000..d2a66f6aaaf0 --- /dev/null +++ b/buffer_usage_util.cc @@ -0,0 +1,37 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/buffer_usage_util.h" + +namespace gfx { + +const char* BufferUsageToString(BufferUsage usage) { + switch (usage) { + case BufferUsage::GPU_READ: + return "GPU_READ"; + case BufferUsage::SCANOUT: + return "SCANOUT"; + case BufferUsage::SCANOUT_CAMERA_READ_WRITE: + return "SCANOUT_CAMERA_READ_WRITE"; + case BufferUsage::CAMERA_AND_CPU_READ_WRITE: + return "CAMERA_AND_CPU_READ_WRITE"; + case BufferUsage::SCANOUT_CPU_READ_WRITE: + return "SCANOUT_CPU_READ_WRITE"; + case BufferUsage::SCANOUT_VDA_WRITE: + return "SCANOUT_VDA_WRITE"; + case BufferUsage::PROTECTED_SCANOUT_VDA_WRITE: + return "PROTECTED_SCANOUT_VDA_WRITE"; + case BufferUsage::GPU_READ_CPU_READ_WRITE: + return "GPU_READ_CPU_READ_WRITE"; + case BufferUsage::SCANOUT_VEA_CPU_READ: + return "SCANOUT_VEA_CPU_READ"; + case BufferUsage::VEA_READ_CAMERA_AND_CPU_READ_WRITE: + return "VEA_READ_CAMERA_AND_CPU_READ_WRITE"; + case BufferUsage::SCANOUT_FRONT_RENDERING: + return "SCANOUT_FRONT_RENDERING"; + } + return "Invalid Usage"; +} + +} // namespace gfx diff --git a/buffer_usage_util.h b/buffer_usage_util.h new file mode 100644 index 000000000000..cf3c41bee52f --- /dev/null +++ b/buffer_usage_util.h @@ -0,0 +1,18 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_BUFFER_USAGE_UTIL_H_ +#define UI_GFX_BUFFER_USAGE_UTIL_H_ + +#include "ui/gfx/buffer_types.h" +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +// Returns the name of |usage| as a string. +GFX_EXPORT const char* BufferUsageToString(BufferUsage usage); + +} // namespace gfx + +#endif // UI_GFX_BUFFER_USAGE_UTIL_H_ diff --git a/ca_layer_params.cc b/ca_layer_params.cc new file mode 100644 index 000000000000..1824bc5a86bc --- /dev/null +++ b/ca_layer_params.cc @@ -0,0 +1,16 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/ca_layer_params.h" + +namespace gfx { + +CALayerParams::CALayerParams() {} +CALayerParams::~CALayerParams() = default; +CALayerParams::CALayerParams(CALayerParams&& params) = default; +CALayerParams::CALayerParams(const CALayerParams& params) = default; +CALayerParams& CALayerParams::operator=(CALayerParams&& params) = default; +CALayerParams& CALayerParams::operator=(const CALayerParams& params) = default; + +} // namespace gfx diff --git a/ca_layer_params.h b/ca_layer_params.h new file mode 100644 index 000000000000..64af450ed36a --- /dev/null +++ b/ca_layer_params.h @@ -0,0 +1,51 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_CA_LAYER_PARAMS_H_ +#define UI_GFX_CA_LAYER_PARAMS_H_ + +#include "build/build_config.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/gfx_export.h" + +#if defined(OS_MAC) +#include "ui/gfx/mac/io_surface.h" +#endif + +namespace gfx { + +// The parameters required to add a composited frame to a CALayer. This +// is used only on macOS. +struct GFX_EXPORT CALayerParams { + CALayerParams(); + CALayerParams(CALayerParams&& params); + CALayerParams(const CALayerParams& params); + CALayerParams& operator=(CALayerParams&& params); + CALayerParams& operator=(const CALayerParams& params); + ~CALayerParams(); + + // The |is_empty| flag is used to short-circuit code to handle CALayerParams + // on non-macOS platforms. + bool is_empty = true; + + // Can be used to instantiate a CALayerTreeHost in the browser process, which + // will display a CALayerTree rooted in the GPU process. This is non-zero when + // using remote CoreAnimation. + uint32_t ca_context_id = 0; + + // Used to set the contents of a CALayer in the browser to an IOSurface that + // is specified by the GPU process. This is non-null iff |ca_context_id| is + // zero. +#if defined(OS_MAC) + gfx::ScopedRefCountedIOSurfaceMachPort io_surface_mach_port; +#endif + + // The geometry of the frame. + gfx::Size pixel_size; + float scale_factor = 1.f; +}; + +} // namespace gfx + +#endif // UI_GFX_CA_LAYER_PARAMS_H_ diff --git a/canvas.cc b/canvas.cc new file mode 100644 index 000000000000..b462f08b3e40 --- /dev/null +++ b/canvas.cc @@ -0,0 +1,550 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/canvas.h" + +#include +#include + +#include "base/i18n/rtl.h" +#include "base/logging.h" +#include "base/numerics/safe_conversions.h" +#include "cc/paint/paint_flags.h" +#include "cc/paint/paint_shader.h" +#include "cc/paint/skottie_wrapper.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkPath.h" +#include "third_party/skia/include/core/SkRefCnt.h" +#include "third_party/skia/include/effects/SkDashPathEffect.h" +#include "third_party/skia/include/effects/SkGradientShader.h" +#include "ui/gfx/font_list.h" +#include "ui/gfx/geometry/insets_f.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/rect_conversions.h" +#include "ui/gfx/geometry/rect_f.h" +#include "ui/gfx/geometry/size_conversions.h" +#include "ui/gfx/geometry/skia_conversions.h" +#include "ui/gfx/geometry/transform.h" +#include "ui/gfx/scoped_canvas.h" +#include "ui/gfx/skia_paint_util.h" +#include "ui/gfx/switches.h" + +namespace gfx { + +Canvas::Canvas(const Size& size, float image_scale, bool is_opaque) + : image_scale_(image_scale) { + Size pixel_size = ScaleToCeiledSize(size, image_scale); + canvas_ = CreateOwnedCanvas(pixel_size, is_opaque); + + SkScalar scale_scalar = SkFloatToScalar(image_scale); + canvas_->scale(scale_scalar, scale_scalar); +} + +Canvas::Canvas() + : image_scale_(1.f), canvas_(CreateOwnedCanvas({0, 0}, false)) {} + +Canvas::Canvas(cc::PaintCanvas* canvas, float image_scale) + : image_scale_(image_scale), canvas_(canvas) { + DCHECK(canvas_); +} + +Canvas::~Canvas() { +} + +void Canvas::RecreateBackingCanvas(const Size& size, + float image_scale, + bool is_opaque) { + image_scale_ = image_scale; + Size pixel_size = ScaleToFlooredSize(size, image_scale); + canvas_ = CreateOwnedCanvas(pixel_size, is_opaque); + + SkScalar scale_scalar = SkFloatToScalar(image_scale); + canvas_->scale(scale_scalar, scale_scalar); +} + +// static +void Canvas::SizeStringInt(const std::u16string& text, + const FontList& font_list, + int* width, + int* height, + int line_height, + int flags) { + float fractional_width = static_cast(*width); + float factional_height = static_cast(*height); + SizeStringFloat(text, font_list, &fractional_width, &factional_height, + line_height, flags); + *width = base::ClampCeil(fractional_width); + *height = base::ClampCeil(factional_height); +} + +// static +int Canvas::GetStringWidth(const std::u16string& text, + const FontList& font_list) { + int width = 0, height = 0; + SizeStringInt(text, font_list, &width, &height, 0, NO_ELLIPSIS); + return width; +} + +// static +float Canvas::GetStringWidthF(const std::u16string& text, + const FontList& font_list) { + float width = 0, height = 0; + SizeStringFloat(text, font_list, &width, &height, 0, NO_ELLIPSIS); + return width; +} + +// static +int Canvas::DefaultCanvasTextAlignment() { + return base::i18n::IsRTL() ? TEXT_ALIGN_RIGHT : TEXT_ALIGN_LEFT; +} + +float Canvas::UndoDeviceScaleFactor() { + SkScalar scale_factor = 1.0f / image_scale_; + canvas_->scale(scale_factor, scale_factor); + return image_scale_; +} + +void Canvas::Save() { + canvas_->save(); +} + +void Canvas::SaveLayerAlpha(uint8_t alpha) { + canvas_->saveLayerAlpha(NULL, alpha); +} + +void Canvas::SaveLayerAlpha(uint8_t alpha, const Rect& layer_bounds) { + SkRect bounds(RectToSkRect(layer_bounds)); + canvas_->saveLayerAlpha(&bounds, alpha); +} + +void Canvas::SaveLayerWithFlags(const cc::PaintFlags& flags) { + canvas_->saveLayer(nullptr /* bounds */, &flags); +} + +void Canvas::Restore() { + canvas_->restore(); +} + +void Canvas::ClipRect(const Rect& rect, SkClipOp op) { + canvas_->clipRect(RectToSkRect(rect), op); +} + +void Canvas::ClipRect(const RectF& rect, SkClipOp op) { + canvas_->clipRect(RectFToSkRect(rect), op); +} + +void Canvas::ClipPath(const SkPath& path, bool do_anti_alias) { + canvas_->clipPath(path, SkClipOp::kIntersect, do_anti_alias); +} + +bool Canvas::GetClipBounds(Rect* bounds) { + SkRect out; + if (canvas_->getLocalClipBounds(&out)) { + *bounds = ToEnclosingRect(SkRectToRectF(out)); + return true; + } + *bounds = gfx::Rect(); + return false; +} + +void Canvas::Translate(const Vector2d& offset) { + canvas_->translate(SkIntToScalar(offset.x()), SkIntToScalar(offset.y())); +} + +void Canvas::Scale(float x_scale, float y_scale) { + canvas_->scale(SkFloatToScalar(x_scale), SkFloatToScalar(y_scale)); +} + +void Canvas::DrawColor(SkColor color) { + DrawColor(color, SkBlendMode::kSrcOver); +} + +void Canvas::DrawColor(SkColor color, SkBlendMode mode) { + canvas_->drawColor(color, mode); +} + +void Canvas::FillRect(const Rect& rect, SkColor color) { + FillRect(rect, color, SkBlendMode::kSrcOver); +} + +void Canvas::FillRect(const Rect& rect, SkColor color, SkBlendMode mode) { + cc::PaintFlags flags; + flags.setColor(color); + flags.setStyle(cc::PaintFlags::kFill_Style); + flags.setBlendMode(mode); + DrawRect(rect, flags); +} + +void Canvas::DrawRect(const RectF& rect, SkColor color) { + DrawRect(rect, color, SkBlendMode::kSrcOver); +} + +void Canvas::DrawRect(const RectF& rect, SkColor color, SkBlendMode mode) { + cc::PaintFlags flags; + flags.setColor(color); + flags.setStyle(cc::PaintFlags::kStroke_Style); + // Set a stroke width of 0, which will put us down the stroke rect path. If + // we set a stroke width of 1, for example, this will internally create a + // path and fill it, which causes problems near the edge of the canvas. + flags.setStrokeWidth(SkIntToScalar(0)); + flags.setBlendMode(mode); + + DrawRect(rect, flags); +} + +void Canvas::DrawRect(const Rect& rect, const cc::PaintFlags& flags) { + DrawRect(RectF(rect), flags); +} + +void Canvas::DrawRect(const RectF& rect, const cc::PaintFlags& flags) { + canvas_->drawRect(RectFToSkRect(rect), flags); +} + +void Canvas::DrawLine(const Point& p1, const Point& p2, SkColor color) { + DrawLine(PointF(p1), PointF(p2), color); +} + +void Canvas::DrawLine(const PointF& p1, const PointF& p2, SkColor color) { + cc::PaintFlags flags; + flags.setColor(color); + flags.setStrokeWidth(SkIntToScalar(1)); + DrawLine(p1, p2, flags); +} + +void Canvas::DrawLine(const Point& p1, + const Point& p2, + const cc::PaintFlags& flags) { + DrawLine(PointF(p1), PointF(p2), flags); +} + +void Canvas::DrawLine(const PointF& p1, + const PointF& p2, + const cc::PaintFlags& flags) { + canvas_->drawLine(SkFloatToScalar(p1.x()), SkFloatToScalar(p1.y()), + SkFloatToScalar(p2.x()), SkFloatToScalar(p2.y()), flags); +} + +void Canvas::DrawSharpLine(PointF p1, PointF p2, SkColor color) { + ScopedCanvas scoped(this); + float dsf = UndoDeviceScaleFactor(); + p1.Scale(dsf); + p2.Scale(dsf); + + cc::PaintFlags flags; + flags.setColor(color); + flags.setStrokeWidth(SkFloatToScalar(std::floor(dsf))); + + DrawLine(p1, p2, flags); +} + +void Canvas::Draw1pxLine(PointF p1, PointF p2, SkColor color) { + ScopedCanvas scoped(this); + float dsf = UndoDeviceScaleFactor(); + p1.Scale(dsf); + p2.Scale(dsf); + + DrawLine(p1, p2, color); +} + +void Canvas::DrawCircle(const Point& center_point, + int radius, + const cc::PaintFlags& flags) { + canvas_->drawOval( + SkRect::MakeLTRB(center_point.x() - radius, center_point.y() - radius, + center_point.x() + radius, center_point.y() + radius), + flags); +} + +void Canvas::DrawCircle(const PointF& center_point, + float radius, + const cc::PaintFlags& flags) { + canvas_->drawOval( + SkRect::MakeLTRB(center_point.x() - radius, center_point.y() - radius, + center_point.x() + radius, center_point.y() + radius), + flags); +} + +void Canvas::DrawRoundRect(const Rect& rect, + int radius, + const cc::PaintFlags& flags) { + DrawRoundRect(RectF(rect), radius, flags); +} + +void Canvas::DrawRoundRect(const RectF& rect, + float radius, + const cc::PaintFlags& flags) { + canvas_->drawRoundRect(RectFToSkRect(rect), SkFloatToScalar(radius), + SkFloatToScalar(radius), flags); +} + +void Canvas::DrawPath(const SkPath& path, const cc::PaintFlags& flags) { + canvas_->drawPath(path, flags); +} + +void Canvas::DrawSolidFocusRect(RectF rect, SkColor color, int thickness) { + cc::PaintFlags flags; + flags.setColor(color); + const float adjusted_thickness = + std::floor(thickness * image_scale_) / image_scale_; + flags.setStrokeWidth(SkFloatToScalar(adjusted_thickness)); + flags.setStyle(cc::PaintFlags::kStroke_Style); + rect.Inset(gfx::InsetsF(adjusted_thickness / 2)); + DrawRect(rect, flags); +} + +void Canvas::DrawImageInt(const ImageSkia& image, int x, int y) { + cc::PaintFlags flags; + DrawImageInt(image, x, y, flags); +} + +void Canvas::DrawImageInt(const ImageSkia& image, int x, int y, uint8_t a) { + cc::PaintFlags flags; + flags.setAlpha(a); + DrawImageInt(image, x, y, flags); +} + +void Canvas::DrawImageInt(const ImageSkia& image, + int x, + int y, + const cc::PaintFlags& flags) { + const ImageSkiaRep& image_rep = image.GetRepresentation(image_scale_); + if (image_rep.is_null()) + return; + float bitmap_scale = image_rep.scale(); + + ScopedCanvas scoper(this); + canvas_->scale(SkFloatToScalar(1.0f / bitmap_scale), + SkFloatToScalar(1.0f / bitmap_scale)); + canvas_->translate(SkFloatToScalar(std::round(x * bitmap_scale)), + SkFloatToScalar(std::round(y * bitmap_scale))); + canvas_->saveLayer(nullptr, &flags); + canvas_->drawPicture(image_rep.GetPaintRecord()); + canvas_->restore(); +} + +void Canvas::DrawImageInt(const ImageSkia& image, + int src_x, + int src_y, + int src_w, + int src_h, + int dest_x, + int dest_y, + int dest_w, + int dest_h, + bool filter) { + cc::PaintFlags flags; + DrawImageInt(image, src_x, src_y, src_w, src_h, dest_x, dest_y, dest_w, + dest_h, filter, flags); +} + +void Canvas::DrawImageInt(const ImageSkia& image, + int src_x, + int src_y, + int src_w, + int src_h, + int dest_x, + int dest_y, + int dest_w, + int dest_h, + bool filter, + const cc::PaintFlags& flags) { + const ImageSkiaRep& image_rep = image.GetRepresentation(image_scale_); + if (image_rep.is_null()) + return; + bool remove_image_scale = true; + DrawImageIntHelper(image_rep, src_x, src_y, src_w, src_h, dest_x, dest_y, + dest_w, dest_h, filter, flags, remove_image_scale); +} + +void Canvas::DrawImageIntInPixel(const ImageSkiaRep& image_rep, + int dest_x, + int dest_y, + int dest_w, + int dest_h, + bool filter, + const cc::PaintFlags& flags) { + int src_x = 0; + int src_y = 0; + int src_w = image_rep.pixel_width(); + int src_h = image_rep.pixel_height(); + // Don't remove image scale here, this function is used to draw the + // (already scaled) |image_rep| at a 1:1 scale with the canvas. + bool remove_image_scale = false; + DrawImageIntHelper(image_rep, src_x, src_y, src_w, src_h, dest_x, dest_y, + dest_w, dest_h, filter, flags, remove_image_scale); +} + +void Canvas::DrawImageInPath(const ImageSkia& image, + int x, + int y, + const SkPath& path, + const cc::PaintFlags& original_flags) { + const ImageSkiaRep& image_rep = image.GetRepresentation(image_scale_); + if (image_rep.is_null()) + return; + + SkMatrix matrix; + matrix.setTranslate(SkIntToScalar(x), SkIntToScalar(y)); + cc::PaintFlags flags(original_flags); + flags.setShader(CreateImageRepShader(image_rep, SkTileMode::kRepeat, + SkTileMode::kRepeat, matrix)); + canvas_->drawPath(path, flags); +} + +void Canvas::DrawSkottie(scoped_refptr skottie, + const Rect& dst, + float t) { + canvas_->drawSkottie(std::move(skottie), RectToSkRect(dst), t); +} + +void Canvas::DrawStringRect(const std::u16string& text, + const FontList& font_list, + SkColor color, + const Rect& display_rect) { + DrawStringRectWithFlags(text, font_list, color, display_rect, + DefaultCanvasTextAlignment()); +} + +void Canvas::TileImageInt(const ImageSkia& image, + int x, + int y, + int w, + int h) { + TileImageInt(image, 0, 0, x, y, w, h); +} + +void Canvas::TileImageInt(const ImageSkia& image, + int src_x, + int src_y, + int dest_x, + int dest_y, + int w, + int h, + float tile_scale, + SkTileMode tile_mode_x, + SkTileMode tile_mode_y, + cc::PaintFlags* flags) { + SkRect dest_rect = { SkIntToScalar(dest_x), + SkIntToScalar(dest_y), + SkIntToScalar(dest_x + w), + SkIntToScalar(dest_y + h) }; + if (!IntersectsClipRect(dest_rect)) + return; + + cc::PaintFlags paint_flags; + if (!flags) + flags = &paint_flags; + + if (InitPaintFlagsForTiling(image, src_x, src_y, tile_scale, tile_scale, + dest_x, dest_y, tile_mode_x, tile_mode_y, flags)) + canvas_->drawRect(dest_rect, *flags); +} + +bool Canvas::InitPaintFlagsForTiling(const ImageSkia& image, + int src_x, + int src_y, + float tile_scale_x, + float tile_scale_y, + int dest_x, + int dest_y, + SkTileMode tile_mode_x, + SkTileMode tile_mode_y, + cc::PaintFlags* flags) { + const ImageSkiaRep& image_rep = image.GetRepresentation(image_scale_); + if (image_rep.is_null()) + return false; + + SkMatrix shader_scale; + shader_scale.setScale(SkFloatToScalar(tile_scale_x), + SkFloatToScalar(tile_scale_y)); + shader_scale.preTranslate(SkIntToScalar(-src_x), SkIntToScalar(-src_y)); + shader_scale.postTranslate(SkIntToScalar(dest_x), SkIntToScalar(dest_y)); + + flags->setShader(CreateImageRepShader(image_rep, tile_mode_x, tile_mode_y, + shader_scale)); + return true; +} + +void Canvas::Transform(const gfx::Transform& transform) { + canvas_->concat(SkMatrix(transform.matrix())); +} + +SkBitmap Canvas::GetBitmap() const { + DCHECK(bitmap_); + return bitmap_.value(); +} + +bool Canvas::IntersectsClipRect(const SkRect& rect) { + SkRect clip; + return canvas_->getLocalClipBounds(&clip) && clip.intersects(rect); +} + +void Canvas::DrawImageIntHelper(const ImageSkiaRep& image_rep, + int src_x, + int src_y, + int src_w, + int src_h, + int dest_x, + int dest_y, + int dest_w, + int dest_h, + bool filter, + const cc::PaintFlags& original_flags, + bool remove_image_scale) { + DLOG_ASSERT(src_x + src_w < std::numeric_limits::max() && + src_y + src_h < std::numeric_limits::max()); + if (src_w <= 0 || src_h <= 0) { + NOTREACHED() << "Attempting to draw bitmap from an empty rect!"; + return; + } + + SkRect dest_rect = { SkIntToScalar(dest_x), + SkIntToScalar(dest_y), + SkIntToScalar(dest_x + dest_w), + SkIntToScalar(dest_y + dest_h) }; + if (!IntersectsClipRect(dest_rect)) + return; + + float user_scale_x = static_cast(dest_w) / src_w; + float user_scale_y = static_cast(dest_h) / src_h; + + // Make a bitmap shader that contains the bitmap we want to draw. This is + // basically what SkCanvas.drawBitmap does internally, but it gives us + // more control over quality and will use the mipmap in the source image if + // it has one, whereas drawBitmap won't. + SkMatrix shader_scale; + shader_scale.setScale(SkFloatToScalar(user_scale_x), + SkFloatToScalar(user_scale_y)); + shader_scale.preTranslate(SkIntToScalar(-src_x), SkIntToScalar(-src_y)); + shader_scale.postTranslate(SkIntToScalar(dest_x), SkIntToScalar(dest_y)); + + cc::PaintFlags flags(original_flags); + flags.setFilterQuality(filter ? cc::PaintFlags::FilterQuality::kLow + : cc::PaintFlags::FilterQuality::kNone); + flags.setShader(CreateImageRepShaderForScale( + image_rep, SkTileMode::kRepeat, SkTileMode::kRepeat, shader_scale, + remove_image_scale ? image_rep.scale() : 1.f)); + + // The rect will be filled by the bitmap. + canvas_->drawRect(dest_rect, flags); +} + +cc::PaintCanvas* Canvas::CreateOwnedCanvas(const Size& size, bool is_opaque) { + // SkBitmap cannot be zero-sized, but clients of Canvas sometimes request + // that (and then later resize). + int width = std::max(size.width(), 1); + int height = std::max(size.height(), 1); + SkAlphaType alpha = is_opaque ? kOpaque_SkAlphaType : kPremul_SkAlphaType; + SkImageInfo info = SkImageInfo::MakeN32(width, height, alpha); + + bitmap_.emplace(); + bitmap_->allocPixels(info); + // Ensure that the bitmap is zeroed, since the code expects that. + memset(bitmap_->getPixels(), 0, bitmap_->computeByteSize()); + + owned_canvas_.emplace(bitmap_.value()); + return &owned_canvas_.value(); +} + +} // namespace gfx diff --git a/canvas.h b/canvas.h new file mode 100644 index 000000000000..207a44e2220e --- /dev/null +++ b/canvas.h @@ -0,0 +1,470 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_CANVAS_H_ +#define UI_GFX_CANVAS_H_ + +#include + +#include +#include +#include + +#include "base/macros.h" +#include "cc/paint/paint_canvas.h" +#include "cc/paint/paint_flags.h" +#include "cc/paint/skia_paint_canvas.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/gfx/image/image_skia.h" +#include "ui/gfx/native_widget_types.h" +#include "ui/gfx/text_constants.h" + +namespace cc { +class SkottieWrapper; +} // namespace cc + +namespace gfx { + +class Rect; +class RectF; +class FontList; +class Point; +class PointF; +class Size; +class Transform; +class Vector2d; + +// Canvas is a PaintCanvas wrapper that provides a number of methods for +// common operations used throughout an application built using ui/gfx. +// +// All methods that take integer arguments (as is used throughout views) +// end with Int. If you need to use methods provided by PaintCanvas, you'll +// need to do a conversion. In particular you'll need to use |SkIntToScalar()|, +// or if converting from a scalar to an integer |SkScalarRound()|. +// +// A handful of methods in this class are overloaded providing an additional +// argument of type SkBlendMode. SkBlendMode specifies how the +// source and destination colors are combined. Unless otherwise specified, +// the variant that does not take a SkBlendMode uses a transfer mode +// of kSrcOver_Mode. +class GFX_EXPORT Canvas { + public: + enum { + // Specifies the alignment for text rendered with the DrawStringRect method. + TEXT_ALIGN_LEFT = 1 << 0, + TEXT_ALIGN_CENTER = 1 << 1, + TEXT_ALIGN_RIGHT = 1 << 2, + TEXT_ALIGN_TO_HEAD = 1 << 3, + + // Specifies the text consists of multiple lines. + MULTI_LINE = 1 << 4, + + // By default DrawStringRect does not process the prefix ('&') character + // specially. That is, the string "&foo" is rendered as "&foo". When + // rendering text from a resource that uses the prefix character for + // mnemonics, the prefix should be processed and can be rendered as an + // underline (SHOW_PREFIX), or not rendered at all (HIDE_PREFIX). + SHOW_PREFIX = 1 << 5, + HIDE_PREFIX = 1 << 6, + + // Prevent ellipsizing + NO_ELLIPSIS = 1 << 7, + + // Specifies if words can be split by new lines. + // This only works with MULTI_LINE. + CHARACTER_BREAKABLE = 1 << 8, + + // Instructs DrawStringRect() to not use subpixel rendering. This is useful + // when rendering text onto a fully- or partially-transparent background + // that will later be blended with another image. + NO_SUBPIXEL_RENDERING = 1 << 9, + }; + + // Creates an empty canvas with image_scale of 1x. + Canvas(); + + // Creates canvas with provided DIP |size| and |image_scale|. + // If this canvas is not opaque, it's explicitly cleared to transparent before + // being returned. + Canvas(const Size& size, float image_scale, bool is_opaque); + + // Creates a Canvas backed by an |sk_canvas| with |image_scale_|. + // |sk_canvas| is assumed to be already scaled based on |image_scale| + // so no additional scaling is applied. + // Note: the caller must ensure that sk_canvas outlives this object, or until + // RecreateBackingCanvas is called. + Canvas(cc::PaintCanvas* sk_canvas, float image_scale); + + Canvas(const Canvas&) = delete; + Canvas& operator=(const Canvas&) = delete; + + virtual ~Canvas(); + + // Recreates the backing platform canvas with DIP |size| and |image_scale_|. + // If the canvas is not opaque, it is explicitly cleared. + // This method is public so that canvas_skia_paint can recreate the platform + // canvas after having initialized the canvas. + // TODO(pkotwicz): Push the image_scale into skia::PlatformCanvas such that + // this method can be private. + void RecreateBackingCanvas(const Size& size, + float image_scale, + bool is_opaque); + + // Compute the size required to draw some text with the provided fonts. + // Attempts to fit the text with the provided width and height. Increases + // height and then width as needed to make the text fit. This method + // supports multiple lines. On Skia only a line_height can be specified and + // specifying a 0 value for it will cause the default height to be used. + static void SizeStringInt(const std::u16string& text, + const FontList& font_list, + int* width, + int* height, + int line_height, + int flags); + + // This is same as SizeStringInt except that fractional size is returned. + // See comment in GetStringWidthF for its usage. + static void SizeStringFloat(const std::u16string& text, + const FontList& font_list, + float* width, + float* height, + int line_height, + int flags); + + // Returns the number of horizontal pixels needed to display the specified + // |text| with |font_list|. + static int GetStringWidth(const std::u16string& text, + const FontList& font_list); + + // This is same as GetStringWidth except that fractional width is returned. + // Use this method for the scenario that multiple string widths need to be + // summed up. This is because GetStringWidth returns the ceiled width and + // adding multiple ceiled widths could cause more precision loss for certain + // platform like Mac where the fractional width is used. + static float GetStringWidthF(const std::u16string& text, + const FontList& font_list); + + // Returns the default text alignment to be used when drawing text on a + // Canvas based on the directionality of the system locale language. + // This function is used by Canvas::DrawStringRect when the text alignment + // is not specified. + // + // This function returns either Canvas::TEXT_ALIGN_LEFT or + // Canvas::TEXT_ALIGN_RIGHT. + static int DefaultCanvasTextAlignment(); + + // Unscales by the image scale factor (aka device scale factor), and returns + // that factor. This is useful when callers want to draw directly in the + // native scale. + float UndoDeviceScaleFactor(); + + // Saves a copy of the drawing state onto a stack, operating on this copy + // until a balanced call to Restore() is made. + void Save(); + + // As with Save(), except draws to a layer that is blended with the canvas + // at the specified alpha once Restore() is called. + // |layer_bounds| are the bounds of the layer relative to the current + // transform. + void SaveLayerAlpha(uint8_t alpha); + void SaveLayerAlpha(uint8_t alpha, const Rect& layer_bounds); + + // Like SaveLayerAlpha but draws the layer with an arbitrary set of + // PaintFlags once Restore() is called. + void SaveLayerWithFlags(const cc::PaintFlags& flags); + + // Restores the drawing state after a call to Save*(). It is an error to + // call Restore() more times than Save*(). + void Restore(); + + // Applies |rect| to the current clip using the specified region |op|. + void ClipRect(const Rect& rect, SkClipOp op = SkClipOp::kIntersect); + void ClipRect(const RectF& rect, SkClipOp op = SkClipOp::kIntersect); + + // Adds |path| to the current clip. |do_anti_alias| is true if the clip + // should be antialiased. + void ClipPath(const SkPath& path, bool do_anti_alias); + + // Returns the bounds of the current clip (in local coordinates) in the + // |bounds| parameter, and returns true if it is non empty. + bool GetClipBounds(Rect* bounds); + + void Translate(const Vector2d& offset); + + void Scale(float x_scale, float y_scale); + + // Fills the entire canvas' bitmap (restricted to current clip) with + // specified |color| using a transfer mode of SkBlendMode::kSrcOver. + void DrawColor(SkColor color); + + // Fills the entire canvas' bitmap (restricted to current clip) with + // specified |color| and |mode|. + void DrawColor(SkColor color, SkBlendMode mode); + + // Fills |rect| with |color| using a transfer mode of + // SkBlendMode::kSrcOver. + void FillRect(const Rect& rect, SkColor color); + + // Fills |rect| with the specified |color| and |mode|. + void FillRect(const Rect& rect, SkColor color, SkBlendMode mode); + + // Draws a single pixel rect in the specified region with the specified + // color, using a transfer mode of SkBlendMode::kSrcOver. + // + // NOTE: if you need a single pixel line, use DrawLine. + void DrawRect(const RectF& rect, SkColor color); + + // Draws a single pixel rect in the specified region with the specified + // color and transfer mode. + // + // NOTE: if you need a single pixel line, use DrawLine. + void DrawRect(const RectF& rect, SkColor color, SkBlendMode mode); + + // Draws the given rectangle with the given |flags| parameters. + // DEPRECATED in favor of the RectF version below. + // TODO(funkysidd): Remove this (http://crbug.com/553726) + void DrawRect(const Rect& rect, const cc::PaintFlags& flags); + + // Draws the given rectangle with the given |flags| parameters. + void DrawRect(const RectF& rect, const cc::PaintFlags& flags); + + // Draws a single pixel line with the specified color. + // DEPRECATED in favor of the RectF version below. + // TODO(funkysidd): Remove this (http://crbug.com/553726) + void DrawLine(const Point& p1, const Point& p2, SkColor color); + + // Draws a single dip line with the specified color. + void DrawLine(const PointF& p1, const PointF& p2, SkColor color); + + // Draws a line with the given |flags| parameters. + // DEPRECATED in favor of the RectF version below. + // TODO(funkysidd): Remove this (http://crbug.com/553726) + void DrawLine(const Point& p1, const Point& p2, const cc::PaintFlags& flags); + + // Draws a line with the given |flags| parameters. + void DrawLine(const PointF& p1, + const PointF& p2, + const cc::PaintFlags& flags); + + // Draws a line that's a single DIP. At fractional scale factors, this is + // floored to the nearest integral number of pixels. + void DrawSharpLine(PointF p1, PointF p2, SkColor color); + + // As above, but draws a single pixel at all scale factors. + void Draw1pxLine(PointF p1, PointF p2, SkColor color); + + // Draws a circle with the given |flags| parameters. + // DEPRECATED in favor of the RectF version below. + // TODO(funkysidd): Remove this (http://crbug.com/553726) + void DrawCircle(const Point& center_point, + int radius, + const cc::PaintFlags& flags); + + // Draws a circle with the given |flags| parameters. + void DrawCircle(const PointF& center_point, + float radius, + const cc::PaintFlags& flags); + + // Draws the given rectangle with rounded corners of |radius| using the + // given |flags| parameters. DEPRECATED in favor of the RectF version below. + // TODO(mgiuca): Remove this (http://crbug.com/553726). + void DrawRoundRect(const Rect& rect, int radius, const cc::PaintFlags& flags); + + // Draws the given rectangle with rounded corners of |radius| using the + // given |flags| parameters. + void DrawRoundRect(const RectF& rect, + float radius, + const cc::PaintFlags& flags); + + // Draws the given path using the given |flags| parameters. + void DrawPath(const SkPath& path, const cc::PaintFlags& flags); + + // Draws an image with the origin at the specified location. The upper left + // corner of the bitmap is rendered at the specified location. + // Parameters are specified relative to current canvas scale not in pixels. + // Thus, x is 2 pixels if canvas scale = 2 & |x| = 1. + void DrawImageInt(const ImageSkia&, int x, int y); + + // Helper for DrawImageInt(..., flags) that constructs a temporary flags and + // calls flags.setAlpha(alpha). + void DrawImageInt(const ImageSkia&, int x, int y, uint8_t alpha); + + // Draws an image with the origin at the specified location, using the + // specified flags. The upper left corner of the bitmap is rendered at the + // specified location. + // Parameters are specified relative to current canvas scale not in pixels. + // Thus, |x| is 2 pixels if canvas scale = 2 & |x| = 1. + void DrawImageInt(const ImageSkia& image, + int x, + int y, + const cc::PaintFlags& flags); + + // Draws a portion of an image in the specified location. The src parameters + // correspond to the region of the bitmap to draw in the region defined + // by the dest coordinates. + // + // If the width or height of the source differs from that of the destination, + // the image will be scaled. When scaling down, a mipmap will be generated. + // Set |filter| to use filtering for images, otherwise the nearest-neighbor + // algorithm is used for resampling. + // + // An optional custom cc::PaintFlags can be provided. + // Parameters are specified relative to current canvas scale not in pixels. + // Thus, |x| is 2 pixels if canvas scale = 2 & |x| = 1. + void DrawImageInt(const ImageSkia& image, + int src_x, + int src_y, + int src_w, + int src_h, + int dest_x, + int dest_y, + int dest_w, + int dest_h, + bool filter); + void DrawImageInt(const ImageSkia& image, + int src_x, + int src_y, + int src_w, + int src_h, + int dest_x, + int dest_y, + int dest_w, + int dest_h, + bool filter, + const cc::PaintFlags& flags); + + // Same as the DrawImageInt functions above. Difference being this does not + // do any scaling, i.e. it does not scale the output by the device scale + // factor (the internal image_scale_). It takes an ImageSkiaRep instead of + // an ImageSkia as the caller chooses the exact scale/pixel representation to + // use, which will not be scaled while drawing it into the canvas. + void DrawImageIntInPixel(const ImageSkiaRep& image_rep, + int dest_x, + int dest_y, + int dest_w, + int dest_h, + bool filter, + const cc::PaintFlags& flags); + + // Draws an |image| with the top left corner at |x| and |y|, clipped to + // |path|. + // Parameters are specified relative to current canvas scale not in pixels. + // Thus, x is 2 pixels if canvas scale = 2 & |x| = 1. + void DrawImageInPath(const ImageSkia& image, + int x, + int y, + const SkPath& path, + const cc::PaintFlags& flags); + + // Draws the frame of the |skottie| animation specified by the normalized time + // instant t [0->first frame .. 1->last frame] onto the region corresponded by + // |dst| in the canvas. + void DrawSkottie(scoped_refptr skottie, + const Rect& dst, + float t); + + // Draws text with the specified color, fonts and location. The text is + // aligned to the left, vertically centered, clipped to the region. If the + // text is too big, it is truncated and '...' is added to the end. + void DrawStringRect(const std::u16string& text, + const FontList& font_list, + SkColor color, + const Rect& display_rect); + + // Draws text with the specified color, fonts and location. The last argument + // specifies flags for how the text should be rendered. It can be one of + // TEXT_ALIGN_CENTER, TEXT_ALIGN_RIGHT or TEXT_ALIGN_LEFT. + void DrawStringRectWithFlags(const std::u16string& text, + const FontList& font_list, + SkColor color, + const Rect& display_rect, + int flags); + + // Draws a |rect| in the specified region with the specified |color|. The + // width of the stroke is |thickness| dip, but the actual pixel width will be + // floored to ensure an integral value. + void DrawSolidFocusRect(RectF rect, SkColor color, int thickness); + + // Tiles the image in the specified region. + // Parameters are specified relative to current canvas scale not in pixels. + // Thus, |x| is 2 pixels if canvas scale = 2 & |x| = 1. + void TileImageInt(const ImageSkia& image, + int x, + int y, + int w, + int h); + void TileImageInt(const ImageSkia& image, + int src_x, + int src_y, + int dest_x, + int dest_y, + int w, + int h, + float tile_scale = 1.0f, + SkTileMode tile_mode_x = SkTileMode::kRepeat, + SkTileMode tile_mode_y = SkTileMode::kRepeat, + cc::PaintFlags* flags = nullptr); + + // Helper for TileImageInt(). Initializes |flags| for tiling |image| with the + // given parameters. Returns false if the provided image does not have a + // representation for the current scale. + bool InitPaintFlagsForTiling(const ImageSkia& image, + int src_x, + int src_y, + float tile_scale_x, + float tile_scale_y, + int dest_x, + int dest_y, + SkTileMode tile_mode_x, + SkTileMode tile_mode_y, + cc::PaintFlags* flags); + + // Apply transformation on the canvas. + void Transform(const Transform& transform); + + // Note that writing to this bitmap will modify pixels stored in this canvas. + SkBitmap GetBitmap() const; + + // TODO(enne): rename sk_canvas members and interface. + cc::PaintCanvas* sk_canvas() { return canvas_; } + float image_scale() const { return image_scale_; } + + private: + // Tests whether the provided rectangle intersects the current clip rect. + bool IntersectsClipRect(const SkRect& rect); + + // Helper for the DrawImageInt functions declared above. The + // |remove_image_scale| parameter indicates if the scale of the |image_rep| + // should be removed when drawing the image, to avoid double-scaling it. + void DrawImageIntHelper(const ImageSkiaRep& image_rep, + int src_x, + int src_y, + int src_w, + int src_h, + int dest_x, + int dest_y, + int dest_w, + int dest_h, + bool filter, + const cc::PaintFlags& flags, + bool remove_image_scale); + cc::PaintCanvas* CreateOwnedCanvas(const Size& size, bool is_opaque); + + // The device scale factor at which drawing on this canvas occurs. + // An additional scale can be applied via Canvas::Scale(). However, + // Canvas::Scale() does not affect |image_scale_|. + float image_scale_; + + // canvas_ is our active canvas object. Sometimes we are also the owner, + // in which case bitmap_ and owned_canvas_ will be set. Other times we are + // just borrowing someone else's canvas, in which case canvas_ will point + // there but bitmap_ and owned_canvas_ will not exist. + absl::optional bitmap_; + absl::optional owned_canvas_; + cc::PaintCanvas* canvas_; +}; + +} // namespace gfx + +#endif // UI_GFX_CANVAS_H_ diff --git a/canvas_paint_mac.h b/canvas_paint_mac.h new file mode 100644 index 000000000000..1623b4baed93 --- /dev/null +++ b/canvas_paint_mac.h @@ -0,0 +1,58 @@ + +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_CANVAS_PAINT_MAC_H_ +#define UI_GFX_CANVAS_PAINT_MAC_H_ + +#include "skia/ext/platform_canvas.h" +#include "ui/gfx/canvas.h" + +#import + +namespace gfx { + +// A class designed to translate skia painting into a region to the current +// graphics context. On construction, it will set up a context for painting +// into, and on destruction, it will commit it to the current context. +// Note: The created context is always inialized to (0, 0, 0, 0). +class GFX_EXPORT CanvasSkiaPaint : public Canvas { + public: + // This constructor assumes the result is opaque. + explicit CanvasSkiaPaint(NSRect dirtyRect); + CanvasSkiaPaint(NSRect dirtyRect, bool opaque); + ~CanvasSkiaPaint() override; + + // If true, the data painted into the CanvasSkiaPaint is blended onto the + // current context, else it is copied. + void set_composite_alpha(bool composite_alpha) { + composite_alpha_ = composite_alpha; + } + + // Returns true if the invalid region is empty. The caller should call this + // function to determine if anything needs painting. + bool is_empty() const { + return NSIsEmptyRect(rectangle_); + } + + const NSRect& rectangle() const { + return rectangle_; + } + + private: + void Init(bool opaque); + + NSRect rectangle_; + // See description above setter. + bool composite_alpha_; + + // Disallow copy and assign. + CanvasSkiaPaint(const CanvasSkiaPaint&); + CanvasSkiaPaint& operator=(const CanvasSkiaPaint&); +}; + +} // namespace gfx + + +#endif // UI_GFX_CANVAS_PAINT_MAC_H_ diff --git a/canvas_paint_mac.mm b/canvas_paint_mac.mm new file mode 100644 index 000000000000..cf22d3a1ead7 --- /dev/null +++ b/canvas_paint_mac.mm @@ -0,0 +1,75 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/mac/mac_util.h" +#include "third_party/skia/include/utils/mac/SkCGUtils.h" +#include "ui/gfx/canvas_paint_mac.h" +#include "ui/gfx/geometry/size.h" + +namespace gfx { + +CanvasSkiaPaint::CanvasSkiaPaint(NSRect dirtyRect) + : rectangle_(dirtyRect), + composite_alpha_(false) { + Init(true); +} + +CanvasSkiaPaint::CanvasSkiaPaint(NSRect dirtyRect, bool opaque) + : rectangle_(dirtyRect), + composite_alpha_(false) { + Init(opaque); +} + +CanvasSkiaPaint::~CanvasSkiaPaint() { + if (!is_empty()) { + sk_canvas()->restoreToCount(1); + + // Blit the dirty rect to the current context. + CGImageRef image = SkCreateCGImageRefWithColorspace( + GetBitmap(), base::mac::GetSystemColorSpace()); + CGRect dest_rect = NSRectToCGRect(rectangle_); + + CGContextRef destination_context = + (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; + CGContextSaveGState(destination_context); + CGContextSetBlendMode( + destination_context, + composite_alpha_ ? kCGBlendModeNormal : kCGBlendModeCopy); + + if ([[NSGraphicsContext currentContext] isFlipped]) { + // Mirror context on the target's rect middle scanline. + CGContextTranslateCTM(destination_context, 0.0, NSMidY(rectangle_)); + CGContextScaleCTM(destination_context, 1.0, -1.0); + CGContextTranslateCTM(destination_context, 0.0, -NSMidY(rectangle_)); + } + + CGContextDrawImage(destination_context, dest_rect, image); + CGContextRestoreGState(destination_context); + + CFRelease(image); + } +} + +void CanvasSkiaPaint::Init(bool opaque) { + CGContextRef destination_context = + (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; + CGRect scaled_unit_rect = CGContextConvertRectToDeviceSpace( + destination_context, CGRectMake(0, 0, 1, 1)); + // Assume that the x scale and the y scale are the same. + CGFloat scale = scaled_unit_rect.size.width; + + gfx::Size size(NSWidth(rectangle_), NSHeight(rectangle_)); + RecreateBackingCanvas(size, scale, opaque); + cc::PaintCanvas* canvas = sk_canvas(); + canvas->clear(SK_ColorTRANSPARENT); + + // Need to translate so that the dirty region appears at the origin of the + // surface. + canvas->translate(-SkDoubleToScalar(NSMinX(rectangle_)), + -SkDoubleToScalar(NSMinY(rectangle_))); +} + +} // namespace skia + + diff --git a/canvas_skia.cc b/canvas_skia.cc new file mode 100644 index 000000000000..f0ee5bfbc659 --- /dev/null +++ b/canvas_skia.cc @@ -0,0 +1,240 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include + +#include + +#include "build/build_config.h" +#include "cc/paint/paint_canvas.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/geometry/insets.h" +#include "ui/gfx/geometry/skia_conversions.h" +#include "ui/gfx/render_text.h" +#include "ui/gfx/text_elider.h" +#include "ui/gfx/text_utils.h" + +namespace gfx { + +namespace { + +// Strips accelerator character prefixes in |text| if needed, based on |flags|. +// Returns a range in |text| to underline or Range::InvalidRange() if +// underlining is not needed. +Range StripAcceleratorChars(int flags, std::u16string* text) { + if (flags & Canvas::SHOW_PREFIX) { + int char_pos = -1; + int char_span = 0; + *text = LocateAndRemoveAcceleratorChar(*text, &char_pos, &char_span); + if (char_pos != -1) + return Range(char_pos, char_pos + char_span); + } else if (flags & Canvas::HIDE_PREFIX) { + *text = RemoveAccelerator(*text); + } + + return Range::InvalidRange(); +} + +// Elides |text| and adjusts |range| appropriately. If eliding causes |range| +// to no longer point to the same character in |text|, |range| is made invalid. +void ElideTextAndAdjustRange(const FontList& font_list, + float width, + std::u16string* text, + Range* range) { + const char16_t start_char = + (range->IsValid() ? text->at(range->start()) : u'\0'); + *text = ElideText(*text, font_list, width, ELIDE_TAIL); + if (!range->IsValid()) + return; + if (range->start() >= text->length() || + text->at(range->start()) != start_char) { + *range = Range::InvalidRange(); + } +} + +// Updates |render_text| from the specified parameters. +void UpdateRenderText(const Rect& rect, + const std::u16string& text, + const FontList& font_list, + int flags, + SkColor color, + RenderText* render_text) { + render_text->SetFontList(font_list); + render_text->SetText(text); + render_text->SetCursorEnabled(false); + render_text->SetDisplayRect(rect); + + // Set the text alignment explicitly based on the directionality of the UI, + // if not specified. + if (!(flags & (Canvas::TEXT_ALIGN_CENTER | + Canvas::TEXT_ALIGN_RIGHT | + Canvas::TEXT_ALIGN_LEFT | + Canvas::TEXT_ALIGN_TO_HEAD))) { + flags |= Canvas::DefaultCanvasTextAlignment(); + } + + if (flags & Canvas::TEXT_ALIGN_TO_HEAD) + render_text->SetHorizontalAlignment(ALIGN_TO_HEAD); + else if (flags & Canvas::TEXT_ALIGN_RIGHT) + render_text->SetHorizontalAlignment(ALIGN_RIGHT); + else if (flags & Canvas::TEXT_ALIGN_CENTER) + render_text->SetHorizontalAlignment(ALIGN_CENTER); + else + render_text->SetHorizontalAlignment(ALIGN_LEFT); + + render_text->set_subpixel_rendering_suppressed( + (flags & Canvas::NO_SUBPIXEL_RENDERING) != 0); + + render_text->SetColor(color); + const int font_style = font_list.GetFontStyle(); + render_text->SetStyle(TEXT_STYLE_ITALIC, (font_style & Font::ITALIC) != 0); + render_text->SetStyle(TEXT_STYLE_UNDERLINE, + (font_style & Font::UNDERLINE) != 0); + render_text->SetWeight(font_list.GetFontWeight()); +} + +} // namespace + +// static +void Canvas::SizeStringFloat(const std::u16string& text, + const FontList& font_list, + float* width, + float* height, + int line_height, + int flags) { + DCHECK_GE(*width, 0); + DCHECK_GE(*height, 0); + + if ((flags & MULTI_LINE) && *width != 0) { + WordWrapBehavior wrap_behavior = TRUNCATE_LONG_WORDS; + if (flags & CHARACTER_BREAKABLE) + wrap_behavior = WRAP_LONG_WORDS; + else if (!(flags & NO_ELLIPSIS)) + wrap_behavior = ELIDE_LONG_WORDS; + + std::vector strings; + ElideRectangleText(text, font_list, *width, INT_MAX, wrap_behavior, + &strings); + Rect rect(base::saturated_cast(*width), INT_MAX); + + std::unique_ptr render_text = RenderText::CreateRenderText(); + + UpdateRenderText(rect, std::u16string(), font_list, flags, 0, + render_text.get()); + + float h = 0; + float w = 0; + for (size_t i = 0; i < strings.size(); ++i) { + StripAcceleratorChars(flags, &strings[i]); + render_text->SetText(strings[i]); + const SizeF& string_size = render_text->GetStringSizeF(); + w = std::max(w, string_size.width()); + h += (i > 0 && line_height > 0) ? + std::max(static_cast(line_height), string_size.height()) + : string_size.height(); + } + *width = w; + *height = h; + } else { + std::unique_ptr render_text = RenderText::CreateRenderText(); + + Rect rect(base::saturated_cast(*width), + base::saturated_cast(*height)); + std::u16string adjusted_text = text; + StripAcceleratorChars(flags, &adjusted_text); + UpdateRenderText(rect, adjusted_text, font_list, flags, 0, + render_text.get()); + const SizeF& string_size = render_text->GetStringSizeF(); + *width = string_size.width(); + *height = string_size.height(); + } +} + +void Canvas::DrawStringRectWithFlags(const std::u16string& text, + const FontList& font_list, + SkColor color, + const Rect& text_bounds, + int flags) { + if (!IntersectsClipRect(RectToSkRect(text_bounds))) + return; + + canvas_->save(); + ClipRect(text_bounds); + + Rect rect(text_bounds); + + std::unique_ptr render_text = RenderText::CreateRenderText(); + + if (flags & MULTI_LINE) { + WordWrapBehavior wrap_behavior = IGNORE_LONG_WORDS; + if (flags & CHARACTER_BREAKABLE) + wrap_behavior = WRAP_LONG_WORDS; + else if (!(flags & NO_ELLIPSIS)) + wrap_behavior = ELIDE_LONG_WORDS; + + std::vector strings; + ElideRectangleText(text, font_list, + static_cast(text_bounds.width()), + text_bounds.height(), wrap_behavior, &strings); + + for (size_t i = 0; i < strings.size(); i++) { + Range range = StripAcceleratorChars(flags, &strings[i]); + UpdateRenderText(rect, strings[i], font_list, flags, color, + render_text.get()); + int line_padding = 0; + const int line_height = render_text->GetStringSize().height(); + + // TODO(msw|asvitkine): Center Windows multi-line text: crbug.com/107357 +#if !defined(OS_WIN) + if (i == 0) { + // TODO(msw|asvitkine): Support multi-line text with varied heights. + const int text_height = strings.size() * line_height - line_padding; + rect += Vector2d(0, (text_bounds.height() - text_height) / 2); + } +#endif + + rect.set_height(line_height - line_padding); + + if (range.IsValid()) + render_text->ApplyStyle(TEXT_STYLE_UNDERLINE, true, range); + render_text->SetDisplayRect(rect); + render_text->Draw(this); + rect += Vector2d(0, line_height); + } + } else { + std::u16string adjusted_text = text; + Range range = StripAcceleratorChars(flags, &adjusted_text); + bool elide_text = ((flags & NO_ELLIPSIS) == 0); + +#if defined(OS_LINUX) || defined(OS_CHROMEOS) + // On Linux, eliding really means fading the end of the string. But only + // for LTR text. RTL text is still elided (on the left) with "...". + if (elide_text) { + render_text->SetText(adjusted_text); + if (render_text->GetDisplayTextDirection() == base::i18n::LEFT_TO_RIGHT) { + render_text->SetElideBehavior(FADE_TAIL); + elide_text = false; + } + } +#endif + + if (elide_text) { + ElideTextAndAdjustRange(font_list, + static_cast(text_bounds.width()), + &adjusted_text, &range); + } + + UpdateRenderText(rect, adjusted_text, font_list, flags, color, + render_text.get()); + if (range.IsValid()) + render_text->ApplyStyle(TEXT_STYLE_UNDERLINE, true, range); + render_text->Draw(this); + } + + canvas_->restore(); +} + +} // namespace gfx diff --git a/canvas_skia_paint.h b/canvas_skia_paint.h new file mode 100644 index 000000000000..01b366a6933b --- /dev/null +++ b/canvas_skia_paint.h @@ -0,0 +1,17 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_CANVAS_SKIA_PAINT_H_ +#define UI_GFX_CANVAS_SKIA_PAINT_H_ + +// This file provides an easy way to include the appropriate CanvasPaint +// header file on your platform. + +#if defined(__APPLE__) +#include "ui/gfx/canvas_paint_mac.h" +#else +#error "No canvas paint for this platform" +#endif + +#endif // UI_GFX_CANVAS_SKIA_PAINT_H_ diff --git a/canvas_unittest.cc b/canvas_unittest.cc new file mode 100644 index 000000000000..c2e161504c00 --- /dev/null +++ b/canvas_unittest.cc @@ -0,0 +1,71 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/canvas.h" + +#include + +#include "base/strings/utf_string_conversions.h" +#include "build/build_config.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/font_list.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/rect_f.h" +#include "ui/gfx/geometry/skia_conversions.h" + +namespace gfx { + +class CanvasTest : public testing::Test { + protected: + int GetStringWidth(const char *text) { + return Canvas::GetStringWidth(base::UTF8ToUTF16(text), font_list_); + } + + gfx::Size SizeStringInt(const char *text, int width, int line_height) { + std::u16string text16 = base::UTF8ToUTF16(text); + int height = 0; + int flags = + (text16.find('\n') != std::u16string::npos) ? Canvas::MULTI_LINE : 0; + Canvas::SizeStringInt(text16, font_list_, &width, &height, line_height, + flags); + return gfx::Size(width, height); + } + + private: + FontList font_list_; +}; + +TEST_F(CanvasTest, StringWidth) { + EXPECT_GT(GetStringWidth("Test"), 0); +} + +TEST_F(CanvasTest, StringWidthEmptyString) { + EXPECT_EQ(0, GetStringWidth("")); +} + +TEST_F(CanvasTest, StringSizeEmptyString) { + gfx::Size size = SizeStringInt("", 0, 0); + EXPECT_EQ(0, size.width()); + EXPECT_GT(size.height(), 0); +} + +// Verifies GetClipBounds() returns the correct value. +TEST_F(CanvasTest, ClipRectWithScaling) { + Canvas canvas(gfx::Size(200, 100), 2.25, true); + canvas.ClipRect(gfx::RectF(100, 0, 20, 1.7f)); + gfx::Rect clip_rect; + ASSERT_TRUE(canvas.GetClipBounds(&clip_rect)); + // Use Contains() rather than Equals() as skia may extend the rect in certain + // directions. None-the-less the clip must contain the region we damaged. + EXPECT_TRUE(clip_rect.Contains(gfx::Rect(100, 0, 20, 2))); +} + +TEST_F(CanvasTest, StringSizeWithLineHeight) { + gfx::Size one_line_size = SizeStringInt("Q", 0, 0); + gfx::Size four_line_size = SizeStringInt("Q\nQ\nQ\nQ", 1000000, 1000); + EXPECT_EQ(one_line_size.width(), four_line_size.width()); + EXPECT_EQ(3 * 1000 + one_line_size.height(), four_line_size.height()); +} + +} // namespace gfx diff --git a/client_native_pixmap.h b/client_native_pixmap.h new file mode 100644 index 000000000000..07cee494472e --- /dev/null +++ b/client_native_pixmap.h @@ -0,0 +1,34 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_CLIENT_NATIVE_PIXMAP_H_ +#define UI_GFX_CLIENT_NATIVE_PIXMAP_H_ + +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +struct NativePixmapHandle; + +// This represents a buffer that can be written to directly by regular CPU code, +// but can also be read by the GPU. +// NativePixmap is its counterpart in GPU process. +class GFX_EXPORT ClientNativePixmap { + public: + virtual ~ClientNativePixmap() {} + + // Map each plane in the client address space. + // Return false on error. + virtual bool Map() = 0; + virtual void Unmap() = 0; + + virtual size_t GetNumberOfPlanes() const = 0; + virtual void* GetMemoryAddress(size_t plane) const = 0; + virtual int GetStride(size_t plane) const = 0; + virtual NativePixmapHandle CloneHandleForIPC() const = 0; +}; + +} // namespace gfx + +#endif // UI_GFX_CLIENT_NATIVE_PIXMAP_H_ diff --git a/client_native_pixmap_factory.h b/client_native_pixmap_factory.h new file mode 100644 index 000000000000..96aede0503cf --- /dev/null +++ b/client_native_pixmap_factory.h @@ -0,0 +1,40 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_CLIENT_NATIVE_PIXMAP_FACTORY_H_ +#define UI_GFX_CLIENT_NATIVE_PIXMAP_FACTORY_H_ + +#include +#include + +#include "base/files/scoped_file.h" +#include "base/macros.h" +#include "ui/gfx/buffer_types.h" +#include "ui/gfx/client_native_pixmap.h" +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +struct NativePixmapHandle; +class Size; + +// The Ozone interface allows external implementations to hook into Chromium to +// provide a client pixmap for non-GPU processes. +class GFX_EXPORT ClientNativePixmapFactory { + public: + virtual ~ClientNativePixmapFactory() {} + + // Import the native pixmap from |handle| to be used in non-GPU processes. + // Implementations must verify that the buffer in |handle| fits an image of + // the specified |size| and |format|. Otherwise nullptr is returned. + virtual std::unique_ptr ImportFromHandle( + gfx::NativePixmapHandle handle, + const gfx::Size& size, + gfx::BufferFormat format, + gfx::BufferUsage usage) = 0; +}; + +} // namespace gfx + +#endif // UI_GFX_CLIENT_NATIVE_PIXMAP_FACTORY_H_ diff --git a/codec/BUILD.gn b/codec/BUILD.gn new file mode 100644 index 000000000000..44401dc7a0e3 --- /dev/null +++ b/codec/BUILD.gn @@ -0,0 +1,44 @@ +# Copyright 2017 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//build/config/ui.gni") + +component("codec") { + sources = [ + "codec_export.h", + "jpeg_codec.cc", + "jpeg_codec.h", + "png_codec.cc", + "png_codec.h", + "vector_wstream.cc", + "vector_wstream.h", + "webp_codec.cc", + "webp_codec.h", + ] + + deps = [ + "//base", + "//skia", + "//third_party/libpng", + "//ui/gfx:gfx_export", + "//ui/gfx:gfx_skia", + "//ui/gfx/geometry", + ] + + if (is_ios) { + sources -= [ + "jpeg_codec.cc", + "jpeg_codec.h", + ] + } else { + deps += [ "//third_party:jpeg" ] + } + + if (is_win) { + cflags = [ "/wd4324" ] # Structure was padded due to __declspec(align()), + # which is uninteresting. + } + + defines = [ "CODEC_IMPLEMENTATION" ] +} diff --git a/codec/DEPS b/codec/DEPS new file mode 100644 index 000000000000..6a58de326fb6 --- /dev/null +++ b/codec/DEPS @@ -0,0 +1,7 @@ +include_rules = [ + "+skia", + "+third_party/libjpeg", + "+third_party/libjpeg_turbo", + "+third_party/libpng", + "+third_party/zlib", +] diff --git a/codec/OWNERS b/codec/OWNERS new file mode 100644 index 000000000000..254c0ece0bea --- /dev/null +++ b/codec/OWNERS @@ -0,0 +1,2 @@ +dcheng@chromium.org +scroggo@google.com diff --git a/codec/codec_export.h b/codec/codec_export.h new file mode 100644 index 000000000000..c56a070f1fa4 --- /dev/null +++ b/codec/codec_export.h @@ -0,0 +1,29 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_CODEC_CODEC_EXPORT_H_ +#define UI_GFX_CODEC_CODEC_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(CODEC_IMPLEMENTATION) +#define CODEC_EXPORT __declspec(dllexport) +#else +#define CODEC_EXPORT __declspec(dllimport) +#endif // defined(CODEC_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(CODEC_IMPLEMENTATION) +#define CODEC_EXPORT __attribute__((visibility("default"))) +#else +#define CODEC_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define CODEC_EXPORT +#endif + +#endif // UI_GFX_CODEC_CODEC_EXPORT_H_ diff --git a/codec/jpeg_codec.cc b/codec/jpeg_codec.cc new file mode 100644 index 000000000000..d1fbdd7f562e --- /dev/null +++ b/codec/jpeg_codec.cc @@ -0,0 +1,280 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/codec/jpeg_codec.h" + +#include + +#include +#include + +#include "base/notreached.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkColorPriv.h" +#include "ui/gfx/codec/vector_wstream.h" + +extern "C" { +#if defined(USE_SYSTEM_LIBJPEG) +#include +#elif defined(USE_LIBJPEG_TURBO) +#include "third_party/libjpeg_turbo/jpeglib.h" +#else +#include "third_party/libjpeg/jpeglib.h" +#endif +} + +namespace gfx { + +// Encoder/decoder shared stuff ------------------------------------------------ + +namespace { + +// used to pass error info through the JPEG library +struct CoderErrorMgr { + jpeg_error_mgr pub; + jmp_buf setjmp_buffer; +}; + +void ErrorExit(jpeg_common_struct* cinfo) { + CoderErrorMgr *err = reinterpret_cast(cinfo->err); + + // Return control to the setjmp point. + longjmp(err->setjmp_buffer, false); +} + +} // namespace + +// Encoder --------------------------------------------------------------------- + +bool JPEGCodec::Encode(const SkPixmap& input, + int quality, + SkJpegEncoder::Downsample downsample, + std::vector* output) { + output->clear(); + VectorWStream dst(output); + + SkJpegEncoder::Options options; + options.fQuality = quality; + options.fDownsample = downsample; + return SkJpegEncoder::Encode(&dst, input, options); +} + +bool JPEGCodec::Encode(const SkPixmap& input, + int quality, + std::vector* output) { + return Encode(input, quality, SkJpegEncoder::Downsample::k420, output); +} + +bool JPEGCodec::Encode(const SkBitmap& src, + int quality, + std::vector* output) { + SkPixmap pixmap; + if (!src.peekPixels(&pixmap)) { + return false; + } + + return JPEGCodec::Encode(pixmap, quality, output); +} + +// Decoder -------------------------------------------------------------------- + +namespace { + +struct JpegDecoderState { + JpegDecoderState(const unsigned char* in, size_t len) + : input_buffer(in), input_buffer_length(len) { + } + + const unsigned char* input_buffer; + size_t input_buffer_length; +}; + +// Callback to initialize the source. +// +// From the JPEG library: +// "Initialize source. This is called by jpeg_read_header() before any data is +// actually read. May leave bytes_in_buffer set to 0 (in which case a +// fill_input_buffer() call will occur immediately)." +void InitSource(j_decompress_ptr cinfo) { + JpegDecoderState* state = static_cast(cinfo->client_data); + cinfo->src->next_input_byte = state->input_buffer; + cinfo->src->bytes_in_buffer = state->input_buffer_length; +} + +// Callback to fill the buffer. Since our buffer already contains all the data, +// we should never need to provide more data. If libjpeg thinks it needs more +// data, our input is probably corrupt. +// +// From the JPEG library: +// "This is called whenever bytes_in_buffer has reached zero and more data is +// wanted. In typical applications, it should read fresh data into the buffer +// (ignoring the current state of next_input_byte and bytes_in_buffer), reset +// the pointer & count to the start of the buffer, and return TRUE indicating +// that the buffer has been reloaded. It is not necessary to fill the buffer +// entirely, only to obtain at least one more byte. bytes_in_buffer MUST be +// set to a positive value if TRUE is returned. A FALSE return should only +// be used when I/O suspension is desired." +boolean FillInputBuffer(j_decompress_ptr cinfo) { + return false; +} + +// Skip data in the buffer. Since we have all the data at once, this operation +// is easy. It is not clear if this ever gets called because the JPEG library +// should be able to do the skip itself (it has all the data). +// +// From the JPEG library: +// "Skip num_bytes worth of data. The buffer pointer and count should be +// advanced over num_bytes input bytes, refilling the buffer as needed. This +// is used to skip over a potentially large amount of uninteresting data +// (such as an APPn marker). In some applications it may be possible to +// optimize away the reading of the skipped data, but it's not clear that +// being smart is worth much trouble; large skips are uncommon. +// bytes_in_buffer may be zero on return. A zero or negative skip count +// should be treated as a no-op." +void SkipInputData(j_decompress_ptr cinfo, long num_bytes) { + if (num_bytes > static_cast(cinfo->src->bytes_in_buffer)) { + // Since all our data should be in the buffer, trying to skip beyond it + // means that there is some kind of error or corrupt input data. A 0 for + // bytes left means it will call FillInputBuffer which will then fail. + cinfo->src->next_input_byte += cinfo->src->bytes_in_buffer; + cinfo->src->bytes_in_buffer = 0; + } else if (num_bytes > 0) { + cinfo->src->bytes_in_buffer -= static_cast(num_bytes); + cinfo->src->next_input_byte += num_bytes; + } +} + +// Our source doesn't need any cleanup, so this is a NOP. +// +// From the JPEG library: +// "Terminate source --- called by jpeg_finish_decompress() after all data has +// been read to clean up JPEG source manager. NOT called by jpeg_abort() or +// jpeg_destroy()." +void TermSource(j_decompress_ptr cinfo) {} + +// jpeg_decompress_struct Deleter. +struct JpegDecompressStructDeleter { + void operator()(jpeg_decompress_struct* ptr) { + jpeg_destroy_decompress(ptr); + delete ptr; + } +}; + +} // namespace + +bool JPEGCodec::Decode(const unsigned char* input, size_t input_size, + ColorFormat format, std::vector* output, + int* w, int* h) { + std::unique_ptr cinfo( + new jpeg_decompress_struct); + output->clear(); + + // We set up the normal JPEG error routines, then override error_exit. + // This must be done before the call to jpeg_create_decompress. + CoderErrorMgr errmgr; + cinfo->err = jpeg_std_error(&errmgr.pub); + errmgr.pub.error_exit = ErrorExit; + // Establish the setjmp return context for ErrorExit to use. + if (setjmp(errmgr.setjmp_buffer)) { + // If we get here, the JPEG code has signaled an error. + // Release |cinfo| by hand to avoid use-after-free of |errmgr|. + cinfo.reset(); + return false; + } + + // The destroyer will destroy() cinfo on exit. We don't want to set the + // destroyer's object until cinfo is initialized. + jpeg_create_decompress(cinfo.get()); + + // set up the source manager + jpeg_source_mgr srcmgr; + srcmgr.init_source = InitSource; + srcmgr.fill_input_buffer = FillInputBuffer; + srcmgr.skip_input_data = SkipInputData; + srcmgr.resync_to_restart = jpeg_resync_to_restart; // use default routine + srcmgr.term_source = TermSource; + cinfo->src = &srcmgr; + + JpegDecoderState state(input, input_size); + cinfo->client_data = &state; + + // fill the file metadata into our buffer + if (jpeg_read_header(cinfo.get(), true) != JPEG_HEADER_OK) + return false; + + // we want to always get RGB data out + switch (cinfo->jpeg_color_space) { + case JCS_GRAYSCALE: + case JCS_RGB: + case JCS_YCbCr: + // Choose an output colorspace and return if it is an unsupported one. + // Same as JPEGCodec::Encode(), libjpeg-turbo supports all input formats + // used by Chromium (i.e. RGBA and BGRA) and we just map the input + // parameters to a colorspace. + if (format == FORMAT_RGBA || + (format == FORMAT_SkBitmap && SK_R32_SHIFT == 0)) { + cinfo->out_color_space = JCS_EXT_RGBX; + cinfo->output_components = 4; + } else if (format == FORMAT_BGRA || + (format == FORMAT_SkBitmap && SK_B32_SHIFT == 0)) { + cinfo->out_color_space = JCS_EXT_BGRX; + cinfo->output_components = 4; + } else { + NOTREACHED() << "Invalid pixel format"; + return false; + } + break; + case JCS_CMYK: + case JCS_YCCK: + default: + // Mozilla errors out on these color spaces, so I presume that the jpeg + // library can't do automatic color space conversion for them. We don't + // care about these anyway. + return false; + } + + jpeg_calc_output_dimensions(cinfo.get()); + *w = cinfo->output_width; + *h = cinfo->output_height; + + jpeg_start_decompress(cinfo.get()); + + // FIXME(brettw) we may want to allow the capability for callers to request + // how to align row lengths as we do for the compressor. + int row_read_stride = cinfo->output_width * cinfo->output_components; + + // Create memory for a decoded image and write decoded lines to the memory + // without conversions same as JPEGCodec::Encode(). + int row_write_stride = row_read_stride; + output->resize(row_write_stride * cinfo->output_height); + + for (int row = 0; row < static_cast(cinfo->output_height); row++) { + unsigned char* rowptr = &(*output)[row * row_write_stride]; + if (!jpeg_read_scanlines(cinfo.get(), &rowptr, 1)) + return false; + } + + jpeg_finish_decompress(cinfo.get()); + return true; +} + +// static +std::unique_ptr JPEGCodec::Decode(const unsigned char* input, + size_t input_size) { + int w, h; + std::vector data_vector; + if (!Decode(input, input_size, FORMAT_SkBitmap, &data_vector, &w, &h)) + return nullptr; + + // Skia only handles 32 bit images. + int data_length = w * h * 4; + + std::unique_ptr bitmap(new SkBitmap()); + bitmap->allocN32Pixels(w, h); + memcpy(bitmap->getAddr32(0, 0), &data_vector[0], data_length); + + return bitmap; +} + +} // namespace gfx diff --git a/codec/jpeg_codec.h b/codec/jpeg_codec.h new file mode 100644 index 000000000000..14f88793d2d7 --- /dev/null +++ b/codec/jpeg_codec.h @@ -0,0 +1,91 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_CODEC_JPEG_CODEC_H_ +#define UI_GFX_CODEC_JPEG_CODEC_H_ + +#include + +#include +#include + +#include "third_party/skia/include/core/SkImageInfo.h" +#include "third_party/skia/include/core/SkPixmap.h" +#include "third_party/skia/include/encode/SkJpegEncoder.h" +#include "ui/gfx/codec/codec_export.h" + +class SkBitmap; + +namespace gfx { + +// Interface for encoding/decoding JPEG data. This is a wrapper around libjpeg, +// which has an inconvenient interface for callers. This is only used for UI +// elements, WebKit has its own more complicated JPEG decoder which handles, +// among other things, partially downloaded data. +class CODEC_EXPORT JPEGCodec { + public: + enum ColorFormat { + // 4 bytes per pixel, in RGBA order in mem regardless of endianness. + FORMAT_RGBA, + + // 4 bytes per pixel, in BGRA order in mem regardless of endianness. + // This is the default Windows DIB order. + FORMAT_BGRA, + + // 4 bytes per pixel, it can be either RGBA or BGRA. It depends on the bit + // order in kARGB_8888_Config skia bitmap. + FORMAT_SkBitmap + }; + + // Encodes the given raw 'input' pixmap, which includes a pointer to pixels + // as well as information describing the pixel format. The encoded JPEG data + // will be written into the supplied vector and true will be returned on + // success. On failure (false), the contents of the output buffer are + // undefined. + // + // downsample: specifies how pixels will be sampled in the encoded JPEG image, + // can be either k420, k422 or k444. + // quality: an integer in the range 0-100, where 100 is the highest quality. + static bool Encode(const SkPixmap& input, + int quality, + SkJpegEncoder::Downsample downsample, + std::vector* output); + + // Encodes the given raw 'input' pixmap, which includes a pointer to pixels + // as well as information describing the pixel format. The encoded JPEG data + // will be written into the supplied vector and true will be returned on + // success. On failure (false), the contents of the output buffer are + // undefined. + // + // quality: an integer in the range 0-100, where 100 is the highest quality. + static bool Encode(const SkPixmap& input, + int quality, + std::vector* output); + + // Encodes the 'input' bitmap. The encoded JPEG data will be written into + // the supplied vector and true will be returned on success. On failure + // (false), the contents of the output buffer are undefined. + // + // quality: an integer in the range 0-100, where 100 is the highest quality. + static bool Encode(const SkBitmap& input, + int quality, + std::vector* output); + + // Decodes the JPEG data contained in input of length input_size. The + // decoded data will be placed in *output with the dimensions in *w and *h + // on success (returns true). This data will be written in the'format' + // format. On failure, the values of these output variables is undefined. + static bool Decode(const unsigned char* input, size_t input_size, + ColorFormat format, std::vector* output, + int* w, int* h); + + // Decodes the JPEG data contained in input of length input_size. If + // successful, a SkBitmap is created and returned. + static std::unique_ptr Decode(const unsigned char* input, + size_t input_size); +}; + +} // namespace gfx + +#endif // UI_GFX_CODEC_JPEG_CODEC_H_ diff --git a/codec/jpeg_codec_unittest.cc b/codec/jpeg_codec_unittest.cc new file mode 100644 index 000000000000..2d0748e85c96 --- /dev/null +++ b/codec/jpeg_codec_unittest.cc @@ -0,0 +1,214 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include + +#include "base/barrier_closure.h" +#include "base/cxx17_backports.h" +#include "base/run_loop.h" +#include "base/task/thread_pool.h" +#include "base/test/bind.h" +#include "base/test/task_environment.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/codec/jpeg_codec.h" + +namespace { + +// A JPEG image used by TopSitesMigrationTest, whose size is 1x1. +// This image causes an invalid-read error to libjpeg-turbo 1.0.1. +const uint8_t kTopSitesMigrationTestImage[] = + "\xff\xd8\xff\xe0\x00\x10\x4a\x46\x49\x46\x00\x01\x01\x00\x00\x01" + "\x00\x01\x00\x00\xff\xdb\x00\x43\x00\x03\x02\x02\x03\x02\x02\x03" + "\x03\x03\x03\x04\x03\x03\x04\x05\x08\x05\x05\x04\x04\x05\x0a\x07" + "\x07\x06\x08\x0c\x0a\x0c\x0c\x0b\x0a\x0b\x0b\x0d\x0e\x12\x10\x0d" + "\x0e\x11\x0e\x0b\x0b\x10\x16\x10\x11\x13\x14\x15\x15\x15\x0c\x0f" + "\x17\x18\x16\x14\x18\x12\x14\x15\x14\xff\xdb\x00\x43\x01\x03\x04" + "\x04\x05\x04\x05\x09\x05\x05\x09\x14\x0d\x0b\x0d\x14\x14\x14\x14" + "\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14" + "\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14" + "\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\xff\xc0" + "\x00\x11\x08\x00\x01\x00\x01\x03\x01\x22\x00\x02\x11\x01\x03\x11" + "\x01\xff\xc4\x00\x1f\x00\x00\x01\x05\x01\x01\x01\x01\x01\x01\x00" + "\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09" + "\x0a\x0b\xff\xc4\x00\xb5\x10\x00\x02\x01\x03\x03\x02\x04\x03\x05" + "\x05\x04\x04\x00\x00\x01\x7d\x01\x02\x03\x00\x04\x11\x05\x12\x21" + "\x31\x41\x06\x13\x51\x61\x07\x22\x71\x14\x32\x81\x91\xa1\x08\x23" + "\x42\xb1\xc1\x15\x52\xd1\xf0\x24\x33\x62\x72\x82\x09\x0a\x16\x17" + "\x18\x19\x1a\x25\x26\x27\x28\x29\x2a\x34\x35\x36\x37\x38\x39\x3a" + "\x43\x44\x45\x46\x47\x48\x49\x4a\x53\x54\x55\x56\x57\x58\x59\x5a" + "\x63\x64\x65\x66\x67\x68\x69\x6a\x73\x74\x75\x76\x77\x78\x79\x7a" + "\x83\x84\x85\x86\x87\x88\x89\x8a\x92\x93\x94\x95\x96\x97\x98\x99" + "\x9a\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xb2\xb3\xb4\xb5\xb6\xb7" + "\xb8\xb9\xba\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xd2\xd3\xd4\xd5" + "\xd6\xd7\xd8\xd9\xda\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xf1" + "\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xff\xc4\x00\x1f\x01\x00\x03" + "\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x01" + "\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\xff\xc4\x00\xb5\x11\x00" + "\x02\x01\x02\x04\x04\x03\x04\x07\x05\x04\x04\x00\x01\x02\x77\x00" + "\x01\x02\x03\x11\x04\x05\x21\x31\x06\x12\x41\x51\x07\x61\x71\x13" + "\x22\x32\x81\x08\x14\x42\x91\xa1\xb1\xc1\x09\x23\x33\x52\xf0\x15" + "\x62\x72\xd1\x0a\x16\x24\x34\xe1\x25\xf1\x17\x18\x19\x1a\x26\x27" + "\x28\x29\x2a\x35\x36\x37\x38\x39\x3a\x43\x44\x45\x46\x47\x48\x49" + "\x4a\x53\x54\x55\x56\x57\x58\x59\x5a\x63\x64\x65\x66\x67\x68\x69" + "\x6a\x73\x74\x75\x76\x77\x78\x79\x7a\x82\x83\x84\x85\x86\x87\x88" + "\x89\x8a\x92\x93\x94\x95\x96\x97\x98\x99\x9a\xa2\xa3\xa4\xa5\xa6" + "\xa7\xa8\xa9\xaa\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xc2\xc3\xc4" + "\xc5\xc6\xc7\xc8\xc9\xca\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xe2" + "\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9" + "\xfa\xff\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11\x00\x3f\x00\xf9" + "\xd2\x8a\x28\xaf\xc3\x0f\xf5\x4c\xff\xd9"; + +} // namespace + +namespace gfx { + +// out of 100, this indicates how compressed it will be, this should be changed +// with jpeg equality threshold +// static int jpeg_quality = 75; // FIXME(brettw) +static int jpeg_quality = 100; + +// The threshold of average color differences where we consider two images +// equal. This number was picked to be a little above the observed difference +// using the above quality. +static double jpeg_equality_threshold = 1.0; + +// Computes the average difference between each value in a and b. A and b +// should be the same size. Used to see if two images are approximately equal +// in the presence of compression. +static double AveragePixelDelta(const std::vector& a, + const std::vector& b) { + // if the sizes are different, say the average difference is the maximum + if (a.size() != b.size()) + return 255.0; + if (a.empty()) + return 0; // prevent divide by 0 below + + double acc = 0.0; + for (size_t i = 0; i < a.size(); i++) + acc += fabs(static_cast(a[i]) - static_cast(b[i])); + + return acc / static_cast(a.size()); +} + +static void MakeRGBAImage(int w, int h, std::vector* dat) { + dat->resize(w * h * 4); + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + unsigned char* org_px = &(*dat)[(y * w + x) * 4]; + org_px[0] = x * 3; // r + org_px[1] = x * 3 + 1; // g + org_px[2] = x * 3 + 2; // b + org_px[3] = 0xFF; // a + } + } +} + +TEST(JPEGCodec, EncodeDecodeRGBA) { + int w = 20, h = 20; + + // create an image with known values, a must be opaque because it will be + // lost during compression + std::vector original; + MakeRGBAImage(w, h, &original); + + // encode, making sure it was compressed some + std::vector encoded; + SkImageInfo info = + SkImageInfo::Make(w, h, kRGBA_8888_SkColorType, kOpaque_SkAlphaType); + SkPixmap src(info, &original[0], w * 4); + EXPECT_TRUE(JPEGCodec::Encode(src, jpeg_quality, &encoded)); + EXPECT_GT(original.size(), encoded.size()); + + // decode, it should have the same size as the original + std::vector decoded; + int outw, outh; + EXPECT_TRUE(JPEGCodec::Decode(&encoded[0], encoded.size(), + JPEGCodec::FORMAT_RGBA, &decoded, + &outw, &outh)); + ASSERT_EQ(w, outw); + ASSERT_EQ(h, outh); + ASSERT_EQ(original.size(), decoded.size()); + + // Images must be approximately equal (compression will have introduced some + // minor artifacts). + ASSERT_GE(jpeg_equality_threshold, AveragePixelDelta(original, decoded)); +} + +// Test that corrupted data decompression causes failures. +TEST(JPEGCodec, DecodeCorrupted) { + int w = 20, h = 20; + + // some random data (an uncompressed image) + std::vector original; + MakeRGBAImage(w, h, &original); + + // it should fail when given non-JPEG compressed data + std::vector output; + int outw, outh; + ASSERT_FALSE(JPEGCodec::Decode(&original[0], original.size(), + JPEGCodec::FORMAT_RGBA, &output, &outw, + &outh)); + + // make some compressed data + std::vector compressed; + SkImageInfo info = + SkImageInfo::Make(w, h, kRGBA_8888_SkColorType, kOpaque_SkAlphaType); + SkPixmap src(info, &original[0], w * 4); + ASSERT_TRUE(JPEGCodec::Encode(src, jpeg_quality, &compressed)); + + // try decompressing a truncated version + ASSERT_FALSE(JPEGCodec::Decode(&compressed[0], compressed.size() / 2, + JPEGCodec::FORMAT_RGBA, &output, &outw, + &outh)); + + // corrupt it and try decompressing that + for (int i = 10; i < 30; i++) + compressed[i] = i; + ASSERT_FALSE(JPEGCodec::Decode(&compressed[0], compressed.size(), + JPEGCodec::FORMAT_RGBA, &output, &outw, + &outh)); +} + +// Test that we can decode JPEG images without invalid-read errors on valgrind. +// This test decodes a 1x1 JPEG image and writes the decoded RGB (or RGBA) pixel +// to the output buffer without OOB reads. +TEST(JPEGCodec, InvalidRead) { + std::vector output; + int outw, outh; + JPEGCodec::Decode(kTopSitesMigrationTestImage, + base::size(kTopSitesMigrationTestImage), + JPEGCodec::FORMAT_RGBA, &output, &outw, &outh); +} + +// Coverage for data races in JPEG encoding when run with TSan. +// Regression test for crbug.com/1056011. +TEST(JPEGCodec, ParallelEncoding) { + constexpr int kImageSize = 32; + std::vector image_data; + MakeRGBAImage(kImageSize, kImageSize, &image_data); + SkImageInfo info = SkImageInfo::Make( + kImageSize, kImageSize, kRGBA_8888_SkColorType, kOpaque_SkAlphaType); + SkPixmap src(info, &image_data[0], kImageSize * 4); + + base::test::TaskEnvironment task_environment; + base::RunLoop encode_loop; + + constexpr int kNumCopies = 100; + base::RepeatingClosure encode_completion_closure = + base::BarrierClosure(kNumCopies, encode_loop.QuitClosure()); + for (int i = 0; i < kNumCopies; ++i) { + base::ThreadPool::PostTask( + FROM_HERE, base::BindLambdaForTesting([&] { + std::vector output; + EXPECT_TRUE(JPEGCodec::Encode(src, jpeg_quality, &output)); + encode_completion_closure.Run(); + })); + } + + encode_loop.Run(); +} + +} // namespace gfx diff --git a/codec/png_codec.cc b/codec/png_codec.cc new file mode 100644 index 000000000000..078294531c4c --- /dev/null +++ b/codec/png_codec.cc @@ -0,0 +1,538 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/codec/png_codec.h" + +#include + +#include "base/logging.h" +#include "base/macros.h" +#include "base/notreached.h" +#include "base/strings/string_util.h" +#include "third_party/libpng/png.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkColorPriv.h" +#include "third_party/skia/include/core/SkUnPreMultiply.h" +#include "third_party/skia/include/encode/SkPngEncoder.h" +#include "third_party/zlib/zlib.h" +#include "ui/gfx/codec/vector_wstream.h" +#include "ui/gfx/geometry/size.h" + +namespace gfx { + +// Decoder -------------------------------------------------------------------- +// +// This code is based on WebKit libpng interface (PNGImageDecoder), which is +// in turn based on the Mozilla png decoder. + +namespace { + +// Gamma constants: We assume we're on Windows which uses a gamma of 2.2. +const double kMaxGamma = 21474.83; // Maximum gamma accepted by png library. +const double kDefaultGamma = 2.2; +const double kInverseGamma = 1.0 / kDefaultGamma; + +class PngDecoderState { + public: + // Output is a vector. + PngDecoderState(PNGCodec::ColorFormat ofmt, std::vector* o) + : output_format(ofmt), + output_channels(0), + bitmap(nullptr), + is_opaque(true), + output(o), + width(0), + height(0), + done(false) {} + + // Output is an SkBitmap. + explicit PngDecoderState(SkBitmap* skbitmap) + : output_format(PNGCodec::FORMAT_SkBitmap), + output_channels(0), + bitmap(skbitmap), + is_opaque(true), + output(nullptr), + width(0), + height(0), + done(false) {} + + PngDecoderState(const PngDecoderState&) = delete; + PngDecoderState& operator=(const PngDecoderState&) = delete; + + PNGCodec::ColorFormat output_format; + int output_channels; + + // An incoming SkBitmap to write to. If NULL, we write to output instead. + SkBitmap* bitmap; + + // Used during the reading of an SkBitmap. Defaults to true until we see a + // pixel with anything other than an alpha of 255. + bool is_opaque; + + // The other way to decode output, where we write into an intermediary buffer + // instead of directly to an SkBitmap. + std::vector* output; + + // Size of the image, set in the info callback. + int width; + int height; + + // Set to true when we've found the end of the data. + bool done; +}; + +// User transform (passed to libpng) which converts a row decoded by libpng to +// Skia format. Expects the row to have 4 channels, otherwise there won't be +// enough room in |data|. +void ConvertRGBARowToSkia(png_structp png_ptr, + png_row_infop row_info, + png_bytep data) { + const int channels = row_info->channels; + DCHECK_EQ(channels, 4); + + PngDecoderState* state = + static_cast(png_get_user_transform_ptr(png_ptr)); + DCHECK(state) << "LibPNG user transform pointer is NULL"; + + unsigned char* const end = data + row_info->rowbytes; + for (unsigned char* p = data; p < end; p += channels) { + uint32_t* sk_pixel = reinterpret_cast(p); + const unsigned char alpha = p[channels - 1]; + if (alpha != 255) { + state->is_opaque = false; + *sk_pixel = SkPreMultiplyARGB(alpha, p[0], p[1], p[2]); + } else { + *sk_pixel = SkPackARGB32(alpha, p[0], p[1], p[2]); + } + } +} + +// Called when the png header has been read. This code is based on the WebKit +// PNGImageDecoder +void DecodeInfoCallback(png_struct* png_ptr, png_info* info_ptr) { + PngDecoderState* state = static_cast( + png_get_progressive_ptr(png_ptr)); + + int bit_depth, color_type, interlace_type, compression_type; + int filter_type; + png_uint_32 w, h; + png_get_IHDR(png_ptr, info_ptr, &w, &h, &bit_depth, &color_type, + &interlace_type, &compression_type, &filter_type); + + // Bounds check. When the image is unreasonably big, we'll error out and + // end up back at the setjmp call when we set up decoding. "Unreasonably big" + // means "big enough that w * h * 32bpp might overflow an int"; we choose this + // threshold to match WebKit and because a number of places in code assume + // that an image's size (in bytes) fits in a (signed) int. + unsigned long long total_size = + static_cast(w) * static_cast(h); + if (total_size > ((1 << 29) - 1)) + longjmp(png_jmpbuf(png_ptr), 1); + state->width = static_cast(w); + state->height = static_cast(h); + + // The following png_set_* calls have to be done in the order dictated by + // the libpng docs. Please take care if you have to move any of them. This + // is also why certain things are done outside of the switch, even though + // they look like they belong there. + + // Expand to ensure we use 24-bit for RGB and 32-bit for RGBA. + if (color_type == PNG_COLOR_TYPE_PALETTE || + (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)) + png_set_expand(png_ptr); + + // The '!= 0' is for silencing a Windows compiler warning. + bool input_has_alpha = ((color_type & PNG_COLOR_MASK_ALPHA) != 0); + + // Transparency for paletted images. + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { + png_set_expand(png_ptr); + input_has_alpha = true; + } + + // Convert 16-bit to 8-bit. + if (bit_depth == 16) + png_set_strip_16(png_ptr); + + // Pick our row format converter necessary for this data. + if (!input_has_alpha) { + switch (state->output_format) { + case PNGCodec::FORMAT_RGBA: + state->output_channels = 4; + png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER); + break; + case PNGCodec::FORMAT_BGRA: + state->output_channels = 4; + png_set_bgr(png_ptr); + png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER); + break; + case PNGCodec::FORMAT_SkBitmap: + state->output_channels = 4; + png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER); + break; + } + } else { + switch (state->output_format) { + case PNGCodec::FORMAT_RGBA: + state->output_channels = 4; + break; + case PNGCodec::FORMAT_BGRA: + state->output_channels = 4; + png_set_bgr(png_ptr); + break; + case PNGCodec::FORMAT_SkBitmap: + state->output_channels = 4; + break; + } + } + + // Expand grayscale to RGB. + if (color_type == PNG_COLOR_TYPE_GRAY || + color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb(png_ptr); + + // Deal with gamma and keep it under our control. + double gamma; + if (png_get_gAMA(png_ptr, info_ptr, &gamma)) { + if (gamma <= 0.0 || gamma > kMaxGamma) { + gamma = kInverseGamma; + png_set_gAMA(png_ptr, info_ptr, gamma); + } + png_set_gamma(png_ptr, kDefaultGamma, gamma); + } else { + png_set_gamma(png_ptr, kDefaultGamma, kInverseGamma); + } + + // Setting the user transforms here (as opposed to inside the switch above) + // because all png_set_* calls need to be done in the specific order + // mandated by libpng. + if (state->output_format == PNGCodec::FORMAT_SkBitmap) { + png_set_read_user_transform_fn(png_ptr, ConvertRGBARowToSkia); + png_set_user_transform_info(png_ptr, state, 0, 0); + } + + // Tell libpng to send us rows for interlaced pngs. + if (interlace_type == PNG_INTERLACE_ADAM7) + png_set_interlace_handling(png_ptr); + + png_read_update_info(png_ptr, info_ptr); + + if (state->bitmap) { + state->bitmap->allocN32Pixels(state->width, state->height); + } else if (state->output) { + state->output->resize( + state->width * state->output_channels * state->height); + } +} + +void DecodeRowCallback(png_struct* png_ptr, png_byte* new_row, + png_uint_32 row_num, int pass) { + if (!new_row) + return; // Interlaced image; row didn't change this pass. + + PngDecoderState* state = static_cast( + png_get_progressive_ptr(png_ptr)); + + if (static_cast(row_num) > state->height) { + NOTREACHED() << "Invalid row"; + return; + } + + unsigned char* base = NULL; + if (state->bitmap) + base = reinterpret_cast(state->bitmap->getAddr32(0, 0)); + else if (state->output) + base = &state->output->front(); + + unsigned char* dest = &base[state->width * state->output_channels * row_num]; + png_progressive_combine_row(png_ptr, dest, new_row); +} + +void DecodeEndCallback(png_struct* png_ptr, png_info* info) { + PngDecoderState* state = static_cast( + png_get_progressive_ptr(png_ptr)); + + // Mark the image as complete, this will tell the Decode function that we + // have successfully found the end of the data. + state->done = true; +} + +// Holds png struct and info ensuring the proper destruction. +class PngReadStructInfo { + public: + PngReadStructInfo(): png_ptr_(nullptr), info_ptr_(nullptr) { + } + + PngReadStructInfo(const PngReadStructInfo&) = delete; + PngReadStructInfo& operator=(const PngReadStructInfo&) = delete; + + ~PngReadStructInfo() { + png_destroy_read_struct(&png_ptr_, &info_ptr_, NULL); + } + + bool Build(const unsigned char* input, size_t input_size) { + if (input_size < 8) + return false; // Input data too small to be a png + + // Have libpng check the signature, it likes the first 8 bytes. + if (png_sig_cmp(const_cast(input), 0, 8) != 0) + return false; + + png_ptr_ = png_create_read_struct( + PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png_ptr_) + return false; + + info_ptr_ = png_create_info_struct(png_ptr_); + if (!info_ptr_) { + return false; + } + return true; + } + + png_struct* png_ptr_; + png_info* info_ptr_; +}; + +// Holds png struct and info ensuring the proper destruction. +class PngWriteStructInfo { + public: + PngWriteStructInfo() : png_ptr_(nullptr), info_ptr_(nullptr) { + } + + PngWriteStructInfo(const PngWriteStructInfo&) = delete; + PngWriteStructInfo& operator=(const PngWriteStructInfo&) = delete; + + ~PngWriteStructInfo() { + png_destroy_write_struct(&png_ptr_, &info_ptr_); + } + + png_struct* png_ptr_; + png_info* info_ptr_; +}; + +// Libpng user error and warning functions which allows us to print libpng +// errors and warnings using Chrome's logging facilities instead of stderr. + +void LogLibPNGDecodeError(png_structp png_ptr, png_const_charp error_msg) { + DLOG(ERROR) << "libpng decode error: " << error_msg; + longjmp(png_jmpbuf(png_ptr), 1); +} + +void LogLibPNGDecodeWarning(png_structp png_ptr, png_const_charp warning_msg) { + DLOG(ERROR) << "libpng decode warning: " << warning_msg; +} + +} // namespace + +// static +bool PNGCodec::Decode(const unsigned char* input, size_t input_size, + ColorFormat format, std::vector* output, + int* w, int* h) { + PngReadStructInfo si; + if (!si.Build(input, input_size)) + return false; + + if (setjmp(png_jmpbuf(si.png_ptr_))) { + // The destroyer will ensure that the structures are cleaned up in this + // case, even though we may get here as a jump from random parts of the + // PNG library called below. + return false; + } + + PngDecoderState state(format, output); + + png_set_error_fn(si.png_ptr_, NULL, + LogLibPNGDecodeError, LogLibPNGDecodeWarning); + png_set_progressive_read_fn(si.png_ptr_, &state, &DecodeInfoCallback, + &DecodeRowCallback, &DecodeEndCallback); + png_process_data(si.png_ptr_, + si.info_ptr_, + const_cast(input), + input_size); + + if (!state.done) { + // Fed it all the data but the library didn't think we got all the data, so + // this file must be truncated. + output->clear(); + return false; + } + + *w = state.width; + *h = state.height; + return true; +} + +// static +bool PNGCodec::Decode(const unsigned char* input, size_t input_size, + SkBitmap* bitmap) { + DCHECK(bitmap); + PngReadStructInfo si; + if (!si.Build(input, input_size)) + return false; + + if (setjmp(png_jmpbuf(si.png_ptr_))) { + // The destroyer will ensure that the structures are cleaned up in this + // case, even though we may get here as a jump from random parts of the + // PNG library called below. + return false; + } + + PngDecoderState state(bitmap); + + png_set_progressive_read_fn(si.png_ptr_, &state, &DecodeInfoCallback, + &DecodeRowCallback, &DecodeEndCallback); + png_process_data(si.png_ptr_, + si.info_ptr_, + const_cast(input), + input_size); + + if (!state.done) { + return false; + } + + // Set the bitmap's opaqueness based on what we saw. + bitmap->setAlphaType(state.is_opaque ? + kOpaque_SkAlphaType : kPremul_SkAlphaType); + + return true; +} + +// Encoder -------------------------------------------------------------------- + +namespace { + +static void AddComments(SkPngEncoder::Options& options, + const std::vector& comments) { + std::vector comment_pointers; + std::vector comment_sizes; + for (const auto& comment : comments) { + comment_pointers.push_back(comment.key.c_str()); + comment_pointers.push_back(comment.text.c_str()); + comment_sizes.push_back(comment.key.length() + 1); + comment_sizes.push_back(comment.text.length() + 1); + } + options.fComments = SkDataTable::MakeCopyArrays( + (void const* const*)comment_pointers.data(), comment_sizes.data(), + static_cast(comment_pointers.size())); +} + +} // namespace + +static bool EncodeSkPixmap(const SkPixmap& src, + const std::vector& comments, + std::vector* output, + int zlib_level) { + output->clear(); + VectorWStream dst(output); + + SkPngEncoder::Options options; + AddComments(options, comments); + options.fZLibLevel = zlib_level; + return SkPngEncoder::Encode(&dst, src, options); +} + +static bool EncodeSkPixmap(const SkPixmap& src, + bool discard_transparency, + const std::vector& comments, + std::vector* output, + int zlib_level) { + if (discard_transparency) { + SkImageInfo opaque_info = src.info().makeAlphaType(kOpaque_SkAlphaType); + SkBitmap copy; + if (!copy.tryAllocPixels(opaque_info)) { + return false; + } + SkPixmap opaque_pixmap; + bool success = copy.peekPixels(&opaque_pixmap); + DCHECK(success); + // The following step does the unpremul as we set the dst alpha type to be + // kUnpremul_SkAlphaType. Later, because opaque_pixmap has + // kOpaque_SkAlphaType, we'll discard the transparency as required. + success = + src.readPixels(opaque_info.makeAlphaType(kUnpremul_SkAlphaType), + opaque_pixmap.writable_addr(), opaque_pixmap.rowBytes()); + DCHECK(success); + return EncodeSkPixmap(opaque_pixmap, comments, output, zlib_level); + } + return EncodeSkPixmap(src, comments, output, zlib_level); +} + +// static +bool PNGCodec::Encode(const unsigned char* input, + ColorFormat format, + const Size& size, + int row_byte_width, + bool discard_transparency, + const std::vector& comments, + std::vector* output) { + // Initialization required for Windows although the switch covers all cases. + SkColorType colorType = kN32_SkColorType; + switch (format) { + case FORMAT_RGBA: + colorType = kRGBA_8888_SkColorType; + break; + case FORMAT_BGRA: + colorType = kBGRA_8888_SkColorType; + break; + case FORMAT_SkBitmap: + colorType = kN32_SkColorType; + break; + } + auto alphaType = + format == FORMAT_SkBitmap ? kPremul_SkAlphaType : kUnpremul_SkAlphaType; + SkImageInfo info = + SkImageInfo::Make(size.width(), size.height(), colorType, alphaType); + SkPixmap src(info, input, row_byte_width); + return EncodeSkPixmap(src, discard_transparency, comments, output, + DEFAULT_ZLIB_COMPRESSION); +} + +static bool EncodeSkBitmap(const SkBitmap& input, + bool discard_transparency, + std::vector* output, + int zlib_level) { + SkPixmap src; + if (!input.peekPixels(&src)) { + return false; + } + return EncodeSkPixmap(src, discard_transparency, + std::vector(), output, zlib_level); +} + +// static +bool PNGCodec::EncodeBGRASkBitmap(const SkBitmap& input, + bool discard_transparency, + std::vector* output) { + return EncodeSkBitmap(input, discard_transparency, output, + DEFAULT_ZLIB_COMPRESSION); +} + +// static +bool PNGCodec::EncodeA8SkBitmap(const SkBitmap& input, + std::vector* output) { + DCHECK_EQ(input.colorType(), kAlpha_8_SkColorType); + auto info = input.info() + .makeColorType(kGray_8_SkColorType) + .makeAlphaType(kOpaque_SkAlphaType); + SkPixmap src(info, input.getAddr(0, 0), input.rowBytes()); + return EncodeSkPixmap(src, std::vector(), output, + DEFAULT_ZLIB_COMPRESSION); +} + +// static +bool PNGCodec::FastEncodeBGRASkBitmap(const SkBitmap& input, + bool discard_transparency, + std::vector* output) { + return EncodeSkBitmap(input, discard_transparency, output, Z_BEST_SPEED); +} + +PNGCodec::Comment::Comment(const std::string& k, const std::string& t) + : key(k), text(t) { +} + +PNGCodec::Comment::~Comment() { +} + +} // namespace gfx diff --git a/codec/png_codec.h b/codec/png_codec.h new file mode 100644 index 000000000000..f5d4eb953e40 --- /dev/null +++ b/codec/png_codec.h @@ -0,0 +1,132 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_CODEC_PNG_CODEC_H_ +#define UI_GFX_CODEC_PNG_CODEC_H_ + +#include + +#include +#include + +#include "base/macros.h" +#include "ui/gfx/codec/codec_export.h" + +class SkBitmap; + +namespace gfx { + +class Size; + +// Interface for encoding and decoding PNG data. This is a wrapper around +// libpng, which has an inconvenient interface for callers. This is currently +// designed for use in tests only (where we control the files), so the handling +// isn't as robust as would be required for a browser (see Decode() for more). +// WebKit has its own more complicated PNG decoder which handles, among other +// things, partially downloaded data. +class CODEC_EXPORT PNGCodec { + public: + static constexpr int DEFAULT_ZLIB_COMPRESSION = 6; + + enum ColorFormat { + // 4 bytes per pixel, in RGBA order in memory regardless of endianness. + FORMAT_RGBA, + + // 4 bytes per pixel, in BGRA order in memory regardless of endianness. + // This is the default Windows DIB order. + FORMAT_BGRA, + + // SkBitmap format. For Encode() kN32_SkColorType (4 bytes per pixel) and + // kAlpha_8_SkColorType (1 byte per pixel) formats are supported. + // kAlpha_8_SkColorType gets encoded into a grayscale PNG treating alpha as + // the color intensity. For Decode() kN32_SkColorType is always used. + FORMAT_SkBitmap + }; + + // Represents a comment in the tEXt ancillary chunk of the png. + struct CODEC_EXPORT Comment { + Comment(const std::string& k, const std::string& t); + ~Comment(); + + std::string key; + std::string text; + }; + + PNGCodec(const PNGCodec&) = delete; + PNGCodec& operator=(const PNGCodec&) = delete; + + // Encodes the given raw 'input' data, with each pixel being represented as + // given in 'format'. The encoded PNG data will be written into the supplied + // vector and true will be returned on success. On failure (false), the + // contents of the output buffer are undefined. + // + // When writing alpha values, the input colors are assumed to be post + // multiplied. + // + // size: dimensions of the image + // row_byte_width: the width in bytes of each row. This may be greater than + // w * bytes_per_pixel if there is extra padding at the end of each row + // (often, each row is padded to the next machine word). + // discard_transparency: when true, and when the input data format includes + // alpha values, these alpha values will be discarded and only RGB will be + // written to the resulting file. Otherwise, alpha values in the input + // will be preserved. + // comments: comments to be written in the png's metadata. + static bool Encode(const unsigned char* input, + ColorFormat format, + const Size& size, + int row_byte_width, + bool discard_transparency, + const std::vector& comments, + std::vector* output); + + // Call PNGCodec::Encode on the supplied SkBitmap |input|, which is assumed to + // be kN32_SkColorType, 32 bits per pixel. The params |discard_transparency| + // and |output| are passed directly to Encode; refer to Encode for more + // information. + static bool EncodeBGRASkBitmap(const SkBitmap& input, + bool discard_transparency, + std::vector* output); + + // Call PNGCodec::Encode on the supplied SkBitmap |input|. The difference + // between this and the previous method is that this restricts compression to + // zlib q1, which is just rle encoding. + static bool FastEncodeBGRASkBitmap(const SkBitmap& input, + bool discard_transparency, + std::vector* output); + + // Call PNGCodec::Encode on the supplied SkBitmap |input|, which is assumed to + // be kAlpha_8_SkColorType, 8 bits per pixel. The bitmap is encoded as a + // grayscale PNG with alpha used for color intensity. The |output| param is + // passed directly to Encode; refer to Encode for more information. + static bool EncodeA8SkBitmap(const SkBitmap& input, + std::vector* output); + + // Decodes the PNG data contained in input of length input_size. The + // decoded data will be placed in *output with the dimensions in *w and *h + // on success (returns true). This data will be written in the 'format' + // format. On failure, the values of these output variables are undefined. + // + // This function may not support all PNG types, and it hasn't been tested + // with a large number of images, so assume a new format may not work. It's + // really designed to be able to read in something written by Encode() above. + static bool Decode(const unsigned char* input, size_t input_size, + ColorFormat format, std::vector* output, + int* w, int* h); + + // Decodes the PNG data directly into the passed in SkBitmap. This is + // significantly faster than the vector version of Decode() + // above when dealing with PNG files that are >500K, which a lot of theme + // images are. (There are a lot of themes that have a NTP image of about ~1 + // megabyte, and those require a 7-10 megabyte side buffer.) + // + // Returns true if data is non-null and can be decoded as a png, false + // otherwise. + static bool Decode(const unsigned char* input, size_t input_size, + SkBitmap* bitmap); +}; + +} // namespace gfx + +#endif // UI_GFX_CODEC_PNG_CODEC_H_ diff --git a/codec/png_codec_unittest.cc b/codec/png_codec_unittest.cc new file mode 100644 index 000000000000..083c6a85ec1b --- /dev/null +++ b/codec/png_codec_unittest.cc @@ -0,0 +1,969 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +#include +#include + +#include "base/cxx17_backports.h" +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/libpng/png.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkColorPriv.h" +#include "third_party/skia/include/core/SkUnPreMultiply.h" +#include "third_party/zlib/zlib.h" +#include "ui/gfx/codec/png_codec.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/skia_util.h" + +namespace gfx { + +namespace { + +void MakeRGBImage(int w, int h, std::vector* data) { + data->resize(w * h * 3); + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + unsigned char* org_px = &(*data)[(y * w + x) * 3]; + org_px[0] = x * 3; // r + org_px[1] = x * 3 + 1; // g + org_px[2] = x * 3 + 2; // b + } + } +} + +// Set use_transparency to write data into the alpha channel, otherwise it will +// be filled with 0xff. With the alpha channel stripped, this should yield the +// same image as MakeRGBImage above, so the code below can make reference +// images for conversion testing. +void MakeRGBAImage(int w, int h, bool use_transparency, + std::vector* data) { + data->resize(w * h * 4); + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + unsigned char* org_px = &(*data)[(y * w + x) * 4]; + org_px[0] = x * 3; // r + org_px[1] = x * 3 + 1; // g + org_px[2] = x * 3 + 2; // b + if (use_transparency) + org_px[3] = x*3 + 3; // a + else + org_px[3] = 0xFF; // a (opaque) + } + } +} + +// Creates a palette-based image. +void MakePaletteImage(int w, int h, + std::vector* data, + std::vector* palette, + std::vector* trans_chunk = 0) { + data->resize(w * h); + palette->resize(w); + for (int i = 0; i < w; ++i) { + png_color& color = (*palette)[i]; + color.red = i * 3; + color.green = color.red + 1; + color.blue = color.red + 2; + } + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + (*data)[y * w + x] = x; // palette index + } + } + if (trans_chunk) { + trans_chunk->resize(palette->size()); + for (std::size_t i = 0; i < trans_chunk->size(); ++i) { + (*trans_chunk)[i] = i % 256; + } + } +} + +// Creates a grayscale image without an alpha channel. +void MakeGrayscaleImage(int w, int h, + std::vector* data) { + data->resize(w * h); + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + (*data)[y * w + x] = x; // gray value + } + } +} + +// Creates a grayscale image with an alpha channel. +void MakeGrayscaleAlphaImage(int w, int h, + std::vector* data) { + data->resize(w * h * 2); + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + unsigned char* px = &(*data)[(y * w + x) * 2]; + px[0] = x; // gray value + px[1] = x % 256; // alpha + } + } +} + +// User write function (to be passed to libpng by EncodeImage) which writes +// into a buffer instead of to a file. +void WriteImageData(png_structp png_ptr, + png_bytep data, + png_size_t length) { + std::vector& v = + *static_cast*>(png_get_io_ptr(png_ptr)); + v.resize(v.size() + length); + memcpy(&v[v.size() - length], data, length); +} + +// User flush function; goes with WriteImageData, above. +void FlushImageData(png_structp /*png_ptr*/) { +} + +// Libpng user error function which allows us to print libpng errors using +// Chrome's logging facilities instead of stderr. +void LogLibPNGError(png_structp png_ptr, + png_const_charp error_msg) { + DLOG(ERROR) << "libpng encode error: " << error_msg; + longjmp(png_jmpbuf(png_ptr), 1); +} + +// Goes with LogLibPNGError, above. +void LogLibPNGWarning(png_structp png_ptr, + png_const_charp warning_msg) { + DLOG(ERROR) << "libpng encode warning: " << warning_msg; +} + +// Color types supported by EncodeImage. Required because neither libpng nor +// PNGCodec::Encode supports all of the required values. +enum ColorType { + COLOR_TYPE_GRAY = PNG_COLOR_TYPE_GRAY, + COLOR_TYPE_GRAY_ALPHA = PNG_COLOR_TYPE_GRAY_ALPHA, + COLOR_TYPE_PALETTE = PNG_COLOR_TYPE_PALETTE, + COLOR_TYPE_RGB = PNG_COLOR_TYPE_RGB, + COLOR_TYPE_RGBA = PNG_COLOR_TYPE_RGBA, + COLOR_TYPE_BGR, + COLOR_TYPE_BGRA +}; + +// PNG encoder used for testing. Required because PNGCodec::Encode doesn't do +// interlaced, palette-based, or grayscale images, but PNGCodec::Decode is +// actually asked to decode these types of images by Chrome. +bool EncodeImage(const std::vector& input, + const int width, + const int height, + ColorType output_color_type, + std::vector* output, + const int interlace_type = PNG_INTERLACE_NONE, + std::vector* palette = 0, + std::vector* palette_alpha = 0) { + DCHECK(output); + + int input_rowbytes = 0; + int transforms = PNG_TRANSFORM_IDENTITY; + + switch (output_color_type) { + case COLOR_TYPE_GRAY: + input_rowbytes = width; + break; + case COLOR_TYPE_GRAY_ALPHA: + input_rowbytes = width * 2; + break; + case COLOR_TYPE_PALETTE: + if (!palette) + return false; + input_rowbytes = width; + break; + case COLOR_TYPE_RGB: + input_rowbytes = width * 3; + break; + case COLOR_TYPE_RGBA: + input_rowbytes = width * 4; + break; + case COLOR_TYPE_BGR: + input_rowbytes = width * 3; + output_color_type = static_cast(PNG_COLOR_TYPE_RGB); + transforms |= PNG_TRANSFORM_BGR; + break; + case COLOR_TYPE_BGRA: + input_rowbytes = width * 4; + output_color_type = static_cast(PNG_COLOR_TYPE_RGBA); + transforms |= PNG_TRANSFORM_BGR; + break; + }; + + png_struct* png_ptr = + png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png_ptr) + return false; + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_write_struct(&png_ptr, NULL); + return false; + } + + std::vector row_pointers(height); + for (int y = 0 ; y < height; ++y) { + row_pointers[y] = const_cast(&input[y * input_rowbytes]); + } + + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_write_struct(&png_ptr, &info_ptr); + return false; + } + + png_set_error_fn(png_ptr, NULL, LogLibPNGError, LogLibPNGWarning); + png_set_rows(png_ptr, info_ptr, &row_pointers[0]); + png_set_write_fn(png_ptr, output, WriteImageData, FlushImageData); + png_set_IHDR(png_ptr, info_ptr, width, height, 8, output_color_type, + interlace_type, PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT); + if (output_color_type == COLOR_TYPE_PALETTE) { + png_set_PLTE(png_ptr, info_ptr, &palette->front(), palette->size()); + if (palette_alpha) { + unsigned char* alpha_data = &palette_alpha->front(); + size_t alpha_size = palette_alpha->size(); + png_set_tRNS(png_ptr, info_ptr, alpha_data, alpha_size, NULL); + } + } + + png_write_png(png_ptr, info_ptr, transforms, NULL); + + png_destroy_write_struct(&png_ptr, &info_ptr); + return true; +} + +} // namespace + +// Returns true if each channel of the given two colors are "close." This is +// used for comparing colors where rounding errors may cause off-by-one. +bool ColorsClose(uint32_t a, uint32_t b) { + return abs(static_cast(SkColorGetB(a) - SkColorGetB(b))) < 2 && + abs(static_cast(SkColorGetG(a) - SkColorGetG(b))) < 2 && + abs(static_cast(SkColorGetR(a) - SkColorGetR(b))) < 2 && + abs(static_cast(SkColorGetA(a) - SkColorGetA(b))) < 2; +} + +// Returns true if the RGB components are "close." +bool NonAlphaColorsClose(uint32_t a, uint32_t b) { + return abs(static_cast(SkColorGetB(a) - SkColorGetB(b))) < 2 && + abs(static_cast(SkColorGetG(a) - SkColorGetG(b))) < 2 && + abs(static_cast(SkColorGetR(a) - SkColorGetR(b))) < 2; +} + +// Returns true if the BGRA 32-bit SkColor specified by |a| is equivalent to the +// 8-bit Gray color specified by |b|. +bool BGRAGrayEqualsA8Gray(uint32_t a, uint8_t b) { + return SkColorGetB(a) == b && SkColorGetG(a) == b && + SkColorGetR(a) == b && SkColorGetA(a) == 255; +} + +void MakeTestBGRASkBitmap(int w, int h, SkBitmap* bmp) { + bmp->allocN32Pixels(w, h); + + uint32_t* src_data = bmp->getAddr32(0, 0); + for (int i = 0; i < w * h; i++) + src_data[i] = SkPreMultiplyARGB(i % 255, i % 250, i % 245, i % 240); +} + +void MakeTestA8SkBitmap(int w, int h, SkBitmap* bmp) { + bmp->allocPixels(SkImageInfo::MakeA8(w, h)); + + uint8_t* src_data = bmp->getAddr8(0, 0); + for (int i = 0; i < w * h; i++) + src_data[i] = i % 255; +} + +TEST(PNGCodec, EncodeDecodeRGBA) { + const int w = 20, h = 20; + + // create an image with known values, a must be opaque because it will be + // lost during encoding + std::vector original; + MakeRGBAImage(w, h, true, &original); + + // encode + std::vector encoded; + ASSERT_TRUE(PNGCodec::Encode(&original[0], PNGCodec::FORMAT_RGBA, + Size(w, h), w * 4, false, + std::vector(), + &encoded)); + + // decode, it should have the same size as the original + std::vector decoded; + int outw, outh; + ASSERT_TRUE(PNGCodec::Decode(&encoded[0], encoded.size(), + PNGCodec::FORMAT_RGBA, &decoded, + &outw, &outh)); + ASSERT_EQ(w, outw); + ASSERT_EQ(h, outh); + ASSERT_EQ(original.size(), decoded.size()); + + // Images must be exactly equal + ASSERT_TRUE(original == decoded); +} + +TEST(PNGCodec, EncodeDecodeBGRA) { + const int w = 20, h = 20; + + // Create an image with known values, alpha must be opaque because it will be + // lost during encoding. + std::vector original; + MakeRGBAImage(w, h, true, &original); + + // Encode. + std::vector encoded; + ASSERT_TRUE(PNGCodec::Encode(&original[0], PNGCodec::FORMAT_BGRA, + Size(w, h), w * 4, false, + std::vector(), + &encoded)); + + // Decode, it should have the same size as the original. + std::vector decoded; + int outw, outh; + ASSERT_TRUE(PNGCodec::Decode(&encoded[0], encoded.size(), + PNGCodec::FORMAT_BGRA, &decoded, + &outw, &outh)); + ASSERT_EQ(w, outw); + ASSERT_EQ(h, outh); + ASSERT_EQ(original.size(), decoded.size()); + + // Images must be exactly equal. + ASSERT_TRUE(original == decoded); +} + +TEST(PNGCodec, DecodePalette) { + const int w = 20, h = 20; + + // create an image with known values + std::vector original; + std::vector original_palette; + std::vector original_trans_chunk; + MakePaletteImage(w, h, &original, &original_palette, &original_trans_chunk); + + // encode + std::vector encoded; + ASSERT_TRUE(EncodeImage(original, + w, h, + COLOR_TYPE_PALETTE, + &encoded, + PNG_INTERLACE_NONE, + &original_palette, + &original_trans_chunk)); + + // decode + std::vector decoded; + int outw, outh; + ASSERT_TRUE(PNGCodec::Decode(&encoded[0], encoded.size(), + PNGCodec::FORMAT_RGBA, &decoded, + &outw, &outh)); + ASSERT_EQ(w, outw); + ASSERT_EQ(h, outh); + ASSERT_EQ(decoded.size(), w * h * 4U); + + // Images must be equal + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + unsigned char palette_pixel = original[y * w + x]; + png_color& palette_color = original_palette[palette_pixel]; + int alpha = original_trans_chunk[palette_pixel]; + unsigned char* rgba_pixel = &decoded[(y * w + x) * 4]; + + EXPECT_EQ(palette_color.red, rgba_pixel[0]); + EXPECT_EQ(palette_color.green, rgba_pixel[1]); + EXPECT_EQ(palette_color.blue, rgba_pixel[2]); + EXPECT_EQ(alpha, rgba_pixel[3]); + } + } +} + +TEST(PNGCodec, DecodeInterlacedPalette) { + const int w = 20, h = 20; + + // create an image with known values + std::vector original; + std::vector original_palette; + std::vector original_trans_chunk; + MakePaletteImage(w, h, &original, &original_palette, &original_trans_chunk); + + // encode + std::vector encoded; + ASSERT_TRUE(EncodeImage(original, + w, h, + COLOR_TYPE_PALETTE, + &encoded, + PNG_INTERLACE_ADAM7, + &original_palette, + &original_trans_chunk)); + + // decode + std::vector decoded; + int outw, outh; + ASSERT_TRUE(PNGCodec::Decode(&encoded[0], encoded.size(), + PNGCodec::FORMAT_RGBA, &decoded, + &outw, &outh)); + ASSERT_EQ(w, outw); + ASSERT_EQ(h, outh); + ASSERT_EQ(decoded.size(), w * h * 4U); + + // Images must be equal + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + unsigned char palette_pixel = original[y * w + x]; + png_color& palette_color = original_palette[palette_pixel]; + int alpha = original_trans_chunk[palette_pixel]; + unsigned char* rgba_pixel = &decoded[(y * w + x) * 4]; + + EXPECT_EQ(palette_color.red, rgba_pixel[0]); + EXPECT_EQ(palette_color.green, rgba_pixel[1]); + EXPECT_EQ(palette_color.blue, rgba_pixel[2]); + EXPECT_EQ(alpha, rgba_pixel[3]); + } + } +} + +TEST(PNGCodec, DecodeGrayscale) { + const int w = 20, h = 20; + + // create an image with known values + std::vector original; + MakeGrayscaleImage(w, h, &original); + + // encode + std::vector encoded; + ASSERT_TRUE(EncodeImage(original, w, h, COLOR_TYPE_GRAY, &encoded)); + + // decode + std::vector decoded; + int outw, outh; + ASSERT_TRUE(PNGCodec::Decode(&encoded[0], encoded.size(), + PNGCodec::FORMAT_RGBA, &decoded, &outw, &outh)); + ASSERT_EQ(w, outw); + ASSERT_EQ(h, outh); + ASSERT_EQ(decoded.size(), original.size() * 4); + + // Images must be equal + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + unsigned char gray_pixel = original[(y * w + x)]; + unsigned char* rgba_pixel = &decoded[(y * w + x) * 4]; + EXPECT_EQ(rgba_pixel[0], gray_pixel); + EXPECT_EQ(rgba_pixel[1], gray_pixel); + EXPECT_EQ(rgba_pixel[2], gray_pixel); + EXPECT_EQ(rgba_pixel[3], 0xff); + } + } +} + +TEST(PNGCodec, DecodeGrayscaleWithAlpha) { + const int w = 20, h = 20; + + // create an image with known values + std::vector original; + MakeGrayscaleAlphaImage(w, h, &original); + + // encode + std::vector encoded; + ASSERT_TRUE(EncodeImage(original, + w, h, + COLOR_TYPE_GRAY_ALPHA, + &encoded)); + + // decode + std::vector decoded; + int outw, outh; + ASSERT_TRUE(PNGCodec::Decode(&encoded[0], encoded.size(), + PNGCodec::FORMAT_RGBA, &decoded, + &outw, &outh)); + ASSERT_EQ(w, outw); + ASSERT_EQ(h, outh); + ASSERT_EQ(decoded.size(), original.size() * 2); + + // Images must be equal + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + unsigned char* gray_pixel = &original[(y * w + x) * 2]; + unsigned char* rgba_pixel = &decoded[(y * w + x) * 4]; + EXPECT_EQ(rgba_pixel[0], gray_pixel[0]); + EXPECT_EQ(rgba_pixel[1], gray_pixel[0]); + EXPECT_EQ(rgba_pixel[2], gray_pixel[0]); + EXPECT_EQ(rgba_pixel[3], gray_pixel[1]); + } + } +} + +TEST(PNGCodec, DecodeInterlacedGrayscale) { + const int w = 20, h = 20; + + // create an image with known values + std::vector original; + MakeGrayscaleImage(w, h, &original); + + // encode + std::vector encoded; + ASSERT_TRUE(EncodeImage(original, + w, h, + COLOR_TYPE_GRAY, + &encoded, + PNG_INTERLACE_ADAM7)); + + // decode + std::vector decoded; + int outw, outh; + ASSERT_TRUE(PNGCodec::Decode(&encoded[0], encoded.size(), + PNGCodec::FORMAT_RGBA, &decoded, + &outw, &outh)); + ASSERT_EQ(w, outw); + ASSERT_EQ(h, outh); + ASSERT_EQ(decoded.size(), original.size() * 4); + + // Images must be equal + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + unsigned char gray_pixel = original[(y * w + x)]; + unsigned char* rgba_pixel = &decoded[(y * w + x) * 4]; + EXPECT_EQ(rgba_pixel[0], gray_pixel); + EXPECT_EQ(rgba_pixel[1], gray_pixel); + EXPECT_EQ(rgba_pixel[2], gray_pixel); + EXPECT_EQ(rgba_pixel[3], 0xFF); + } + } +} + +TEST(PNGCodec, DecodeInterlacedGrayscaleWithAlpha) { + const int w = 20, h = 20; + + // create an image with known values + std::vector original; + MakeGrayscaleAlphaImage(w, h, &original); + + // encode + std::vector encoded; + ASSERT_TRUE(EncodeImage(original, + w, h, + COLOR_TYPE_GRAY_ALPHA, + &encoded, + PNG_INTERLACE_ADAM7)); + + // decode + std::vector decoded; + int outw, outh; + ASSERT_TRUE(PNGCodec::Decode(&encoded[0], encoded.size(), + PNGCodec::FORMAT_RGBA, &decoded, + &outw, &outh)); + ASSERT_EQ(w, outw); + ASSERT_EQ(h, outh); + ASSERT_EQ(decoded.size(), original.size() * 2); + + // Images must be equal + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + unsigned char* gray_pixel = &original[(y * w + x) * 2]; + unsigned char* rgba_pixel = &decoded[(y * w + x) * 4]; + EXPECT_EQ(rgba_pixel[0], gray_pixel[0]); + EXPECT_EQ(rgba_pixel[1], gray_pixel[0]); + EXPECT_EQ(rgba_pixel[2], gray_pixel[0]); + EXPECT_EQ(rgba_pixel[3], gray_pixel[1]); + } + } +} + +TEST(PNGCodec, DecodeInterlacedRGBA) { + const int w = 20, h = 20; + + // create an image with known values + std::vector original; + MakeRGBAImage(w, h, false, &original); + + // encode + std::vector encoded; + ASSERT_TRUE(EncodeImage(original, + w, h, + COLOR_TYPE_RGBA, + &encoded, + PNG_INTERLACE_ADAM7)); + + // decode, it should have the same size as the original + std::vector decoded; + int outw, outh; + ASSERT_TRUE(PNGCodec::Decode(&encoded[0], encoded.size(), + PNGCodec::FORMAT_RGBA, &decoded, + &outw, &outh)); + ASSERT_EQ(w, outw); + ASSERT_EQ(h, outh); + ASSERT_EQ(original.size(), decoded.size()); + + // Images must be equal + ASSERT_EQ(original, decoded); +} + +TEST(PNGCodec, DecodeInterlacedBGR) { + const int w = 20, h = 20; + + // create an image with known values + std::vector original; + MakeRGBImage(w, h, &original); + + // encode + std::vector encoded; + ASSERT_TRUE(EncodeImage(original, + w, h, + COLOR_TYPE_BGR, + &encoded, + PNG_INTERLACE_ADAM7)); + + // decode, it should have the same size as the original + std::vector decoded; + int outw, outh; + ASSERT_TRUE(PNGCodec::Decode(&encoded[0], encoded.size(), + PNGCodec::FORMAT_BGRA, &decoded, + &outw, &outh)); + ASSERT_EQ(w, outw); + ASSERT_EQ(h, outh); + ASSERT_EQ(decoded.size(), w * h * 4U); + + // Images must be equal + for (int x = 0; x < w; x++) { + for (int y = 0; y < h; y++) { + unsigned char* orig_px = &original[(y * w + x) * 3]; + unsigned char* dec_px = &decoded[(y * w + x) * 4]; + EXPECT_EQ(dec_px[0], orig_px[0]); + EXPECT_EQ(dec_px[1], orig_px[1]); + EXPECT_EQ(dec_px[2], orig_px[2]); + } + } +} + +TEST(PNGCodec, DecodeInterlacedBGRA) { + const int w = 20, h = 20; + + // create an image with known values + std::vector original; + MakeRGBAImage(w, h, false, &original); + + // encode + std::vector encoded; + ASSERT_TRUE(EncodeImage(original, + w, h, + COLOR_TYPE_BGRA, + &encoded, + PNG_INTERLACE_ADAM7)); + + // decode, it should have the same size as the original + std::vector decoded; + int outw, outh; + ASSERT_TRUE(PNGCodec::Decode(&encoded[0], encoded.size(), + PNGCodec::FORMAT_BGRA, &decoded, + &outw, &outh)); + ASSERT_EQ(w, outw); + ASSERT_EQ(h, outh); + ASSERT_EQ(original.size(), decoded.size()); + + // Images must be equal + ASSERT_EQ(original, decoded); +} + +// Not encoding an interlaced PNG from SkBitmap because we don't do it +// anywhere, and the ability to do that requires more code changes. +TEST(PNGCodec, DecodeInterlacedRGBtoSkBitmap) { + const int w = 20, h = 20; + + // create an image with known values + std::vector original; + MakeRGBImage(w, h, &original); + + // encode + std::vector encoded; + ASSERT_TRUE(EncodeImage(original, + w, h, + COLOR_TYPE_RGB, + &encoded, + PNG_INTERLACE_ADAM7)); + + // Decode the encoded string. + SkBitmap decoded_bitmap; + ASSERT_TRUE(PNGCodec::Decode(&encoded.front(), encoded.size(), + &decoded_bitmap)); + + for (int x = 0; x < w; x++) { + for (int y = 0; y < h; y++) { + const unsigned char* original_pixel = &original[(y * w + x) * 3]; + const uint32_t original_pixel_sk = SkPackARGB32(0xFF, + original_pixel[0], + original_pixel[1], + original_pixel[2]); + const uint32_t decoded_pixel = decoded_bitmap.getAddr32(0, y)[x]; + EXPECT_EQ(original_pixel_sk, decoded_pixel); + } + } +} + +TEST(PNGCodec, DecodeInterlacedRGBAtoSkBitmap) { + const int w = 20, h = 20; + + // create an image with known values + std::vector original; + MakeRGBAImage(w, h, false, &original); + + // encode + std::vector encoded; + ASSERT_TRUE(EncodeImage(original, + w, h, + COLOR_TYPE_RGBA, + &encoded, + PNG_INTERLACE_ADAM7)); + + // Decode the encoded string. + SkBitmap decoded_bitmap; + ASSERT_TRUE(PNGCodec::Decode(&encoded.front(), encoded.size(), + &decoded_bitmap)); + + for (int x = 0; x < w; x++) { + for (int y = 0; y < h; y++) { + const unsigned char* original_pixel = &original[(y * w + x) * 4]; + const uint32_t original_pixel_sk = SkPackARGB32(original_pixel[3], + original_pixel[0], + original_pixel[1], + original_pixel[2]); + const uint32_t decoded_pixel = decoded_bitmap.getAddr32(0, y)[x]; + EXPECT_EQ(original_pixel_sk, decoded_pixel); + } + } +} + +// Test that corrupted data decompression causes failures. +TEST(PNGCodec, DecodeCorrupted) { + int w = 20, h = 20; + + // Make some random data (an uncompressed image). + std::vector original; + MakeRGBAImage(w, h, false, &original); + + // It should fail when given non-PNG compressed data. + std::vector output; + int outw, outh; + EXPECT_FALSE(PNGCodec::Decode(&original[0], original.size(), + PNGCodec::FORMAT_RGBA, &output, &outw, &outh)); + + // Make some compressed data. + std::vector compressed; + ASSERT_TRUE(PNGCodec::Encode(&original[0], PNGCodec::FORMAT_RGBA, Size(w, h), + w * 4, false, std::vector(), + &compressed)); + + // Try decompressing a truncated version. + EXPECT_FALSE(PNGCodec::Decode(&compressed[0], compressed.size() / 2, + PNGCodec::FORMAT_RGBA, &output, &outw, &outh)); + + // Corrupt it and try decompressing that. + for (int i = 10; i < 30; i++) + compressed[i] = i; + EXPECT_FALSE(PNGCodec::Decode(&compressed[0], compressed.size(), + PNGCodec::FORMAT_RGBA, &output, &outw, &outh)); +} + +TEST(PNGCodec, EncodeBGRASkBitmapStridePadded) { + const int kWidth = 20; + const int kHeight = 20; + const int kPaddedWidth = 32; + const int kBytesPerPixel = 4; + const int kRowBytes = kPaddedWidth * kBytesPerPixel; + + SkImageInfo info = SkImageInfo::MakeN32Premul(kWidth, kHeight); + SkBitmap original_bitmap; + original_bitmap.setInfo(info, kRowBytes); + original_bitmap.allocPixels(); + + // Write data over the source bitmap. + // We write on the pad area here too. + // The encoder should ignore the pad area. + uint32_t* src_data = original_bitmap.getAddr32(0, 0); + const int count = + original_bitmap.computeByteSize() / original_bitmap.bytesPerPixel(); + for (int i = 0; i < count; i++) { + src_data[i] = SkPreMultiplyARGB(i % 255, i % 250, i % 245, i % 240); + } + + // Encode the bitmap. + std::vector encoded; + PNGCodec::EncodeBGRASkBitmap(original_bitmap, false, &encoded); + + // Decode the encoded string. + SkBitmap decoded_bitmap; + EXPECT_TRUE(PNGCodec::Decode(&encoded.front(), encoded.size(), + &decoded_bitmap)); + + // Compare the original bitmap and the output bitmap. We use ColorsClose + // as SkBitmaps are considered to be pre-multiplied, the unpremultiplication + // (in Encode) and repremultiplication (in Decode) can be lossy. + for (int x = 0; x < kWidth; x++) { + for (int y = 0; y < kHeight; y++) { + uint32_t original_pixel = original_bitmap.getAddr32(0, y)[x]; + uint32_t decoded_pixel = decoded_bitmap.getAddr32(0, y)[x]; + EXPECT_TRUE(ColorsClose(original_pixel, decoded_pixel)); + } + } +} + +TEST(PNGCodec, EncodeBGRASkBitmap) { + const int w = 20, h = 20; + + SkBitmap original_bitmap; + MakeTestBGRASkBitmap(w, h, &original_bitmap); + + // Encode the bitmap. + std::vector encoded; + PNGCodec::EncodeBGRASkBitmap(original_bitmap, false, &encoded); + + // Decode the encoded string. + SkBitmap decoded_bitmap; + EXPECT_TRUE(PNGCodec::Decode(&encoded.front(), encoded.size(), + &decoded_bitmap)); + + // Compare the original bitmap and the output bitmap. We use ColorsClose + // as SkBitmaps are considered to be pre-multiplied, the unpremultiplication + // (in Encode) and repremultiplication (in Decode) can be lossy. + for (int x = 0; x < w; x++) { + for (int y = 0; y < h; y++) { + uint32_t original_pixel = original_bitmap.getAddr32(0, y)[x]; + uint32_t decoded_pixel = decoded_bitmap.getAddr32(0, y)[x]; + EXPECT_TRUE(ColorsClose(original_pixel, decoded_pixel)); + } + } +} + +TEST(PNGCodec, EncodeA8SkBitmap) { + const int w = 20, h = 20; + + SkBitmap original_bitmap; + MakeTestA8SkBitmap(w, h, &original_bitmap); + + // Encode the bitmap. + std::vector encoded; + EXPECT_TRUE(PNGCodec::EncodeA8SkBitmap(original_bitmap, &encoded)); + + // Decode the encoded string. + SkBitmap decoded_bitmap; + EXPECT_TRUE(PNGCodec::Decode(&encoded.front(), encoded.size(), + &decoded_bitmap)); + + for (int x = 0; x < w; x++) { + for (int y = 0; y < h; y++) { + uint8_t original_pixel = *original_bitmap.getAddr8(x, y); + uint32_t decoded_pixel = *decoded_bitmap.getAddr32(x, y); + EXPECT_TRUE(BGRAGrayEqualsA8Gray(decoded_pixel, original_pixel)); + } + } +} + +TEST(PNGCodec, EncodeBGRASkBitmapDiscardTransparency) { + const int w = 20, h = 20; + + SkBitmap original_bitmap; + MakeTestBGRASkBitmap(w, h, &original_bitmap); + + // Encode the bitmap. + std::vector encoded; + PNGCodec::EncodeBGRASkBitmap(original_bitmap, true, &encoded); + + // Decode the encoded string. + SkBitmap decoded_bitmap; + EXPECT_TRUE(PNGCodec::Decode(&encoded.front(), encoded.size(), + &decoded_bitmap)); + + // Compare the original bitmap and the output bitmap. We need to + // unpremultiply original_pixel, as the decoded bitmap doesn't have an alpha + // channel. + for (int x = 0; x < w; x++) { + for (int y = 0; y < h; y++) { + uint32_t original_pixel = original_bitmap.getAddr32(0, y)[x]; + uint32_t unpremultiplied = + SkUnPreMultiply::PMColorToColor(original_pixel); + uint32_t decoded_pixel = decoded_bitmap.getAddr32(0, y)[x]; + uint32_t unpremultiplied_decoded = + SkUnPreMultiply::PMColorToColor(decoded_pixel); + + EXPECT_TRUE(NonAlphaColorsClose(unpremultiplied, unpremultiplied_decoded)) + << "Original_pixel: (" + << SkColorGetR(unpremultiplied) << ", " + << SkColorGetG(unpremultiplied) << ", " + << SkColorGetB(unpremultiplied) << "), " + << "Decoded pixel: (" + << SkColorGetR(unpremultiplied_decoded) << ", " + << SkColorGetG(unpremultiplied_decoded) << ", " + << SkColorGetB(unpremultiplied_decoded) << ")"; + } + } +} + +TEST(PNGCodec, EncodeWithComment) { + const int w = 10, h = 10; + + std::vector original; + MakeRGBAImage(w, h, true, &original); + + std::vector encoded; + std::vector comments; + comments.push_back(PNGCodec::Comment("key", "text")); + comments.push_back(PNGCodec::Comment("test", "something")); + comments.push_back(PNGCodec::Comment("have some", "spaces in both")); + EXPECT_TRUE(PNGCodec::Encode(&original[0], PNGCodec::FORMAT_RGBA, Size(w, h), + w * 4, false, comments, &encoded)); + + // Each chunk is of the form length (4 bytes), chunk type (tEXt), data, + // checksum (4 bytes). Make sure we find all of them in the encoded + // results. + const unsigned char kExpected1[] = + "\x00\x00\x00\x08tEXtkey\x00text\x9e\xe7\x66\x51"; + const unsigned char kExpected2[] = + "\x00\x00\x00\x0etEXttest\x00something\x29\xba\xef\xac"; + const unsigned char kExpected3[] = + "\x00\x00\x00\x18tEXthave some\x00spaces in both\x8d\x69\x34\x2d"; + + EXPECT_NE(std::search(encoded.begin(), encoded.end(), kExpected1, + kExpected1 + base::size(kExpected1)), + encoded.end()); + EXPECT_NE(std::search(encoded.begin(), encoded.end(), kExpected2, + kExpected2 + base::size(kExpected2)), + encoded.end()); + EXPECT_NE(std::search(encoded.begin(), encoded.end(), kExpected3, + kExpected3 + base::size(kExpected3)), + encoded.end()); +} + +TEST(PNGCodec, EncodeDecodeWithVaryingCompressionLevels) { + const int w = 20, h = 20; + + // create an image with known values, a must be opaque because it will be + // lost during encoding + SkBitmap original_bitmap; + MakeTestBGRASkBitmap(w, h, &original_bitmap); + + // encode + std::vector encoded_normal; + EXPECT_TRUE( + PNGCodec::EncodeBGRASkBitmap(original_bitmap, false, &encoded_normal)); + + std::vector encoded_fast; + EXPECT_TRUE( + PNGCodec::FastEncodeBGRASkBitmap(original_bitmap, false, &encoded_fast)); + + // Make sure the different compression settings actually do something; the + // sizes should be different. + EXPECT_NE(encoded_normal.size(), encoded_fast.size()); + + // decode, they should be identical to the original. + SkBitmap decoded; + EXPECT_TRUE( + PNGCodec::Decode(&encoded_normal[0], encoded_normal.size(), &decoded)); + EXPECT_TRUE(BitmapsAreEqual(decoded, original_bitmap)); + + EXPECT_TRUE( + PNGCodec::Decode(&encoded_fast[0], encoded_fast.size(), &decoded)); + EXPECT_TRUE(BitmapsAreEqual(decoded, original_bitmap)); +} + + +} // namespace gfx diff --git a/codec/vector_wstream.cc b/codec/vector_wstream.cc new file mode 100644 index 000000000000..6d485a2558c0 --- /dev/null +++ b/codec/vector_wstream.cc @@ -0,0 +1,19 @@ +// Copyright (c) 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/codec/vector_wstream.h" + +namespace gfx { + +bool VectorWStream::write(const void* buffer, size_t size) { + const unsigned char* ptr = reinterpret_cast(buffer); + dst_->insert(dst_->end(), ptr, ptr + size); + return true; +} + +size_t VectorWStream::bytesWritten() const { + return dst_->size(); +} + +} // namespace gfx diff --git a/codec/vector_wstream.h b/codec/vector_wstream.h new file mode 100644 index 000000000000..b740e7e4dfe8 --- /dev/null +++ b/codec/vector_wstream.h @@ -0,0 +1,36 @@ +// Copyright (c) 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_CODEC_VECTOR_WSTREAM_H_ +#define UI_GFX_CODEC_VECTOR_WSTREAM_H_ + +#include + +#include + +#include "base/check_op.h" +#include "third_party/skia/include/core/SkStream.h" + +namespace gfx { + +class VectorWStream : public SkWStream { + public: + // We do not take ownership of dst + VectorWStream(std::vector* dst) : dst_(dst) { + DCHECK(dst_); + DCHECK_EQ(0UL, dst_->size()); + } + + bool write(const void* buffer, size_t size) override; + + size_t bytesWritten() const override; + + private: + // Does not have ownership. + std::vector* dst_; +}; + +} // namespace gfx + +#endif // UI_GFX_CODEC_VECTOR_WSTREAM_H_ diff --git a/codec/webp_codec.cc b/codec/webp_codec.cc new file mode 100644 index 000000000000..d325fe114250 --- /dev/null +++ b/codec/webp_codec.cc @@ -0,0 +1,36 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/codec/webp_codec.h" + +#include "third_party/skia/include/encode/SkWebpEncoder.h" +#include "ui/gfx/codec/vector_wstream.h" + +namespace gfx { + +// Encoder --------------------------------------------------------------------- + +bool WebpCodec::Encode(const SkPixmap& input, + int quality, + std::vector* output) { + output->clear(); + VectorWStream dst(output); + + SkWebpEncoder::Options options; + options.fQuality = quality; + return SkWebpEncoder::Encode(&dst, input, options); +} + +bool WebpCodec::Encode(const SkBitmap& src, + int quality, + std::vector* output) { + SkPixmap pixmap; + if (!src.peekPixels(&pixmap)) { + return false; + } + + return WebpCodec::Encode(pixmap, quality, output); +} + +} // namespace gfx diff --git a/codec/webp_codec.h b/codec/webp_codec.h new file mode 100644 index 000000000000..ce4e0aaf877b --- /dev/null +++ b/codec/webp_codec.h @@ -0,0 +1,56 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_CODEC_WEBP_CODEC_H_ +#define UI_GFX_CODEC_WEBP_CODEC_H_ + +#include + +#include "base/macros.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkPixmap.h" +#include "ui/gfx/codec/codec_export.h" + +class SkBitmap; + +namespace gfx { + +class Size; + +// Interface for encoding WebP data. This is currently only used +// in the devtools protocol to encode screenshots, so currently only minimally +// supports lossy encoding. +class CODEC_EXPORT WebpCodec { + public: + WebpCodec(const WebpCodec&) = delete; + WebpCodec& operator=(const WebpCodec&) = delete; + + // Encodes (lossy) the given raw 'input' pixmap, which includes a pointer to + // pixels as well as information describing the pixel format. The encoded WebP + // data will be written into the supplied vector and true will be returned on + // success. On failure (false), the contents of the output buffer are + // undefined. + // + // quality: an integer in the range 0-100, where 100 is the highest quality. + // Since this currently only supports lossy encoding, a higher + // quality means a higher visual quality. + static bool Encode(const SkPixmap& input, + int quality, + std::vector* output); + + // Encodes (lossy) the 'input' bitmap. The encoded WebP data will be written + // into the supplied vector and true will be returned on success. On failure + // (false), the contents of the output buffer are undefined. + // + // quality: an integer in the range 0-100, where 100 is the highest quality. + // Since this currently only supports lossy encoding, a higher + // quality means a higher visual quality. + static bool Encode(const SkBitmap& input, + int quality, + std::vector* output); +}; + +} // namespace gfx + +#endif // UI_GFX_CODEC_WEBP_CODEC_H_ diff --git a/color_analysis.cc b/color_analysis.cc new file mode 100644 index 000000000000..8a74f0b9e39d --- /dev/null +++ b/color_analysis.cc @@ -0,0 +1,967 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/color_analysis.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "base/bind.h" +#include "base/callback.h" +#include "base/check_op.h" +#include "base/cxx17_backports.h" +#include "base/notreached.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkUnPreMultiply.h" +#include "ui/gfx/codec/png_codec.h" +#include "ui/gfx/color_palette.h" +#include "ui/gfx/color_utils.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/range/range.h" + +namespace color_utils { +namespace { + +// RGBA KMean Constants +const int kNumberOfClusters = 4; +const int kNumberOfIterations = 50; + +const HSL kDefaultLowerHSLBound = {-1, -1, 0.15}; +const HSL kDefaultUpperHSLBound = {-1, -1, 0.85}; + +// Background Color Modification Constants +const SkColor kDefaultBgColor = SK_ColorWHITE; + +// Support class to hold information about each cluster of pixel data in +// the KMean algorithm. While this class does not contain all of the points +// that exist in the cluster, it keeps track of the aggregate sum so it can +// compute the new center appropriately. +class KMeanCluster { + public: + KMeanCluster() { + Reset(); + } + + void Reset() { + centroid_[0] = centroid_[1] = centroid_[2] = 0; + aggregate_[0] = aggregate_[1] = aggregate_[2] = 0; + counter_ = 0; + weight_ = 0; + } + + inline void SetCentroid(uint8_t r, uint8_t g, uint8_t b) { + centroid_[0] = r; + centroid_[1] = g; + centroid_[2] = b; + } + + inline void GetCentroid(uint8_t* r, uint8_t* g, uint8_t* b) { + *r = centroid_[0]; + *g = centroid_[1]; + *b = centroid_[2]; + } + + inline bool IsAtCentroid(uint8_t r, uint8_t g, uint8_t b) { + return r == centroid_[0] && g == centroid_[1] && b == centroid_[2]; + } + + // Recomputes the centroid of the cluster based on the aggregate data. The + // number of points used to calculate this center is stored for weighting + // purposes. The aggregate and counter are then cleared to be ready for the + // next iteration. + inline void RecomputeCentroid() { + if (counter_ > 0) { + centroid_[0] = static_cast(aggregate_[0] / counter_); + centroid_[1] = static_cast(aggregate_[1] / counter_); + centroid_[2] = static_cast(aggregate_[2] / counter_); + + aggregate_[0] = aggregate_[1] = aggregate_[2] = 0; + weight_ = counter_; + counter_ = 0; + } + } + + inline void AddPoint(uint8_t r, uint8_t g, uint8_t b) { + aggregate_[0] += r; + aggregate_[1] += g; + aggregate_[2] += b; + ++counter_; + } + + // Just returns the distance^2. Since we are comparing relative distances + // there is no need to perform the expensive sqrt() operation. + inline uint32_t GetDistanceSqr(uint8_t r, uint8_t g, uint8_t b) { + return (r - centroid_[0]) * (r - centroid_[0]) + + (g - centroid_[1]) * (g - centroid_[1]) + + (b - centroid_[2]) * (b - centroid_[2]); + } + + // In order to determine if we have hit convergence or not we need to see + // if the centroid of the cluster has moved. This determines whether or + // not the centroid is the same as the aggregate sum of points that will be + // used to generate the next centroid. + inline bool CompareCentroidWithAggregate() { + if (counter_ == 0) + return false; + + return aggregate_[0] / counter_ == centroid_[0] && + aggregate_[1] / counter_ == centroid_[1] && + aggregate_[2] / counter_ == centroid_[2]; + } + + // Returns the previous counter, which is used to determine the weight + // of the cluster for sorting. + inline uint32_t GetWeight() const { + return weight_; + } + + static bool SortKMeanClusterByWeight(const KMeanCluster& a, + const KMeanCluster& b) { + return a.GetWeight() > b.GetWeight(); + } + + private: + uint8_t centroid_[3]; + + // Holds the sum of all the points that make up this cluster. Used to + // generate the next centroid as well as to check for convergence. + uint32_t aggregate_[3]; + uint32_t counter_; + + // The weight of the cluster, determined by how many points were used + // to generate the previous centroid. + uint32_t weight_; +}; + +// Prominent color utilities --------------------------------------------------- + +// A |ColorBox| represents a 3-dimensional region in a color space (an ordered +// set of colors). It is a range in the ordered set, with a low index and a high +// index. The diversity (volume) of the box is computed by looking at the range +// of color values it spans, where r, g, and b components are considered +// separately. +class ColorBox { + public: + explicit ColorBox(std::vector* color_space) + : ColorBox(color_space, gfx::Range(0, color_space->size())) {} + ColorBox(const ColorBox& other) = default; + ColorBox& operator=(const ColorBox& other) = default; + ~ColorBox() {} + + // Can't split if there's only one color in the box. + bool CanSplit() const { return color_range_.length() > 1; } + + // Splits |this| in two and returns the other half. + ColorBox Split() { + // Calculate which component has the largest range... + const uint8_t r_dimension = max_r_ - min_r_; + const uint8_t g_dimension = max_g_ - min_g_; + const uint8_t b_dimension = max_b_ - min_b_; + const uint8_t long_dimension = + std::max({r_dimension, g_dimension, b_dimension}); + const enum { + RED, + GREEN, + BLUE, + } channel = long_dimension == r_dimension + ? RED + : long_dimension == g_dimension ? GREEN : BLUE; + + // ... and sort along that axis. + auto sort_function = [channel](SkColor a, SkColor b) { + switch (channel) { + case RED: + return SkColorGetR(a) < SkColorGetR(b); + case GREEN: + return SkColorGetG(a) < SkColorGetG(b); + case BLUE: + return SkColorGetB(a) < SkColorGetB(b); + } + NOTREACHED(); + return SkColorGetB(a) < SkColorGetB(b); + }; + // Just the portion of |color_space_| that's covered by this box should be + // sorted. + std::sort(color_space_->begin() + color_range_.start(), + color_space_->begin() + color_range_.end(), sort_function); + + // Split at the first color value that's not less than the midpoint (mean of + // the start and values). + uint32_t split_point = color_range_.end() - 1; + for (uint32_t i = color_range_.start() + 1; i < color_range_.end() - 1; + ++i) { + bool past_midpoint = false; + switch (channel) { + case RED: + past_midpoint = + static_cast(SkColorGetR((*color_space_)[i])) > + (min_r_ + max_r_) / 2; + break; + case GREEN: + past_midpoint = + static_cast(SkColorGetG((*color_space_)[i])) > + (min_g_ + max_g_) / 2; + break; + case BLUE: + past_midpoint = + static_cast(SkColorGetB((*color_space_)[i])) > + (min_b_ + max_b_) / 2; + break; + } + if (past_midpoint) { + split_point = i; + break; + } + } + + // Break off half and return it. + gfx::Range other_range = color_range_; + other_range.set_end(split_point); + ColorBox other_box(color_space_, other_range); + + // Keep the other half in |this| and recalculate our color bounds. + color_range_.set_start(split_point); + RecomputeBounds(); + return other_box; + } + + // Returns the average color of this box, weighted by its popularity in + // |color_counts|. + Swatch GetWeightedAverageColor( + const std::unordered_map& color_counts) const { + size_t sum_r = 0; + size_t sum_g = 0; + size_t sum_b = 0; + size_t total_count_in_box = 0; + + for (size_t i = color_range_.start(); i < color_range_.end(); ++i) { + const SkColor color = (*color_space_)[i]; + const auto color_count_iter = color_counts.find(color); + DCHECK(color_count_iter != color_counts.end()); + const size_t color_count = color_count_iter->second; + + total_count_in_box += color_count; + sum_r += color_count * SkColorGetR(color); + sum_g += color_count * SkColorGetG(color); + sum_b += color_count * SkColorGetB(color); + } + + return Swatch( + SkColorSetRGB( + std::round(static_cast(sum_r) / total_count_in_box), + std::round(static_cast(sum_g) / total_count_in_box), + std::round(static_cast(sum_b) / total_count_in_box)), + total_count_in_box); + } + + static bool CompareByVolume(const ColorBox& a, const ColorBox& b) { + return a.volume_ < b.volume_; + } + + private: + ColorBox(std::vector* color_space, const gfx::Range& color_range) + : color_space_(color_space), color_range_(color_range) { + RecomputeBounds(); + } + + void RecomputeBounds() { + DCHECK(!color_range_.is_reversed()); + DCHECK(!color_range_.is_empty()); + DCHECK_LE(color_range_.end(), color_space_->size()); + + min_r_ = 0xFF; + min_g_ = 0xFF; + min_b_ = 0xFF; + max_r_ = 0; + max_g_ = 0; + max_b_ = 0; + + for (uint32_t i = color_range_.start(); i < color_range_.end(); ++i) { + SkColor color = (*color_space_)[i]; + min_r_ = std::min(SkColorGetR(color), min_r_); + min_g_ = std::min(SkColorGetG(color), min_g_); + min_b_ = std::min(SkColorGetB(color), min_b_); + max_r_ = std::max(SkColorGetR(color), max_r_); + max_g_ = std::max(SkColorGetG(color), max_g_); + max_b_ = std::max(SkColorGetB(color), max_b_); + } + + volume_ = + (max_r_ - min_r_ + 1) * (max_g_ - min_g_ + 1) * (max_b_ - min_b_ + 1); + } + + // The set of colors of which this box captures a subset. This vector is not + // owned but may be modified during the split operation. + std::vector* color_space_; + + // The range of indexes into |color_space_| that are part of this box. + gfx::Range color_range_; + + // Cached min and max color component values for the colors in this box. + uint8_t min_r_ = 0; + uint8_t min_g_ = 0; + uint8_t min_b_ = 0; + uint8_t max_r_ = 0; + uint8_t max_g_ = 0; + uint8_t max_b_ = 0; + + // Cached volume value, which is the product of the range of each color + // component. + int volume_ = 0; +}; + +// Some color values should be ignored for the purposes of determining prominent +// colors. +bool IsInterestingColor(const SkColor& color) { + const float average_channel_value = + (SkColorGetR(color) + SkColorGetG(color) + SkColorGetB(color)) / 3.0f; + // If a color is too close to white or black, ignore it. + if (average_channel_value >= 237 || average_channel_value <= 22) + return false; + + HSL hsl; + SkColorToHSL(color, &hsl); + return !(hsl.h >= 0.028f && hsl.h <= 0.10f && hsl.s <= 0.82f); +} + +// Used to group lower_bound, upper_bound, goal HSL color together for prominent +// color calculation. +struct ColorBracket { + HSL lower_bound = {-1}; + HSL upper_bound = {-1}; + HSL goal = {-1}; +}; + +std::vector CalculateProminentColors( + const SkBitmap& bitmap, + const std::vector& color_brackets, + const gfx::Rect& region, + absl::optional filter) { + DCHECK(!bitmap.empty()); + DCHECK(!bitmap.isNull()); + + std::vector box_colors = + CalculateColorSwatches(bitmap, 12, region, filter); + + std::vector best_colors(color_brackets.size(), Swatch()); + if (box_colors.empty()) + return best_colors; + + size_t max_weight = 0; + for (auto& weighted : box_colors) + max_weight = std::max(max_weight, weighted.population); + + // Given these box average colors, find the best one for each desired color + // profile. "Best" in this case means the color which fits in the provided + // bounds and comes closest to |goal|. It's possible that no color will fit in + // the provided bounds, in which case we'll return an empty color. + for (size_t i = 0; i < color_brackets.size(); ++i) { + double best_suitability = 0; + for (const auto& box_color : box_colors) { + HSL hsl; + SkColorToHSL(box_color.color, &hsl); + if (!IsWithinHSLRange(hsl, color_brackets[i].lower_bound, + color_brackets[i].upper_bound)) { + continue; + } + + double suitability = + (1 - std::abs(hsl.s - color_brackets[i].goal.s)) * 3 + + (1 - std::abs(hsl.l - color_brackets[i].goal.l)) * 6.5 + + (box_color.population / static_cast(max_weight)) * 0.5; + if (suitability > best_suitability) { + best_suitability = suitability; + best_colors[i] = box_color; + } + } + } + + return best_colors; +} + +} // namespace + +KMeanImageSampler::KMeanImageSampler() { +} + +KMeanImageSampler::~KMeanImageSampler() { +} + +GridSampler::GridSampler() : calls_(0) { +} + +GridSampler::~GridSampler() { +} + +int GridSampler::GetSample(int width, int height) { + // Hand-drawn bitmaps often have special outlines or feathering at the edges. + // Start our sampling inset from the top and left edges. For example, a 10x10 + // image with 4 clusters would be sampled like this: + // .......... + // .0.4.8.... + // .......... + // .1.5.9.... + // .......... + // .2.6...... + // .......... + // .3.7...... + // .......... + // But don't inset if the image is too narrow or too short. + const int kInsetX = (width > 2 ? 1 : 0); + const int kInsetY = (height > 2 ? 1 : 0); + int x = kInsetX + (calls_ / kNumberOfClusters) * + ((width - 2 * kInsetX) / kNumberOfClusters); + int y = kInsetY + (calls_ % kNumberOfClusters) * + ((height - 2 * kInsetY) / kNumberOfClusters); + int index = x + (y * width); + ++calls_; + return index % (width * height); +} + +SkColor FindClosestColor(const uint8_t* image, + int width, + int height, + SkColor color) { + uint8_t in_r = SkColorGetR(color); + uint8_t in_g = SkColorGetG(color); + uint8_t in_b = SkColorGetB(color); + // Search using distance-squared to avoid expensive sqrt() operations. + int best_distance_squared = std::numeric_limits::max(); + SkColor best_color = color; + const uint8_t* byte = image; + for (int i = 0; i < width * height; ++i) { + uint8_t b = *(byte++); + uint8_t g = *(byte++); + uint8_t r = *(byte++); + uint8_t a = *(byte++); + // Ignore fully transparent pixels. + if (a == 0) + continue; + int distance_squared = + (in_b - b) * (in_b - b) + + (in_g - g) * (in_g - g) + + (in_r - r) * (in_r - r); + if (distance_squared < best_distance_squared) { + best_distance_squared = distance_squared; + best_color = SkColorSetRGB(r, g, b); + } + } + return best_color; +} + +// For a 16x16 icon on an Intel Core i5 this function takes approximately +// 0.5 ms to run. +// TODO(port): This code assumes the CPU architecture is little-endian. +SkColor CalculateKMeanColorOfBuffer(uint8_t* decoded_data, + int img_width, + int img_height, + const HSL& lower_bound, + const HSL& upper_bound, + KMeanImageSampler* sampler, + bool find_closest) { + SkColor color = kDefaultBgColor; + if (img_width > 0 && img_height > 0) { + std::vector clusters; + clusters.resize(static_cast(kNumberOfClusters), KMeanCluster()); + + // Pick a starting point for each cluster + auto new_cluster = clusters.begin(); + while (new_cluster != clusters.end()) { + // Try up to 10 times to find a unique color. If no unique color can be + // found, destroy this cluster. + bool color_unique = false; + for (int i = 0; i < 10; ++i) { + int pixel_pos = sampler->GetSample(img_width, img_height) % + (img_width * img_height); + + uint8_t b = decoded_data[pixel_pos * 4]; + uint8_t g = decoded_data[pixel_pos * 4 + 1]; + uint8_t r = decoded_data[pixel_pos * 4 + 2]; + uint8_t a = decoded_data[pixel_pos * 4 + 3]; + // Skip fully transparent pixels as they usually contain black in their + // RGB channels but do not contribute to the visual image. + if (a == 0) + continue; + + // Loop through the previous clusters and check to see if we have seen + // this color before. + color_unique = true; + for (auto cluster = clusters.begin(); cluster != new_cluster; + ++cluster) { + if (cluster->IsAtCentroid(r, g, b)) { + color_unique = false; + break; + } + } + + // If we have a unique color set the center of the cluster to + // that color. + if (color_unique) { + new_cluster->SetCentroid(r, g, b); + break; + } + } + + // If we don't have a unique color erase this cluster. + if (!color_unique) { + new_cluster = clusters.erase(new_cluster); + } else { + // Have to increment the iterator here, otherwise the increment in the + // for loop will skip a cluster due to the erase if the color wasn't + // unique. + ++new_cluster; + } + } + + // If all pixels in the image are transparent we will have no clusters. + if (clusters.empty()) + return color; + + bool convergence = false; + for (int iteration = 0; + iteration < kNumberOfIterations && !convergence; + ++iteration) { + + // Loop through each pixel so we can place it in the appropriate cluster. + uint8_t* pixel = decoded_data; + uint8_t* decoded_data_end = decoded_data + (img_width * img_height * 4); + while (pixel < decoded_data_end) { + uint8_t b = *(pixel++); + uint8_t g = *(pixel++); + uint8_t r = *(pixel++); + uint8_t a = *(pixel++); + // Skip transparent pixels, see above. + if (a == 0) + continue; + + uint32_t distance_sqr_to_closest_cluster = UINT_MAX; + auto closest_cluster = clusters.begin(); + + // Figure out which cluster this color is closest to in RGB space. + for (auto cluster = clusters.begin(); cluster != clusters.end(); + ++cluster) { + uint32_t distance_sqr = cluster->GetDistanceSqr(r, g, b); + + if (distance_sqr < distance_sqr_to_closest_cluster) { + distance_sqr_to_closest_cluster = distance_sqr; + closest_cluster = cluster; + } + } + + closest_cluster->AddPoint(r, g, b); + } + + // Calculate the new cluster centers and see if we've converged or not. + convergence = true; + for (auto cluster = clusters.begin(); cluster != clusters.end(); + ++cluster) { + convergence &= cluster->CompareCentroidWithAggregate(); + + cluster->RecomputeCentroid(); + } + } + + // Sort the clusters by population so we can tell what the most popular + // color is. + std::sort(clusters.begin(), clusters.end(), + KMeanCluster::SortKMeanClusterByWeight); + + // Loop through the clusters to figure out which cluster has an appropriate + // color. Skip any that are too bright/dark and go in order of weight. + for (auto cluster = clusters.begin(); cluster != clusters.end(); + ++cluster) { + uint8_t r, g, b; + cluster->GetCentroid(&r, &g, &b); + + SkColor current_color = SkColorSetARGB(SK_AlphaOPAQUE, r, g, b); + HSL hsl; + SkColorToHSL(current_color, &hsl); + if (IsWithinHSLRange(hsl, lower_bound, upper_bound)) { + // If we found a valid color just set it and break. We don't want to + // check the other ones. + color = current_color; + break; + } else if (cluster == clusters.begin()) { + // We haven't found a valid color, but we are at the first color so + // set the color anyway to make sure we at least have a value here. + color = current_color; + } + } + } + + // The K-mean cluster center will not usually be a color that appears in the + // image. If desired, find a color that actually appears. + return find_closest + ? FindClosestColor(decoded_data, img_width, img_height, color) + : color; +} + +SkColor CalculateKMeanColorOfPNG(scoped_refptr png, + const HSL& lower_bound, + const HSL& upper_bound, + KMeanImageSampler* sampler) { + int img_width = 0; + int img_height = 0; + std::vector decoded_data; + SkColor color = kDefaultBgColor; + + if (png.get() && png->size() && + gfx::PNGCodec::Decode(png->front(), png->size(), + gfx::PNGCodec::FORMAT_BGRA, &decoded_data, + &img_width, &img_height)) { + return CalculateKMeanColorOfBuffer(&decoded_data[0], img_width, img_height, + lower_bound, upper_bound, sampler, true); + } + return color; +} + +SkColor CalculateKMeanColorOfPNG(scoped_refptr png) { + GridSampler sampler; + return CalculateKMeanColorOfPNG( + png, kDefaultLowerHSLBound, kDefaultUpperHSLBound, &sampler); +} + +SkColor CalculateKMeanColorOfBitmap(const SkBitmap& bitmap, + int height, + const HSL& lower_bound, + const HSL& upper_bound, + bool find_closest) { + // Clamp the height being used to the height of the provided image (otherwise, + // we can end up creating a larger buffer than we have data for, and the end + // of the buffer will remain uninitialized after we copy/UnPreMultiply the + // image data into it). + height = base::clamp(height, 0, bitmap.height()); + + // SkBitmap uses pre-multiplied alpha but the KMean clustering function + // above uses non-pre-multiplied alpha. Transform the bitmap before we + // analyze it because the function reads each pixel multiple times. + int pixel_count = bitmap.width() * height; + std::unique_ptr image(new uint32_t[pixel_count]); + + // Un-premultiplies each pixel in bitmap into the buffer. Requires + // approximately 10 microseconds for a 16x16 icon on an Intel Core i5. + uint32_t* in = static_cast(bitmap.getPixels()); + uint32_t* out = image.get(); + for (int i = 0; i < pixel_count; ++i) + *out++ = SkUnPreMultiply::PMColorToColor(*in++); + + GridSampler sampler; + return CalculateKMeanColorOfBuffer(reinterpret_cast(image.get()), + bitmap.width(), height, lower_bound, + upper_bound, &sampler, find_closest); +} + +SkColor CalculateKMeanColorOfBitmap(const SkBitmap& bitmap) { + return CalculateKMeanColorOfBitmap( + bitmap, bitmap.height(), kDefaultLowerHSLBound, kDefaultUpperHSLBound, + true); +} + +const int kMaxConsideredPixelsForSwatches = 10007; + +// This algorithm is a port of Android's Palette API. Compare to package +// android.support.v7.graphics and see that code for additional high-level +// explanation of this algorithm. There are some minor differences: +// * This code doesn't exclude the same color from being used for +// different color profiles. +// * This code doesn't try to heuristically derive missing colors from +// existing colors. +std::vector CalculateColorSwatches( + const SkBitmap& bitmap, + size_t max_swatches, + const gfx::Rect& region, + absl::optional filter) { + DCHECK(!bitmap.empty()); + DCHECK(!bitmap.isNull()); + DCHECK(!region.IsEmpty()); + DCHECK_LE(region.width(), bitmap.width()); + DCHECK_LE(region.height(), bitmap.height()); + + const int pixel_count = region.width() * region.height(); + + // For better performance, only consider at most 10k pixels (evenly + // distributed throughout the image). This has a very minor impact on the + // outcome but improves runtime substantially for large images. 10,007 is a + // prime number to reduce the chance of picking an unrepresentative sample. + const int pixel_increment = + std::max(1, pixel_count / kMaxConsideredPixelsForSwatches); + std::unordered_map color_counts( + kMaxConsideredPixelsForSwatches); + + // First extract all colors into counts. + for (int i = 0; i < pixel_count; i += pixel_increment) { + const int x = region.x() + (i % region.width()); + const int y = region.y() + (i / region.width()); + + const SkColor pixel = bitmap.getColor(x, y); + if (SkColorGetA(pixel) == SK_AlphaTRANSPARENT) + continue; + + color_counts[pixel]++; + } + + // Now throw out some uninteresting colors if there is a filter. + std::vector interesting_colors; + interesting_colors.reserve(color_counts.size()); + for (auto color_count : color_counts) { + SkColor color = color_count.first; + if (!filter || filter->Run(color)) + interesting_colors.push_back(color); + } + + if (interesting_colors.empty()) + return {}; + + // Group the colors into "boxes" and repeatedly split the most voluminous box. + // We stop the process when a box can no longer be split (there's only one + // color in it) or when the number of color boxes reaches |max_colors|. + // + // Boxes are sorted by volume with the most voluminous at the front of the PQ. + std::priority_queue, + bool (*)(const ColorBox&, const ColorBox&)> + boxes(&ColorBox::CompareByVolume); + boxes.emplace(&interesting_colors); + while (boxes.size() < max_swatches) { + auto box = boxes.top(); + if (!box.CanSplit()) + break; + boxes.pop(); + boxes.push(box.Split()); + boxes.push(box); + } + + // Now extract a single color to represent each box. This is the average color + // in the box, weighted by the frequency of that color in the source image. + size_t max_weight = 0; + std::vector box_colors; + box_colors.reserve(max_swatches); + while (!boxes.empty()) { + box_colors.push_back(boxes.top().GetWeightedAverageColor(color_counts)); + boxes.pop(); + max_weight = std::max(max_weight, box_colors.back().population); + } + + return box_colors; +} + +std::vector CalculateProminentColorsOfBitmap( + const SkBitmap& bitmap, + const std::vector& color_profiles, + gfx::Rect* region, + ColorSwatchFilter filter) { + if (color_profiles.empty()) + return std::vector(); + + size_t size = color_profiles.size(); + if (bitmap.empty() || bitmap.isNull()) + return std::vector(size, Swatch()); + + // The hue is not relevant to our bounds or goal colors. + std::vector color_brackets(size); + for (size_t i = 0; i < size; ++i) { + switch (color_profiles[i].luma) { + case LumaRange::ANY: + color_brackets[i].lower_bound.l = 0; + color_brackets[i].upper_bound.l = 1; + color_brackets[i].goal.l = 0.5f; + break; + case LumaRange::LIGHT: + color_brackets[i].lower_bound.l = 0.55f; + color_brackets[i].upper_bound.l = 1; + color_brackets[i].goal.l = 0.74f; + break; + case LumaRange::NORMAL: + color_brackets[i].lower_bound.l = 0.3f; + color_brackets[i].upper_bound.l = 0.7f; + color_brackets[i].goal.l = 0.5f; + break; + case LumaRange::DARK: + color_brackets[i].lower_bound.l = 0; + color_brackets[i].upper_bound.l = 0.45f; + color_brackets[i].goal.l = 0.26f; + break; + } + + switch (color_profiles[i].saturation) { + case SaturationRange::ANY: + color_brackets[i].lower_bound.s = 0; + color_brackets[i].upper_bound.s = 1; + color_brackets[i].goal.s = 0.5f; + break; + case SaturationRange::VIBRANT: + color_brackets[i].lower_bound.s = 0.35f; + color_brackets[i].upper_bound.s = 1; + color_brackets[i].goal.s = 1; + break; + case SaturationRange::MUTED: + color_brackets[i].lower_bound.s = 0; + color_brackets[i].upper_bound.s = 0.4f; + color_brackets[i].goal.s = 0.3f; + break; + } + } + + return CalculateProminentColors( + bitmap, color_brackets, + region ? *region : gfx::Rect(bitmap.width(), bitmap.height()), + filter.is_null() ? base::BindRepeating(&IsInterestingColor) : filter); +} + +gfx::Matrix3F ComputeColorCovariance(const SkBitmap& bitmap) { + // First need basic stats to normalize each channel separately. + gfx::Matrix3F covariance = gfx::Matrix3F::Zeros(); + if (!bitmap.getPixels()) + return covariance; + + // Assume ARGB_8888 format. + DCHECK(bitmap.colorType() == kN32_SkColorType); + + int64_t r_sum = 0; + int64_t g_sum = 0; + int64_t b_sum = 0; + int64_t rr_sum = 0; + int64_t gg_sum = 0; + int64_t bb_sum = 0; + int64_t rg_sum = 0; + int64_t rb_sum = 0; + int64_t gb_sum = 0; + + for (int y = 0; y < bitmap.height(); ++y) { + SkPMColor* current_color = static_cast(bitmap.getAddr32(0, y)); + for (int x = 0; x < bitmap.width(); ++x, ++current_color) { + SkColor c = SkUnPreMultiply::PMColorToColor(*current_color); + SkColor r = SkColorGetR(c); + SkColor g = SkColorGetG(c); + SkColor b = SkColorGetB(c); + + r_sum += r; + g_sum += g; + b_sum += b; + rr_sum += r * r; + gg_sum += g * g; + bb_sum += b * b; + rg_sum += r * g; + rb_sum += r * b; + gb_sum += g * b; + } + } + + // Covariance (not normalized) is E(X*X.t) - m * m.t and this is how it + // is calculated below. + // Each row below represents a row of the matrix describing (co)variances + // of R, G and B channels with (R, G, B) + int pixel_n = bitmap.width() * bitmap.height(); + covariance.set( + static_cast( + static_cast(rr_sum) / pixel_n - + static_cast(r_sum * r_sum) / pixel_n / pixel_n), + static_cast( + static_cast(rg_sum) / pixel_n - + static_cast(r_sum * g_sum) / pixel_n / pixel_n), + static_cast( + static_cast(rb_sum) / pixel_n - + static_cast(r_sum * b_sum) / pixel_n / pixel_n), + static_cast( + static_cast(rg_sum) / pixel_n - + static_cast(r_sum * g_sum) / pixel_n / pixel_n), + static_cast( + static_cast(gg_sum) / pixel_n - + static_cast(g_sum * g_sum) / pixel_n / pixel_n), + static_cast( + static_cast(gb_sum) / pixel_n - + static_cast(g_sum * b_sum) / pixel_n / pixel_n), + static_cast( + static_cast(rb_sum) / pixel_n - + static_cast(r_sum * b_sum) / pixel_n / pixel_n), + static_cast( + static_cast(gb_sum) / pixel_n - + static_cast(g_sum * b_sum) / pixel_n / pixel_n), + static_cast( + static_cast(bb_sum) / pixel_n - + static_cast(b_sum * b_sum) / pixel_n / pixel_n)); + return covariance; +} + +bool ApplyColorReduction(const SkBitmap& source_bitmap, + const gfx::Vector3dF& color_transform, + bool fit_to_range, + SkBitmap* target_bitmap) { + DCHECK(target_bitmap); + DCHECK(source_bitmap.getPixels()); + DCHECK(target_bitmap->getPixels()); + DCHECK_EQ(kN32_SkColorType, source_bitmap.colorType()); + DCHECK_EQ(kAlpha_8_SkColorType, target_bitmap->colorType()); + DCHECK_EQ(source_bitmap.height(), target_bitmap->height()); + DCHECK_EQ(source_bitmap.width(), target_bitmap->width()); + DCHECK(!source_bitmap.empty()); + + // Elements of color_transform are explicitly off-loaded to local values for + // efficiency reasons. Note that in practice images may correspond to entire + // tab captures. + float t0 = 0.0; + float tr = color_transform.x(); + float tg = color_transform.y(); + float tb = color_transform.z(); + + if (fit_to_range) { + // We will figure out min/max in a preprocessing step and adjust + // actual_transform as required. + float max_val = std::numeric_limits::min(); + float min_val = std::numeric_limits::max(); + for (int y = 0; y < source_bitmap.height(); ++y) { + const SkPMColor* source_color_row = static_cast( + source_bitmap.getAddr32(0, y)); + for (int x = 0; x < source_bitmap.width(); ++x) { + SkColor c = SkUnPreMultiply::PMColorToColor(source_color_row[x]); + uint8_t r = SkColorGetR(c); + uint8_t g = SkColorGetG(c); + uint8_t b = SkColorGetB(c); + float gray_level = tr * r + tg * g + tb * b; + max_val = std::max(max_val, gray_level); + min_val = std::min(min_val, gray_level); + } + } + + // Adjust the transform so that the result is scaling. + float scale = 0.0; + t0 = -min_val; + if (max_val > min_val) + scale = 255.0f / (max_val - min_val); + t0 *= scale; + tr *= scale; + tg *= scale; + tb *= scale; + } + + for (int y = 0; y < source_bitmap.height(); ++y) { + const SkPMColor* source_color_row = static_cast( + source_bitmap.getAddr32(0, y)); + uint8_t* target_color_row = target_bitmap->getAddr8(0, y); + for (int x = 0; x < source_bitmap.width(); ++x) { + SkColor c = SkUnPreMultiply::PMColorToColor(source_color_row[x]); + uint8_t r = SkColorGetR(c); + uint8_t g = SkColorGetG(c); + uint8_t b = SkColorGetB(c); + + float gl = t0 + tr * r + tg * g + tb * b; + if (gl < 0) + gl = 0; + if (gl > 0xFF) + gl = 0xFF; + target_color_row[x] = static_cast(gl); + } + } + + return true; +} + +} // color_utils diff --git a/color_analysis.h b/color_analysis.h new file mode 100644 index 000000000000..fdaa8f35dea9 --- /dev/null +++ b/color_analysis.h @@ -0,0 +1,212 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_COLOR_ANALYSIS_H_ +#define UI_GFX_COLOR_ANALYSIS_H_ + +#include + +#include "base/callback_forward.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/memory/ref_counted_memory.h" +#include "third_party/skia/include/core/SkColor.h" +#include "ui/gfx/geometry/matrix3_f.h" +#include "ui/gfx/gfx_export.h" + +class SkBitmap; + +namespace gfx { +class Rect; +} // namespace gfx + +namespace color_utils { + +struct HSL; + +// This class exposes the sampling method to the caller, which allows +// stubbing out for things like unit tests. Might be useful to pass more +// arguments into the GetSample method in the future (such as which +// cluster is being worked on, etc.). +// +// Note: Samplers should be deterministic, as the same image may be analyzed +// twice with two sampler instances and the results displayed side-by-side +// to the user. +class GFX_EXPORT KMeanImageSampler { + public: + virtual int GetSample(int width, int height) = 0; + + protected: + KMeanImageSampler(); + virtual ~KMeanImageSampler(); +}; + +// This sampler will pick pixels from an evenly spaced grid. +class GFX_EXPORT GridSampler : public KMeanImageSampler { + public: + GridSampler(); + ~GridSampler() override; + + int GetSample(int width, int height) override; + + private: + // The number of times GetSample has been called. + int calls_; +}; + +// Returns the color in an ARGB |image| that is closest in RGB-space to the +// provided |color|. Exported for testing. +GFX_EXPORT SkColor FindClosestColor(const uint8_t* image, int width, int height, + SkColor color); + +// Returns an SkColor that represents the calculated dominant color in the +// image. This uses a KMean clustering algorithm to find clusters of pixel +// colors in RGB space. +// |png|/|bitmap| represents the data of a png/bitmap encoded image. +// |lower_bound| represents the minimum bound of HSL values to allow. +// |upper_bound| represents the maximum bound of HSL values to allow. +// See color_utils::IsWithinHSLRange() for description of these bounds. +// +// RGB KMean Algorithm (N clusters, M iterations): +// 1.Pick N starting colors by randomly sampling the pixels. If you see a +// color you already saw keep sampling. After a certain number of tries +// just remove the cluster and continue with N = N-1 clusters (for an image +// with just one color this should devolve to N=1). These colors are the +// centers of your N clusters. +// 2.For each pixel in the image find the cluster that it is closest to in RGB +// space. Add that pixel's color to that cluster (we keep a sum and a count +// of all of the pixels added to the space, so just add it to the sum and +// increment count). +// 3.Calculate the new cluster centroids by getting the average color of all of +// the pixels in each cluster (dividing the sum by the count). +// 4.See if the new centroids are the same as the old centroids. +// a) If this is the case for all N clusters than we have converged and +// can move on. +// b) If any centroid moved, repeat step 2 with the new centroids for up +// to M iterations. +// 5.Once the clusters have converged or M iterations have been tried, sort +// the clusters by weight (where weight is the number of pixels that make up +// this cluster). +// 6.Going through the sorted list of clusters, pick the first cluster with the +// largest weight that's centroid falls between |lower_bound| and +// |upper_bound|. Return that color. +// If no color fulfills that requirement return the color with the largest +// weight regardless of whether or not it fulfills the equation above. +GFX_EXPORT SkColor + CalculateKMeanColorOfPNG(scoped_refptr png, + const HSL& lower_bound, + const HSL& upper_bound, + KMeanImageSampler* sampler); +// Computes a dominant color using the above algorithm and reasonable defaults +// for |lower_bound|, |upper_bound| and |sampler|. +GFX_EXPORT SkColor CalculateKMeanColorOfPNG( + scoped_refptr png); + +// Computes a dominant color for the first |height| rows of |bitmap| using the +// above algorithm and a reasonable default sampler. If |find_closest| is true, +// the returned color will be the closest color to the true K-mean color that +// actually appears in the image; if false, the true color is returned +// regardless of whether it actually appears. +GFX_EXPORT SkColor CalculateKMeanColorOfBitmap(const SkBitmap& bitmap, + int height, + const HSL& lower_bound, + const HSL& upper_bound, + bool find_closest); + +// Computes a dominant color using the above algorithm and reasonable defaults +// for |lower_bound|, |upper_bound| and |sampler|. +GFX_EXPORT SkColor CalculateKMeanColorOfBitmap(const SkBitmap& bitmap); + +// These enums specify general values to look for when calculating prominent +// colors from an image. For example, a "light vibrant" prominent color would +// tend to be brighter and more saturated. The best combination of color +// attributes depends on how you plan to apply the color. +enum class LumaRange { + ANY, + LIGHT, + NORMAL, + DARK, +}; + +enum class SaturationRange { + ANY, + VIBRANT, + MUTED, +}; + +struct ColorProfile { + ColorProfile() = default; + ColorProfile(LumaRange l, SaturationRange s) : luma(l), saturation(s) {} + + LumaRange luma = LumaRange::DARK; + SaturationRange saturation = SaturationRange::MUTED; +}; + +// A color value with an associated weight. +struct Swatch { + Swatch() : Swatch(SK_ColorTRANSPARENT, 0) {} + + Swatch(SkColor color, size_t population) + : color(color), population(population) {} + + SkColor color; + + // The population correlates to a count, so it should be 1 or greater. + size_t population; + + bool operator==(const Swatch& other) const { + return color == other.color && population == other.population; + } +}; + +// Used to filter colors from swatches. Called with the candidate color and will +// return true if the color should be allowed. +using ColorSwatchFilter = base::RepeatingCallback; + +// The maximum number of pixels to consider when generating swatches. +GFX_EXPORT extern const int kMaxConsideredPixelsForSwatches; + +// Returns a vector of |Swatch| that represent the prominent colors of the +// bitmap within |region|. The |max_swatches| is the maximum number of swatches. +// For landscapes, good values are in the range 12-16. For images which are +// largely made up of people's faces then this value should be increased to +// 24-32. |filter| is an optional filter that can filter out unwanted colors. +// This is an implementation of the Android Palette API: +// https://developer.android.com/reference/android/support/v7/graphics/Palette +GFX_EXPORT std::vector CalculateColorSwatches( + const SkBitmap& bitmap, + size_t max_swatches, + const gfx::Rect& region, + absl::optional filter); + +// Returns a vector of RGB colors that represents the bitmap based on the +// |color_profiles| provided. For each value, if a value is succesfully +// calculated, the calculated value is fully opaque. For failure, the calculated +// value is transparent. |region| can be provided to select a specific area of +// the bitmap. |filter| is an optional filter that can filter out unwanted +// colors. If |filter| is not provided then we will filter out uninteresting +// colors. +GFX_EXPORT std::vector CalculateProminentColorsOfBitmap( + const SkBitmap& bitmap, + const std::vector& color_profiles, + gfx::Rect* region, + ColorSwatchFilter filter); + +// Compute color covariance matrix for the input bitmap. +GFX_EXPORT gfx::Matrix3F ComputeColorCovariance(const SkBitmap& bitmap); + +// Apply a color reduction transform defined by |color_transform| vector to +// |source_bitmap|. The result is put into |target_bitmap|, which is expected +// to be initialized to the required size and type (SkBitmap::kA8_Config). +// If |fit_to_range|, result is transfored linearly to fit 0-0xFF range. +// Otherwise, data is clipped. +// Returns true if the target has been computed. +GFX_EXPORT bool ApplyColorReduction(const SkBitmap& source_bitmap, + const gfx::Vector3dF& color_transform, + bool fit_to_range, + SkBitmap* target_bitmap); + +} // namespace color_utils + +#endif // UI_GFX_COLOR_ANALYSIS_H_ diff --git a/color_analysis_fuzzer.cc b/color_analysis_fuzzer.cc new file mode 100644 index 000000000000..ef2f6d836dea --- /dev/null +++ b/color_analysis_fuzzer.cc @@ -0,0 +1,52 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/gfx/color_analysis.h" +#include "ui/gfx/color_utils.h" + +double ConsumeDouble(FuzzedDataProvider* provider) { + std::vector v = provider->ConsumeBytes(sizeof(double)); + if (v.size() == sizeof(double)) + return reinterpret_cast(v.data())[0]; + + return 0; +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + FuzzedDataProvider provider(data, size); + + // Limit width and height for performance. + int width = provider.ConsumeIntegralInRange(1, 100); + int height = provider.ConsumeIntegralInRange(1, 100); + SkImageInfo info = SkImageInfo::MakeN32Premul(width, height); + size_t expected_size = info.computeMinByteSize(); + + color_utils::HSL upper_bound = {ConsumeDouble(&provider), + ConsumeDouble(&provider), + ConsumeDouble(&provider)}; + color_utils::HSL lower_bound = {ConsumeDouble(&provider), + ConsumeDouble(&provider), + ConsumeDouble(&provider)}; + + bool find_closest = provider.ConsumeBool(); + + // Ensure that we have enough data for this image. + std::vector image_data = + provider.ConsumeBytes(expected_size); + if (image_data.size() < expected_size) + return 0; + + SkBitmap bitmap; + bitmap.installPixels(info, image_data.data(), info.minRowBytes()); + + color_utils::CalculateKMeanColorOfBitmap( + bitmap, provider.ConsumeIntegralInRange(-1, height + 2), lower_bound, + upper_bound, find_closest); + + return 0; +} diff --git a/color_analysis_unittest.cc b/color_analysis_unittest.cc new file mode 100644 index 000000000000..bbf1e877d583 --- /dev/null +++ b/color_analysis_unittest.cc @@ -0,0 +1,620 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/color_analysis.h" + +#include +#include + +#include +#include + +#include "base/bind.h" +#include "skia/ext/platform_canvas.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/color_utils.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/image/image.h" + +namespace color_utils { + +const unsigned char k1x1White[] = { + 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, + 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, + 0x08, 0x02, 0x00, 0x00, 0x00, 0x90, 0x77, 0x53, + 0xde, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47, + 0x42, 0x00, 0xae, 0xce, 0x1c, 0xe9, 0x00, 0x00, + 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, + 0x0b, 0x13, 0x00, 0x00, 0x0b, 0x13, 0x01, 0x00, + 0x9a, 0x9c, 0x18, 0x00, 0x00, 0x00, 0x07, 0x74, + 0x49, 0x4d, 0x45, 0x07, 0xdb, 0x02, 0x11, 0x15, + 0x16, 0x1b, 0xaa, 0x58, 0x38, 0x76, 0x00, 0x00, + 0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x43, 0x6f, + 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x00, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x77, 0x69, + 0x74, 0x68, 0x20, 0x47, 0x49, 0x4d, 0x50, 0x57, + 0x81, 0x0e, 0x17, 0x00, 0x00, 0x00, 0x0c, 0x49, + 0x44, 0x41, 0x54, 0x08, 0xd7, 0x63, 0xf8, 0xff, + 0xff, 0x3f, 0x00, 0x05, 0xfe, 0x02, 0xfe, 0xdc, + 0xcc, 0x59, 0xe7, 0x00, 0x00, 0x00, 0x00, 0x49, + 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 +}; + +const unsigned char k1x3BlueWhite[] = { + 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, + 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, + 0x08, 0x02, 0x00, 0x00, 0x00, 0xdd, 0xbf, 0xf2, + 0xd5, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47, + 0x42, 0x00, 0xae, 0xce, 0x1c, 0xe9, 0x00, 0x00, + 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, + 0x0b, 0x13, 0x00, 0x00, 0x0b, 0x13, 0x01, 0x00, + 0x9a, 0x9c, 0x18, 0x00, 0x00, 0x00, 0x07, 0x74, + 0x49, 0x4d, 0x45, 0x07, 0xdb, 0x02, 0x12, 0x01, + 0x0a, 0x2c, 0xfd, 0x08, 0x64, 0x66, 0x00, 0x00, + 0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x43, 0x6f, + 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x00, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x77, 0x69, + 0x74, 0x68, 0x20, 0x47, 0x49, 0x4d, 0x50, 0x57, + 0x81, 0x0e, 0x17, 0x00, 0x00, 0x00, 0x14, 0x49, + 0x44, 0x41, 0x54, 0x08, 0xd7, 0x63, 0xf8, 0xff, + 0xff, 0x3f, 0x13, 0x03, 0x03, 0x03, 0x03, 0x03, + 0xc3, 0x7f, 0x00, 0x1e, 0xfd, 0x03, 0xff, 0xde, + 0x72, 0x58, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x49, + 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 +}; + +const unsigned char k1x3BlueRed[] = { + 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, + 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, + 0x08, 0x02, 0x00, 0x00, 0x00, 0xdd, 0xbf, 0xf2, + 0xd5, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47, + 0x42, 0x00, 0xae, 0xce, 0x1c, 0xe9, 0x00, 0x00, + 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, + 0x0b, 0x13, 0x00, 0x00, 0x0b, 0x13, 0x01, 0x00, + 0x9a, 0x9c, 0x18, 0x00, 0x00, 0x00, 0x07, 0x74, + 0x49, 0x4d, 0x45, 0x07, 0xdb, 0x02, 0x12, 0x01, + 0x07, 0x09, 0x03, 0xa2, 0xce, 0x6c, 0x00, 0x00, + 0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x43, 0x6f, + 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x00, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x77, 0x69, + 0x74, 0x68, 0x20, 0x47, 0x49, 0x4d, 0x50, 0x57, + 0x81, 0x0e, 0x17, 0x00, 0x00, 0x00, 0x14, 0x49, + 0x44, 0x41, 0x54, 0x08, 0xd7, 0x63, 0xf8, 0xcf, + 0xc0, 0xc0, 0xc4, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0xf0, 0x1f, 0x00, 0x0c, 0x10, 0x02, 0x01, 0x2c, + 0x8f, 0x8b, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x49, + 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 +}; + +const HSL kDefaultLowerBound = {-1, -1, 0.15}; +const HSL kDefaultUpperBound = {-1, -1, 0.85}; + +// Creates a 1-dimensional png of the pixel colors found in |colors|. +scoped_refptr CreateTestPNG( + const std::vector& colors) { + SkBitmap bitmap; + bitmap.allocN32Pixels(colors.size(), 1); + + for (size_t i = 0; i < colors.size(); ++i) { + bitmap.eraseArea(SkIRect::MakeXYWH(i, 0, 1, 1), colors[i]); + } + return gfx::Image::CreateFrom1xBitmap(bitmap).As1xPNGBytes(); +} + +class MockKMeanImageSampler : public KMeanImageSampler { + public: + MockKMeanImageSampler() : current_result_index_(0) { + } + + explicit MockKMeanImageSampler(const std::vector& samples) + : prebaked_sample_results_(samples), + current_result_index_(0) { + } + + ~MockKMeanImageSampler() override {} + + void AddSample(int sample) { + prebaked_sample_results_.push_back(sample); + } + + int GetSample(int width, int height) override { + if (current_result_index_ >= prebaked_sample_results_.size()) { + current_result_index_ = 0; + } + + if (prebaked_sample_results_.empty()) { + return 0; + } + + return prebaked_sample_results_[current_result_index_++]; + } + + protected: + std::vector prebaked_sample_results_; + size_t current_result_index_; +}; + +// Return true if a color channel is approximately equal to an expected value. +bool ChannelApproximatelyEqual(int expected, uint8_t channel) { + return (abs(expected - static_cast(channel)) <= 1); +} + +// Compute minimal and maximal graylevel (or alphalevel) of the input |bitmap|. +// |bitmap| has to be allocated and configured to kA8_Config. +void Calculate8bitBitmapMinMax(const SkBitmap& bitmap, + uint8_t* min_gl, + uint8_t* max_gl) { + DCHECK(bitmap.getPixels()); + DCHECK_EQ(bitmap.colorType(), kAlpha_8_SkColorType); + DCHECK(min_gl); + DCHECK(max_gl); + *min_gl = std::numeric_limits::max(); + *max_gl = std::numeric_limits::min(); + for (int y = 0; y < bitmap.height(); ++y) { + uint8_t* current_color = bitmap.getAddr8(0, y); + for (int x = 0; x < bitmap.width(); ++x, ++current_color) { + *min_gl = std::min(*min_gl, *current_color); + *max_gl = std::max(*max_gl, *current_color); + } + } +} + +class ColorAnalysisTest : public testing::Test { +}; + +TEST_F(ColorAnalysisTest, CalculatePNGKMeanAllWhite) { + MockKMeanImageSampler test_sampler; + test_sampler.AddSample(0); + + scoped_refptr png( + new base::RefCountedBytes( + std::vector( + k1x1White, + k1x1White + sizeof(k1x1White) / sizeof(unsigned char)))); + + SkColor color = CalculateKMeanColorOfPNG( + png, kDefaultLowerBound, kDefaultUpperBound, &test_sampler); + + EXPECT_EQ(color, SK_ColorWHITE); +} + +TEST_F(ColorAnalysisTest, CalculatePNGKMeanIgnoreWhiteLightness) { + MockKMeanImageSampler test_sampler; + test_sampler.AddSample(0); + test_sampler.AddSample(1); + test_sampler.AddSample(2); + + scoped_refptr png( + new base::RefCountedBytes( + std::vector( + k1x3BlueWhite, + k1x3BlueWhite + sizeof(k1x3BlueWhite) / sizeof(unsigned char)))); + + SkColor color = CalculateKMeanColorOfPNG( + png, kDefaultLowerBound, kDefaultUpperBound, &test_sampler); + + EXPECT_EQ(SkColorSetARGB(0xFF, 0x00, 0x00, 0xFF), color); +} + +TEST_F(ColorAnalysisTest, CalculatePNGKMeanPickMostCommon) { + MockKMeanImageSampler test_sampler; + test_sampler.AddSample(0); + test_sampler.AddSample(1); + test_sampler.AddSample(2); + + scoped_refptr png( + new base::RefCountedBytes( + std::vector( + k1x3BlueRed, + k1x3BlueRed + sizeof(k1x3BlueRed) / sizeof(unsigned char)))); + + SkColor color = CalculateKMeanColorOfPNG( + png, kDefaultLowerBound, kDefaultUpperBound, &test_sampler); + + EXPECT_EQ(SkColorSetARGB(0xFF, 0xFF, 0x00, 0x00), color); +} + +TEST_F(ColorAnalysisTest, CalculatePNGKMeanIgnoreRedHue) { + MockKMeanImageSampler test_sampler; + test_sampler.AddSample(0); + test_sampler.AddSample(1); + test_sampler.AddSample(2); + + std::vector colors(4, SK_ColorRED); + colors[1] = SK_ColorBLUE; + + scoped_refptr png = CreateTestPNG(colors); + + HSL lower = {0.2, -1, 0.15}; + HSL upper = {0.8, -1, 0.85}; + SkColor color = CalculateKMeanColorOfPNG( + png, lower, upper, &test_sampler); + + EXPECT_EQ(SK_ColorBLUE, color); +} + +TEST_F(ColorAnalysisTest, CalculatePNGKMeanIgnoreGreySaturation) { + MockKMeanImageSampler test_sampler; + test_sampler.AddSample(0); + test_sampler.AddSample(1); + test_sampler.AddSample(2); + + std::vector colors(4, SK_ColorGRAY); + colors[1] = SK_ColorBLUE; + + scoped_refptr png = CreateTestPNG(colors); + HSL lower = {-1, 0.3, -1}; + HSL upper = {-1, 1, -1}; + SkColor color = CalculateKMeanColorOfPNG( + png, lower, upper, &test_sampler); + + EXPECT_EQ(SK_ColorBLUE, color); +} + +TEST_F(ColorAnalysisTest, GridSampler) { + GridSampler sampler; + const int kWidth = 16; + const int kHeight = 16; + // Sample starts at 1,1. + EXPECT_EQ(1 + 1 * kWidth, sampler.GetSample(kWidth, kHeight)); + EXPECT_EQ(1 + 4 * kWidth, sampler.GetSample(kWidth, kHeight)); + EXPECT_EQ(1 + 7 * kWidth, sampler.GetSample(kWidth, kHeight)); + EXPECT_EQ(1 + 10 * kWidth, sampler.GetSample(kWidth, kHeight)); + // Step over by 3. + EXPECT_EQ(4 + 1 * kWidth, sampler.GetSample(kWidth, kHeight)); + EXPECT_EQ(4 + 4 * kWidth, sampler.GetSample(kWidth, kHeight)); + EXPECT_EQ(4 + 7 * kWidth, sampler.GetSample(kWidth, kHeight)); + EXPECT_EQ(4 + 10 * kWidth, sampler.GetSample(kWidth, kHeight)); +} + +TEST_F(ColorAnalysisTest, FindClosestColor) { + // Empty image returns input color. + SkColor color = FindClosestColor(NULL, 0, 0, SK_ColorRED); + EXPECT_EQ(SK_ColorRED, color); + + // Single color image returns that color. + SkBitmap bitmap; + bitmap.allocN32Pixels(16, 16); + bitmap.eraseColor(SK_ColorWHITE); + color = FindClosestColor(static_cast(bitmap.getPixels()), + bitmap.width(), + bitmap.height(), + SK_ColorRED); + EXPECT_EQ(SK_ColorWHITE, color); + + // Write a black pixel into the image. A dark grey input pixel should match + // the black one in the image. + uint32_t* pixel = bitmap.getAddr32(0, 0); + *pixel = SK_ColorBLACK; + color = FindClosestColor(static_cast(bitmap.getPixels()), + bitmap.width(), + bitmap.height(), + SK_ColorDKGRAY); + EXPECT_EQ(SK_ColorBLACK, color); +} + +TEST_F(ColorAnalysisTest, CalculateKMeanColorOfBitmap) { + // Create a 16x16 bitmap to represent a favicon. + SkBitmap bitmap; + bitmap.allocN32Pixels(16, 16); + bitmap.eraseARGB(255, 100, 150, 200); + + SkColor color = CalculateKMeanColorOfBitmap(bitmap); + EXPECT_EQ(255u, SkColorGetA(color)); + // Color values are not exactly equal due to reversal of premultiplied alpha. + EXPECT_TRUE(ChannelApproximatelyEqual(100, SkColorGetR(color))); + EXPECT_TRUE(ChannelApproximatelyEqual(150, SkColorGetG(color))); + EXPECT_TRUE(ChannelApproximatelyEqual(200, SkColorGetB(color))); + + // Test a bitmap with an alpha channel. + bitmap.eraseARGB(128, 100, 150, 200); + color = CalculateKMeanColorOfBitmap(bitmap); + + // Alpha channel should be ignored for dominant color calculation. + EXPECT_EQ(255u, SkColorGetA(color)); + EXPECT_TRUE(ChannelApproximatelyEqual(100, SkColorGetR(color))); + EXPECT_TRUE(ChannelApproximatelyEqual(150, SkColorGetG(color))); + EXPECT_TRUE(ChannelApproximatelyEqual(200, SkColorGetB(color))); +} + +// Regression test for heap-buffer-underflow. https://crbug.com/970343 +TEST_F(ColorAnalysisTest, CalculateKMeanColorOfSmallImage) { + SkBitmap bitmap; + + // Create a 1x41 bitmap, so it is not wide enough to have 1 pixel of padding + // on both sides. + bitmap.allocN32Pixels(1, 41); + bitmap.eraseARGB(255, 100, 150, 200); + + SkColor color = CalculateKMeanColorOfBitmap(bitmap); + EXPECT_EQ(255u, SkColorGetA(color)); + EXPECT_TRUE(ChannelApproximatelyEqual(100, SkColorGetR(color))); + EXPECT_TRUE(ChannelApproximatelyEqual(150, SkColorGetG(color))); + EXPECT_TRUE(ChannelApproximatelyEqual(200, SkColorGetB(color))); + + // Test a wide but narrow bitmap. + bitmap.allocN32Pixels(41, 1); + bitmap.eraseARGB(255, 100, 150, 200); + color = CalculateKMeanColorOfBitmap(bitmap); + EXPECT_EQ(255u, SkColorGetA(color)); + EXPECT_TRUE(ChannelApproximatelyEqual(100, SkColorGetR(color))); + EXPECT_TRUE(ChannelApproximatelyEqual(150, SkColorGetG(color))); + EXPECT_TRUE(ChannelApproximatelyEqual(200, SkColorGetB(color))); + + // Test a tiny bitmap. + bitmap.allocN32Pixels(1, 1); + bitmap.eraseARGB(255, 100, 150, 200); + color = CalculateKMeanColorOfBitmap(bitmap); + EXPECT_EQ(255u, SkColorGetA(color)); + EXPECT_TRUE(ChannelApproximatelyEqual(100, SkColorGetR(color))); + EXPECT_TRUE(ChannelApproximatelyEqual(150, SkColorGetG(color))); + EXPECT_TRUE(ChannelApproximatelyEqual(200, SkColorGetB(color))); +} + +TEST_F(ColorAnalysisTest, ComputeColorCovarianceTrivial) { + SkBitmap bitmap; + bitmap.setInfo(SkImageInfo::MakeN32Premul(100, 200)); + + EXPECT_EQ(gfx::Matrix3F::Zeros(), ComputeColorCovariance(bitmap)); + bitmap.allocPixels(); + bitmap.eraseARGB(255, 50, 150, 200); + gfx::Matrix3F covariance = ComputeColorCovariance(bitmap); + // The answer should be all zeros. + EXPECT_TRUE(covariance == gfx::Matrix3F::Zeros()); +} + +TEST_F(ColorAnalysisTest, ComputeColorCovarianceWithCanvas) { + gfx::Canvas canvas(gfx::Size(250, 200), 1.0f, true); + // The image consists of vertical stripes, with color bands set to 100 + // in overlapping stripes 150 pixels wide. + canvas.FillRect(gfx::Rect(0, 0, 50, 200), SkColorSetRGB(100, 0, 0)); + canvas.FillRect(gfx::Rect(50, 0, 50, 200), SkColorSetRGB(100, 100, 0)); + canvas.FillRect(gfx::Rect(100, 0, 50, 200), SkColorSetRGB(100, 100, 100)); + canvas.FillRect(gfx::Rect(150, 0, 50, 200), SkColorSetRGB(0, 100, 100)); + canvas.FillRect(gfx::Rect(200, 0, 50, 200), SkColorSetRGB(0, 0, 100)); + + gfx::Matrix3F covariance = ComputeColorCovariance(canvas.GetBitmap()); + + gfx::Matrix3F expected_covariance = gfx::Matrix3F::Zeros(); + expected_covariance.set(2400, 400, -1600, + 400, 2400, 400, + -1600, 400, 2400); + EXPECT_EQ(expected_covariance, covariance); +} + +TEST_F(ColorAnalysisTest, ApplyColorReductionSingleColor) { + // The test runs color reduction on a single-colot image, where results are + // bound to be uninteresting. This is an important edge case, though. + SkBitmap source, result; + source.allocN32Pixels(300, 200); + result.allocPixels(SkImageInfo::MakeA8(300, 200)); + + source.eraseARGB(255, 50, 150, 200); + + gfx::Vector3dF transform(1.0f, .5f, 0.1f); + // This transform, if not scaled, should result in GL=145. + EXPECT_TRUE(ApplyColorReduction(source, transform, false, &result)); + + uint8_t min_gl = 0; + uint8_t max_gl = 0; + Calculate8bitBitmapMinMax(result, &min_gl, &max_gl); + EXPECT_EQ(145, min_gl); + EXPECT_EQ(145, max_gl); + + // Now scan requesting rescale. Expect all 0. + EXPECT_TRUE(ApplyColorReduction(source, transform, true, &result)); + Calculate8bitBitmapMinMax(result, &min_gl, &max_gl); + EXPECT_EQ(0, min_gl); + EXPECT_EQ(0, max_gl); + + // Test cliping to upper limit. + transform.set_z(1.1f); + EXPECT_TRUE(ApplyColorReduction(source, transform, false, &result)); + Calculate8bitBitmapMinMax(result, &min_gl, &max_gl); + EXPECT_EQ(0xFF, min_gl); + EXPECT_EQ(0xFF, max_gl); + + // Test cliping to upper limit. + transform.Scale(-1.0f); + EXPECT_TRUE(ApplyColorReduction(source, transform, false, &result)); + Calculate8bitBitmapMinMax(result, &min_gl, &max_gl); + EXPECT_EQ(0x0, min_gl); + EXPECT_EQ(0x0, max_gl); +} + +TEST_F(ColorAnalysisTest, ApplyColorReductionBlackAndWhite) { + // Check with images with multiple colors. This is really different only when + // the result is scaled. + gfx::Canvas canvas(gfx::Size(300, 200), 1.0f, true); + + // The image consists of vertical non-overlapping stripes 150 pixels wide. + canvas.FillRect(gfx::Rect(0, 0, 150, 200), SkColorSetRGB(0, 0, 0)); + canvas.FillRect(gfx::Rect(150, 0, 150, 200), SkColorSetRGB(255, 255, 255)); + SkBitmap source = canvas.GetBitmap(); + SkBitmap result; + result.allocPixels(SkImageInfo::MakeA8(300, 200)); + + gfx::Vector3dF transform(1.0f, 0.5f, 0.1f); + EXPECT_TRUE(ApplyColorReduction(source, transform, true, &result)); + uint8_t min_gl = 0; + uint8_t max_gl = 0; + Calculate8bitBitmapMinMax(result, &min_gl, &max_gl); + + EXPECT_EQ(0, min_gl); + EXPECT_EQ(255, max_gl); + EXPECT_EQ(min_gl, SkColorGetA(result.getColor(0, 0))); + EXPECT_EQ(max_gl, SkColorGetA(result.getColor(299, 199))); + + // Reverse test. + transform.Scale(-1.0f); + EXPECT_TRUE(ApplyColorReduction(source, transform, true, &result)); + min_gl = 0; + max_gl = 0; + Calculate8bitBitmapMinMax(result, &min_gl, &max_gl); + + EXPECT_EQ(0, min_gl); + EXPECT_EQ(255, max_gl); + EXPECT_EQ(max_gl, SkColorGetA(result.getColor(0, 0))); + EXPECT_EQ(min_gl, SkColorGetA(result.getColor(299, 199))); +} + +TEST_F(ColorAnalysisTest, ApplyColorReductionMultiColor) { + // Check with images with multiple colors. This is really different only when + // the result is scaled. + gfx::Canvas canvas(gfx::Size(300, 200), 1.0f, true); + + // The image consists of vertical non-overlapping stripes 100 pixels wide. + canvas.FillRect(gfx::Rect(0, 0, 100, 200), SkColorSetRGB(100, 0, 0)); + canvas.FillRect(gfx::Rect(100, 0, 100, 200), SkColorSetRGB(0, 255, 0)); + canvas.FillRect(gfx::Rect(200, 0, 100, 200), SkColorSetRGB(0, 0, 128)); + SkBitmap source = canvas.GetBitmap(); + SkBitmap result; + result.allocPixels(SkImageInfo::MakeA8(300, 200)); + + gfx::Vector3dF transform(1.0f, 0.5f, 0.1f); + EXPECT_TRUE(ApplyColorReduction(source, transform, false, &result)); + uint8_t min_gl = 0; + uint8_t max_gl = 0; + Calculate8bitBitmapMinMax(result, &min_gl, &max_gl); + EXPECT_EQ(12, min_gl); + EXPECT_EQ(127, max_gl); + EXPECT_EQ(min_gl, SkColorGetA(result.getColor(299, 199))); + EXPECT_EQ(max_gl, SkColorGetA(result.getColor(150, 0))); + EXPECT_EQ(100U, SkColorGetA(result.getColor(0, 0))); + + EXPECT_TRUE(ApplyColorReduction(source, transform, true, &result)); + Calculate8bitBitmapMinMax(result, &min_gl, &max_gl); + EXPECT_EQ(0, min_gl); + EXPECT_EQ(255, max_gl); + EXPECT_EQ(min_gl, SkColorGetA(result.getColor(299, 199))); + EXPECT_EQ(max_gl, SkColorGetA(result.getColor(150, 0))); + EXPECT_EQ(193U, SkColorGetA(result.getColor(0, 0))); +} + +TEST_F(ColorAnalysisTest, ComputeProminentColors) { + LumaRange lumas[] = {LumaRange::DARK, LumaRange::NORMAL, LumaRange::LIGHT}; + SaturationRange saturations[] = {SaturationRange::VIBRANT, + SaturationRange::MUTED}; + std::vector color_profiles; + for (auto s : saturations) { + for (auto l : lumas) + color_profiles.emplace_back(l, s); + } + + // A totally dark gray image, which yields no prominent color as it's too + // close to black. + gfx::Canvas canvas(gfx::Size(300, 200), 1.0f, true); + canvas.FillRect(gfx::Rect(0, 0, 300, 200), SkColorSetRGB(10, 10, 10)); + SkBitmap bitmap = canvas.GetBitmap(); + + // All expectations start at SK_ColorTRANSPARENT (i.e. 0). + std::vector expectations(color_profiles.size(), + Swatch(SK_ColorTRANSPARENT, 0)); + std::vector computations = CalculateProminentColorsOfBitmap( + bitmap, color_profiles, nullptr /* region */, ColorSwatchFilter()); + EXPECT_EQ(expectations, computations); + + // Add a green that could hit a couple values. + const SkColor kVibrantGreen = SkColorSetRGB(25, 200, 25); + canvas.FillRect(gfx::Rect(0, 1, 300, 1), kVibrantGreen); + bitmap = canvas.GetBitmap(); + expectations[0] = Swatch(kVibrantGreen, 60); + expectations[1] = Swatch(kVibrantGreen, 60); + computations = CalculateProminentColorsOfBitmap( + bitmap, color_profiles, nullptr /* region */, ColorSwatchFilter()); + EXPECT_EQ(expectations, computations); + + // Add a stripe of a dark, muted green (saturation .33, luma .29). + const SkColor kDarkGreen = SkColorSetRGB(50, 100, 50); + canvas.FillRect(gfx::Rect(0, 2, 300, 1), kDarkGreen); + bitmap = canvas.GetBitmap(); + expectations[3] = Swatch(kDarkGreen, 60); + computations = CalculateProminentColorsOfBitmap( + bitmap, color_profiles, nullptr /* region */, ColorSwatchFilter()); + EXPECT_EQ(expectations, computations); + + // Now draw a little bit of pure green. That should be closer to the goal for + // normal vibrant, but is out of range for other color profiles. + const SkColor kPureGreen = SkColorSetRGB(0, 255, 0); + canvas.FillRect(gfx::Rect(0, 3, 300, 1), kPureGreen); + bitmap = canvas.GetBitmap(); + expectations[1] = Swatch(kPureGreen, 60); + computations = CalculateProminentColorsOfBitmap( + bitmap, color_profiles, nullptr /* region */, ColorSwatchFilter()); + EXPECT_EQ(expectations, computations); +} + +TEST_F(ColorAnalysisTest, ComputeColorSwatches) { + SkBitmap bitmap; + bitmap.allocN32Pixels(100, 100); + bitmap.eraseColor(SK_ColorMAGENTA); + bitmap.erase(SK_ColorGREEN, {10, 10, 90, 90}); + bitmap.erase(SK_ColorYELLOW, {40, 40, 60, 60}); + + const Swatch kYellowSwatch = Swatch(SK_ColorYELLOW, (20u * 20u)); + const Swatch kGreenSwatch = + Swatch(SK_ColorGREEN, (80u * 80u) - kYellowSwatch.population); + const Swatch kMagentaSwatch = + Swatch(SK_ColorMAGENTA, (100u * 100u) - kGreenSwatch.population - + kYellowSwatch.population); + + { + std::vector colors = + CalculateColorSwatches(bitmap, 10, gfx::Rect(100, 100), absl::nullopt); + EXPECT_EQ(3u, colors.size()); + EXPECT_EQ(kGreenSwatch, colors[0]); + EXPECT_EQ(kMagentaSwatch, colors[1]); + EXPECT_EQ(kYellowSwatch, colors[2]); + } + + { + std::vector colors = CalculateColorSwatches( + bitmap, 10, gfx::Rect(10, 10, 80, 80), absl::nullopt); + EXPECT_EQ(2u, colors.size()); + EXPECT_EQ(kGreenSwatch, colors[0]); + EXPECT_EQ(kYellowSwatch, colors[1]); + } +} + +TEST_F(ColorAnalysisTest, ComputeColorSwatches_Filter) { + SkBitmap bitmap; + bitmap.allocN32Pixels(100, 100); + bitmap.eraseColor(SK_ColorMAGENTA); + bitmap.erase(SK_ColorBLACK, {10, 10, 90, 90}); + bitmap.erase(SK_ColorWHITE, {40, 40, 60, 60}); + + const Swatch kWhiteSwatch = Swatch(SK_ColorWHITE, (20u * 20u)); + const Swatch kBlackSwatch = + Swatch(SK_ColorBLACK, (80u * 80u) - kWhiteSwatch.population); + const Swatch kMagentaSwatch = + Swatch(SK_ColorMAGENTA, + (100u * 100u) - kBlackSwatch.population - kWhiteSwatch.population); + + { + std::vector colors = CalculateColorSwatches( + bitmap, 10, gfx::Rect(100, 100), + base::BindRepeating([](const SkColor& candidate) { + return candidate != SK_ColorBLACK; + })); + EXPECT_EQ(2u, colors.size()); + EXPECT_EQ(kMagentaSwatch, colors[0]); + EXPECT_EQ(kWhiteSwatch, colors[1]); + } + + { + std::vector colors = + CalculateColorSwatches(bitmap, 10, gfx::Rect(100, 100), absl::nullopt); + EXPECT_EQ(3u, colors.size()); + EXPECT_EQ(kBlackSwatch, colors[0]); + EXPECT_EQ(kMagentaSwatch, colors[1]); + EXPECT_EQ(kWhiteSwatch, colors[2]); + } +} + +} // namespace color_utils diff --git a/color_palette.h b/color_palette.h new file mode 100644 index 000000000000..ebf2cdedc555 --- /dev/null +++ b/color_palette.h @@ -0,0 +1,154 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_COLOR_PALETTE_H_ +#define UI_GFX_COLOR_PALETTE_H_ + +#include "third_party/skia/include/core/SkColor.h" + +namespace gfx { + +// A placeholder value for unset colors. This should never be visible and is red +// as a visual flag for misbehaving code. +constexpr SkColor kPlaceholderColor = SK_ColorRED; + +// The number refers to the shade of darkness. Each color in the MD +// palette ranges from 050-900. +constexpr SkColor kGoogleBlue050 = SkColorSetRGB(0xE8, 0xF0, 0xFE); +constexpr SkColor kGoogleBlue100 = SkColorSetRGB(0xD2, 0xE3, 0xFC); +constexpr SkColor kGoogleBlue200 = SkColorSetRGB(0xAE, 0xCB, 0xFA); +constexpr SkColor kGoogleBlue300 = SkColorSetRGB(0x8A, 0xB4, 0xF8); +constexpr SkColor kGoogleBlue400 = SkColorSetRGB(0x66, 0x9D, 0xF6); +constexpr SkColor kGoogleBlue500 = SkColorSetRGB(0x42, 0x85, 0xF4); +constexpr SkColor kGoogleBlue600 = SkColorSetRGB(0x1A, 0x73, 0xE8); +constexpr SkColor kGoogleBlue700 = SkColorSetRGB(0x19, 0x67, 0xD2); +constexpr SkColor kGoogleBlue800 = SkColorSetRGB(0x18, 0x5A, 0xBC); +constexpr SkColor kGoogleBlue900 = SkColorSetRGB(0x17, 0x4E, 0xA6); + +constexpr SkColor kGoogleBlueDark400 = SkColorSetRGB(0x6B, 0xA5, 0xED); +constexpr SkColor kGoogleBlueDark600 = SkColorSetRGB(0x25, 0x81, 0xDF); + +constexpr SkColor kGoogleRed050 = SkColorSetRGB(0xFC, 0xE8, 0xE6); +constexpr SkColor kGoogleRed100 = SkColorSetRGB(0xFA, 0xD2, 0xCF); +constexpr SkColor kGoogleRed200 = SkColorSetRGB(0xF6, 0xAE, 0xA9); +constexpr SkColor kGoogleRed300 = SkColorSetRGB(0xF2, 0x8B, 0x82); +constexpr SkColor kGoogleRed400 = SkColorSetRGB(0xEE, 0x67, 0x5C); +constexpr SkColor kGoogleRed500 = SkColorSetRGB(0xEA, 0x43, 0x35); +constexpr SkColor kGoogleRed600 = SkColorSetRGB(0xD9, 0x30, 0x25); +constexpr SkColor kGoogleRed700 = SkColorSetRGB(0xC5, 0x22, 0x1F); +constexpr SkColor kGoogleRed800 = SkColorSetRGB(0xB3, 0x14, 0x12); +constexpr SkColor kGoogleRed900 = SkColorSetRGB(0xA5, 0x0E, 0x0E); + +constexpr SkColor kGoogleRedDark500 = SkColorSetRGB(0xE6, 0x6A, 0x5E); +constexpr SkColor kGoogleRedDark600 = SkColorSetRGB(0xD3, 0x3B, 0x30); +constexpr SkColor kGoogleRedDark800 = SkColorSetRGB(0xB4, 0x1B, 0x1A); + +constexpr SkColor kGoogleGreen050 = SkColorSetRGB(0xE6, 0xF4, 0xEA); +constexpr SkColor kGoogleGreen100 = SkColorSetRGB(0xCE, 0xEA, 0xD6); +constexpr SkColor kGoogleGreen200 = SkColorSetRGB(0xA8, 0xDA, 0xB5); +constexpr SkColor kGoogleGreen300 = SkColorSetRGB(0x81, 0xC9, 0x95); +constexpr SkColor kGoogleGreen400 = SkColorSetRGB(0x5B, 0xB9, 0x74); +constexpr SkColor kGoogleGreen500 = SkColorSetRGB(0x34, 0xA8, 0x53); +constexpr SkColor kGoogleGreen600 = SkColorSetRGB(0x1E, 0x8E, 0x3E); +constexpr SkColor kGoogleGreen700 = SkColorSetRGB(0x18, 0x80, 0x38); +constexpr SkColor kGoogleGreen800 = SkColorSetRGB(0x13, 0x73, 0x33); +constexpr SkColor kGoogleGreen900 = SkColorSetRGB(0x0D, 0x65, 0x2D); + +constexpr SkColor kGoogleGreenDark500 = SkColorSetRGB(0x41, 0xAF, 0x6A); +constexpr SkColor kGoogleGreenDark600 = SkColorSetRGB(0x28, 0x99, 0x4F); + +constexpr SkColor kGoogleYellow050 = SkColorSetRGB(0xFE, 0xF7, 0xE0); +constexpr SkColor kGoogleYellow100 = SkColorSetRGB(0xFE, 0xEF, 0xC3); +constexpr SkColor kGoogleYellow200 = SkColorSetRGB(0xFD, 0xE2, 0x93); +constexpr SkColor kGoogleYellow300 = SkColorSetRGB(0xFD, 0xD6, 0x63); +constexpr SkColor kGoogleYellow400 = SkColorSetRGB(0xFC, 0xC9, 0x34); +constexpr SkColor kGoogleYellow500 = SkColorSetRGB(0xFB, 0xBC, 0x04); +constexpr SkColor kGoogleYellow600 = SkColorSetRGB(0xF9, 0xAB, 0x00); +constexpr SkColor kGoogleYellow700 = SkColorSetRGB(0xF2, 0x99, 0x00); +constexpr SkColor kGoogleYellow800 = SkColorSetRGB(0xEA, 0x86, 0x00); +constexpr SkColor kGoogleYellow900 = SkColorSetRGB(0xE3, 0x74, 0x00); + +constexpr SkColor kGoogleGrey050 = SkColorSetRGB(0xF8, 0xF9, 0xFA); +constexpr SkColor kGoogleGrey100 = SkColorSetRGB(0xF1, 0xF3, 0xF4); +constexpr SkColor kGoogleGrey200 = SkColorSetRGB(0xE8, 0xEA, 0xED); +constexpr SkColor kGoogleGrey300 = SkColorSetRGB(0xDA, 0xDC, 0xE0); +constexpr SkColor kGoogleGrey400 = SkColorSetRGB(0xBD, 0xC1, 0xC6); +constexpr SkColor kGoogleGrey500 = SkColorSetRGB(0x9A, 0xA0, 0xA6); +constexpr SkColor kGoogleGrey600 = SkColorSetRGB(0x80, 0x86, 0x8B); +constexpr SkColor kGoogleGrey700 = SkColorSetRGB(0x5F, 0x63, 0x68); +constexpr SkColor kGoogleGrey800 = SkColorSetRGB(0x3C, 0x40, 0x43); +constexpr SkColor kGoogleGrey900 = SkColorSetRGB(0x20, 0x21, 0x24); + +constexpr SkColor kGoogleOrange050 = SkColorSetRGB(0xFE, 0xEF, 0xE3); +constexpr SkColor kGoogleOrange100 = SkColorSetRGB(0xFE, 0xDF, 0xC8); +constexpr SkColor kGoogleOrange200 = SkColorSetRGB(0xFD, 0xC6, 0x9C); +constexpr SkColor kGoogleOrange300 = SkColorSetRGB(0xFC, 0xAD, 0x70); +constexpr SkColor kGoogleOrange400 = SkColorSetRGB(0xFA, 0x90, 0x3E); +constexpr SkColor kGoogleOrange500 = SkColorSetRGB(0xFA, 0x7B, 0x17); +constexpr SkColor kGoogleOrange600 = SkColorSetRGB(0xE8, 0x71, 0x0A); +constexpr SkColor kGoogleOrange700 = SkColorSetRGB(0xD5, 0x6E, 0x0C); +constexpr SkColor kGoogleOrange800 = SkColorSetRGB(0xC2, 0x64, 0x01); +constexpr SkColor kGoogleOrange900 = SkColorSetRGB(0xB0, 0x60, 0x00); + +constexpr SkColor kGooglePink050 = SkColorSetRGB(0xFD, 0xE7, 0xF3); +constexpr SkColor kGooglePink100 = SkColorSetRGB(0xFD, 0xCF, 0xE8); +constexpr SkColor kGooglePink200 = SkColorSetRGB(0xFB, 0xA9, 0xD6); +constexpr SkColor kGooglePink300 = SkColorSetRGB(0xFF, 0x8B, 0xCB); +constexpr SkColor kGooglePink400 = SkColorSetRGB(0xFF, 0x63, 0xB8); +constexpr SkColor kGooglePink500 = SkColorSetRGB(0xF4, 0x39, 0xA0); +constexpr SkColor kGooglePink600 = SkColorSetRGB(0xE5, 0x25, 0x92); +constexpr SkColor kGooglePink700 = SkColorSetRGB(0xD0, 0x18, 0x84); +constexpr SkColor kGooglePink800 = SkColorSetRGB(0xB8, 0x06, 0x72); +constexpr SkColor kGooglePink900 = SkColorSetRGB(0x9C, 0x16, 0x6B); + +constexpr SkColor kGooglePurple050 = SkColorSetRGB(0xF3, 0xE8, 0xFD); +constexpr SkColor kGooglePurple100 = SkColorSetRGB(0xE9, 0xD2, 0xFD); +constexpr SkColor kGooglePurple200 = SkColorSetRGB(0xD7, 0xAE, 0xFB); +constexpr SkColor kGooglePurple300 = SkColorSetRGB(0xC5, 0x8A, 0xF9); +constexpr SkColor kGooglePurple400 = SkColorSetRGB(0xAF, 0x5C, 0xF7); +constexpr SkColor kGooglePurple500 = SkColorSetRGB(0xA1, 0x42, 0xF4); +constexpr SkColor kGooglePurple600 = SkColorSetRGB(0x93, 0x34, 0xE6); +constexpr SkColor kGooglePurple700 = SkColorSetRGB(0x84, 0x30, 0xCE); +constexpr SkColor kGooglePurple800 = SkColorSetRGB(0x76, 0x27, 0xBB); +constexpr SkColor kGooglePurple900 = SkColorSetRGB(0x68, 0x1D, 0xA8); + +constexpr SkColor kGoogleCyan050 = SkColorSetRGB(0xE4, 0xF7, 0xFB); +constexpr SkColor kGoogleCyan100 = SkColorSetRGB(0xCB, 0xF0, 0xF8); +constexpr SkColor kGoogleCyan200 = SkColorSetRGB(0xA1, 0xE4, 0xF2); +constexpr SkColor kGoogleCyan300 = SkColorSetRGB(0x78, 0xD9, 0xEC); +constexpr SkColor kGoogleCyan400 = SkColorSetRGB(0x4E, 0xCD, 0xE6); +constexpr SkColor kGoogleCyan500 = SkColorSetRGB(0x24, 0xC1, 0xE0); +constexpr SkColor kGoogleCyan600 = SkColorSetRGB(0x12, 0xB5, 0xCB); +constexpr SkColor kGoogleCyan700 = SkColorSetRGB(0x12, 0x9E, 0xAF); +constexpr SkColor kGoogleCyan800 = SkColorSetRGB(0x09, 0x85, 0x91); +constexpr SkColor kGoogleCyan900 = SkColorSetRGB(0x00, 0x7B, 0x83); + +// The following are the values that correspond to the above kGoogleGreyXXX +// values, which are the opaque colors created from the following alpha values +// applied to kGoogleGrey900 on a white background. +// These values are from the palette of greys specified in the Material Refresh +// spec. +constexpr SkAlpha kGoogleGreyAlpha050 = 0x08; // 3% +constexpr SkAlpha kGoogleGreyAlpha100 = 0x0F; // 6% +constexpr SkAlpha kGoogleGreyAlpha200 = 0x1A; // 10% +constexpr SkAlpha kGoogleGreyAlpha300 = 0x29; // 16% +constexpr SkAlpha kGoogleGreyAlpha400 = 0x47; // 28% +constexpr SkAlpha kGoogleGreyAlpha500 = 0x6E; // 43% +constexpr SkAlpha kGoogleGreyAlpha600 = 0x8C; // 55% +constexpr SkAlpha kGoogleGreyAlpha700 = 0xB5; // 71% +constexpr SkAlpha kGoogleGreyAlpha800 = 0xDB; // 86% + +// kChromeIconGrey is subject to change in the future, kGoogleGrey700 is set in +// stone. If you're semantically looking for "the icon color Chrome uses" then +// use kChromeIconGrey, if you're looking for GG700 grey specifically, use the +// Google-grey constant directly. +constexpr SkColor kChromeIconGrey = kGoogleGrey700; + +// An alpha value for designating a control's disabled state. In specs this is +// sometimes listed as 0.38a. +constexpr SkAlpha kDisabledControlAlpha = 0x61; + +} // namespace gfx + +#endif // UI_GFX_COLOR_PALETTE_H_ diff --git a/color_space.cc b/color_space.cc new file mode 100644 index 000000000000..02a4feec4481 --- /dev/null +++ b/color_space.cc @@ -0,0 +1,1286 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/color_space.h" + +#include +#include +#include +#include + +#include "base/atomic_sequence_num.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/synchronization/lock.h" +#include "third_party/skia/include/core/SkColorSpace.h" +#include "third_party/skia/include/core/SkData.h" +#include "third_party/skia/include/core/SkICC.h" +#include "third_party/skia/include/core/SkImageInfo.h" +#include "ui/gfx/display_color_spaces.h" +#include "ui/gfx/icc_profile.h" +#include "ui/gfx/skia_color_space_util.h" + +namespace gfx { + +namespace { + +static bool IsAlmostZero(float value) { + return std::abs(value) < std::numeric_limits::epsilon(); +} + +static bool FloatsEqualWithinTolerance(const float* a, + const float* b, + int n, + float tol) { + for (int i = 0; i < n; ++i) { + if (std::abs(a[i] - b[i]) > tol) { + return false; + } + } + return true; +} + +skcms_TransferFunction GetPQSkTransferFunction(float sdr_white_level) { + // Note that SkColorSpace doesn't have the notion of an unspecified SDR white + // level. + if (sdr_white_level == 0.f) + sdr_white_level = ColorSpace::kDefaultSDRWhiteLevel; + + // The generic PQ transfer function produces normalized luminance values i.e. + // the range 0-1 represents 0-10000 nits for the reference display, but we + // want to map 1.0 to |sdr_white_level| nits so we need to scale accordingly. + const double w = 10000. / sdr_white_level; + // Distribute scaling factor W by scaling A and B with X ^ (1/F): + // ((A + Bx^C) / (D + Ex^C))^F * W = ((A + Bx^C) / (D + Ex^C) * W^(1/F))^F + // See https://crbug.com/1058580#c32 for discussion. + skcms_TransferFunction fn = SkNamedTransferFn::kPQ; + const double ws = pow(w, 1. / fn.f); + fn.a = ws * fn.a; + fn.b = ws * fn.b; + return fn; +} + +skcms_TransferFunction GetHLGSkTransferFunction(float sdr_white_level) { + // Note that SkColorSpace doesn't have the notion of an unspecified SDR white + // level. + if (sdr_white_level == 0.f) + sdr_white_level = ColorSpace::kDefaultSDRWhiteLevel; + + // The reference white level for HLG is 100 nits. We want to setup the + // returned transfer function such that output values are scaled by the white + // level; Skia uses the |f| transfer function parameter for this. + skcms_TransferFunction fn = SkNamedTransferFn::kHLG; + fn.f = ColorSpace::kDefaultSDRWhiteLevel / sdr_white_level - 1; + return fn; +} + +float GetSDRWhiteLevelFromPQSkTransferFunction( + const skcms_TransferFunction& fn) { + DCHECK_EQ(fn.g, SkNamedTransferFn::kPQ.g); + const double ws_a = static_cast(fn.a) / SkNamedTransferFn::kPQ.a; + const double w_a = pow(ws_a, fn.f); + const double sdr_white_level_a = 10000.0f / w_a; + return sdr_white_level_a; +} + +float GetSDRWhiteLevelFromHLGSkTransferFunction( + const skcms_TransferFunction& fn) { + DCHECK_EQ(fn.g, SkNamedTransferFn::kHLG.g); + if (fn.f == 0) + return ColorSpace::kDefaultSDRWhiteLevel; + return 1.0f / ((fn.f + 1) / ColorSpace::kDefaultSDRWhiteLevel); +} + +bool PrimaryIdContainsSRGB(ColorSpace::PrimaryID id) { + DCHECK(id != ColorSpace::PrimaryID::INVALID && + id != ColorSpace::PrimaryID::CUSTOM); + + switch (id) { + case ColorSpace::PrimaryID::BT709: + case ColorSpace::PrimaryID::BT2020: + case ColorSpace::PrimaryID::SMPTEST428_1: + case ColorSpace::PrimaryID::SMPTEST431_2: + case ColorSpace::PrimaryID::SMPTEST432_1: + case ColorSpace::PrimaryID::XYZ_D50: + case ColorSpace::PrimaryID::ADOBE_RGB: + case ColorSpace::PrimaryID::WIDE_GAMUT_COLOR_SPIN: + return true; + default: + return false; + } +} + +} // namespace + +// static +constexpr float ColorSpace::kDefaultSDRWhiteLevel; + +ColorSpace::ColorSpace(PrimaryID primaries, + TransferID transfer, + MatrixID matrix, + RangeID range, + const skcms_Matrix3x3* custom_primary_matrix, + const skcms_TransferFunction* custom_transfer_fn) + : primaries_(primaries), + transfer_(transfer), + matrix_(matrix), + range_(range) { + if (custom_primary_matrix) { + DCHECK_EQ(PrimaryID::CUSTOM, primaries_); + SetCustomPrimaries(*custom_primary_matrix); + } + if (custom_transfer_fn) + SetCustomTransferFunction(*custom_transfer_fn); +} + +ColorSpace::ColorSpace(const SkColorSpace& sk_color_space) + : ColorSpace(PrimaryID::INVALID, + TransferID::INVALID, + MatrixID::RGB, + RangeID::FULL) { + skcms_TransferFunction fn; + if (sk_color_space.isNumericalTransferFn(&fn)) { + transfer_ = TransferID::CUSTOM; + SetCustomTransferFunction(fn); + } else if (skcms_TransferFunction_isHLGish(&fn)) { + transfer_ = TransferID::ARIB_STD_B67; + transfer_params_[0] = GetSDRWhiteLevelFromHLGSkTransferFunction(fn); + } else if (skcms_TransferFunction_isPQish(&fn)) { + transfer_ = TransferID::SMPTEST2084; + transfer_params_[0] = GetSDRWhiteLevelFromPQSkTransferFunction(fn); + } else { + // Construct an invalid result: Unable to extract necessary parameters + return; + } + + skcms_Matrix3x3 to_XYZD50; + if (!sk_color_space.toXYZD50(&to_XYZD50)) { + // Construct an invalid result: Unable to extract necessary parameters + return; + } + SetCustomPrimaries(to_XYZD50); +} + +bool ColorSpace::IsValid() const { + return primaries_ != PrimaryID::INVALID && transfer_ != TransferID::INVALID && + matrix_ != MatrixID::INVALID && range_ != RangeID::INVALID; +} + +// static +ColorSpace ColorSpace::CreateSCRGBLinear(float sdr_white_level) { + skcms_TransferFunction fn = {0}; + fn.g = 1.0f; + fn.a = kDefaultScrgbLinearSdrWhiteLevel / sdr_white_level; + return ColorSpace(PrimaryID::BT709, TransferID::CUSTOM_HDR, MatrixID::RGB, + RangeID::FULL, nullptr, &fn); +} + +// static +ColorSpace ColorSpace::CreateHDR10(float sdr_white_level) { + ColorSpace result(PrimaryID::BT2020, TransferID::SMPTEST2084, MatrixID::RGB, + RangeID::FULL); + result.transfer_params_[0] = sdr_white_level; + return result; +} + +// static +ColorSpace ColorSpace::CreateHLG() { + return ColorSpace(PrimaryID::BT2020, TransferID::ARIB_STD_B67, MatrixID::RGB, + RangeID::FULL); +} + +// static +ColorSpace ColorSpace::CreatePiecewiseHDR( + PrimaryID primaries, + float sdr_joint, + float hdr_level, + const skcms_Matrix3x3* custom_primary_matrix) { + // If |sdr_joint| is 1, then this is just sRGB (and so |hdr_level| must be 1). + // An |sdr_joint| higher than 1 breaks. + DCHECK_LE(sdr_joint, 1.f); + if (sdr_joint == 1.f) + DCHECK_EQ(hdr_level, 1.f); + // An |hdr_level| of 1 has no HDR. An |hdr_level| less than 1 breaks. + DCHECK_GE(hdr_level, 1.f); + ColorSpace result(primaries, TransferID::PIECEWISE_HDR, MatrixID::RGB, + RangeID::FULL, custom_primary_matrix, nullptr); + result.transfer_params_[0] = sdr_joint; + result.transfer_params_[1] = hdr_level; + return result; +} + +// static +ColorSpace ColorSpace::CreateCustom(const skcms_Matrix3x3& to_XYZD50, + const skcms_TransferFunction& fn) { + ColorSpace result(ColorSpace::PrimaryID::CUSTOM, + ColorSpace::TransferID::CUSTOM, ColorSpace::MatrixID::RGB, + ColorSpace::RangeID::FULL, &to_XYZD50, &fn); + return result; +} + +// static +ColorSpace ColorSpace::CreateCustom(const skcms_Matrix3x3& to_XYZD50, + TransferID transfer) { + ColorSpace result(ColorSpace::PrimaryID::CUSTOM, transfer, + ColorSpace::MatrixID::RGB, ColorSpace::RangeID::FULL, + &to_XYZD50, nullptr); + return result; +} + +void ColorSpace::SetCustomPrimaries(const skcms_Matrix3x3& to_XYZD50) { + const PrimaryID kIDsToCheck[] = { + PrimaryID::BT709, + PrimaryID::BT470M, + PrimaryID::BT470BG, + PrimaryID::SMPTE170M, + PrimaryID::SMPTE240M, + PrimaryID::FILM, + PrimaryID::BT2020, + PrimaryID::SMPTEST428_1, + PrimaryID::SMPTEST431_2, + PrimaryID::SMPTEST432_1, + PrimaryID::XYZ_D50, + PrimaryID::ADOBE_RGB, + PrimaryID::APPLE_GENERIC_RGB, + PrimaryID::WIDE_GAMUT_COLOR_SPIN, + }; + for (PrimaryID id : kIDsToCheck) { + skcms_Matrix3x3 matrix; + GetPrimaryMatrix(id, &matrix); + if (FloatsEqualWithinTolerance(&to_XYZD50.vals[0][0], &matrix.vals[0][0], 9, + 0.001f)) { + primaries_ = id; + return; + } + } + + memcpy(custom_primary_matrix_, &to_XYZD50, 9 * sizeof(float)); + primaries_ = PrimaryID::CUSTOM; +} + +void ColorSpace::SetCustomTransferFunction(const skcms_TransferFunction& fn) { + DCHECK(transfer_ == TransferID::CUSTOM || + transfer_ == TransferID::CUSTOM_HDR); + // These are all TransferIDs that will return a transfer function from + // GetTransferFunction. When multiple ids map to the same function, this list + // prioritizes the most common name (eg IEC61966_2_1). This applies only to + // SDR transfer functions. + if (transfer_ == TransferID::CUSTOM) { + const TransferID kIDsToCheck[] = { + TransferID::IEC61966_2_1, TransferID::LINEAR, + TransferID::GAMMA18, TransferID::GAMMA22, + TransferID::GAMMA24, TransferID::GAMMA28, + TransferID::SMPTE240M, TransferID::BT709_APPLE, + TransferID::SMPTEST428_1, + }; + for (TransferID id : kIDsToCheck) { + skcms_TransferFunction id_fn; + GetTransferFunction(id, &id_fn); + if (FloatsEqualWithinTolerance(&fn.g, &id_fn.g, 7, 0.001f)) { + transfer_ = id; + return; + } + } + } + transfer_params_[0] = fn.a; + transfer_params_[1] = fn.b; + transfer_params_[2] = fn.c; + transfer_params_[3] = fn.d; + transfer_params_[4] = fn.e; + transfer_params_[5] = fn.f; + transfer_params_[6] = fn.g; +} + +// static +size_t ColorSpace::TransferParamCount(TransferID transfer) { + switch (transfer) { + case TransferID::CUSTOM: + return 7; + case TransferID::CUSTOM_HDR: + return 7; + case TransferID::PIECEWISE_HDR: + return 2; + case TransferID::SMPTEST2084: + case TransferID::ARIB_STD_B67: + return 1; + default: + return 0; + } +} + +bool ColorSpace::operator==(const ColorSpace& other) const { + if (primaries_ != other.primaries_ || transfer_ != other.transfer_ || + matrix_ != other.matrix_ || range_ != other.range_) { + return false; + } + if (primaries_ == PrimaryID::CUSTOM) { + if (memcmp(custom_primary_matrix_, other.custom_primary_matrix_, + sizeof(custom_primary_matrix_))) { + return false; + } + } + if (size_t param_count = TransferParamCount(transfer_)) { + if (memcmp(transfer_params_, other.transfer_params_, + param_count * sizeof(float))) { + return false; + } + } + return true; +} + +bool ColorSpace::IsWide() const { + // These HDR transfer functions are always wide + if (transfer_ == TransferID::IEC61966_2_1_HDR || + transfer_ == TransferID::LINEAR_HDR || + transfer_ == TransferID::CUSTOM_HDR) + return true; + + if (primaries_ == PrimaryID::BT2020 || + primaries_ == PrimaryID::SMPTEST431_2 || + primaries_ == PrimaryID::SMPTEST432_1 || + primaries_ == PrimaryID::ADOBE_RGB || + primaries_ == PrimaryID::WIDE_GAMUT_COLOR_SPIN || + // TODO(cblume/ccameron): Compute if the custom primaries actually are + // wide. For now, assume so. + primaries_ == PrimaryID::CUSTOM) + return true; + + return false; +} + +bool ColorSpace::IsHDR() const { + return transfer_ == TransferID::SMPTEST2084 || + transfer_ == TransferID::ARIB_STD_B67 || + transfer_ == TransferID::LINEAR_HDR || + transfer_ == TransferID::IEC61966_2_1_HDR || + transfer_ == TransferID::CUSTOM_HDR || + transfer_ == TransferID::PIECEWISE_HDR; +} + +bool ColorSpace::FullRangeEncodedValues() const { + return transfer_ == TransferID::LINEAR_HDR || + transfer_ == TransferID::IEC61966_2_1_HDR || + transfer_ == TransferID::CUSTOM_HDR || + transfer_ == TransferID::PIECEWISE_HDR || + transfer_ == TransferID::BT1361_ECG || + transfer_ == TransferID::IEC61966_2_4; +} + +bool ColorSpace::operator!=(const ColorSpace& other) const { + return !(*this == other); +} + +bool ColorSpace::operator<(const ColorSpace& other) const { + if (primaries_ < other.primaries_) + return true; + if (primaries_ > other.primaries_) + return false; + if (transfer_ < other.transfer_) + return true; + if (transfer_ > other.transfer_) + return false; + if (matrix_ < other.matrix_) + return true; + if (matrix_ > other.matrix_) + return false; + if (range_ < other.range_) + return true; + if (range_ > other.range_) + return false; + if (primaries_ == PrimaryID::CUSTOM) { + int primary_result = + memcmp(custom_primary_matrix_, other.custom_primary_matrix_, + sizeof(custom_primary_matrix_)); + if (primary_result < 0) + return true; + if (primary_result > 0) + return false; + } + if (size_t param_count = TransferParamCount(transfer_)) { + int transfer_result = memcmp(transfer_params_, other.transfer_params_, + param_count * sizeof(float)); + if (transfer_result < 0) + return true; + if (transfer_result > 0) + return false; + } + return false; +} + +size_t ColorSpace::GetHash() const { + size_t result = (static_cast(primaries_) << 0) | + (static_cast(transfer_) << 8) | + (static_cast(matrix_) << 16) | + (static_cast(range_) << 24); + if (primaries_ == PrimaryID::CUSTOM) { + const uint32_t* params = + reinterpret_cast(custom_primary_matrix_); + result ^= params[0]; + result ^= params[4]; + result ^= params[8]; + } + { + // Note that |transfer_params_| must be zero when they are unused. + const uint32_t* params = + reinterpret_cast(transfer_params_); + result ^= params[3]; + result ^= params[6]; + } + return result; +} + +#define PRINT_ENUM_CASE(TYPE, NAME) \ + case TYPE::NAME: \ + ss << #NAME; \ + break; + +std::string ColorSpace::ToString() const { + std::stringstream ss; + ss << std::fixed << std::setprecision(4); + if (primaries_ != PrimaryID::CUSTOM) + ss << "{primaries:"; + switch (primaries_) { + PRINT_ENUM_CASE(PrimaryID, INVALID) + PRINT_ENUM_CASE(PrimaryID, BT709) + PRINT_ENUM_CASE(PrimaryID, BT470M) + PRINT_ENUM_CASE(PrimaryID, BT470BG) + PRINT_ENUM_CASE(PrimaryID, SMPTE170M) + PRINT_ENUM_CASE(PrimaryID, SMPTE240M) + PRINT_ENUM_CASE(PrimaryID, FILM) + PRINT_ENUM_CASE(PrimaryID, BT2020) + PRINT_ENUM_CASE(PrimaryID, SMPTEST428_1) + PRINT_ENUM_CASE(PrimaryID, SMPTEST431_2) + PRINT_ENUM_CASE(PrimaryID, SMPTEST432_1) + PRINT_ENUM_CASE(PrimaryID, XYZ_D50) + PRINT_ENUM_CASE(PrimaryID, ADOBE_RGB) + PRINT_ENUM_CASE(PrimaryID, APPLE_GENERIC_RGB) + PRINT_ENUM_CASE(PrimaryID, WIDE_GAMUT_COLOR_SPIN) + case PrimaryID::CUSTOM: + // |custom_primary_matrix_| is in row-major order. + const float sum_R = custom_primary_matrix_[0] + + custom_primary_matrix_[3] + custom_primary_matrix_[6]; + const float sum_G = custom_primary_matrix_[1] + + custom_primary_matrix_[4] + custom_primary_matrix_[7]; + const float sum_B = custom_primary_matrix_[2] + + custom_primary_matrix_[5] + custom_primary_matrix_[8]; + if (IsAlmostZero(sum_R) || IsAlmostZero(sum_G) || IsAlmostZero(sum_B)) + break; + + ss << "{primaries_d50_referred: [[" << (custom_primary_matrix_[0] / sum_R) + << ", " << (custom_primary_matrix_[3] / sum_R) << "], " + << " [" << (custom_primary_matrix_[1] / sum_G) << ", " + << (custom_primary_matrix_[4] / sum_G) << "], " + << " [" << (custom_primary_matrix_[2] / sum_B) << ", " + << (custom_primary_matrix_[5] / sum_B) << "]]"; + break; + } + ss << ", transfer:"; + switch (transfer_) { + PRINT_ENUM_CASE(TransferID, INVALID) + PRINT_ENUM_CASE(TransferID, BT709) + PRINT_ENUM_CASE(TransferID, BT709_APPLE) + PRINT_ENUM_CASE(TransferID, GAMMA18) + PRINT_ENUM_CASE(TransferID, GAMMA22) + PRINT_ENUM_CASE(TransferID, GAMMA24) + PRINT_ENUM_CASE(TransferID, GAMMA28) + PRINT_ENUM_CASE(TransferID, SMPTE170M) + PRINT_ENUM_CASE(TransferID, SMPTE240M) + PRINT_ENUM_CASE(TransferID, LINEAR) + PRINT_ENUM_CASE(TransferID, LOG) + PRINT_ENUM_CASE(TransferID, LOG_SQRT) + PRINT_ENUM_CASE(TransferID, IEC61966_2_4) + PRINT_ENUM_CASE(TransferID, BT1361_ECG) + PRINT_ENUM_CASE(TransferID, IEC61966_2_1) + PRINT_ENUM_CASE(TransferID, BT2020_10) + PRINT_ENUM_CASE(TransferID, BT2020_12) + PRINT_ENUM_CASE(TransferID, SMPTEST428_1) + PRINT_ENUM_CASE(TransferID, IEC61966_2_1_HDR) + PRINT_ENUM_CASE(TransferID, LINEAR_HDR) + case TransferID::ARIB_STD_B67: + ss << "HLG (SDR white point "; + if (transfer_params_[0] == 0.f) + ss << "default " << kDefaultSDRWhiteLevel; + else + ss << transfer_params_[0]; + ss << " nits)"; + break; + case TransferID::SMPTEST2084: + ss << "PQ (SDR white point "; + if (transfer_params_[0] == 0.f) + ss << "default " << kDefaultSDRWhiteLevel; + else + ss << transfer_params_[0]; + ss << " nits)"; + break; + case TransferID::CUSTOM: { + skcms_TransferFunction fn; + GetTransferFunction(&fn); + ss << fn.c << "*x + " << fn.f << " if x < " << fn.d << " else (" << fn.a + << "*x + " << fn.b << ")**" << fn.g << " + " << fn.e; + break; + } + case TransferID::CUSTOM_HDR: { + skcms_TransferFunction fn; + GetTransferFunction(&fn); + if (fn.g == 1.0f && fn.a > 0.0f && fn.b == 0.0f && fn.c == 0.0f && + fn.d == 0.0f && fn.e == 0.0f && fn.f == 0.0f) { + ss << "LINEAR_HDR (slope " << fn.a << ", SDR white point " + << kDefaultScrgbLinearSdrWhiteLevel / fn.a << " nits)"; + break; + } + ss << fn.c << "*x + " << fn.f << " if |x| < " << fn.d << " else sign(x)*(" + << fn.a << "*|x| + " << fn.b << ")**" << fn.g << " + " << fn.e; + break; + } + case TransferID::PIECEWISE_HDR: { + skcms_TransferFunction fn; + GetTransferFunction(&fn); + ss << "sRGB to 1 at " << transfer_params_[0] << ", linear to " + << transfer_params_[1] << " at 1"; + break; + } + } + ss << ", matrix:"; + switch (matrix_) { + PRINT_ENUM_CASE(MatrixID, INVALID) + PRINT_ENUM_CASE(MatrixID, RGB) + PRINT_ENUM_CASE(MatrixID, BT709) + PRINT_ENUM_CASE(MatrixID, FCC) + PRINT_ENUM_CASE(MatrixID, BT470BG) + PRINT_ENUM_CASE(MatrixID, SMPTE170M) + PRINT_ENUM_CASE(MatrixID, SMPTE240M) + PRINT_ENUM_CASE(MatrixID, YCOCG) + PRINT_ENUM_CASE(MatrixID, BT2020_NCL) + PRINT_ENUM_CASE(MatrixID, BT2020_CL) + PRINT_ENUM_CASE(MatrixID, YDZDX) + PRINT_ENUM_CASE(MatrixID, GBR) + } + ss << ", range:"; + switch (range_) { + PRINT_ENUM_CASE(RangeID, INVALID) + PRINT_ENUM_CASE(RangeID, LIMITED) + PRINT_ENUM_CASE(RangeID, FULL) + PRINT_ENUM_CASE(RangeID, DERIVED) + } + ss << "}"; + return ss.str(); +} + +#undef PRINT_ENUM_CASE + +ColorSpace ColorSpace::GetAsFullRangeRGB() const { + ColorSpace result(*this); + if (!IsValid()) + return result; + result.matrix_ = MatrixID::RGB; + result.range_ = RangeID::FULL; + return result; +} + +ContentColorUsage ColorSpace::GetContentColorUsage() const { + if (IsHDR()) + return ContentColorUsage::kHDR; + if (IsWide()) + return ContentColorUsage::kWideColorGamut; + return ContentColorUsage::kSRGB; +} + +ColorSpace ColorSpace::GetAsRGB() const { + ColorSpace result(*this); + if (IsValid()) + result.matrix_ = MatrixID::RGB; + return result; +} + +ColorSpace ColorSpace::GetScaledColorSpace(float factor) const { + ColorSpace result(*this); + skcms_Matrix3x3 to_XYZD50; + GetPrimaryMatrix(&to_XYZD50); + for (int row = 0; row < 3; ++row) { + for (int col = 0; col < 3; ++col) { + to_XYZD50.vals[row][col] *= factor; + } + } + result.SetCustomPrimaries(to_XYZD50); + return result; +} + +bool ColorSpace::IsSuitableForBlending() const { + switch (transfer_) { + case TransferID::SMPTEST2084: + // PQ is not an acceptable space to do blending in -- blending 0 and 1 + // evenly will get a result of sRGB 0.259 (instead of 0.5). + return false; + case TransferID::ARIB_STD_B67: + case TransferID::LINEAR_HDR: + // If the color space is nearly-linear, then it is not suitable for + // blending -- blending 0 and 1 evenly will get a result of sRGB 0.735 + // (instead of 0.5). + return false; + case TransferID::CUSTOM_HDR: { + // A gamma close enough to linear is treated as linear. + skcms_TransferFunction fn; + if (GetTransferFunction(&fn)) { + constexpr float kMinGamma = 1.25; + if (fn.g < kMinGamma) + return false; + } + break; + } + default: + break; + } + return true; +} + +ColorSpace ColorSpace::GetWithMatrixAndRange(MatrixID matrix, + RangeID range) const { + ColorSpace result(*this); + if (!IsValid()) + return result; + + result.matrix_ = matrix; + result.range_ = range; + return result; +} + +ColorSpace ColorSpace::GetWithSDRWhiteLevel(float sdr_white_level) const { + ColorSpace result = *this; + if (transfer_ == TransferID::SMPTEST2084 || + transfer_ == TransferID::ARIB_STD_B67) { + result.transfer_params_[0] = sdr_white_level; + } else if (transfer_ == TransferID::LINEAR_HDR) { + result.transfer_ = TransferID::CUSTOM_HDR; + skcms_TransferFunction fn = {0}; + fn.g = 1.f; + fn.a = kDefaultScrgbLinearSdrWhiteLevel / sdr_white_level; + result.SetCustomTransferFunction(fn); + } + return result; +} + +sk_sp ColorSpace::ToSkColorSpace() const { + // Unspecified color spaces correspond to the null SkColorSpace. + if (!IsValid()) + return nullptr; + + // Handle only full-range RGB spaces. + if (matrix_ != MatrixID::RGB) { + DLOG(ERROR) << "Not creating non-RGB SkColorSpace"; + return nullptr; + } + if (range_ != RangeID::FULL) { + DLOG(ERROR) << "Not creating non-full-range SkColorSpace"; + return nullptr; + } + + // Use the named SRGB and linear-SRGB instead of the generic constructors. + if (primaries_ == PrimaryID::BT709) { + if (transfer_ == TransferID::IEC61966_2_1) + return SkColorSpace::MakeSRGB(); + if (transfer_ == TransferID::LINEAR || transfer_ == TransferID::LINEAR_HDR) + return SkColorSpace::MakeSRGBLinear(); + } + + skcms_TransferFunction transfer_fn = SkNamedTransferFn::kSRGB; + switch (transfer_) { + case TransferID::IEC61966_2_1: + break; + case TransferID::LINEAR: + case TransferID::LINEAR_HDR: + transfer_fn = SkNamedTransferFn::kLinear; + break; + case TransferID::ARIB_STD_B67: + transfer_fn = GetHLGSkTransferFunction(transfer_params_[0]); + break; + case TransferID::SMPTEST2084: + transfer_fn = GetPQSkTransferFunction(transfer_params_[0]); + break; + default: + if (!GetTransferFunction(&transfer_fn)) { + DLOG(ERROR) << "Failed to get transfer function for SkColorSpace"; + return nullptr; + } + break; + } + skcms_Matrix3x3 gamut = SkNamedGamut::kSRGB; + switch (primaries_) { + case PrimaryID::BT709: + break; + case PrimaryID::ADOBE_RGB: + gamut = SkNamedGamut::kAdobeRGB; + break; + case PrimaryID::SMPTEST432_1: + gamut = SkNamedGamut::kDisplayP3; + break; + case PrimaryID::BT2020: + gamut = SkNamedGamut::kRec2020; + break; + default: + GetPrimaryMatrix(&gamut); + break; + } + + sk_sp sk_color_space = + SkColorSpace::MakeRGB(transfer_fn, gamut); + if (!sk_color_space) + DLOG(ERROR) << "SkColorSpace::MakeRGB failed."; + + return sk_color_space; +} + +const struct _GLcolorSpace* ColorSpace::AsGLColorSpace() const { + return reinterpret_cast(this); +} + +ColorSpace::PrimaryID ColorSpace::GetPrimaryID() const { + return primaries_; +} + +ColorSpace::TransferID ColorSpace::GetTransferID() const { + return transfer_; +} + +ColorSpace::MatrixID ColorSpace::GetMatrixID() const { + return matrix_; +} + +ColorSpace::RangeID ColorSpace::GetRangeID() const { + return range_; +} + +bool ColorSpace::HasExtendedSkTransferFn() const { + return transfer_ == TransferID::LINEAR_HDR || + transfer_ == TransferID::IEC61966_2_1_HDR; +} + +bool ColorSpace::Contains(const ColorSpace& other) const { + if (primaries_ == PrimaryID::INVALID || + other.primaries_ == PrimaryID::INVALID) + return false; + + // Contains() is commonly used to check if a color space contains sRGB. The + // computation can be bypassed for known primary IDs. + if (primaries_ != PrimaryID::CUSTOM && other.primaries_ == PrimaryID::BT709) + return PrimaryIdContainsSRGB(primaries_); + + // |matrix| is the primary transform matrix from |other| to this color space. + skcms_Matrix3x3 other_to_xyz; + skcms_Matrix3x3 this_to_xyz; + skcms_Matrix3x3 xyz_to_this; + other.GetPrimaryMatrix(&other_to_xyz); + GetPrimaryMatrix(&this_to_xyz); + skcms_Matrix3x3_invert(&this_to_xyz, &xyz_to_this); + skcms_Matrix3x3 matrix = skcms_Matrix3x3_concat(&xyz_to_this, &other_to_xyz); + + // Return true iff each primary is in the range [0, 1] after transforming. + // Transforming a primary vector by |matrix| always results in a column of + // |matrix|. So the multiplication can be skipped, and we can just check if + // each value in the matrix is in the range [0, 1]. + constexpr float epsilon = 0.001f; + for (int r = 0; r < 3; r++) { + for (int c = 0; c < 3; c++) { + if (matrix.vals[r][c] < -epsilon || matrix.vals[r][c] > 1 + epsilon) + return false; + } + } + return true; +} + +// static +void ColorSpace::GetPrimaryMatrix(PrimaryID primary_id, + skcms_Matrix3x3* to_XYZD50) { + SkColorSpacePrimaries primaries = {0}; + switch (primary_id) { + case ColorSpace::PrimaryID::CUSTOM: + case ColorSpace::PrimaryID::INVALID: + *to_XYZD50 = SkNamedGamut::kXYZ; // Identity + return; + + case ColorSpace::PrimaryID::BT709: + // BT709 is our default case. Put it after the switch just + // in case we somehow get an id which is not listed in the switch. + // (We don't want to use "default", because we want the compiler + // to tell us if we forgot some enum values.) + primaries.fRX = 0.640f; + primaries.fRY = 0.330f; + primaries.fGX = 0.300f; + primaries.fGY = 0.600f; + primaries.fBX = 0.150f; + primaries.fBY = 0.060f; + primaries.fWX = 0.3127f; + primaries.fWY = 0.3290f; + break; + + case ColorSpace::PrimaryID::BT470M: + primaries.fRX = 0.67f; + primaries.fRY = 0.33f; + primaries.fGX = 0.21f; + primaries.fGY = 0.71f; + primaries.fBX = 0.14f; + primaries.fBY = 0.08f; + primaries.fWX = 0.31f; + primaries.fWY = 0.316f; + break; + + case ColorSpace::PrimaryID::BT470BG: + primaries.fRX = 0.64f; + primaries.fRY = 0.33f; + primaries.fGX = 0.29f; + primaries.fGY = 0.60f; + primaries.fBX = 0.15f; + primaries.fBY = 0.06f; + primaries.fWX = 0.3127f; + primaries.fWY = 0.3290f; + break; + + case ColorSpace::PrimaryID::SMPTE170M: + case ColorSpace::PrimaryID::SMPTE240M: + primaries.fRX = 0.630f; + primaries.fRY = 0.340f; + primaries.fGX = 0.310f; + primaries.fGY = 0.595f; + primaries.fBX = 0.155f; + primaries.fBY = 0.070f; + primaries.fWX = 0.3127f; + primaries.fWY = 0.3290f; + break; + + case ColorSpace::PrimaryID::APPLE_GENERIC_RGB: + primaries.fRX = 0.63002f; + primaries.fRY = 0.34000f; + primaries.fGX = 0.29505f; + primaries.fGY = 0.60498f; + primaries.fBX = 0.15501f; + primaries.fBY = 0.07701f; + primaries.fWX = 0.3127f; + primaries.fWY = 0.3290f; + break; + + case ColorSpace::PrimaryID::WIDE_GAMUT_COLOR_SPIN: + primaries.fRX = 0.01f; + primaries.fRY = 0.98f; + primaries.fGX = 0.01f; + primaries.fGY = 0.01f; + primaries.fBX = 0.98f; + primaries.fBY = 0.01f; + primaries.fWX = 0.3127f; + primaries.fWY = 0.3290f; + break; + + case ColorSpace::PrimaryID::FILM: + primaries.fRX = 0.681f; + primaries.fRY = 0.319f; + primaries.fGX = 0.243f; + primaries.fGY = 0.692f; + primaries.fBX = 0.145f; + primaries.fBY = 0.049f; + primaries.fWX = 0.310f; + primaries.fWY = 0.136f; + break; + + case ColorSpace::PrimaryID::BT2020: + primaries.fRX = 0.708f; + primaries.fRY = 0.292f; + primaries.fGX = 0.170f; + primaries.fGY = 0.797f; + primaries.fBX = 0.131f; + primaries.fBY = 0.046f; + primaries.fWX = 0.3127f; + primaries.fWY = 0.3290f; + break; + + case ColorSpace::PrimaryID::SMPTEST428_1: + primaries.fRX = 1.0f; + primaries.fRY = 0.0f; + primaries.fGX = 0.0f; + primaries.fGY = 1.0f; + primaries.fBX = 0.0f; + primaries.fBY = 0.0f; + primaries.fWX = 1.0f / 3.0f; + primaries.fWY = 1.0f / 3.0f; + break; + + case ColorSpace::PrimaryID::SMPTEST431_2: + primaries.fRX = 0.680f; + primaries.fRY = 0.320f; + primaries.fGX = 0.265f; + primaries.fGY = 0.690f; + primaries.fBX = 0.150f; + primaries.fBY = 0.060f; + primaries.fWX = 0.314f; + primaries.fWY = 0.351f; + break; + + case ColorSpace::PrimaryID::SMPTEST432_1: + primaries.fRX = 0.680f; + primaries.fRY = 0.320f; + primaries.fGX = 0.265f; + primaries.fGY = 0.690f; + primaries.fBX = 0.150f; + primaries.fBY = 0.060f; + primaries.fWX = 0.3127f; + primaries.fWY = 0.3290f; + break; + + case ColorSpace::PrimaryID::XYZ_D50: + primaries.fRX = 1.0f; + primaries.fRY = 0.0f; + primaries.fGX = 0.0f; + primaries.fGY = 1.0f; + primaries.fBX = 0.0f; + primaries.fBY = 0.0f; + primaries.fWX = 0.34567f; + primaries.fWY = 0.35850f; + break; + + case ColorSpace::PrimaryID::ADOBE_RGB: + primaries.fRX = 0.6400f; + primaries.fRY = 0.3300f; + primaries.fGX = 0.2100f; + primaries.fGY = 0.7100f; + primaries.fBX = 0.1500f; + primaries.fBY = 0.0600f; + primaries.fWX = 0.3127f; + primaries.fWY = 0.3290f; + break; + } + primaries.toXYZD50(to_XYZD50); +} + +void ColorSpace::GetPrimaryMatrix(skcms_Matrix3x3* to_XYZD50) const { + if (primaries_ == PrimaryID::CUSTOM) { + memcpy(to_XYZD50, custom_primary_matrix_, 9 * sizeof(float)); + } else { + GetPrimaryMatrix(primaries_, to_XYZD50); + } +} + +void ColorSpace::GetPrimaryMatrix(skia::Matrix44* to_XYZD50) const { + skcms_Matrix3x3 toXYZ_3x3; + GetPrimaryMatrix(&toXYZ_3x3); + to_XYZD50->set3x3RowMajorf(&toXYZ_3x3.vals[0][0]); +} + +// static +bool ColorSpace::GetTransferFunction(TransferID transfer, + skcms_TransferFunction* fn) { + // Default to F(x) = pow(x, 1) + fn->a = 1; + fn->b = 0; + fn->c = 0; + fn->d = 0; + fn->e = 0; + fn->f = 0; + fn->g = 1; + + switch (transfer) { + case ColorSpace::TransferID::LINEAR: + case ColorSpace::TransferID::LINEAR_HDR: + return true; + case ColorSpace::TransferID::GAMMA18: + fn->g = 1.801f; + return true; + case ColorSpace::TransferID::GAMMA22: + fn->g = 2.2f; + return true; + case ColorSpace::TransferID::GAMMA24: + fn->g = 2.4f; + return true; + case ColorSpace::TransferID::GAMMA28: + fn->g = 2.8f; + return true; + case ColorSpace::TransferID::SMPTE240M: + fn->a = 0.899626676224f; + fn->b = 0.100373323776f; + fn->c = 0.250000000000f; + fn->d = 0.091286342118f; + fn->g = 2.222222222222f; + return true; + case ColorSpace::TransferID::BT709: + case ColorSpace::TransferID::SMPTE170M: + case ColorSpace::TransferID::BT2020_10: + case ColorSpace::TransferID::BT2020_12: + // With respect to rendering BT709 + // * SMPTE 1886 suggests that we should be using gamma 2.4. + // * Most displays actually use a gamma of 2.2, and most media playing + // software uses the sRGB transfer function. + // * User studies shows that users don't really care. + // * Apple's CoreVideo uses gamma=1.961. + // Bearing all of that in mind, use the same transfer function as sRGB, + // which will allow more optimization, and will more closely match other + // media players. + case ColorSpace::TransferID::IEC61966_2_1: + case ColorSpace::TransferID::IEC61966_2_1_HDR: + fn->a = 0.947867345704f; + fn->b = 0.052132654296f; + fn->c = 0.077399380805f; + fn->d = 0.040449937172f; + fn->g = 2.400000000000f; + return true; + case ColorSpace::TransferID::BT709_APPLE: + fn->g = 1.961000000000f; + return true; + case ColorSpace::TransferID::SMPTEST428_1: + fn->a = 1.034080527699f; // (52.37 / 48.0) ^ (1.0 / 2.6) per ITU-T H.273. + fn->g = 2.600000000000f; + return true; + case ColorSpace::TransferID::IEC61966_2_4: + // This could potentially be represented the same as IEC61966_2_1, but + // it handles negative values differently. + break; + case ColorSpace::TransferID::ARIB_STD_B67: + case ColorSpace::TransferID::BT1361_ECG: + case ColorSpace::TransferID::LOG: + case ColorSpace::TransferID::LOG_SQRT: + case ColorSpace::TransferID::SMPTEST2084: + case ColorSpace::TransferID::CUSTOM: + case ColorSpace::TransferID::CUSTOM_HDR: + case ColorSpace::TransferID::PIECEWISE_HDR: + case ColorSpace::TransferID::INVALID: + break; + } + + return false; +} + +bool ColorSpace::GetTransferFunction(skcms_TransferFunction* fn) const { + if (transfer_ == TransferID::CUSTOM || transfer_ == TransferID::CUSTOM_HDR) { + fn->a = transfer_params_[0]; + fn->b = transfer_params_[1]; + fn->c = transfer_params_[2]; + fn->d = transfer_params_[3]; + fn->e = transfer_params_[4]; + fn->f = transfer_params_[5]; + fn->g = transfer_params_[6]; + return true; + } else { + return GetTransferFunction(transfer_, fn); + } +} + +bool ColorSpace::GetInverseTransferFunction(skcms_TransferFunction* fn) const { + if (!GetTransferFunction(fn)) + return false; + *fn = SkTransferFnInverse(*fn); + return true; +} + +bool ColorSpace::GetSDRWhiteLevel(float* sdr_white_level) const { + if (transfer_ != TransferID::SMPTEST2084 && + transfer_ != TransferID::ARIB_STD_B67) { + return false; + } + if (transfer_params_[0] == 0.0f) + *sdr_white_level = kDefaultSDRWhiteLevel; + else + *sdr_white_level = transfer_params_[0]; + return true; +} + +bool ColorSpace::GetPiecewiseHDRParams(float* sdr_joint, + float* hdr_level) const { + if (transfer_ != TransferID::PIECEWISE_HDR) + return false; + *sdr_joint = transfer_params_[0]; + *hdr_level = transfer_params_[1]; + return true; +} + +void ColorSpace::GetTransferMatrix(int bit_depth, + skia::Matrix44* matrix) const { + DCHECK_GE(bit_depth, 8); + // If chroma samples are real numbers in the range of −0.5 to 0.5, an offset + // of 0.5 is added to get real numbers in the range of 0 to 1. When + // represented as an unsigned |bit_depth|-bit integer, this 0.5 offset is + // approximated by 1 << (bit_depth - 1). chroma_0_5 is this approximate value + // converted to a real number in the range of 0 to 1. + // + // TODO(wtc): For now chroma_0_5 is only used for YCgCo. It should also be + // used for YUV. + const float chroma_0_5 = + static_cast(1 << (bit_depth - 1)) / ((1 << bit_depth) - 1); + float Kr = 0; + float Kb = 0; + switch (matrix_) { + case ColorSpace::MatrixID::RGB: + case ColorSpace::MatrixID::INVALID: + matrix->setIdentity(); + return; + + case ColorSpace::MatrixID::BT709: + Kr = 0.2126f; + Kb = 0.0722f; + break; + + case ColorSpace::MatrixID::FCC: + Kr = 0.30f; + Kb = 0.11f; + break; + + case ColorSpace::MatrixID::BT470BG: + case ColorSpace::MatrixID::SMPTE170M: + Kr = 0.299f; + Kb = 0.114f; + break; + + case ColorSpace::MatrixID::SMPTE240M: + Kr = 0.212f; + Kb = 0.087f; + break; + + case ColorSpace::MatrixID::YCOCG: { + float data[16] = {0.25f, 0.5f, 0.25f, 0.0f, // Y + -0.25f, 0.5f, -0.25f, chroma_0_5, // Cg + 0.5f, 0.0f, -0.5f, chroma_0_5, // Co + 0.0f, 0.0f, 0.0f, 1.0f}; + matrix->setRowMajorf(data); + return; + } + + // BT2020_CL is a special case. + // Basically we return a matrix that transforms RYB values + // to YUV values. (Note that the green component have been replaced + // with the luminance.) + case ColorSpace::MatrixID::BT2020_CL: { + Kr = 0.2627f; + Kb = 0.0593f; + float data[16] = {1.0f, 0.0f, 0.0f, 0.0f, // R + Kr, 1.0f - Kr - Kb, Kb, 0.0f, // Y + 0.0f, 0.0f, 1.0f, 0.0f, // B + 0.0f, 0.0f, 0.0f, 1.0f}; + matrix->setRowMajorf(data); + return; + } + + case ColorSpace::MatrixID::BT2020_NCL: + Kr = 0.2627f; + Kb = 0.0593f; + break; + + case ColorSpace::MatrixID::YDZDX: { + // clang-format off + float data[16] = { + 0.0f, 1.0f, 0.0f, 0.0f, // Y + 0.0f, -0.5f, 0.986566f / 2.0f, 0.5f, // DX or DZ + 0.5f, -0.991902f / 2.0f, 0.0f, 0.5f, // DZ or DX + 0.0f, 0.0f, 0.0f, 1.0f, + }; + // clang-format on + matrix->setRowMajorf(data); + return; + } + case ColorSpace::MatrixID::GBR: { + float data[16] = {0.0f, 1.0f, 0.0f, 0.0f, // G + 0.0f, 0.0f, 1.0f, 0.0f, // B + 1.0f, 0.0f, 0.0f, 0.0f, // R + 0.0f, 0.0f, 0.0f, 1.0f}; + matrix->setRowMajorf(data); + return; + } + } + float Kg = 1.0f - Kr - Kb; + float u_m = 0.5f / (1.0f - Kb); + float v_m = 0.5f / (1.0f - Kr); + // clang-format off + float data[16] = { + Kr, Kg, Kb, 0.0f, // Y + u_m * -Kr, u_m * -Kg, u_m * (1.0f - Kb), 0.5f, // U + v_m * (1.0f - Kr), v_m * -Kg, v_m * -Kb, 0.5f, // V + 0.0f, 0.0f, 0.0f, 1.0f, + }; + // clang-format on + matrix->setRowMajorf(data); +} + +void ColorSpace::GetRangeAdjustMatrix(int bit_depth, + skia::Matrix44* matrix) const { + DCHECK_GE(bit_depth, 8); + switch (range_) { + case RangeID::FULL: + case RangeID::INVALID: + matrix->setIdentity(); + return; + + case RangeID::DERIVED: + case RangeID::LIMITED: + break; + } + + // See ITU-T H.273 (2016), Section 8.3. The following is derived from + // Equations 20-31. + const int shift = bit_depth - 8; + const float a_y = 219 << shift; + const float c = (1 << bit_depth) - 1; + const float scale_y = c / a_y; + switch (matrix_) { + case MatrixID::RGB: + case MatrixID::GBR: + case MatrixID::INVALID: + case MatrixID::YCOCG: { + matrix->setScale(scale_y, scale_y, scale_y); + matrix->postTranslate(-16.0f / 219.0f, -16.0f / 219.0f, -16.0f / 219.0f); + break; + } + + case MatrixID::BT709: + case MatrixID::FCC: + case MatrixID::BT470BG: + case MatrixID::SMPTE170M: + case MatrixID::SMPTE240M: + case MatrixID::BT2020_NCL: + case MatrixID::BT2020_CL: + case MatrixID::YDZDX: { + const float a_uv = 224 << shift; + const float scale_uv = c / a_uv; + const float translate_uv = (a_uv - c) / (2.0f * a_uv); + matrix->setScale(scale_y, scale_uv, scale_uv); + matrix->postTranslate(-16.0f / 219.0f, translate_uv, translate_uv); + break; + } + } +} + +bool ColorSpace::ToSkYUVColorSpace(int bit_depth, SkYUVColorSpace* out) const { + switch (matrix_) { + case MatrixID::BT709: + *out = range_ == RangeID::FULL ? kRec709_Full_SkYUVColorSpace + : kRec709_Limited_SkYUVColorSpace; + return true; + + case MatrixID::BT470BG: + case MatrixID::SMPTE170M: + *out = range_ == RangeID::FULL ? kJPEG_SkYUVColorSpace + : kRec601_Limited_SkYUVColorSpace; + return true; + + case MatrixID::BT2020_NCL: + if (bit_depth == 8) { + *out = range_ == RangeID::FULL ? kBT2020_8bit_Full_SkYUVColorSpace + : kBT2020_8bit_Limited_SkYUVColorSpace; + return true; + } + if (bit_depth == 10) { + *out = range_ == RangeID::FULL ? kBT2020_10bit_Full_SkYUVColorSpace + : kBT2020_10bit_Limited_SkYUVColorSpace; + return true; + } + if (bit_depth == 12) { + *out = range_ == RangeID::FULL ? kBT2020_12bit_Full_SkYUVColorSpace + : kBT2020_12bit_Limited_SkYUVColorSpace; + return true; + } + return false; + + default: + break; + } + return false; +} + +std::ostream& operator<<(std::ostream& out, const ColorSpace& color_space) { + return out << color_space.ToString(); +} + +} // namespace gfx diff --git a/color_space.h b/color_space.h new file mode 100644 index 000000000000..4c3f068c8687 --- /dev/null +++ b/color_space.h @@ -0,0 +1,403 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_COLOR_SPACE_H_ +#define UI_GFX_COLOR_SPACE_H_ + +#include + +#include +#include + +#include "base/gtest_prod_util.h" +#include "build/build_config.h" +#include "third_party/skia/include/core/SkRefCnt.h" +#include "ui/gfx/color_space_export.h" + +struct skcms_Matrix3x3; +struct skcms_TransferFunction; +class SkColorSpace; +enum SkYUVColorSpace : int; + +namespace skia { +class Matrix44; +} // namespace skia + +// These forward declarations are used to give IPC code friend access to private +// fields of gfx::ColorSpace for the purpose of serialization and +// deserialization. +namespace IPC { +template +struct ParamTraits; +} // namespace IPC + +namespace mojo { +template +struct StructTraits; +} // namespace mojo + +// Used to serialize a gfx::ColorSpace through the GPU command buffer. +struct _GLcolorSpace; + +namespace gfx { + +enum class ContentColorUsage : uint8_t; + +namespace mojom { +class ColorSpaceDataView; +} // namespace mojom + +// Used to represet a color space for the purpose of color conversion. +// This is designed to be safe and compact enough to send over IPC +// between any processes. +class COLOR_SPACE_EXPORT ColorSpace { + public: + enum class PrimaryID : uint8_t { + INVALID, + BT709, + BT470M, + BT470BG, + SMPTE170M, + SMPTE240M, + FILM, + BT2020, + SMPTEST428_1, + SMPTEST431_2, + SMPTEST432_1, + XYZ_D50, + ADOBE_RGB, + // Corresponds the the primaries of the "Generic RGB" profile used in the + // Apple ColorSync application, used by layout tests on Mac. + APPLE_GENERIC_RGB, + // A very wide gamut space with rotated primaries. Used by layout tests. + WIDE_GAMUT_COLOR_SPIN, + // Primaries defined by the primary matrix |custom_primary_matrix_|. + CUSTOM, + kMaxValue = CUSTOM, + }; + + enum class TransferID : uint8_t { + INVALID, + BT709, + // On macOS, BT709 hardware decoded video frames, when displayed as + // overlays, will have a transfer function of gamma=1.961. + BT709_APPLE, + GAMMA18, + GAMMA22, + GAMMA24, + GAMMA28, + SMPTE170M, + SMPTE240M, + LINEAR, + LOG, + LOG_SQRT, + IEC61966_2_4, + BT1361_ECG, + IEC61966_2_1, + BT2020_10, + BT2020_12, + SMPTEST2084, + SMPTEST428_1, + ARIB_STD_B67, // AKA hybrid-log gamma, HLG. + // The same as IEC61966_2_1 on the interval [0, 1], with the nonlinear + // segment continuing beyond 1 and point symmetry defining values below 0. + IEC61966_2_1_HDR, + // The same as LINEAR but is defined for all real values. + LINEAR_HDR, + // A parametric transfer function defined by |transfer_params_|. + CUSTOM, + // An HDR parametric transfer function defined by |transfer_params_|. + CUSTOM_HDR, + // An HDR transfer function that is piecewise sRGB, and piecewise linear. + PIECEWISE_HDR, + kMaxValue = PIECEWISE_HDR, + }; + + enum class MatrixID : uint8_t { + INVALID, + RGB, + BT709, + FCC, + BT470BG, + SMPTE170M, + SMPTE240M, + YCOCG, + BT2020_NCL, + BT2020_CL, + YDZDX, + GBR, + kMaxValue = GBR, + }; + + enum class RangeID : uint8_t { + INVALID, + // Limited Rec. 709 color range with RGB values ranging from 16 to 235. + LIMITED, + // Full RGB color range with RGB valees from 0 to 255. + FULL, + // Range is defined by TransferID/MatrixID. + DERIVED, + kMaxValue = DERIVED, + }; + + constexpr ColorSpace() {} + constexpr ColorSpace(PrimaryID primaries, TransferID transfer) + : ColorSpace(primaries, transfer, MatrixID::RGB, RangeID::FULL) {} + constexpr ColorSpace(PrimaryID primaries, + TransferID transfer, + MatrixID matrix, + RangeID range) + : primaries_(primaries), + transfer_(transfer), + matrix_(matrix), + range_(range) {} + ColorSpace(PrimaryID primaries, + TransferID transfer, + MatrixID matrix, + RangeID range, + const skcms_Matrix3x3* custom_primary_matrix, + const skcms_TransferFunction* cunstom_transfer_fn); + + explicit ColorSpace(const SkColorSpace& sk_color_space); + + // Returns true if this is not the default-constructor object. + bool IsValid() const; + + static constexpr ColorSpace CreateSRGB() { + return ColorSpace(PrimaryID::BT709, TransferID::IEC61966_2_1, MatrixID::RGB, + RangeID::FULL); + } + + static constexpr ColorSpace CreateDisplayP3D65() { + return ColorSpace(PrimaryID::SMPTEST432_1, TransferID::IEC61966_2_1, + MatrixID::RGB, RangeID::FULL); + } + static ColorSpace CreateCustom(const skcms_Matrix3x3& to_XYZD50, + const skcms_TransferFunction& fn); + static ColorSpace CreateCustom(const skcms_Matrix3x3& to_XYZD50, + TransferID transfer); + static constexpr ColorSpace CreateXYZD50() { + return ColorSpace(PrimaryID::XYZ_D50, TransferID::LINEAR, MatrixID::RGB, + RangeID::FULL); + } + + // Extended sRGB matches sRGB for values in [0, 1], and extends the transfer + // function to all real values. + static constexpr ColorSpace CreateExtendedSRGB() { + return ColorSpace(PrimaryID::BT709, TransferID::IEC61966_2_1_HDR, + MatrixID::RGB, RangeID::FULL); + } + + // scRGB uses the same primaries as sRGB but has a linear transfer function + // for all real values, and a white point of kDefaultScrgbLinearSdrWhiteLevel. + static constexpr ColorSpace CreateSCRGBLinear() { + return ColorSpace(PrimaryID::BT709, TransferID::LINEAR_HDR, MatrixID::RGB, + RangeID::FULL); + } + // Allows specifying a custom SDR white level. Only used on Windows. + static ColorSpace CreateSCRGBLinear(float sdr_white_level); + + // HDR10 uses BT.2020 primaries with SMPTE ST 2084 PQ transfer function. + static constexpr ColorSpace CreateHDR10() { + return ColorSpace(PrimaryID::BT2020, TransferID::SMPTEST2084, MatrixID::RGB, + RangeID::FULL); + } + // Allows specifying a custom SDR white level. Only used on Windows. + static ColorSpace CreateHDR10(float sdr_white_level); + + // HLG uses the BT.2020 primaries with the ARIB_STD_B67 transfer function. + static ColorSpace CreateHLG(); + + // Create a piecewise-HDR color space. + // - If |primaries| is CUSTOM, then |custom_primary_matrix| must be + // non-nullptr. + // - The SDR joint is the encoded pixel value where the SDR portion reaches 1, + // usually 0.25 or 0.5, corresponding to giving 8 or 9 of 10 bits to SDR. + // This must be in the open interval (0, 1). + // - The HDR level the value that the transfer function will evaluate to at 1, + // and represents the maximum HDR brightness relative to the maximum SDR + // brightness. This must be strictly greater than 1. + static ColorSpace CreatePiecewiseHDR( + PrimaryID primaries, + float sdr_joint, + float hdr_level, + const skcms_Matrix3x3* custom_primary_matrix = nullptr); + + // TODO(ccameron): Remove these, and replace with more generic constructors. + static constexpr ColorSpace CreateJpeg() { + // TODO(ccameron): Determine which primaries and transfer function were + // intended here. + return ColorSpace(PrimaryID::BT709, TransferID::IEC61966_2_1, + MatrixID::SMPTE170M, RangeID::FULL); + } + static constexpr ColorSpace CreateREC601() { + return ColorSpace(PrimaryID::SMPTE170M, TransferID::SMPTE170M, + MatrixID::SMPTE170M, RangeID::LIMITED); + } + static constexpr ColorSpace CreateREC709() { + return ColorSpace(PrimaryID::BT709, TransferID::BT709, MatrixID::BT709, + RangeID::LIMITED); + } + + // On macOS and on ChromeOS, sRGB's (1,1,1) always coincides with PQ's 100 + // nits (which may not be 100 physical nits). On Windows, sRGB's (1,1,1) + // maps to scRGB linear's (1,1,1) when the SDR white level is set to 80 nits. + // See also kDefaultScrgbLinearSdrWhiteLevel. + static constexpr float kDefaultSDRWhiteLevel = 100.f; + + // The default white level in nits for scRGB linear color space. On Windows, + // sRGB's (1,1,1) maps to scRGB linear's (1,1,1) when the SDR white level is + // set to 80 nits. On Mac and ChromeOS, sRGB's (1,1,1) maps to PQ's 100 nits. + // Using a platform specific value here satisfies both constraints. +#if defined(OS_WIN) + static constexpr float kDefaultScrgbLinearSdrWhiteLevel = 80.0f; +#else + static constexpr float kDefaultScrgbLinearSdrWhiteLevel = + kDefaultSDRWhiteLevel; +#endif // OS_WIN + + bool operator==(const ColorSpace& other) const; + bool operator!=(const ColorSpace& other) const; + bool operator<(const ColorSpace& other) const; + size_t GetHash() const; + std::string ToString() const; + + bool IsWide() const; + + // Returns true if the transfer function is an HDR one (SMPTE 2084, HLG, etc). + bool IsHDR() const; + + // Returns true if the encoded values can be outside of the 0.0-1.0 range. + bool FullRangeEncodedValues() const; + + // Returns the color space's content color usage category (sRGB, WCG, or HDR). + ContentColorUsage GetContentColorUsage() const; + + // Return this color space with any YUV to RGB conversion stripped off. + ColorSpace GetAsRGB() const; + + // Return this color space with any range adjust or YUV to RGB conversion + // stripped off. + ColorSpace GetAsFullRangeRGB() const; + + // Return a color space where all values are bigger/smaller by the given + // factor. If you convert colors from SRGB to SRGB.GetScaledColorSpace(2.0) + // everything will be half as bright in linear lumens. + ColorSpace GetScaledColorSpace(float factor) const; + + // Return true if blending in |this| is close enough to blending in sRGB to + // be considered acceptable (only PQ and nearly-linear transfer functions + // return false). + bool IsSuitableForBlending() const; + + // Return a combined color space with has the same primary and transfer than + // the caller but replacing the matrix and range with the given values. + ColorSpace GetWithMatrixAndRange(MatrixID matrix, RangeID range) const; + + // If this color space has a PQ or scRGB linear transfer function, then return + // |this| with its SDR white level set to |sdr_white_level|. Otherwise return + // |this| unmodified. + ColorSpace GetWithSDRWhiteLevel(float sdr_white_level) const; + + // This will return nullptr for non-RGB spaces, spaces with non-FULL + // range, and unspecified spaces. + sk_sp ToSkColorSpace() const; + + // Return a GLcolorSpace value that is valid for the lifetime of |this|. This + // function is used to serialize ColorSpace objects across the GPU command + // buffer. + const _GLcolorSpace* AsGLColorSpace() const; + + // For YUV color spaces, return the closest SkYUVColorSpace. Returns true if a + // close match is found. Otherwise, leaves *out unchanged and returns false. + // If |matrix_id| is MatrixID::BT2020_NCL and |bit_depth| is provided, a bit + // depth appropriate SkYUVColorSpace will be provided. + bool ToSkYUVColorSpace(int bit_depth, SkYUVColorSpace* out) const; + bool ToSkYUVColorSpace(SkYUVColorSpace* out) const { + return ToSkYUVColorSpace(kDefaultBitDepth, out); + } + + void GetPrimaryMatrix(skcms_Matrix3x3* to_XYZD50) const; + void GetPrimaryMatrix(skia::Matrix44* to_XYZD50) const; + bool GetTransferFunction(skcms_TransferFunction* fn) const; + bool GetInverseTransferFunction(skcms_TransferFunction* fn) const; + + // Returns the SDR white level specified for the PQ or HLG transfer functions. + // If no value was specified, then use kDefaultSDRWhiteLevel. If the transfer + // function is not PQ then return false. + bool GetSDRWhiteLevel(float* sdr_white_level) const; + + // Returns the parameters for a PIECEWISE_HDR transfer function. See + // CreatePiecewiseHDR for parameter meanings. + bool GetPiecewiseHDRParams(float* sdr_point, float* hdr_level) const; + + // Returns the transfer matrix for |bit_depth|. For most formats, this is the + // RGB to YUV matrix. + void GetTransferMatrix(int bit_depth, skia::Matrix44* matrix) const; + + // Returns the range adjust matrix that converts from |range_| to full range + // for |bit_depth|. + void GetRangeAdjustMatrix(int bit_depth, skia::Matrix44* matrix) const; + + // Returns the current primary ID. + // Note: if SetCustomPrimaries() has been used, the primary ID returned + // may have been set to PrimaryID::CUSTOM, or been coerced to another + // PrimaryID if it was very close. + PrimaryID GetPrimaryID() const; + + // Returns the current transfer ID. + TransferID GetTransferID() const; + + // Returns the current matrix ID. + MatrixID GetMatrixID() const; + + // Returns the current range ID. + RangeID GetRangeID() const; + + // Returns true if the transfer function is defined by an + // skcms_TransferFunction which is extended to all real values. + bool HasExtendedSkTransferFn() const; + + // Returns true if each color in |other| can be expressed in this color space. + bool Contains(const ColorSpace& other) const; + + private: + // The default bit depth assumed by ToSkYUVColorSpace(). + static constexpr int kDefaultBitDepth = 8; + + static void GetPrimaryMatrix(PrimaryID, skcms_Matrix3x3* to_XYZD50); + static bool GetTransferFunction(TransferID, skcms_TransferFunction* fn); + static size_t TransferParamCount(TransferID); + + void SetCustomTransferFunction(const skcms_TransferFunction& fn); + void SetCustomPrimaries(const skcms_Matrix3x3& to_XYZD50); + + PrimaryID primaries_ = PrimaryID::INVALID; + TransferID transfer_ = TransferID::INVALID; + MatrixID matrix_ = MatrixID::INVALID; + RangeID range_ = RangeID::INVALID; + + // Only used if primaries_ is PrimaryID::CUSTOM. + float custom_primary_matrix_[9] = {0, 0, 0, 0, 0, 0, 0, 0}; + + // Parameters for the transfer function. The interpretation depends on + // |transfer_|. Only TransferParamCount() of these parameters are used, all + // others must be zero. + // - CUSTOM and CUSTOM_HDR: Entries A through G of the skcms_TransferFunction + // structure in alphabetical order. + // - SMPTEST2084: SDR white point. + float transfer_params_[7] = {0, 0, 0, 0, 0, 0, 0}; + + friend struct IPC::ParamTraits; + friend struct mojo::StructTraits; +}; + +// Stream operator so ColorSpace can be used in assertion statements. +COLOR_SPACE_EXPORT std::ostream& operator<<(std::ostream& out, + const ColorSpace& color_space); + +} // namespace gfx + +#endif // UI_GFX_COLOR_SPACE_H_ diff --git a/color_space_export.h b/color_space_export.h new file mode 100644 index 000000000000..a0402b73cf07 --- /dev/null +++ b/color_space_export.h @@ -0,0 +1,29 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_COLOR_SPACE_EXPORT_H_ +#define UI_GFX_COLOR_SPACE_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(COLOR_SPACE_IMPLEMENTATION) +#define COLOR_SPACE_EXPORT __declspec(dllexport) +#else +#define COLOR_SPACE_EXPORT __declspec(dllimport) +#endif // defined(COLOR_SPACE_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(COLOR_SPACE_IMPLEMENTATION) +#define COLOR_SPACE_EXPORT __attribute__((visibility("default"))) +#else +#define COLOR_SPACE_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define COLOR_SPACE_EXPORT +#endif + +#endif // UI_GFX_COLOR_SPACE_EXPORT_H_ diff --git a/color_space_unittest.cc b/color_space_unittest.cc new file mode 100644 index 000000000000..2fcff5faedec --- /dev/null +++ b/color_space_unittest.cc @@ -0,0 +1,435 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include + +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/color_space.h" +#include "ui/gfx/skia_color_space_util.h" + +namespace gfx { +namespace { + +// Returns the L-infty difference of u and v. +float Diff(const skia::Vector4& u, const skia::Vector4& v) { + float result = 0; + for (size_t i = 0; i < 4; ++i) + result = std::max(result, std::abs(u.fData[i] - v.fData[i])); + return result; +} + +TEST(ColorSpace, RGBToYUV) { + const float kEpsilon = 1.0e-3f; + const size_t kNumTestRGBs = 3; + skia::Vector4 test_rgbs[kNumTestRGBs] = { + skia::Vector4(1.f, 0.f, 0.f, 1.f), + skia::Vector4(0.f, 1.f, 0.f, 1.f), + skia::Vector4(0.f, 0.f, 1.f, 1.f), + }; + + const size_t kNumColorSpaces = 4; + gfx::ColorSpace color_spaces[kNumColorSpaces] = { + gfx::ColorSpace::CreateREC601(), + gfx::ColorSpace::CreateREC709(), + gfx::ColorSpace::CreateJpeg(), + gfx::ColorSpace::CreateXYZD50(), + }; + + skia::Vector4 expected_yuvs[kNumColorSpaces][kNumTestRGBs] = { + // REC601 + { + skia::Vector4(0.3195f, 0.3518f, 0.9392f, 1.0000f), + skia::Vector4(0.5669f, 0.2090f, 0.1322f, 1.0000f), + skia::Vector4(0.1607f, 0.9392f, 0.4286f, 1.0000f), + }, + // REC709 + { + skia::Vector4(0.2453f, 0.3994f, 0.9392f, 1.0000f), + skia::Vector4(0.6770f, 0.1614f, 0.1011f, 1.0000f), + skia::Vector4(0.1248f, 0.9392f, 0.4597f, 1.0000f), + }, + // Jpeg + { + skia::Vector4(0.2990f, 0.3313f, 1.0000f, 1.0000f), + skia::Vector4(0.5870f, 0.1687f, 0.0813f, 1.0000f), + skia::Vector4(0.1140f, 1.0000f, 0.4187f, 1.0000f), + }, + // XYZD50 + { + skia::Vector4(1.0000f, 0.0000f, 0.0000f, 1.0000f), + skia::Vector4(0.0000f, 1.0000f, 0.0000f, 1.0000f), + skia::Vector4(0.0000f, 0.0000f, 1.0000f, 1.0000f), + }, + }; + + for (size_t i = 0; i < kNumColorSpaces; ++i) { + skia::Matrix44 transfer; + color_spaces[i].GetTransferMatrix(/*bit_depth=*/8, &transfer); + + skia::Matrix44 range_adjust; + color_spaces[i].GetRangeAdjustMatrix(/*bit_depth=*/8, &range_adjust); + + skia::Matrix44 range_adjust_inv; + range_adjust.invert(&range_adjust_inv); + + for (size_t j = 0; j < kNumTestRGBs; ++j) { + skia::Vector4 yuv = range_adjust_inv * transfer * test_rgbs[j]; + EXPECT_LT(Diff(yuv, expected_yuvs[i][j]), kEpsilon); + } + } +} + +TEST(ColorSpace, RangeAdjust) { + const float kEpsilon = 1.0e-3f; + const size_t kNumTestYUVs = 2; + skia::Vector4 test_yuvs[kNumTestYUVs] = { + skia::Vector4(1.f, 1.f, 1.f, 1.f), + skia::Vector4(0.f, 0.f, 0.f, 1.f), + }; + + const size_t kNumBitDepths = 3; + int bit_depths[kNumBitDepths] = {8, 10, 12}; + + const size_t kNumColorSpaces = 3; + ColorSpace color_spaces[kNumColorSpaces] = { + ColorSpace::CreateREC601(), + ColorSpace::CreateJpeg(), + ColorSpace(ColorSpace::PrimaryID::INVALID, + ColorSpace::TransferID::INVALID, ColorSpace::MatrixID::YCOCG, + ColorSpace::RangeID::LIMITED), + }; + + skia::Vector4 expected_yuvs[kNumColorSpaces][kNumBitDepths][kNumTestYUVs] = { + // REC601 + { + // 8bpc + { + skia::Vector4(235.f / 255.f, 239.5f / 255.f, 239.5f / 255.f, + 1.0000f), + skia::Vector4(16.f / 255.f, 15.5f / 255.f, 15.5f / 255.f, + 1.0000f), + }, + // 10bpc + { + skia::Vector4(940.f / 1023.f, 959.5f / 1023.f, 959.5f / 1023.f, + 1.0000f), + skia::Vector4(64.f / 1023.f, 63.5f / 1023.f, 63.5f / 1023.f, + 1.0000f), + }, + // 12bpc + { + skia::Vector4(3760.f / 4095.f, 3839.5f / 4095.f, 3839.5f / 4095.f, + 1.0000f), + skia::Vector4(256.f / 4095.f, 255.5f / 4095.f, 255.5f / 4095.f, + 1.0000f), + }, + }, + // Jpeg + { + // 8bpc + { + skia::Vector4(1.0000f, 1.0000f, 1.0000f, 1.0000f), + skia::Vector4(0.0000f, 0.0000f, 0.0000f, 1.0000f), + }, + // 10bpc + { + skia::Vector4(1.0000f, 1.0000f, 1.0000f, 1.0000f), + skia::Vector4(0.0000f, 0.0000f, 0.0000f, 1.0000f), + }, + // 12bpc + { + skia::Vector4(1.0000f, 1.0000f, 1.0000f, 1.0000f), + skia::Vector4(0.0000f, 0.0000f, 0.0000f, 1.0000f), + }, + }, + // YCoCg + { + // 8bpc + { + skia::Vector4(235.f / 255.f, 235.f / 255.f, 235.f / 255.f, + 1.0000f), + skia::Vector4(16.f / 255.f, 16.f / 255.f, 16.f / 255.f, 1.0000f), + }, + // 10bpc + { + skia::Vector4(940.f / 1023.f, 940.f / 1023.f, 940.f / 1023.f, + 1.0000f), + skia::Vector4(64.f / 1023.f, 64.f / 1023.f, 64.f / 1023.f, + 1.0000f), + }, + // 12bpc + { + skia::Vector4(3760.f / 4095.f, 3760.f / 4095.f, 3760.f / 4095.f, + 1.0000f), + skia::Vector4(256.f / 4095.f, 256.f / 4095.f, 256.f / 4095.f, + 1.0000f), + }, + }, + }; + + for (size_t i = 0; i < kNumColorSpaces; ++i) { + for (size_t j = 0; j < kNumBitDepths; ++j) { + skia::Matrix44 range_adjust; + color_spaces[i].GetRangeAdjustMatrix(bit_depths[j], &range_adjust); + + skia::Matrix44 range_adjust_inv; + range_adjust.invert(&range_adjust_inv); + + for (size_t k = 0; k < kNumTestYUVs; ++k) { + skia::Vector4 yuv = range_adjust_inv * test_yuvs[k]; + EXPECT_LT(Diff(yuv, expected_yuvs[i][j][k]), kEpsilon); + } + } + } +} + +TEST(ColorSpace, Blending) { + ColorSpace display_color_space; + + // A linear transfer function being used for HDR should be blended using an + // sRGB-like transfer function. + display_color_space = ColorSpace::CreateSCRGBLinear(); + EXPECT_FALSE(display_color_space.IsSuitableForBlending()); + + // If not used for HDR, a linear transfer function should be left unchanged. + display_color_space = ColorSpace::CreateXYZD50(); + EXPECT_TRUE(display_color_space.IsSuitableForBlending()); +} + +TEST(ColorSpace, ConversionToAndFromSkColorSpace) { + const size_t kNumTests = 5; + skcms_Matrix3x3 primary_matrix = {{ + {0.205276f, 0.625671f, 0.060867f}, + {0.149185f, 0.063217f, 0.744553f}, + {0.609741f, 0.311111f, 0.019470f}, + }}; + skcms_TransferFunction transfer_fn = {2.1f, 1.f, 0.f, 0.f, 0.f, 0.f, 0.f}; + + ColorSpace color_spaces[kNumTests] = { + ColorSpace(ColorSpace::PrimaryID::BT709, + ColorSpace::TransferID::IEC61966_2_1), + ColorSpace(ColorSpace::PrimaryID::ADOBE_RGB, + ColorSpace::TransferID::IEC61966_2_1), + ColorSpace(ColorSpace::PrimaryID::SMPTEST432_1, + ColorSpace::TransferID::LINEAR), + ColorSpace(ColorSpace::PrimaryID::BT2020, + ColorSpace::TransferID::IEC61966_2_1), + ColorSpace::CreateCustom(primary_matrix, transfer_fn), + }; + sk_sp sk_color_spaces[kNumTests] = { + SkColorSpace::MakeSRGB(), + SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kAdobeRGB), + SkColorSpace::MakeRGB(SkNamedTransferFn::kLinear, + SkNamedGamut::kDisplayP3), + SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kRec2020), + SkColorSpace::MakeRGB(transfer_fn, primary_matrix), + }; + + // Test that converting from ColorSpace to SkColorSpace is producing an + // equivalent representation. + for (size_t i = 0; i < kNumTests; ++i) { + EXPECT_TRUE(SkColorSpace::Equals(color_spaces[i].ToSkColorSpace().get(), + sk_color_spaces[i].get())) + << " on iteration i = " << i; + } + + // Invariant test: Test that converting a SkColorSpace to a ColorSpace is + // producing an equivalent representation; and then converting the converted + // ColorSpace back to SkColorSpace is also producing an equivalent + // representation. + for (size_t i = 0; i < kNumTests; ++i) { + const ColorSpace from_sk_color_space(*sk_color_spaces[i]); + EXPECT_EQ(color_spaces[i], from_sk_color_space); + EXPECT_TRUE(SkColorSpace::Equals( + sk_color_spaces[i].get(), from_sk_color_space.ToSkColorSpace().get())); + } +} + +TEST(ColorSpace, PQToSkColorSpace) { + ColorSpace color_space; + ColorSpace roundtrip_color_space; + float roundtrip_sdr_white_level; + const float kEpsilon = 1.e-5f; + + // We expect that when a white point is specified, the conversion from + // ColorSpace -> SkColorSpace -> ColorSpace be the identity. Because of + // rounding error, this will not quite be the case. + color_space = ColorSpace::CreateHDR10(50.f); + roundtrip_color_space = ColorSpace(*color_space.ToSkColorSpace()); + EXPECT_TRUE( + roundtrip_color_space.GetSDRWhiteLevel(&roundtrip_sdr_white_level)); + EXPECT_NEAR(50.f, roundtrip_sdr_white_level, kEpsilon); + EXPECT_EQ(ColorSpace::TransferID::SMPTEST2084, + roundtrip_color_space.GetTransferID()); + + // When no white level is specified, we should get an SkColorSpace that + // specifies the default white level. Of note is that in the roundtrip, the + // value of kDefaultSDRWhiteLevel gets baked in. + color_space = ColorSpace::CreateHDR10(); + roundtrip_color_space = ColorSpace(*color_space.ToSkColorSpace()); + EXPECT_TRUE( + roundtrip_color_space.GetSDRWhiteLevel(&roundtrip_sdr_white_level)); + EXPECT_NEAR(ColorSpace::kDefaultSDRWhiteLevel, roundtrip_sdr_white_level, + kEpsilon); +} + +TEST(ColorSpace, HLGToSkColorSpace) { + ColorSpace color_space; + ColorSpace roundtrip_color_space; + float roundtrip_sdr_white_level; + const float kEpsilon = 1.0e-3f; + + // We expect that when a white point is specified, the conversion from + // ColorSpace -> SkColorSpace -> ColorSpace be the identity. Because of + // rounding error, this will not quite be the case. + constexpr float kSDRWhiteLevel = 50.0f; + color_space = ColorSpace::CreateHLG().GetWithSDRWhiteLevel(kSDRWhiteLevel); + roundtrip_color_space = ColorSpace(*color_space.ToSkColorSpace()); + EXPECT_TRUE( + roundtrip_color_space.GetSDRWhiteLevel(&roundtrip_sdr_white_level)); + EXPECT_FLOAT_EQ(kSDRWhiteLevel, roundtrip_sdr_white_level); + EXPECT_EQ(ColorSpace::TransferID::ARIB_STD_B67, + roundtrip_color_space.GetTransferID()); + + // When no white level is specified, we should get an SkColorSpace that + // specifies the default white level. Of note is that in the roundtrip, the + // value of kDefaultSDRWhiteLevel gets baked in. + color_space = ColorSpace::CreateHLG(); + roundtrip_color_space = ColorSpace(*color_space.ToSkColorSpace()); + EXPECT_TRUE( + roundtrip_color_space.GetSDRWhiteLevel(&roundtrip_sdr_white_level)); + EXPECT_NEAR(ColorSpace::kDefaultSDRWhiteLevel, roundtrip_sdr_white_level, + kEpsilon); +} + +TEST(ColorSpace, MixedInvalid) { + ColorSpace color_space; + color_space = color_space.GetWithMatrixAndRange(ColorSpace::MatrixID::INVALID, + ColorSpace::RangeID::INVALID); + EXPECT_TRUE(!color_space.IsValid()); + color_space = color_space.GetWithMatrixAndRange( + ColorSpace::MatrixID::SMPTE170M, ColorSpace::RangeID::LIMITED); + EXPECT_TRUE(!color_space.IsValid()); +} + +TEST(ColorSpace, MixedSRGBWithRec601) { + const ColorSpace expected_color_space = ColorSpace( + ColorSpace::PrimaryID::BT709, ColorSpace::TransferID::IEC61966_2_1, + ColorSpace::MatrixID::SMPTE170M, ColorSpace::RangeID::LIMITED); + ColorSpace color_space = ColorSpace::CreateSRGB(); + color_space = color_space.GetWithMatrixAndRange( + ColorSpace::MatrixID::SMPTE170M, ColorSpace::RangeID::LIMITED); + EXPECT_TRUE(expected_color_space.IsValid()); + EXPECT_EQ(color_space, expected_color_space); +} + +TEST(ColorSpace, MixedHDR10WithRec709) { + const ColorSpace expected_color_space = ColorSpace( + ColorSpace::PrimaryID::BT2020, ColorSpace::TransferID::SMPTEST2084, + ColorSpace::MatrixID::BT709, ColorSpace::RangeID::LIMITED); + ColorSpace color_space = ColorSpace::CreateHDR10(); + color_space = color_space.GetWithMatrixAndRange(ColorSpace::MatrixID::BT709, + ColorSpace::RangeID::LIMITED); + EXPECT_TRUE(expected_color_space.IsValid()); + EXPECT_EQ(color_space, expected_color_space); +} + +TEST(ColorSpace, GetsPrimariesTransferMatrixAndRange) { + ColorSpace color_space( + ColorSpace::PrimaryID::BT709, ColorSpace::TransferID::BT709, + ColorSpace::MatrixID::BT709, ColorSpace::RangeID::LIMITED); + EXPECT_EQ(color_space.GetPrimaryID(), ColorSpace::PrimaryID::BT709); + EXPECT_EQ(color_space.GetTransferID(), ColorSpace::TransferID::BT709); + EXPECT_EQ(color_space.GetMatrixID(), ColorSpace::MatrixID::BT709); + EXPECT_EQ(color_space.GetRangeID(), ColorSpace::RangeID::LIMITED); +} + +TEST(ColorSpace, PQWhiteLevel) { + constexpr float kCustomWhiteLevel = 200.f; + + ColorSpace color_space = ColorSpace::CreateHDR10(kCustomWhiteLevel); + EXPECT_EQ(color_space.GetTransferID(), ColorSpace::TransferID::SMPTEST2084); + float sdr_white_level; + EXPECT_TRUE(color_space.GetSDRWhiteLevel(&sdr_white_level)); + EXPECT_EQ(sdr_white_level, kCustomWhiteLevel); + + color_space = ColorSpace::CreateHDR10(); + EXPECT_EQ(color_space.GetTransferID(), ColorSpace::TransferID::SMPTEST2084); + EXPECT_TRUE(color_space.GetSDRWhiteLevel(&sdr_white_level)); + EXPECT_EQ(sdr_white_level, ColorSpace::kDefaultSDRWhiteLevel); + + color_space = color_space.GetWithSDRWhiteLevel(kCustomWhiteLevel); + EXPECT_EQ(color_space.GetTransferID(), ColorSpace::TransferID::SMPTEST2084); + EXPECT_TRUE(color_space.GetSDRWhiteLevel(&sdr_white_level)); + EXPECT_EQ(sdr_white_level, kCustomWhiteLevel); + + constexpr float kCustomWhiteLevel2 = kCustomWhiteLevel * 2; + color_space = color_space.GetWithSDRWhiteLevel(kCustomWhiteLevel2); + EXPECT_EQ(color_space.GetTransferID(), ColorSpace::TransferID::SMPTEST2084); + EXPECT_TRUE(color_space.GetSDRWhiteLevel(&sdr_white_level)); + EXPECT_EQ(sdr_white_level, kCustomWhiteLevel2); +} + +TEST(ColorSpace, LinearHDRWhiteLevel) { + constexpr float kCustomWhiteLevel = 200.f; + constexpr float kCustomSlope = + ColorSpace::kDefaultScrgbLinearSdrWhiteLevel / kCustomWhiteLevel; + + ColorSpace color_space = ColorSpace::CreateSCRGBLinear(kCustomWhiteLevel); + skcms_TransferFunction fn; + EXPECT_EQ(color_space.GetTransferID(), ColorSpace::TransferID::CUSTOM_HDR); + EXPECT_TRUE(color_space.GetTransferFunction(&fn)); + EXPECT_EQ(std::make_tuple(fn.g, fn.a, fn.b, fn.c, fn.d, fn.e, fn.f), + std::make_tuple(1.f, kCustomSlope, 0.f, 0.f, 0.f, 0.f, 0.f)); + + color_space = ColorSpace::CreateSCRGBLinear(); + EXPECT_EQ(color_space.GetTransferID(), ColorSpace::TransferID::LINEAR_HDR); + EXPECT_TRUE(color_space.GetTransferFunction(&fn)); + EXPECT_EQ(std::make_tuple(fn.g, fn.a, fn.b, fn.c, fn.d, fn.e, fn.f), + std::make_tuple(1.f, 1.f, 0.f, 0.f, 0.f, 0.f, 0.f)); + + color_space = color_space.GetWithSDRWhiteLevel(kCustomWhiteLevel); + EXPECT_EQ(color_space.GetTransferID(), ColorSpace::TransferID::CUSTOM_HDR); + EXPECT_TRUE(color_space.GetTransferFunction(&fn)); + EXPECT_EQ(std::make_tuple(fn.g, fn.a, fn.b, fn.c, fn.d, fn.e, fn.f), + std::make_tuple(1.f, kCustomSlope, 0.f, 0.f, 0.f, 0.f, 0.f)); +} + +TEST(ColorSpace, ExpectationsMatchSRGB) { + ColorSpace::PrimaryID primary_ids[] = { + ColorSpace::PrimaryID::BT709, + ColorSpace::PrimaryID::BT470M, + ColorSpace::PrimaryID::BT470BG, + ColorSpace::PrimaryID::SMPTE170M, + ColorSpace::PrimaryID::SMPTE240M, + ColorSpace::PrimaryID::FILM, + ColorSpace::PrimaryID::BT2020, + ColorSpace::PrimaryID::SMPTEST428_1, + ColorSpace::PrimaryID::SMPTEST431_2, + ColorSpace::PrimaryID::SMPTEST432_1, + ColorSpace::PrimaryID::XYZ_D50, + ColorSpace::PrimaryID::ADOBE_RGB, + ColorSpace::PrimaryID::APPLE_GENERIC_RGB, + ColorSpace::PrimaryID::WIDE_GAMUT_COLOR_SPIN, + }; + + // Create a custom color space with the sRGB primary matrix. + ColorSpace srgb = ColorSpace::CreateSRGB(); + skcms_Matrix3x3 to_XYZD50; + srgb.GetPrimaryMatrix(&to_XYZD50); + ColorSpace custom_srgb = + ColorSpace::CreateCustom(to_XYZD50, ColorSpace::TransferID::IEC61966_2_1); + + for (auto id : primary_ids) { + ColorSpace color_space(id, ColorSpace::TransferID::IEC61966_2_1); + // The precomputed results for Contains(sRGB) should match the calculation + // performed on a custom color space with sRGB primaries. + EXPECT_EQ(color_space.Contains(srgb), color_space.Contains(custom_srgb)); + } +} + +} // namespace +} // namespace gfx diff --git a/color_space_win.cc b/color_space_win.cc new file mode 100644 index 000000000000..03886d4b7fe5 --- /dev/null +++ b/color_space_win.cc @@ -0,0 +1,285 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/color_space_win.h" + +#include "base/logging.h" +#include "third_party/skia/include/third_party/skcms/skcms.h" + +namespace gfx { + +DXVA2_ExtendedFormat ColorSpaceWin::GetExtendedFormat( + const ColorSpace& color_space) { + DXVA2_ExtendedFormat format; + memset(&format, 0, sizeof(format)); + format.SampleFormat = DXVA2_SampleProgressiveFrame; + format.VideoLighting = DXVA2_VideoLighting_dim; + format.NominalRange = DXVA2_NominalRange_16_235; + format.VideoTransferMatrix = DXVA2_VideoTransferMatrix_BT709; + format.VideoPrimaries = DXVA2_VideoPrimaries_BT709; + format.VideoTransferFunction = DXVA2_VideoTransFunc_709; + + switch (color_space.GetRangeID()) { + case gfx::ColorSpace::RangeID::LIMITED: + format.NominalRange = DXVA2_NominalRange_16_235; + break; + case gfx::ColorSpace::RangeID::FULL: + format.NominalRange = DXVA2_NominalRange_0_255; + break; + + case gfx::ColorSpace::RangeID::DERIVED: + case gfx::ColorSpace::RangeID::INVALID: + // Not handled + break; + } + + switch (color_space.GetMatrixID()) { + case gfx::ColorSpace::MatrixID::BT709: + format.VideoTransferMatrix = DXVA2_VideoTransferMatrix_BT709; + break; + case gfx::ColorSpace::MatrixID::BT470BG: + case gfx::ColorSpace::MatrixID::SMPTE170M: + format.VideoTransferMatrix = DXVA2_VideoTransferMatrix_BT601; + break; + case gfx::ColorSpace::MatrixID::SMPTE240M: + format.VideoTransferMatrix = DXVA2_VideoTransferMatrix_SMPTE240M; + break; + + case gfx::ColorSpace::MatrixID::RGB: + case gfx::ColorSpace::MatrixID::GBR: + case gfx::ColorSpace::MatrixID::FCC: + case gfx::ColorSpace::MatrixID::YCOCG: + case gfx::ColorSpace::MatrixID::BT2020_NCL: + case gfx::ColorSpace::MatrixID::BT2020_CL: + case gfx::ColorSpace::MatrixID::YDZDX: + case gfx::ColorSpace::MatrixID::INVALID: + // Not handled + break; + } + + switch (color_space.GetPrimaryID()) { + case gfx::ColorSpace::PrimaryID::BT709: + format.VideoPrimaries = DXVA2_VideoPrimaries_BT709; + break; + case gfx::ColorSpace::PrimaryID::BT470M: + format.VideoPrimaries = DXVA2_VideoPrimaries_BT470_2_SysM; + break; + case gfx::ColorSpace::PrimaryID::BT470BG: + format.VideoPrimaries = DXVA2_VideoPrimaries_BT470_2_SysBG; + break; + case gfx::ColorSpace::PrimaryID::SMPTE170M: + format.VideoPrimaries = DXVA2_VideoPrimaries_SMPTE170M; + break; + case gfx::ColorSpace::PrimaryID::SMPTE240M: + format.VideoPrimaries = DXVA2_VideoPrimaries_SMPTE240M; + break; + + case gfx::ColorSpace::PrimaryID::FILM: + case gfx::ColorSpace::PrimaryID::BT2020: + case gfx::ColorSpace::PrimaryID::SMPTEST428_1: + case gfx::ColorSpace::PrimaryID::SMPTEST431_2: + case gfx::ColorSpace::PrimaryID::SMPTEST432_1: + case gfx::ColorSpace::PrimaryID::XYZ_D50: + case gfx::ColorSpace::PrimaryID::ADOBE_RGB: + case gfx::ColorSpace::PrimaryID::APPLE_GENERIC_RGB: + case gfx::ColorSpace::PrimaryID::WIDE_GAMUT_COLOR_SPIN: + case gfx::ColorSpace::PrimaryID::CUSTOM: + case gfx::ColorSpace::PrimaryID::INVALID: + // Not handled + break; + } + + switch (color_space.GetTransferID()) { + case gfx::ColorSpace::TransferID::BT709: + case gfx::ColorSpace::TransferID::SMPTE170M: + format.VideoTransferFunction = DXVA2_VideoTransFunc_709; + break; + case gfx::ColorSpace::TransferID::SMPTE240M: + format.VideoTransferFunction = DXVA2_VideoTransFunc_240M; + break; + case gfx::ColorSpace::TransferID::GAMMA22: + format.VideoTransferFunction = DXVA2_VideoTransFunc_22; + break; + case gfx::ColorSpace::TransferID::GAMMA28: + format.VideoTransferFunction = DXVA2_VideoTransFunc_28; + break; + case gfx::ColorSpace::TransferID::LINEAR: + case gfx::ColorSpace::TransferID::LINEAR_HDR: + format.VideoTransferFunction = DXVA2_VideoTransFunc_10; + break; + case gfx::ColorSpace::TransferID::IEC61966_2_1: + case gfx::ColorSpace::TransferID::IEC61966_2_1_HDR: + format.VideoTransferFunction = DXVA2_VideoTransFunc_sRGB; + break; + + case gfx::ColorSpace::TransferID::LOG: + case gfx::ColorSpace::TransferID::LOG_SQRT: + case gfx::ColorSpace::TransferID::IEC61966_2_4: + case gfx::ColorSpace::TransferID::BT1361_ECG: + case gfx::ColorSpace::TransferID::BT2020_10: + case gfx::ColorSpace::TransferID::BT2020_12: + case gfx::ColorSpace::TransferID::SMPTEST2084: + case gfx::ColorSpace::TransferID::SMPTEST428_1: + case gfx::ColorSpace::TransferID::ARIB_STD_B67: + case gfx::ColorSpace::TransferID::BT709_APPLE: + case gfx::ColorSpace::TransferID::GAMMA18: + case gfx::ColorSpace::TransferID::GAMMA24: + case gfx::ColorSpace::TransferID::CUSTOM: + case gfx::ColorSpace::TransferID::CUSTOM_HDR: + case gfx::ColorSpace::TransferID::PIECEWISE_HDR: + case gfx::ColorSpace::TransferID::INVALID: + // Not handled + break; + } + + return format; +} + +DXGI_COLOR_SPACE_TYPE ColorSpaceWin::GetDXGIColorSpace( + const ColorSpace& color_space, + bool force_yuv) { + // Treat invalid color space as sRGB. + if (!color_space.IsValid()) + return DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709; + + if (color_space.GetMatrixID() == gfx::ColorSpace::MatrixID::RGB && + !force_yuv) { + // For RGB, we default to FULL + if (color_space.GetRangeID() == gfx::ColorSpace::RangeID::LIMITED) { + if (color_space.GetPrimaryID() == gfx::ColorSpace::PrimaryID::BT2020) { + if (color_space.GetTransferID() == + gfx::ColorSpace::TransferID::SMPTEST2084) { + return DXGI_COLOR_SPACE_RGB_STUDIO_G2084_NONE_P2020; + } else { + return DXGI_COLOR_SPACE_RGB_STUDIO_G22_NONE_P2020; + } + } else { + return DXGI_COLOR_SPACE_RGB_STUDIO_G22_NONE_P709; + } + } else { + if (color_space.GetPrimaryID() == gfx::ColorSpace::PrimaryID::BT2020) { + if (color_space.GetTransferID() == + gfx::ColorSpace::TransferID::SMPTEST2084) { + return DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020; + } else { + return DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P2020; + } + } else { + if (color_space.GetTransferID() == + gfx::ColorSpace::TransferID::LINEAR || + color_space.GetTransferID() == + gfx::ColorSpace::TransferID::LINEAR_HDR) { + return DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709; + } else if (color_space.GetTransferID() == + gfx::ColorSpace::TransferID::CUSTOM_HDR) { + skcms_TransferFunction fn; + color_space.GetTransferFunction(&fn); + if (fn.g == 1.f) + return DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709; + else + DLOG(ERROR) << "Windows HDR only supports gamma=1.0."; + } + return DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709; + } + } + } else { + if (color_space.GetPrimaryID() == gfx::ColorSpace::PrimaryID::BT2020) { + if (color_space.GetTransferID() == + gfx::ColorSpace::TransferID::SMPTEST2084) { + return DXGI_COLOR_SPACE_YCBCR_STUDIO_G2084_LEFT_P2020; + // Could also be: + // DXGI_COLOR_SPACE_YCBCR_STUDIO_G2084_TOPLEFT_P2020 + } else if (color_space.GetTransferID() == + gfx::ColorSpace::TransferID::ARIB_STD_B67) { + // Note: This may not always work. See https://crbug.com/1144260#c6. + return DXGI_COLOR_SPACE_YCBCR_STUDIO_GHLG_TOPLEFT_P2020; + } else { + // For YUV, we default to LIMITED + if (color_space.GetRangeID() == gfx::ColorSpace::RangeID::FULL) { + return DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P2020; + + } else { + return DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P2020; + // Could also be: + // DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_TOPLEFT_P2020 + } + } + } else if (color_space.GetPrimaryID() == + gfx::ColorSpace::PrimaryID::BT470BG || + color_space.GetPrimaryID() == + gfx::ColorSpace::PrimaryID::SMPTE170M) { + // For YUV, we default to LIMITED + if (color_space.GetRangeID() == gfx::ColorSpace::RangeID::FULL) { + return DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P601; + } else { + return DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P601; + } + } else { + // For YUV, we default to LIMITED + if (color_space.GetRangeID() == gfx::ColorSpace::RangeID::FULL) { + // TODO(hubbe): Check if this is correct. + if (color_space.GetTransferID() == + gfx::ColorSpace::TransferID::SMPTE170M) { + return DXGI_COLOR_SPACE_YCBCR_FULL_G22_NONE_P709_X601; + } else { + return DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P709; + } + } else { + return DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P709; + } + } + } +} + +DXGI_FORMAT ColorSpaceWin::GetDXGIFormat(const gfx::ColorSpace& color_space) { + // The PQ transfer function needs 10 bits. + if (color_space.GetTransferID() == gfx::ColorSpace::TransferID::SMPTEST2084) + return DXGI_FORMAT_R10G10B10A2_UNORM; + + // Non-PQ HDR color spaces use half-float. + if (color_space.IsHDR()) + return DXGI_FORMAT_R16G16B16A16_FLOAT; + + // For now just give everything else 8 bits. We will want to use 10 or 16 bits + // for BT2020 gamuts. + return DXGI_FORMAT_B8G8R8A8_UNORM; +} + +D3D11_VIDEO_PROCESSOR_COLOR_SPACE ColorSpaceWin::GetD3D11ColorSpace( + const ColorSpace& color_space) { + D3D11_VIDEO_PROCESSOR_COLOR_SPACE ret = {0}; + if (color_space.GetRangeID() == gfx::ColorSpace::RangeID::FULL) { + ret.RGB_Range = 0; // FULL + ret.Nominal_Range = D3D11_VIDEO_PROCESSOR_NOMINAL_RANGE_0_255; + } else { + ret.RGB_Range = 1; // LIMITED + ret.Nominal_Range = D3D11_VIDEO_PROCESSOR_NOMINAL_RANGE_16_235; + } + + switch (color_space.GetMatrixID()) { + case gfx::ColorSpace::MatrixID::BT709: + ret.YCbCr_Matrix = 1; + break; + + case gfx::ColorSpace::MatrixID::BT470BG: + case gfx::ColorSpace::MatrixID::SMPTE170M: + ret.YCbCr_Matrix = 0; + break; + + case gfx::ColorSpace::MatrixID::SMPTE240M: + case gfx::ColorSpace::MatrixID::RGB: + case gfx::ColorSpace::MatrixID::GBR: + case gfx::ColorSpace::MatrixID::FCC: + case gfx::ColorSpace::MatrixID::YCOCG: + case gfx::ColorSpace::MatrixID::BT2020_NCL: + case gfx::ColorSpace::MatrixID::BT2020_CL: + case gfx::ColorSpace::MatrixID::YDZDX: + case gfx::ColorSpace::MatrixID::INVALID: + // Not handled + break; + } + return ret; +} + +} // namespace gfx diff --git a/color_space_win.h b/color_space_win.h new file mode 100644 index 000000000000..1c718915e176 --- /dev/null +++ b/color_space_win.h @@ -0,0 +1,47 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_COLOR_SPACE_WIN_H_ +#define UI_GFX_COLOR_SPACE_WIN_H_ + +#include +#include + +// Must be included after d3d headers, use #if to avoid lint errors. +#if 1 +#include +#endif + +// Work around bug in this header by disabling the relevant warning for it. +// https://connect.microsoft.com/VisualStudio/feedback/details/911260/dxva2api-h-in-win8-sdk-triggers-c4201-with-w4 +#pragma warning(push) +#pragma warning(disable : 4201) +#include +#pragma warning(pop) + +#include "ui/gfx/color_space.h" + +namespace gfx { + +class COLOR_SPACE_EXPORT ColorSpaceWin { + public: + static DXVA2_ExtendedFormat GetExtendedFormat(const ColorSpace& color_space); + + // Returns a DXGI_COLOR_SPACE value based on the primaries and transfer + // function of |color_space|. If the color space's MatrixID is RGB, then the + // returned color space is also RGB unless |force_yuv| is true in which case + // it is a YUV color space. + static DXGI_COLOR_SPACE_TYPE GetDXGIColorSpace(const ColorSpace& color_space, + bool force_yuv = false); + + // Get DXGI format for swap chain. This will default to 8-bit, but will use + // 10-bit or half-float for HDR color spaces. + static DXGI_FORMAT GetDXGIFormat(const gfx::ColorSpace& color_space); + + static D3D11_VIDEO_PROCESSOR_COLOR_SPACE GetD3D11ColorSpace( + const ColorSpace& color_space); +}; + +} // namespace gfx +#endif // UI_GFX_COLOR_SPACE_WIN_H_ diff --git a/color_transform.cc b/color_transform.cc new file mode 100644 index 000000000000..f53eb60f99f8 --- /dev/null +++ b/color_transform.cc @@ -0,0 +1,1139 @@ +// Copyright (c) 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/color_transform.h" + +#include +#include +#include +#include +#include +#include + +#include "base/logging.h" +#include "base/notreached.h" +#include "third_party/skia/include/core/SkColor.h" +#include "third_party/skia/include/third_party/skcms/skcms.h" +#include "ui/gfx/color_space.h" +#include "ui/gfx/geometry/transform.h" +#include "ui/gfx/icc_profile.h" +#include "ui/gfx/skia_color_space_util.h" + +using std::abs; +using std::copysign; +using std::endl; +using std::exp; +using std::log; +using std::max; +using std::min; +using std::pow; +using std::sqrt; + +namespace gfx { + +namespace { + +void InitStringStream(std::stringstream* ss) { + ss->imbue(std::locale::classic()); + ss->precision(8); + *ss << std::scientific; +} + +std::string Str(float f) { + std::stringstream ss; + InitStringStream(&ss); + ss << f; + return ss.str(); +} + +Transform Invert(const Transform& t) { + Transform ret = t; + if (!t.GetInverse(&ret)) { + LOG(ERROR) << "Inverse should always be possible."; + } + return ret; +} + +float FromLinear(ColorSpace::TransferID id, float v) { + switch (id) { + case ColorSpace::TransferID::LOG: + if (v < 0.01f) + return 0.0f; + return 1.0f + log(v) / log(10.0f) / 2.0f; + + case ColorSpace::TransferID::LOG_SQRT: + if (v < sqrt(10.0f) / 1000.0f) + return 0.0f; + return 1.0f + log(v) / log(10.0f) / 2.5f; + + case ColorSpace::TransferID::IEC61966_2_4: { + float a = 1.099296826809442f; + float b = 0.018053968510807f; + if (v < -b) + return -a * pow(-v, 0.45f) + (a - 1.0f); + else if (v <= b) + return 4.5f * v; + return a * pow(v, 0.45f) - (a - 1.0f); + } + + case ColorSpace::TransferID::BT1361_ECG: { + float a = 1.099f; + float b = 0.018f; + float l = 0.0045f; + if (v < -l) + return -(a * pow(-4.0f * v, 0.45f) + (a - 1.0f)) / 4.0f; + else if (v <= b) + return 4.5f * v; + else + return a * pow(v, 0.45f) - (a - 1.0f); + } + + default: + // Handled by skcms_TransferFunction. + break; + } + NOTREACHED(); + return 0; +} + +float ToLinear(ColorSpace::TransferID id, float v) { + switch (id) { + case ColorSpace::TransferID::LOG: + if (v < 0.0f) + return 0.0f; + return pow(10.0f, (v - 1.0f) * 2.0f); + + case ColorSpace::TransferID::LOG_SQRT: + if (v < 0.0f) + return 0.0f; + return pow(10.0f, (v - 1.0f) * 2.5f); + + case ColorSpace::TransferID::IEC61966_2_4: { + float a = 1.099296826809442f; + // Equal to FromLinear(ColorSpace::TransferID::IEC61966_2_4, -a). + float from_linear_neg_a = -1.047844f; + // Equal to FromLinear(ColorSpace::TransferID::IEC61966_2_4, b). + float from_linear_b = 0.081243f; + if (v < from_linear_neg_a) + return -pow((a - 1.0f - v) / a, 1.0f / 0.45f); + else if (v <= from_linear_b) + return v / 4.5f; + return pow((v + a - 1.0f) / a, 1.0f / 0.45f); + } + + case ColorSpace::TransferID::BT1361_ECG: { + float a = 1.099f; + // Equal to FromLinear(ColorSpace::TransferID::BT1361_ECG, -l). + float from_linear_neg_l = -0.020250f; + // Equal to FromLinear(ColorSpace::TransferID::BT1361_ECG, b). + float from_linear_b = 0.081000f; + if (v < from_linear_neg_l) + return -pow((1.0f - a - v * 4.0f) / a, 1.0f / 0.45f) / 4.0f; + else if (v <= from_linear_b) + return v / 4.5f; + return pow((v + a - 1.0f) / a, 1.0f / 0.45f); + } + + default: + // Handled by skcms_TransferFunction. + break; + } + NOTREACHED(); + return 0; +} + +Transform GetTransferMatrix(const gfx::ColorSpace& color_space, int bit_depth) { + skia::Matrix44 transfer_matrix; + color_space.GetTransferMatrix(bit_depth, &transfer_matrix); + return Transform(transfer_matrix); +} + +Transform GetRangeAdjustMatrix(const gfx::ColorSpace& color_space, + int bit_depth) { + skia::Matrix44 range_adjust_matrix; + color_space.GetRangeAdjustMatrix(bit_depth, &range_adjust_matrix); + return Transform(range_adjust_matrix); +} + +Transform GetPrimaryTransform(const gfx::ColorSpace& color_space) { + skia::Matrix44 primary_matrix; + color_space.GetPrimaryMatrix(&primary_matrix); + return Transform(primary_matrix); +} + +} // namespace + +class ColorTransformMatrix; +class ColorTransformSkTransferFn; +class ColorTransformFromLinear; +class ColorTransformFromBT2020CL; +class ColorTransformNull; + +class ColorTransformStep { + public: + ColorTransformStep() {} + + ColorTransformStep(const ColorTransformStep&) = delete; + ColorTransformStep& operator=(const ColorTransformStep&) = delete; + + virtual ~ColorTransformStep() {} + virtual ColorTransformFromLinear* GetFromLinear() { return nullptr; } + virtual ColorTransformFromBT2020CL* GetFromBT2020CL() { return nullptr; } + virtual ColorTransformSkTransferFn* GetSkTransferFn() { return nullptr; } + virtual ColorTransformMatrix* GetMatrix() { return nullptr; } + virtual ColorTransformNull* GetNull() { return nullptr; } + + // Join methods, returns true if the |next| transform was successfully + // assimilated into |this|. + // If Join() returns true, |next| is no longer needed and can be deleted. + virtual bool Join(ColorTransformStep* next) { return false; } + + // Return true if this is a null transform. + virtual bool IsNull() { return false; } + virtual void Transform(ColorTransform::TriStim* color, size_t num) const = 0; + // In the shader, |hdr| will appear before |src|, so any helper functions that + // are created should be put in |hdr|. Any helper functions should have + // |step_index| included in the function name, to ensure that there are no + // naming conflicts. + virtual void AppendShaderSource(std::stringstream* hdr, + std::stringstream* src, + size_t step_index) const = 0; + virtual void AppendSkShaderSource(std::stringstream* src) const = 0; +}; + +class ColorTransformInternal : public ColorTransform { + public: + ColorTransformInternal(const ColorSpace& src, + const ColorSpace& dst, + const Options& options); + ~ColorTransformInternal() override; + + gfx::ColorSpace GetSrcColorSpace() const override { return src_; } + gfx::ColorSpace GetDstColorSpace() const override { return dst_; } + + void Transform(TriStim* colors, size_t num) const override { + for (const auto& step : steps_) { + step->Transform(colors, num); + } + } + std::string GetShaderSource() const override; + std::string GetSkShaderSource() const override; + bool IsIdentity() const override { return steps_.empty(); } + size_t NumberOfStepsForTesting() const override { return steps_.size(); } + + private: + void AppendColorSpaceToColorSpaceTransform(const ColorSpace& src, + const ColorSpace& dst, + const Options& options); + void Simplify(); + + std::list> steps_; + gfx::ColorSpace src_; + gfx::ColorSpace dst_; +}; + +class ColorTransformNull : public ColorTransformStep { + public: + ColorTransformNull* GetNull() override { return this; } + bool IsNull() override { return true; } + void Transform(ColorTransform::TriStim* color, size_t num) const override {} + void AppendShaderSource(std::stringstream* hdr, + std::stringstream* src, + size_t step_index) const override {} + void AppendSkShaderSource(std::stringstream* src) const override {} +}; + +class ColorTransformMatrix : public ColorTransformStep { + public: + explicit ColorTransformMatrix(const class Transform& matrix) + : matrix_(matrix) {} + ColorTransformMatrix* GetMatrix() override { return this; } + bool Join(ColorTransformStep* next_untyped) override { + ColorTransformMatrix* next = next_untyped->GetMatrix(); + if (!next) + return false; + class Transform tmp = next->matrix_; + tmp *= matrix_; + matrix_ = tmp; + return true; + } + + bool IsNull() override { + return SkMatrixIsApproximatelyIdentity(matrix_.matrix()); + } + + void Transform(ColorTransform::TriStim* colors, size_t num) const override { + for (size_t i = 0; i < num; i++) + matrix_.TransformPoint(colors + i); + } + + void AppendShaderSource(std::stringstream* hdr, + std::stringstream* src, + size_t step_index) const override { + const skia::Matrix44& m = matrix_.matrix(); + *src << " color = mat3("; + *src << m.get(0, 0) << ", " << m.get(1, 0) << ", " << m.get(2, 0) << ","; + *src << endl; + *src << " "; + *src << m.get(0, 1) << ", " << m.get(1, 1) << ", " << m.get(2, 1) << ","; + *src << endl; + *src << " "; + *src << m.get(0, 2) << ", " << m.get(1, 2) << ", " << m.get(2, 2) << ")"; + *src << " * color;" << endl; + + // Only print the translational component if it isn't the identity. + if (m.get(0, 3) != 0.f || m.get(1, 3) != 0.f || m.get(2, 3) != 0.f) { + *src << " color += vec3("; + *src << m.get(0, 3) << ", " << m.get(1, 3) << ", " << m.get(2, 3); + *src << ");" << endl; + } + } + + void AppendSkShaderSource(std::stringstream* src) const override { + const skia::Matrix44& m = matrix_.matrix(); + *src << " color = half4x4("; + *src << m.get(0, 0) << ", " << m.get(1, 0) << ", " << m.get(2, 0) << ", 0,"; + *src << endl; + *src << " "; + *src << m.get(0, 1) << ", " << m.get(1, 1) << ", " << m.get(2, 1) << ", 0,"; + *src << endl; + *src << " "; + *src << m.get(0, 2) << ", " << m.get(1, 2) << ", " << m.get(2, 2) << ", 0,"; + *src << endl; + *src << "0, 0, 0, 1)"; + *src << " * color;" << endl; + + // Only print the translational component if it isn't the identity. + if (m.get(0, 3) != 0.f || m.get(1, 3) != 0.f || m.get(2, 3) != 0.f) { + *src << " color += half4("; + *src << m.get(0, 3) << ", " << m.get(1, 3) << ", " << m.get(2, 3); + *src << ", 0);" << endl; + } + } + + private: + class Transform matrix_; +}; + +class ColorTransformPerChannelTransferFn : public ColorTransformStep { + public: + explicit ColorTransformPerChannelTransferFn(bool extended) + : extended_(extended) {} + + void Transform(ColorTransform::TriStim* colors, size_t num) const override { + for (size_t i = 0; i < num; i++) { + ColorTransform::TriStim& c = colors[i]; + if (extended_) { + c.set_x(copysign(Evaluate(abs(c.x())), c.x())); + c.set_y(copysign(Evaluate(abs(c.y())), c.y())); + c.set_z(copysign(Evaluate(abs(c.z())), c.z())); + } else { + c.set_x(Evaluate(c.x())); + c.set_y(Evaluate(c.y())); + c.set_z(Evaluate(c.z())); + } + } + } + + void AppendShaderSource(std::stringstream* hdr, + std::stringstream* src, + size_t step_index) const override { + *hdr << "float TransferFn" << step_index << "(float v) {" << endl; + AppendTransferShaderSource(hdr, true /* is_glsl */); + *hdr << " return v;" << endl; + *hdr << "}" << endl; + if (extended_) { + *src << " color.r = sign(color.r) * TransferFn" << step_index + << "(abs(color.r));" << endl; + *src << " color.g = sign(color.g) * TransferFn" << step_index + << "(abs(color.g));" << endl; + *src << " color.b = sign(color.b) * TransferFn" << step_index + << "(abs(color.b));" << endl; + } else { + *src << " color.r = TransferFn" << step_index << "(color.r);" << endl; + *src << " color.g = TransferFn" << step_index << "(color.g);" << endl; + *src << " color.b = TransferFn" << step_index << "(color.b);" << endl; + } + } + + void AppendSkShaderSource(std::stringstream* src) const override { + if (extended_) { + *src << "{ half v = abs(color.r);" << endl; + AppendTransferShaderSource(src, false /* is_glsl */); + *src << " color.r = sign(color.r) * v; }" << endl; + *src << "{ half v = abs(color.g);" << endl; + AppendTransferShaderSource(src, false /* is_glsl */); + *src << " color.g = sign(color.g) * v; }" << endl; + *src << "{ half v = abs(color.b);" << endl; + AppendTransferShaderSource(src, false /* is_glsl */); + *src << " color.b = sign(color.b) * v; }" << endl; + } else { + *src << "{ half v = color.r;" << endl; + AppendTransferShaderSource(src, false /* is_glsl */); + *src << " color.r = v; }" << endl; + *src << "{ half v = color.g;" << endl; + AppendTransferShaderSource(src, false /* is_glsl */); + *src << " color.g = v; }" << endl; + *src << "{ half v = color.b;" << endl; + AppendTransferShaderSource(src, false /* is_glsl */); + *src << " color.b = v; }" << endl; + } + } + + virtual float Evaluate(float x) const = 0; + virtual void AppendTransferShaderSource(std::stringstream* src, + bool is_glsl) const = 0; + + protected: + // True if the transfer function is extended to be defined for all real + // values by point symmetry. + bool extended_ = false; +}; + +// This class represents the piecewise-HDR function using three new parameters, +// P, Q, and R. The function is defined as: +// 0 : x < 0 +// T(x) = sRGB(x/P) : x < P +// Q*x+R : x >= P +// This then expands to +// 0 : x < 0 +// T(x) = C*x/P+F : x < P*D +// (A*x/P+B)**G + E : x < P +// Q*x+R : else +class ColorTransformPiecewiseHDR : public ColorTransformPerChannelTransferFn { + public: + static void GetParams(const gfx::ColorSpace color_space, + skcms_TransferFunction* fn, + float* p, + float* q, + float* r) { + float sdr_joint = 1; + float hdr_level = 1; + color_space.GetPiecewiseHDRParams(&sdr_joint, &hdr_level); + + // P is exactly |sdr_joint|. + *p = sdr_joint; + + if (sdr_joint < 1.f) { + // Q and R are computed such that |sdr_joint| maps to 1 and 1) maps to + // |hdr_level|. + *q = (hdr_level - 1.f) / (1.f - sdr_joint); + *r = (1.f - hdr_level * sdr_joint) / (1.f - sdr_joint); + } else { + // If |sdr_joint| is exactly 1, then just saturate at 1 (there is no HDR). + *q = 0; + *r = 1; + } + + // Compute |fn| so that, at x, it evaluates to sRGB(x*P). + ColorSpace::CreateSRGB().GetTransferFunction(fn); + fn->d *= sdr_joint; + if (sdr_joint != 0) { + // If |sdr_joint| is 0, then we will never evaluate |fn| anyway. + fn->a /= sdr_joint; + fn->c /= sdr_joint; + } + } + static void InvertParams(skcms_TransferFunction* fn, + float* p, + float* q, + float* r) { + *fn = SkTransferFnInverse(*fn); + float old_p = *p; + float old_q = *q; + float old_r = *r; + *p = old_q * old_p + old_r; + if (old_q != 0.f) { + *q = 1.f / old_q; + *r = -old_r / old_q; + } else { + *q = 0.f; + *r = 1.f; + } + } + + ColorTransformPiecewiseHDR(const skcms_TransferFunction fn, + float p, + float q, + float r) + : ColorTransformPerChannelTransferFn(false), + fn_(fn), + p_(p), + q_(q), + r_(r) {} + + // ColorTransformPerChannelTransferFn implementation: + float Evaluate(float v) const override { + if (v < 0) + return 0; + else if (v < fn_.d) + return fn_.c * v + fn_.f; + else if (v < p_) + return std::pow(fn_.a * v + fn_.b, fn_.g) + fn_.e; + else + return q_ * v + r_; + } + void AppendTransferShaderSource(std::stringstream* result, + bool is_glsl) const override { + *result << " if (v < 0.0) {\n"; + *result << " v = 0.0;\n"; + *result << " } else if (v < " << Str(fn_.d) << ") {\n"; + *result << " v = " << Str(fn_.c) << " * v + " << Str(fn_.f) << ";" + << endl; + *result << " } else if (v < " << Str(p_) << ") {\n"; + *result << " v = pow(" << Str(fn_.a) << " * v + " << Str(fn_.b) << ", " + << Str(fn_.g) << ") + " << Str(fn_.e) << ";\n"; + *result << " } else {\n"; + *result << " v = " << Str(q_) << " * v + " << Str(r_) << ";\n"; + *result << " }\n"; + } + + private: + // Parameters of the SDR part. + const skcms_TransferFunction fn_; + // The SDR joint. Below this value in the domain, the function is defined by + // |fn_|. + const float p_; + // The slope of the linear HDR part. + const float q_; + // The intercept of the linear HDR part. + const float r_; +}; + +class ColorTransformSkTransferFn : public ColorTransformPerChannelTransferFn { + public: + explicit ColorTransformSkTransferFn(const skcms_TransferFunction& fn, + bool extended) + : ColorTransformPerChannelTransferFn(extended), fn_(fn) {} + // ColorTransformStep implementation. + ColorTransformSkTransferFn* GetSkTransferFn() override { return this; } + bool Join(ColorTransformStep* next_untyped) override { + ColorTransformSkTransferFn* next = next_untyped->GetSkTransferFn(); + if (!next) + return false; + if (!extended_ && !next->extended_ && + SkTransferFnsApproximatelyCancel(fn_, next->fn_)) { + // Set to be the identity. + fn_.a = 1; + fn_.b = 0; + fn_.c = 1; + fn_.d = 0; + fn_.e = 0; + fn_.f = 0; + fn_.g = 1; + return true; + } + return false; + } + bool IsNull() override { return SkTransferFnIsApproximatelyIdentity(fn_); } + + // ColorTransformPerChannelTransferFn implementation: + float Evaluate(float v) const override { + // Note that the sign-extension is performed by the caller. + return SkTransferFnEvalUnclamped(fn_, v); + } + void AppendTransferShaderSource(std::stringstream* result, + bool is_glsl) const override { + const float kEpsilon = 1.f / 1024.f; + + // Construct the linear segment + // linear = C * x + F + // Elide operations that will be close to the identity. + std::string linear = "v"; + if (std::abs(fn_.c - 1.f) > kEpsilon) + linear = Str(fn_.c) + " * " + linear; + if (std::abs(fn_.f) > kEpsilon) + linear = linear + " + " + Str(fn_.f); + + // Construct the nonlinear segment. + // nonlinear = pow(A * x + B, G) + E + // Elide operations (especially the pow) that will be close to the + // identity. + std::string nonlinear = "v"; + if (std::abs(fn_.a - 1.f) > kEpsilon) + nonlinear = Str(fn_.a) + " * " + nonlinear; + if (std::abs(fn_.b) > kEpsilon) + nonlinear = nonlinear + " + " + Str(fn_.b); + if (std::abs(fn_.g - 1.f) > kEpsilon) + nonlinear = "pow(" + nonlinear + ", " + Str(fn_.g) + ")"; + if (std::abs(fn_.e) > kEpsilon) + nonlinear = nonlinear + " + " + Str(fn_.e); + + *result << " if (v < " << Str(fn_.d) << ")" << endl; + *result << " v = " << linear << ";" << endl; + *result << " else" << endl; + *result << " v = " << nonlinear << ";" << endl; + } + + private: + skcms_TransferFunction fn_; +}; + +class ColorTransformHLGFromLinear : public ColorTransformPerChannelTransferFn { + public: + explicit ColorTransformHLGFromLinear(float sdr_white_level) + : ColorTransformPerChannelTransferFn(false), + sdr_scale_factor_(sdr_white_level / + gfx::ColorSpace::kDefaultSDRWhiteLevel) {} + + // ColorTransformPerChannelTransferFn implementation: + float Evaluate(float v) const override { + v *= sdr_scale_factor_; + + // Spec: http://www.arib.or.jp/english/html/overview/doc/2-STD-B67v1_0.pdf + constexpr float a = 0.17883277f; + constexpr float b = 0.28466892f; + constexpr float c = 0.55991073f; + v = max(0.0f, v); + if (v <= 1) + return 0.5f * sqrt(v); + return a * log(v - b) + c; + } + + void AppendTransferShaderSource(std::stringstream* src, + bool is_glsl) const override { + std::string scalar_type = is_glsl ? "float" : "half"; + *src << " v = v * " << sdr_scale_factor_ << ";\n" + << " v = max(0.0, v);\n" + << " " << scalar_type << " a = 0.17883277;\n" + << " " << scalar_type << " b = 0.28466892;\n" + << " " << scalar_type << " c = 0.55991073;\n" + << " if (v <= 1.0)\n" + " v = 0.5 * sqrt(v);\n" + " else\n" + " v = a * log(v - b) + c;\n"; + } + + private: + const float sdr_scale_factor_; +}; + +class ColorTransformPQFromLinear : public ColorTransformPerChannelTransferFn { + public: + explicit ColorTransformPQFromLinear(float sdr_white_level) + : ColorTransformPerChannelTransferFn(false), + sdr_white_level_(sdr_white_level) {} + + // ColorTransformPerChannelTransferFn implementation: + float Evaluate(float v) const override { + v *= sdr_white_level_ / 10000.0f; + v = max(0.0f, v); + float m1 = (2610.0f / 4096.0f) / 4.0f; + float m2 = (2523.0f / 4096.0f) * 128.0f; + float c1 = 3424.0f / 4096.0f; + float c2 = (2413.0f / 4096.0f) * 32.0f; + float c3 = (2392.0f / 4096.0f) * 32.0f; + float p = powf(v, m1); + return powf((c1 + c2 * p) / (1.0f + c3 * p), m2); + } + void AppendTransferShaderSource(std::stringstream* src, + bool is_glsl) const override { + std::string scalar_type = is_glsl ? "float" : "half"; + *src << " v *= " << sdr_white_level_ + << " / 10000.0;\n" + " v = max(0.0, v);\n" + << " " << scalar_type << " m1 = (2610.0 / 4096.0) / 4.0;\n" + << " " << scalar_type << " m2 = (2523.0 / 4096.0) * 128.0;\n" + << " " << scalar_type << " c1 = 3424.0 / 4096.0;\n" + << " " << scalar_type << " c2 = (2413.0 / 4096.0) * 32.0;\n" + << " " << scalar_type + << " c3 = (2392.0 / 4096.0) * 32.0;\n" + " v = pow((c1 + c2 * pow(v, m1)) / \n" + " (1.0 + c3 * pow(v, m1)), m2);\n"; + } + + private: + const float sdr_white_level_; +}; + +class ColorTransformHLGToLinear : public ColorTransformPerChannelTransferFn { + public: + explicit ColorTransformHLGToLinear(float sdr_white_level) + : ColorTransformPerChannelTransferFn(false), + sdr_scale_factor_(gfx::ColorSpace::kDefaultSDRWhiteLevel / + sdr_white_level) {} + + // ColorTransformPerChannelTransferFn implementation: + float Evaluate(float v) const override { + // Spec: http://www.arib.or.jp/english/html/overview/doc/2-STD-B67v1_0.pdf + v = max(0.0f, v); + constexpr float a = 0.17883277f; + constexpr float b = 0.28466892f; + constexpr float c = 0.55991073f; + if (v <= 0.5f) + v = v * v * 4.0f; + else + v = exp((v - c) / a) + b; + return v * sdr_scale_factor_; + } + + void AppendTransferShaderSource(std::stringstream* src, + bool is_glsl) const override { + std::string scalar_type = is_glsl ? "float" : "half"; + + *src << " v = max(0.0, v);\n" + << " " << scalar_type << " a = 0.17883277;\n" + << " " << scalar_type << " b = 0.28466892;\n" + << " " << scalar_type << " c = 0.55991073;\n" + << " if (v <= 0.5)\n" + " v = v * v * 4.0;\n" + " else\n" + " v = exp((v - c) / a) + b;\n" + " v = v * " + << sdr_scale_factor_ << ";\n"; + } + + private: + const float sdr_scale_factor_; +}; + +class ColorTransformPQToLinear : public ColorTransformPerChannelTransferFn { + public: + explicit ColorTransformPQToLinear(float sdr_white_level) + : ColorTransformPerChannelTransferFn(false), + sdr_white_level_(sdr_white_level) {} + + // ColorTransformPerChannelTransferFn implementation: + float Evaluate(float v) const override { + v = max(0.0f, v); + float m1 = (2610.0f / 4096.0f) / 4.0f; + float m2 = (2523.0f / 4096.0f) * 128.0f; + float c1 = 3424.0f / 4096.0f; + float c2 = (2413.0f / 4096.0f) * 32.0f; + float c3 = (2392.0f / 4096.0f) * 32.0f; + float p = pow(v, 1.0f / m2); + v = powf(max(p - c1, 0.0f) / (c2 - c3 * p), 1.0f / m1); + v *= 10000.0f / sdr_white_level_; + return v; + } + void AppendTransferShaderSource(std::stringstream* src, + bool is_glsl) const override { + std::string scalar_type = is_glsl ? "float" : "half"; + *src << " v = max(0.0, v);\n" + << " " << scalar_type << " m1 = (2610.0 / 4096.0) / 4.0;\n" + << " " << scalar_type << " m2 = (2523.0 / 4096.0) * 128.0;\n" + << " " << scalar_type << " c1 = 3424.0 / 4096.0;\n" + << " " << scalar_type << " c2 = (2413.0 / 4096.0) * 32.0;\n" + << " " << scalar_type << " c3 = (2392.0 / 4096.0) * 32.0;\n"; + if (is_glsl) { + *src << " #ifdef GL_FRAGMENT_PRECISION_HIGH\n" + " highp float v2 = v;\n" + " #else\n" + " float v2 = v;\n" + " #endif\n"; + } else { + *src << " " << scalar_type << " v2 = v;\n"; + } + *src << " v2 = pow(max(pow(v2, 1.0 / m2) - c1, 0.0) /\n" + " (c2 - c3 * pow(v2, 1.0 / m2)), 1.0 / m1);\n" + " v = v2 * 10000.0 / " + << sdr_white_level_ << ";\n"; + } + + private: + const float sdr_white_level_; +}; + +class ColorTransformFromLinear : public ColorTransformPerChannelTransferFn { + public: + // ColorTransformStep implementation. + explicit ColorTransformFromLinear(ColorSpace::TransferID transfer) + : ColorTransformPerChannelTransferFn(false), transfer_(transfer) {} + ColorTransformFromLinear* GetFromLinear() override { return this; } + bool IsNull() override { return transfer_ == ColorSpace::TransferID::LINEAR; } + + // ColorTransformPerChannelTransferFn implementation: + float Evaluate(float v) const override { return FromLinear(transfer_, v); } + void AppendTransferShaderSource(std::stringstream* src, + bool is_glsl) const override { + std::string scalar_type = is_glsl ? "float" : "half"; + // This is a string-ized copy-paste from FromLinear. + switch (transfer_) { + case ColorSpace::TransferID::LOG: + *src << " if (v < 0.01)\n" + " v = 0.0;\n" + " else\n" + " v = 1.0 + log(v) / log(10.0) / 2.0;\n"; + return; + case ColorSpace::TransferID::LOG_SQRT: + *src << " if (v < sqrt(10.0) / 1000.0)\n" + " v = 0.0;\n" + " else\n" + " v = 1.0 + log(v) / log(10.0) / 2.5;\n"; + return; + case ColorSpace::TransferID::IEC61966_2_4: + *src << " " << scalar_type << " a = 1.099296826809442;\n" + << " " << scalar_type << " b = 0.018053968510807;\n" + << " if (v < -b)\n" + " v = -a * pow(-v, 0.45) + (a - 1.0);\n" + " else if (v <= b)\n" + " v = 4.5 * v;\n" + " else\n" + " v = a * pow(v, 0.45) - (a - 1.0);\n"; + return; + case ColorSpace::TransferID::BT1361_ECG: + *src << " " << scalar_type << " a = 1.099;\n" + << " " << scalar_type << " b = 0.018;\n" + << " " << scalar_type << " l = 0.0045;\n" + << " if (v < -l)\n" + " v = -(a * pow(-4.0 * v, 0.45) + (a - 1.0)) / 4.0;\n" + " else if (v <= b)\n" + " v = 4.5 * v;\n" + " else\n" + " v = a * pow(v, 0.45) - (a - 1.0);\n"; + return; + default: + break; + } + NOTREACHED(); + } + + private: + friend class ColorTransformToLinear; + ColorSpace::TransferID transfer_; +}; + +class ColorTransformToLinear : public ColorTransformPerChannelTransferFn { + public: + explicit ColorTransformToLinear(ColorSpace::TransferID transfer) + : ColorTransformPerChannelTransferFn(false), transfer_(transfer) {} + // ColorTransformStep implementation: + bool Join(ColorTransformStep* next_untyped) override { + ColorTransformFromLinear* next = next_untyped->GetFromLinear(); + if (!next) + return false; + if (transfer_ == next->transfer_) { + transfer_ = ColorSpace::TransferID::LINEAR; + return true; + } + return false; + } + bool IsNull() override { return transfer_ == ColorSpace::TransferID::LINEAR; } + + // ColorTransformPerChannelTransferFn implementation: + float Evaluate(float v) const override { return ToLinear(transfer_, v); } + + // This is a string-ized copy-paste from ToLinear. + void AppendTransferShaderSource(std::stringstream* src, + bool is_glsl) const override { + std::string scalar_type = is_glsl ? "float" : "half"; + switch (transfer_) { + case ColorSpace::TransferID::LOG: + *src << " if (v < 0.0)\n" + " v = 0.0;\n" + " else\n" + " v = pow(10.0, (v - 1.0) * 2.0);\n"; + return; + case ColorSpace::TransferID::LOG_SQRT: + *src << " if (v < 0.0)\n" + " v = 0.0;\n" + " else\n" + " v = pow(10.0, (v - 1.0) * 2.5);\n"; + return; + case ColorSpace::TransferID::IEC61966_2_4: + *src << " " << scalar_type << " a = 1.099296826809442;\n" + << " " << scalar_type << " from_linear_neg_a = -1.047844;\n" + << " " << scalar_type << " from_linear_b = 0.081243;\n" + << " if (v < from_linear_neg_a)\n" + " v = -pow((a - 1.0 - v) / a, 1.0 / 0.45);\n" + " else if (v <= from_linear_b)\n" + " v = v / 4.5;\n" + " else\n" + " v = pow((v + a - 1.0) / a, 1.0 / 0.45);\n"; + return; + case ColorSpace::TransferID::BT1361_ECG: + *src << " " << scalar_type << " a = 1.099;\n" + << " " << scalar_type << " from_linear_neg_l = -0.020250;\n" + << " " << scalar_type << " from_linear_b = 0.081000;\n" + << " if (v < from_linear_neg_l)\n" + " v = -pow((1.0 - a - v * 4.0) / a, 1.0 / 0.45) / 4.0;\n" + " else if (v <= from_linear_b)\n" + " v = v / 4.5;\n" + " else\n" + " v = pow((v + a - 1.0) / a, 1.0 / 0.45);\n"; + return; + default: + break; + } + NOTREACHED(); + } + + private: + ColorSpace::TransferID transfer_; +}; + +// BT2020 Constant Luminance is different than most other +// ways to encode RGB values as YUV. The basic idea is that +// transfer functions are applied on the Y value instead of +// on the RGB values. However, running the transfer function +// on the U and V values doesn't make any sense since they +// are centered at 0.5. To work around this, the transfer function +// is applied to the Y, R and B values, and then the U and V +// values are calculated from that. +// In our implementation, the YUV->RGB matrix is used to +// convert YUV to RYB (the G value is replaced with an Y value.) +// Then we run the transfer function like normal, and finally +// this class is inserted as an extra step which takes calculates +// the U and V values. +class ColorTransformFromBT2020CL : public ColorTransformStep { + public: + void Transform(ColorTransform::TriStim* YUV, size_t num) const override { + for (size_t i = 0; i < num; i++) { + float Y = YUV[i].x(); + float U = YUV[i].y() - 0.5; + float V = YUV[i].z() - 0.5; + float B_Y, R_Y; + if (U <= 0) { + B_Y = U * (-2.0 * -0.9702); + } else { + B_Y = U * (2.0 * 0.7910); + } + if (V <= 0) { + R_Y = V * (-2.0 * -0.8591); + } else { + R_Y = V * (2.0 * 0.4969); + } + // Return an RYB value, later steps will fix it. + YUV[i] = ColorTransform::TriStim(R_Y + Y, Y, B_Y + Y); + } + } + void AppendShaderSource(std::stringstream* hdr, + std::stringstream* src, + size_t step_index) const override { + *hdr << "vec3 BT2020_YUV_to_RYB_Step" << step_index << "(vec3 color) {" + << endl; + *hdr << " float Y = color.x;" << endl; + *hdr << " float U = color.y - 0.5;" << endl; + *hdr << " float V = color.z - 0.5;" << endl; + *hdr << " float B_Y = 0.0;" << endl; + *hdr << " float R_Y = 0.0;" << endl; + *hdr << " if (U <= 0.0) {" << endl; + *hdr << " B_Y = U * (-2.0 * -0.9702);" << endl; + *hdr << " } else {" << endl; + *hdr << " B_Y = U * (2.0 * 0.7910);" << endl; + *hdr << " }" << endl; + *hdr << " if (V <= 0.0) {" << endl; + *hdr << " R_Y = V * (-2.0 * -0.8591);" << endl; + *hdr << " } else {" << endl; + *hdr << " R_Y = V * (2.0 * 0.4969);" << endl; + *hdr << " }" << endl; + *hdr << " return vec3(R_Y + Y, Y, B_Y + Y);" << endl; + *hdr << "}" << endl; + + *src << " color.rgb = BT2020_YUV_to_RYB_Step" << step_index + << "(color.rgb);" << endl; + } + + void AppendSkShaderSource(std::stringstream* src) const override { + NOTREACHED(); + } +}; + +void ColorTransformInternal::AppendColorSpaceToColorSpaceTransform( + const ColorSpace& src, + const ColorSpace& dst, + const Options& options) { + // ITU-T H.273: If MatrixCoefficients is equal to 0 (Identity) or 8 (YCgCo), + // range adjustment is performed on R,G,B samples rather than Y,U,V samples. + const bool src_matrix_is_identity_or_ycgco = + src.GetMatrixID() == ColorSpace::MatrixID::GBR || + src.GetMatrixID() == ColorSpace::MatrixID::YCOCG; + auto src_range_adjust_matrix = std::make_unique( + GetRangeAdjustMatrix(src, options.src_bit_depth)); + + if (!src_matrix_is_identity_or_ycgco) + steps_.push_back(std::move(src_range_adjust_matrix)); + + if (src.GetMatrixID() == ColorSpace::MatrixID::BT2020_CL) { + // BT2020 CL is a special case. + steps_.push_back(std::make_unique()); + } else { + steps_.push_back(std::make_unique( + Invert(GetTransferMatrix(src, options.src_bit_depth)))); + } + + if (src_matrix_is_identity_or_ycgco) + steps_.push_back(std::move(src_range_adjust_matrix)); + + // If the target color space is not defined, just apply the adjust and + // tranfer matrices. This path is used by YUV to RGB color conversion + // when full color conversion is not enabled. + if (!dst.IsValid()) + return; + + skcms_TransferFunction src_to_linear_fn; + if (src.GetTransferFunction(&src_to_linear_fn)) { + steps_.push_back(std::make_unique( + src_to_linear_fn, src.HasExtendedSkTransferFn())); + } else if (src.GetTransferID() == ColorSpace::TransferID::ARIB_STD_B67) { + float sdr_white_level = 0.f; + src.GetSDRWhiteLevel(&sdr_white_level); + steps_.push_back( + std::make_unique(sdr_white_level)); + } else if (src.GetTransferID() == ColorSpace::TransferID::SMPTEST2084) { + float sdr_white_level = 0.f; + src.GetSDRWhiteLevel(&sdr_white_level); + steps_.push_back( + std::make_unique(sdr_white_level)); + } else if (src.GetTransferID() == ColorSpace::TransferID::PIECEWISE_HDR) { + skcms_TransferFunction fn; + float p, q, r; + ColorTransformPiecewiseHDR::GetParams(src, &fn, &p, &q, &r); + steps_.push_back(std::make_unique(fn, p, q, r)); + } else { + steps_.push_back( + std::make_unique(src.GetTransferID())); + } + + if (src.GetMatrixID() == ColorSpace::MatrixID::BT2020_CL) { + // BT2020 CL is a special case. + steps_.push_back(std::make_unique( + Invert(GetTransferMatrix(src, options.src_bit_depth)))); + } + steps_.push_back( + std::make_unique(GetPrimaryTransform(src))); + + steps_.push_back( + std::make_unique(Invert(GetPrimaryTransform(dst)))); + if (dst.GetMatrixID() == ColorSpace::MatrixID::BT2020_CL) { + // BT2020 CL is a special case. + steps_.push_back(std::make_unique( + GetTransferMatrix(dst, options.dst_bit_depth))); + } + + skcms_TransferFunction dst_from_linear_fn; + if (dst.GetInverseTransferFunction(&dst_from_linear_fn)) { + steps_.push_back(std::make_unique( + dst_from_linear_fn, dst.HasExtendedSkTransferFn())); + } else if (dst.GetTransferID() == ColorSpace::TransferID::ARIB_STD_B67) { + float sdr_white_level = 0.f; + dst.GetSDRWhiteLevel(&sdr_white_level); + steps_.push_back( + std::make_unique(sdr_white_level)); + } else if (dst.GetTransferID() == ColorSpace::TransferID::SMPTEST2084) { + float sdr_white_level = 0.f; + dst.GetSDRWhiteLevel(&sdr_white_level); + steps_.push_back( + std::make_unique(sdr_white_level)); + } else if (dst.GetTransferID() == ColorSpace::TransferID::PIECEWISE_HDR) { + skcms_TransferFunction fn; + float p, q, r; + ColorTransformPiecewiseHDR::GetParams(dst, &fn, &p, &q, &r); + ColorTransformPiecewiseHDR::InvertParams(&fn, &p, &q, &r); + steps_.push_back(std::make_unique(fn, p, q, r)); + } else { + steps_.push_back( + std::make_unique(dst.GetTransferID())); + } + + // ITU-T H.273: If MatrixCoefficients is equal to 0 (Identity) or 8 (YCgCo), + // range adjustment is performed on R,G,B samples rather than Y,U,V samples. + const bool dst_matrix_is_identity_or_ycgco = + dst.GetMatrixID() == ColorSpace::MatrixID::GBR || + dst.GetMatrixID() == ColorSpace::MatrixID::YCOCG; + auto dst_range_adjust_matrix = std::make_unique( + Invert(GetRangeAdjustMatrix(dst, options.dst_bit_depth))); + + if (dst_matrix_is_identity_or_ycgco) + steps_.push_back(std::move(dst_range_adjust_matrix)); + + if (dst.GetMatrixID() == ColorSpace::MatrixID::BT2020_CL) { + NOTREACHED(); + } else { + steps_.push_back(std::make_unique( + GetTransferMatrix(dst, options.dst_bit_depth))); + } + + if (!dst_matrix_is_identity_or_ycgco) + steps_.push_back(std::move(dst_range_adjust_matrix)); +} + +ColorTransformInternal::ColorTransformInternal(const ColorSpace& src, + const ColorSpace& dst, + const Options& options) + : src_(src), dst_(dst) { + // If no source color space is specified, do no transformation. + // TODO(ccameron): We may want dst assume sRGB at some point in the future. + if (!src_.IsValid()) + return; + AppendColorSpaceToColorSpaceTransform(src_, dst_, options); + if (!options.disable_optimizations) + Simplify(); +} + +std::string ColorTransformInternal::GetShaderSource() const { + std::stringstream hdr; + std::stringstream src; + InitStringStream(&hdr); + InitStringStream(&src); + src << "vec3 DoColorConversion(vec3 color) {" << endl; + size_t step_index = 0; + for (const auto& step : steps_) + step->AppendShaderSource(&hdr, &src, step_index++); + src << " return color;" << endl; + src << "}" << endl; + return hdr.str() + src.str(); +} + +std::string ColorTransformInternal::GetSkShaderSource() const { + std::stringstream src; + InitStringStream(&src); + for (const auto& step : steps_) + step->AppendSkShaderSource(&src); + return src.str(); +} + +ColorTransformInternal::~ColorTransformInternal() {} + +void ColorTransformInternal::Simplify() { + for (auto iter = steps_.begin(); iter != steps_.end();) { + std::unique_ptr& this_step = *iter; + + // Try to Join |next_step| into |this_step|. If successful, re-visit the + // step before |this_step|. + auto iter_next = iter; + iter_next++; + if (iter_next != steps_.end()) { + std::unique_ptr& next_step = *iter_next; + if (this_step->Join(next_step.get())) { + steps_.erase(iter_next); + if (iter != steps_.begin()) + --iter; + continue; + } + } + + // If |this_step| step is a no-op, remove it, and re-visit the step before + // |this_step|. + if (this_step->IsNull()) { + iter = steps_.erase(iter); + if (iter != steps_.begin()) + --iter; + continue; + } + + ++iter; + } +} + +// static +std::unique_ptr ColorTransform::NewColorTransform( + const ColorSpace& src, + const ColorSpace& dst) { + Options options; + return std::make_unique(src, dst, options); +} + +// static +std::unique_ptr ColorTransform::NewColorTransform( + const ColorSpace& src, + const ColorSpace& dst, + const Options& options) { + return std::make_unique(src, dst, options); +} + +ColorTransform::ColorTransform() {} +ColorTransform::~ColorTransform() {} + +} // namespace gfx diff --git a/color_transform.h b/color_transform.h new file mode 100644 index 000000000000..62bc935f9799 --- /dev/null +++ b/color_transform.h @@ -0,0 +1,80 @@ +// Copyright (c) 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_COLOR_TRANSFORM_H_ +#define UI_GFX_COLOR_TRANSFORM_H_ + +#include +#include + +#include "base/macros.h" +#include "ui/gfx/color_space.h" +#include "ui/gfx/geometry/point3_f.h" +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +class GFX_EXPORT ColorTransform { + public: + struct Options { + // Used in testing to verify that optimizations have no effect. + bool disable_optimizations = false; + + // Used to adjust the transfer and range adjust matrices. + uint32_t src_bit_depth = kDefaultBitDepth; + uint32_t dst_bit_depth = kDefaultBitDepth; + }; + + // TriStimulus is a color coordinate in any color space. + // Channel order is XYZ, RGB or YUV. + typedef Point3F TriStim; + + ColorTransform(); + + ColorTransform(const ColorTransform&) = delete; + ColorTransform& operator=(const ColorTransform&) = delete; + + virtual ~ColorTransform(); + virtual gfx::ColorSpace GetSrcColorSpace() const = 0; + virtual gfx::ColorSpace GetDstColorSpace() const = 0; + + // Perform transformation of colors, |colors| is both input and output. + virtual void Transform(TriStim* colors, size_t num) const = 0; + + // Return GLSL shader source that defines a function DoColorConversion that + // converts a vec3 according to this transform. + virtual std::string GetShaderSource() const = 0; + + // Return SKSL shader sources that modifies an "inout half4 color" according + // to this transform. Input and output are non-premultiplied alpha. + virtual std::string GetSkShaderSource() const = 0; + + // Returns true if this transform is the identity. + virtual bool IsIdentity() const = 0; + + virtual size_t NumberOfStepsForTesting() const = 0; + + // Two special cases: + // 1. If no source color space is specified (i.e., src.IsValid() is false), do + // no transformation. + // 2. If the target color space is not defined (i.e., dst.IsValid() is false), + // just apply the range adjust and inverse transfer matrices. This can be used + // for YUV to RGB color conversion. + static std::unique_ptr NewColorTransform( + const ColorSpace& src, + const ColorSpace& dst); + + static std::unique_ptr NewColorTransform( + const ColorSpace& src, + const ColorSpace& dst, + const Options& options); + + private: + // The default bit depth assumed by NewColorTransform(). + static constexpr int kDefaultBitDepth = 8; +}; + +} // namespace gfx + +#endif // UI_GFX_COLOR_TRANSFORM_H_ diff --git a/color_transform_fuzzer.cc b/color_transform_fuzzer.cc new file mode 100644 index 000000000000..c3f8a93764ab --- /dev/null +++ b/color_transform_fuzzer.cc @@ -0,0 +1,87 @@ +// Copyright (c) 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include + +#include "base/at_exit.h" +#include "base/logging.h" +#include "ui/gfx/color_space.h" +#include "ui/gfx/color_transform.h" +#include "ui/gfx/icc_profile.h" + +static constexpr size_t kPixels = 256; + +static gfx::ColorTransform::TriStim pixels[kPixels]; + +static void GeneratePixels(size_t hash) { + static std::uniform_real_distribution uniform(-0.1f, 1.1f); + + std::mt19937_64 random(hash); + for (size_t i = 0; i < kPixels; ++i) + pixels[i].SetPoint(uniform(random), uniform(random), uniform(random)); +} + +static gfx::ColorSpace test; +static gfx::ColorSpace srgb; + +static void ColorTransform(size_t hash) { + const gfx::ColorTransform::Options options; + + std::unique_ptr transform; + if (hash & 2) { + transform = gfx::ColorTransform::NewColorTransform(test, srgb, options); + } else { + transform = gfx::ColorTransform::NewColorTransform(srgb, test, options); + } + + transform->Transform(pixels, kPixels); +} + +static gfx::ColorSpace CreateRGBColorSpace(size_t hash) { + auto primaries = static_cast( + 1 + ((hash >> 0) % (size_t)gfx::ColorSpace::PrimaryID::kMaxValue)); + auto transfer = static_cast( + 1 + ((hash >> 8) % (size_t)gfx::ColorSpace::TransferID::kMaxValue)); + auto matrix = static_cast( + 1 + ((hash >> 16) % (size_t)gfx::ColorSpace::MatrixID::kMaxValue)); + auto range = static_cast( + 1 + ((hash >> 24) % (size_t)gfx::ColorSpace::RangeID::kMaxValue)); + + return gfx::ColorSpace(primaries, transfer, matrix, range); +} + +inline size_t Hash(const char* data, size_t size, size_t hash = ~0) { + for (size_t i = 0; i < size; ++i) + hash = hash * 131 + *data++; + return hash; +} + +struct Environment { + Environment() { logging::SetMinLogLevel(logging::LOG_FATAL); } +}; + +Environment* environment = new Environment(); + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + base::AtExitManager at_exit; + + constexpr size_t kSizeLimit = 4 * 1024 * 1024; + if (size < 128 || size > kSizeLimit) + return 0; + + gfx::ICCProfile profile = + gfx::ICCProfile::FromData(reinterpret_cast(data), size); + if (!profile.GetColorSpace().IsValid()) + return 0; + test = profile.GetColorSpace(); + + const size_t hash = Hash(reinterpret_cast(data), size); + srgb = CreateRGBColorSpace(hash); + GeneratePixels(hash); + + ColorTransform(hash); + return 0; +} diff --git a/color_transform_unittest.cc b/color_transform_unittest.cc new file mode 100644 index 000000000000..d8fac5b789fc --- /dev/null +++ b/color_transform_unittest.cc @@ -0,0 +1,928 @@ +// Copyright (c) 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/effects/SkRuntimeEffect.h" +#include "ui/gfx/color_space.h" +#include "ui/gfx/color_transform.h" +#include "ui/gfx/geometry/transform.h" +#include "ui/gfx/icc_profile.h" +#include "ui/gfx/skia_color_space_util.h" +#include "ui/gfx/test/icc_profiles.h" + +namespace gfx { + +// Allowed math error. +const float kMathEpsilon = 0.001f; + +// Internal functions, exposted for testing. +GFX_EXPORT Transform GetTransferMatrix(ColorSpace::MatrixID id); + +ColorSpace::PrimaryID all_primaries[] = { + ColorSpace::PrimaryID::BT709, ColorSpace::PrimaryID::BT470M, + ColorSpace::PrimaryID::BT470BG, ColorSpace::PrimaryID::SMPTE170M, + ColorSpace::PrimaryID::SMPTE240M, ColorSpace::PrimaryID::FILM, + ColorSpace::PrimaryID::BT2020, ColorSpace::PrimaryID::SMPTEST428_1, + ColorSpace::PrimaryID::SMPTEST431_2, ColorSpace::PrimaryID::SMPTEST432_1, +}; + +ColorSpace::TransferID simple_transfers[] = { + ColorSpace::TransferID::BT709, + ColorSpace::TransferID::GAMMA22, + ColorSpace::TransferID::GAMMA28, + ColorSpace::TransferID::SMPTE170M, + ColorSpace::TransferID::SMPTE240M, + ColorSpace::TransferID::SMPTEST428_1, + ColorSpace::TransferID::LINEAR, + ColorSpace::TransferID::LOG, + ColorSpace::TransferID::LOG_SQRT, + ColorSpace::TransferID::IEC61966_2_4, + ColorSpace::TransferID::BT1361_ECG, + ColorSpace::TransferID::IEC61966_2_1, + ColorSpace::TransferID::BT2020_10, + ColorSpace::TransferID::BT2020_12, + ColorSpace::TransferID::SMPTEST2084, + ColorSpace::TransferID::ARIB_STD_B67, + ColorSpace::TransferID::IEC61966_2_1_HDR, +}; + +ColorSpace::TransferID extended_transfers[] = { + ColorSpace::TransferID::LINEAR_HDR, + ColorSpace::TransferID::IEC61966_2_1_HDR, +}; + +ColorSpace::MatrixID all_matrices[] = { + ColorSpace::MatrixID::RGB, ColorSpace::MatrixID::BT709, + ColorSpace::MatrixID::FCC, ColorSpace::MatrixID::BT470BG, + ColorSpace::MatrixID::SMPTE170M, ColorSpace::MatrixID::SMPTE240M, + ColorSpace::MatrixID::YCOCG, ColorSpace::MatrixID::BT2020_NCL, + ColorSpace::MatrixID::YDZDX, +}; + +ColorSpace::RangeID all_ranges[] = {ColorSpace::RangeID::FULL, + ColorSpace::RangeID::LIMITED, + ColorSpace::RangeID::DERIVED}; + +bool optimizations[] = {true, false}; + +TEST(SimpleColorSpace, BT709toSRGB) { + ColorSpace bt709 = ColorSpace::CreateREC709(); + ColorSpace sRGB = ColorSpace::CreateSRGB(); + std::unique_ptr t( + ColorTransform::NewColorTransform(bt709, sRGB)); + + ColorTransform::TriStim tmp(16.0f / 255.0f, 0.5f, 0.5f); + t->Transform(&tmp, 1); + EXPECT_NEAR(tmp.x(), 0.0f, kMathEpsilon); + EXPECT_NEAR(tmp.y(), 0.0f, kMathEpsilon); + EXPECT_NEAR(tmp.z(), 0.0f, kMathEpsilon); + + tmp = ColorTransform::TriStim(235.0f / 255.0f, 0.5f, 0.5f); + t->Transform(&tmp, 1); + EXPECT_NEAR(tmp.x(), 1.0f, kMathEpsilon); + EXPECT_NEAR(tmp.y(), 1.0f, kMathEpsilon); + EXPECT_NEAR(tmp.z(), 1.0f, kMathEpsilon); + + // Test a blue color + tmp = ColorTransform::TriStim(128.0f / 255.0f, 240.0f / 255.0f, 0.5f); + t->Transform(&tmp, 1); + EXPECT_GT(tmp.z(), tmp.x()); + EXPECT_GT(tmp.z(), tmp.y()); +} + +TEST(SimpleColorSpace, BT2020CLtoBT2020RGB) { + ColorSpace bt2020cl( + ColorSpace::PrimaryID::BT2020, ColorSpace::TransferID::BT2020_10, + ColorSpace::MatrixID::BT2020_CL, ColorSpace::RangeID::LIMITED); + ColorSpace bt2020rgb(ColorSpace::PrimaryID::BT2020, + ColorSpace::TransferID::BT2020_10, + ColorSpace::MatrixID::RGB, ColorSpace::RangeID::FULL); + std::unique_ptr t( + ColorTransform::NewColorTransform(bt2020cl, bt2020rgb)); + + ColorTransform::TriStim tmp(16.0f / 255.0f, 0.5f, 0.5f); + t->Transform(&tmp, 1); + EXPECT_NEAR(tmp.x(), 0.0f, kMathEpsilon); + EXPECT_NEAR(tmp.y(), 0.0f, kMathEpsilon); + EXPECT_NEAR(tmp.z(), 0.0f, kMathEpsilon); + + tmp = ColorTransform::TriStim(235.0f / 255.0f, 0.5f, 0.5f); + t->Transform(&tmp, 1); + EXPECT_NEAR(tmp.x(), 1.0f, kMathEpsilon); + EXPECT_NEAR(tmp.y(), 1.0f, kMathEpsilon); + EXPECT_NEAR(tmp.z(), 1.0f, kMathEpsilon); + + // Test a blue color + tmp = ColorTransform::TriStim(128.0f / 255.0f, 240.0f / 255.0f, 0.5f); + t->Transform(&tmp, 1); + EXPECT_GT(tmp.z(), tmp.x()); + EXPECT_GT(tmp.z(), tmp.y()); +} + +TEST(SimpleColorSpace, YCOCGLimitedToSRGB) { + ColorSpace ycocg(ColorSpace::PrimaryID::BT709, + ColorSpace::TransferID::IEC61966_2_1, + ColorSpace::MatrixID::YCOCG, ColorSpace::RangeID::LIMITED); + ColorSpace sRGB = ColorSpace::CreateSRGB(); + std::unique_ptr t( + ColorTransform::NewColorTransform(ycocg, sRGB)); + + ColorTransform::TriStim tmp(16.0f / 255.0f, 128.0f / 255.0f, 128.0f / 255.0f); + t->Transform(&tmp, 1); + EXPECT_NEAR(tmp.x(), 0.0f, kMathEpsilon); + EXPECT_NEAR(tmp.y(), 0.0f, kMathEpsilon); + EXPECT_NEAR(tmp.z(), 0.0f, kMathEpsilon); + + tmp = ColorTransform::TriStim(235.0f / 255.0f, 128.0f / 255.0f, + 128.0f / 255.0f); + t->Transform(&tmp, 1); + EXPECT_NEAR(tmp.x(), 1.0f, kMathEpsilon); + EXPECT_NEAR(tmp.y(), 1.0f, kMathEpsilon); + EXPECT_NEAR(tmp.z(), 1.0f, kMathEpsilon); + + // Test a blue color + // Use the equations for MatrixCoefficients 8 and VideoFullRangeFlag 0 in + // ITU-T H.273: + // Equations 11-13: E'_R = 0.0, E'_G = 0.0, E'_B = 1.0 + // Equations 20-22: R = 16, G = 16, B = 219 + 16 = 235 + // Equations 44-46: + // Y = Round(0.5 * 16 + 0.25 * (16 + 235)) = Round(70.75) = 71 + // Cb = Round(0.5 * 16 - 0.25 * (16 + 235)) + 128 = Round(-54.75) + 128 = 73 + // Cr = Round(0.5 * (16 - 235)) + 128 = Round(-109.5) + 128 = 18 + // In this test we omit the Round() calls to avoid rounding errors. + // Y = 0.5 * 16 + 0.25 * (16 + 235) = 70.75 + // Cb = 0.5 * 16 - 0.25 * (16 + 235) + 128 = -54.75 + 128 = 73.25 + // Cr = 0.5 * (16 - 235) + 128 = -109.5 + 128 = 18.5 + tmp = + ColorTransform::TriStim(70.75f / 255.0f, 73.25f / 255.0f, 18.5f / 255.0f); + t->Transform(&tmp, 1); + EXPECT_NEAR(tmp.x(), 0.0f, kMathEpsilon); + EXPECT_NEAR(tmp.y(), 0.0f, kMathEpsilon); + EXPECT_NEAR(tmp.z(), 1.0f, kMathEpsilon); +} + +TEST(SimpleColorSpace, TransferFnCancel) { + ColorSpace::PrimaryID primary = ColorSpace::PrimaryID::BT709; + ColorSpace::MatrixID matrix = ColorSpace::MatrixID::RGB; + ColorSpace::RangeID range = ColorSpace::RangeID::FULL; + + // BT709 has a gamma of 2.2222 (with some adjustments) + ColorSpace bt709(primary, ColorSpace::TransferID::BT709, matrix, range); + + // IEC61966_2_1 has the sRGB gamma of 2.4 (with some adjustments) + ColorSpace srgb(primary, ColorSpace::TransferID::IEC61966_2_1, matrix, range); + + // gamma28 is a simple exponential + ColorSpace gamma28(primary, ColorSpace::TransferID::GAMMA28, matrix, range); + + // gamma24 is a simple exponential + ColorSpace gamma24(primary, ColorSpace::TransferID::GAMMA24, matrix, range); + + // BT709 source is common for video and sRGB destination is common for + // monitors. The two transfer functions are very close, and should cancel + // out (so the transfer between them should be the identity). This particular + // case is important for power reasons. + std::unique_ptr bt709_to_srgb( + ColorTransform::NewColorTransform(bt709, srgb)); + EXPECT_EQ(bt709_to_srgb->NumberOfStepsForTesting(), 0u); + + // Gamma 2.8 isn't even close to BT709 and won't cancel out (so we will have + // two steps in the transform -- to-linear and from-linear). + std::unique_ptr bt709_to_gamma28( + ColorTransform::NewColorTransform(bt709, gamma28)); + EXPECT_EQ(bt709_to_gamma28->NumberOfStepsForTesting(), 2u); + + // Gamma 2.4 is closer to BT709, but not close enough to actually cancel out. + std::unique_ptr bt709_to_gamma24( + ColorTransform::NewColorTransform(bt709, gamma24)); + EXPECT_EQ(bt709_to_gamma24->NumberOfStepsForTesting(), 2u); + + // Rec 601 YUV to RGB conversion should have a single step. + gfx::ColorSpace rec601 = gfx::ColorSpace::CreateREC601(); + std::unique_ptr rec601_yuv_to_rgb( + ColorTransform::NewColorTransform(rec601, rec601.GetAsFullRangeRGB())); + EXPECT_EQ(rec601_yuv_to_rgb->NumberOfStepsForTesting(), 1u); +} + +TEST(SimpleColorSpace, SRGBFromICCAndNotICC) { + float kPixelEpsilon = kMathEpsilon; + ColorTransform::TriStim value_fromicc; + ColorTransform::TriStim value_default; + + ICCProfile srgb_icc_profile = ICCProfileForTestingSRGB(); + ColorSpace srgb_fromicc = srgb_icc_profile.GetColorSpace(); + ColorSpace srgb_default = gfx::ColorSpace::CreateSRGB(); + ColorSpace xyzd50 = gfx::ColorSpace::CreateXYZD50(); + + value_fromicc = value_default = ColorTransform::TriStim(0.1f, 0.5f, 0.9f); + + std::unique_ptr toxyzd50_fromicc( + ColorTransform::NewColorTransform(srgb_fromicc, xyzd50)); + // This will be converted to a transfer function and then linear transform. + EXPECT_EQ(toxyzd50_fromicc->NumberOfStepsForTesting(), 2u); + toxyzd50_fromicc->Transform(&value_fromicc, 1); + + std::unique_ptr toxyzd50_default( + ColorTransform::NewColorTransform(srgb_default, xyzd50)); + // This will have a transfer function and then linear transform. + EXPECT_EQ(toxyzd50_default->NumberOfStepsForTesting(), 2u); + toxyzd50_default->Transform(&value_default, 1); + + EXPECT_NEAR(value_fromicc.x(), value_default.x(), kPixelEpsilon); + EXPECT_NEAR(value_fromicc.y(), value_default.y(), kPixelEpsilon); + EXPECT_NEAR(value_fromicc.z(), value_default.z(), kPixelEpsilon); + + value_fromicc = value_default = ColorTransform::TriStim(0.1f, 0.5f, 0.9f); + + std::unique_ptr fromxyzd50_fromicc( + ColorTransform::NewColorTransform(xyzd50, srgb_fromicc)); + fromxyzd50_fromicc->Transform(&value_fromicc, 1); + + std::unique_ptr fromxyzd50_default( + ColorTransform::NewColorTransform(xyzd50, srgb_default)); + fromxyzd50_default->Transform(&value_default, 1); + + EXPECT_NEAR(value_fromicc.x(), value_default.x(), kPixelEpsilon); + EXPECT_NEAR(value_fromicc.y(), value_default.y(), kPixelEpsilon); + EXPECT_NEAR(value_fromicc.z(), value_default.z(), kPixelEpsilon); +} + +TEST(SimpleColorSpace, BT709toSRGBICC) { + ICCProfile srgb_icc = ICCProfileForTestingSRGB(); + ColorSpace bt709 = ColorSpace::CreateREC709(); + ColorSpace sRGB = srgb_icc.GetColorSpace(); + std::unique_ptr t( + ColorTransform::NewColorTransform(bt709, sRGB)); + + ColorTransform::TriStim tmp(16.0f / 255.0f, 0.5f, 0.5f); + t->Transform(&tmp, 1); + EXPECT_NEAR(tmp.x(), 0.0f, kMathEpsilon); + EXPECT_NEAR(tmp.y(), 0.0f, kMathEpsilon); + EXPECT_NEAR(tmp.z(), 0.0f, kMathEpsilon); + + tmp = ColorTransform::TriStim(235.0f / 255.0f, 0.5f, 0.5f); + t->Transform(&tmp, 1); + EXPECT_NEAR(tmp.x(), 1.0f, kMathEpsilon); + EXPECT_NEAR(tmp.y(), 1.0f, kMathEpsilon); + EXPECT_NEAR(tmp.z(), 1.0f, kMathEpsilon); + + // Test a blue color + tmp = ColorTransform::TriStim(128.0f / 255.0f, 240.0f / 255.0f, 0.5f); + t->Transform(&tmp, 1); + EXPECT_GT(tmp.z(), tmp.x()); + EXPECT_GT(tmp.z(), tmp.y()); +} + +TEST(SimpleColorSpace, ICCProfileOnlyXYZ) { + const float kPixelEpsilon = 2.5f / 255.f; + ICCProfile icc_profile = ICCProfileForTestingNoAnalyticTrFn(); + ColorSpace icc_space = icc_profile.GetColorSpace(); + ColorSpace xyzd50 = ColorSpace::CreateXYZD50(); + + ColorTransform::TriStim input_value(127.f / 255, 187.f / 255, 157.f / 255); + ColorTransform::TriStim transformed_value = input_value; + ColorTransform::TriStim expected_transformed_value( + 0.34090986847877502f, 0.42633286118507385f, 0.3408740758895874f); + + // Two steps should be needed, transfer fn and matrix. + std::unique_ptr icc_to_xyzd50( + ColorTransform::NewColorTransform(icc_space, xyzd50)); + EXPECT_EQ(icc_to_xyzd50->NumberOfStepsForTesting(), 2u); + icc_to_xyzd50->Transform(&transformed_value, 1); + EXPECT_NEAR(transformed_value.x(), expected_transformed_value.x(), + kPixelEpsilon); + EXPECT_NEAR(transformed_value.y(), expected_transformed_value.y(), + kPixelEpsilon); + EXPECT_NEAR(transformed_value.z(), expected_transformed_value.z(), + kPixelEpsilon); + + // Two steps should be needed, matrix and transfer fn. + std::unique_ptr xyzd50_to_icc( + ColorTransform::NewColorTransform(xyzd50, icc_space)); + EXPECT_EQ(xyzd50_to_icc->NumberOfStepsForTesting(), 2u); + xyzd50_to_icc->Transform(&transformed_value, 1); + EXPECT_NEAR(input_value.x(), transformed_value.x(), kPixelEpsilon); + EXPECT_NEAR(input_value.y(), transformed_value.y(), kPixelEpsilon); + EXPECT_NEAR(input_value.z(), transformed_value.z(), kPixelEpsilon); +} + +TEST(SimpleColorSpace, ICCProfileOnlyColorSpin) { + const float kPixelEpsilon = 3.0f / 255.f; + ICCProfile icc_profile = ICCProfileForTestingNoAnalyticTrFn(); + ColorSpace icc_space = icc_profile.GetColorSpace(); + ColorSpace colorspin = ICCProfileForTestingColorSpin().GetColorSpace(); + + ColorTransform::TriStim input_value(0.25f, 0.5f, 0.75f); + ColorTransform::TriStim transformed_value = input_value; + ColorTransform::TriStim expected_transformed_value( + 0.49694931507110596f, 0.74937951564788818f, 0.31359460949897766f); + + // Three steps will be needed. + std::unique_ptr icc_to_colorspin( + ColorTransform::NewColorTransform(icc_space, colorspin)); + EXPECT_EQ(icc_to_colorspin->NumberOfStepsForTesting(), 3u); + icc_to_colorspin->Transform(&transformed_value, 1); + EXPECT_NEAR(transformed_value.x(), expected_transformed_value.x(), + kPixelEpsilon); + EXPECT_NEAR(transformed_value.y(), expected_transformed_value.y(), + kPixelEpsilon); + EXPECT_NEAR(transformed_value.z(), expected_transformed_value.z(), + kPixelEpsilon); + + transformed_value = expected_transformed_value; + std::unique_ptr colorspin_to_icc( + ColorTransform::NewColorTransform(colorspin, icc_space)); + EXPECT_EQ(colorspin_to_icc->NumberOfStepsForTesting(), 3u); + transformed_value = expected_transformed_value; + colorspin_to_icc->Transform(&transformed_value, 1); + EXPECT_NEAR(input_value.x(), transformed_value.x(), kPixelEpsilon); + EXPECT_NEAR(input_value.y(), transformed_value.y(), kPixelEpsilon); + EXPECT_NEAR(input_value.z(), transformed_value.z(), kPixelEpsilon); +} + +TEST(SimpleColorSpace, GetColorSpace) { + const float kPixelEpsilon = 1.5f / 255.f; + ICCProfile srgb_icc = ICCProfileForTestingSRGB(); + ColorSpace sRGB = srgb_icc.GetColorSpace(); + ColorSpace sRGB2 = sRGB; + + std::unique_ptr t( + ColorTransform::NewColorTransform(sRGB, sRGB2)); + + ColorTransform::TriStim tmp(1.0f, 1.0f, 1.0f); + t->Transform(&tmp, 1); + EXPECT_NEAR(tmp.x(), 1.0f, kPixelEpsilon); + EXPECT_NEAR(tmp.y(), 1.0f, kPixelEpsilon); + EXPECT_NEAR(tmp.z(), 1.0f, kPixelEpsilon); + + tmp = ColorTransform::TriStim(1.0f, 0.0f, 0.0f); + t->Transform(&tmp, 1); + EXPECT_NEAR(tmp.x(), 1.0f, kPixelEpsilon); + EXPECT_NEAR(tmp.y(), 0.0f, kPixelEpsilon); + EXPECT_NEAR(tmp.z(), 0.0f, kPixelEpsilon); + + tmp = ColorTransform::TriStim(0.0f, 1.0f, 0.0f); + t->Transform(&tmp, 1); + EXPECT_NEAR(tmp.x(), 0.0f, kPixelEpsilon); + EXPECT_NEAR(tmp.y(), 1.0f, kPixelEpsilon); + EXPECT_NEAR(tmp.z(), 0.0f, kPixelEpsilon); + + tmp = ColorTransform::TriStim(0.0f, 0.0f, 1.0f); + t->Transform(&tmp, 1); + EXPECT_NEAR(tmp.x(), 0.0f, kPixelEpsilon); + EXPECT_NEAR(tmp.y(), 0.0f, kPixelEpsilon); + EXPECT_NEAR(tmp.z(), 1.0f, kPixelEpsilon); +} + +TEST(SimpleColorSpace, Scale) { + const float kPixelEpsilon = 1.5f / 255.f; + ColorSpace srgb = ColorSpace::CreateSRGB(); + ColorSpace srgb_scaled = srgb.GetScaledColorSpace(2.0f); + std::unique_ptr t( + ColorTransform::NewColorTransform(srgb, srgb_scaled)); + + ColorTransform::TriStim tmp(1.0f, 1.0f, 1.0f); + t->Transform(&tmp, 1); + EXPECT_NEAR(tmp.x(), 0.735356983052449f, kPixelEpsilon); + EXPECT_NEAR(tmp.y(), 0.735356983052449f, kPixelEpsilon); + EXPECT_NEAR(tmp.z(), 0.735356983052449f, kPixelEpsilon); +} + +TEST(SimpleColorSpace, ToUndefined) { + ColorSpace null; + ColorSpace nonnull = gfx::ColorSpace::CreateSRGB(); + // Video should have 1 step: YUV to RGB. + // Anything else should have 0 steps. + ColorSpace video = gfx::ColorSpace::CreateREC709(); + std::unique_ptr video_to_null( + ColorTransform::NewColorTransform(video, null)); + EXPECT_EQ(video_to_null->NumberOfStepsForTesting(), 1u); + // Without optimization, video should have 2 steps: limited range to full + // range, and YUV to RGB. + ColorTransform::Options options; + options.disable_optimizations = true; + std::unique_ptr video_to_null_no_opt( + ColorTransform::NewColorTransform(video, null, options)); + EXPECT_EQ(video_to_null_no_opt->NumberOfStepsForTesting(), 2u); + + // Test with an ICC profile that can't be represented as matrix+transfer. + ColorSpace luttrcicc = ICCProfileForTestingNoAnalyticTrFn().GetColorSpace(); + std::unique_ptr luttrcicc_to_null( + ColorTransform::NewColorTransform(luttrcicc, null)); + EXPECT_EQ(luttrcicc_to_null->NumberOfStepsForTesting(), 0u); + std::unique_ptr luttrcicc_to_nonnull( + ColorTransform::NewColorTransform(luttrcicc, nonnull)); + EXPECT_GT(luttrcicc_to_nonnull->NumberOfStepsForTesting(), 0u); + + // Test with an ICC profile that can. + ColorSpace adobeicc = ICCProfileForTestingAdobeRGB().GetColorSpace(); + std::unique_ptr adobeicc_to_null( + ColorTransform::NewColorTransform(adobeicc, null)); + EXPECT_EQ(adobeicc_to_null->NumberOfStepsForTesting(), 0u); + std::unique_ptr adobeicc_to_nonnull( + ColorTransform::NewColorTransform(adobeicc, nonnull)); + EXPECT_GT(adobeicc_to_nonnull->NumberOfStepsForTesting(), 0u); + + // And with something analytic. + ColorSpace xyzd50 = gfx::ColorSpace::CreateXYZD50(); + std::unique_ptr xyzd50_to_null( + ColorTransform::NewColorTransform(xyzd50, null)); + EXPECT_EQ(xyzd50_to_null->NumberOfStepsForTesting(), 0u); + std::unique_ptr xyzd50_to_nonnull( + ColorTransform::NewColorTransform(xyzd50, nonnull)); + EXPECT_GT(xyzd50_to_nonnull->NumberOfStepsForTesting(), 0u); +} + +TEST(SimpleColorSpace, DefaultToSRGB) { + // The default value should do no transformation, regardless of destination. + ColorSpace unknown; + std::unique_ptr t1( + ColorTransform::NewColorTransform(unknown, ColorSpace::CreateSRGB())); + EXPECT_EQ(t1->NumberOfStepsForTesting(), 0u); + std::unique_ptr t2( + ColorTransform::NewColorTransform(unknown, ColorSpace::CreateXYZD50())); + EXPECT_EQ(t2->NumberOfStepsForTesting(), 0u); +} + +// This tests to make sure that we don't emit "pow" parts of a +// transfer function unless necessary. +TEST(SimpleColorSpace, ShaderSourceTrFnOptimizations) { + skcms_Matrix3x3 primaries; + gfx::ColorSpace::CreateSRGB().GetPrimaryMatrix(&primaries); + + skcms_TransferFunction fn_no_pow = { + 1.f, 2.f, 0.f, 1.f, 0.f, 0.f, 0.f, + }; + skcms_TransferFunction fn_yes_pow = { + 2.f, 2.f, 0.f, 1.f, 0.f, 0.f, 0.f, + }; + gfx::ColorSpace src; + gfx::ColorSpace dst = gfx::ColorSpace::CreateXYZD50(); + std::string shader_string; + + src = gfx::ColorSpace::CreateCustom(primaries, fn_no_pow); + shader_string = + ColorTransform::NewColorTransform(src, dst)->GetShaderSource(); + EXPECT_EQ(shader_string.find("pow("), std::string::npos); + + src = gfx::ColorSpace::CreateCustom(primaries, fn_yes_pow); + shader_string = + ColorTransform::NewColorTransform(src, dst)->GetShaderSource(); + EXPECT_NE(shader_string.find("pow("), std::string::npos); +} + +// Note: This is not actually "testing" anything -- the goal of this test is to +// to make reviewing shader code simpler by giving an example of the resulting +// shader source. This should be updated whenever shader generation is updated. +// This test produces slightly different results on Android. +TEST(SimpleColorSpace, SampleShaderSource) { + ColorSpace bt709 = ColorSpace::CreateREC709(); + ColorSpace output(ColorSpace::PrimaryID::BT2020, + ColorSpace::TransferID::GAMMA28); + std::string source = + ColorTransform::NewColorTransform(bt709, output)->GetShaderSource(); + std::string expected = + "float TransferFn1(float v) {\n" + " if (v < 4.04499359e-02)\n" + " v = 7.73993805e-02 * v;\n" + " else\n" + " v = pow(9.47867334e-01 * v + 5.21326549e-02, 2.40000010e+00);\n" + " return v;\n" + "}\n" + "float TransferFn3(float v) {\n" + " if (v < 0.00000000e+00)\n" + " v = 0.00000000e+00 * v;\n" + " else\n" + " v = pow(v, 3.57142866e-01);\n" + " return v;\n" + "}\n" + "vec3 DoColorConversion(vec3 color) {\n" + " color = mat3(1.16438353e+00, 1.16438353e+00, 1.16438353e+00,\n" + " -2.28029018e-09, -2.13248596e-01, 2.11240172e+00,\n" + " 1.79274118e+00, -5.32909274e-01, -5.96049432e-10) " + "* color;\n" + " color += vec3(-9.69429970e-01, 3.00019622e-01, -1.12926030e+00);\n" + " color.r = TransferFn1(color.r);\n" + " color.g = TransferFn1(color.g);\n" + " color.b = TransferFn1(color.b);\n" + " color = mat3(6.27404153e-01, 6.90974146e-02, 1.63914431e-02,\n" + " 3.29283088e-01, 9.19540644e-01, 8.80132765e-02,\n" + " 4.33131084e-02, 1.13623096e-02, 8.95595253e-01) " + "* color;\n" + " color.r = TransferFn3(color.r);\n" + " color.g = TransferFn3(color.g);\n" + " color.b = TransferFn3(color.b);\n" + " return color;\n" + "}\n"; + EXPECT_EQ(source, expected); +} + +// Checks that the generated SkSL fragment shaders can be parsed by +// SkSL::Compiler. +TEST(SimpleColorSpace, CanParseSkShaderSource) { + std::vector common_color_spaces = { + ColorSpace::CreateSRGB(), ColorSpace::CreateDisplayP3D65(), + ColorSpace::CreateExtendedSRGB(), ColorSpace::CreateSCRGBLinear(), + ColorSpace::CreateJpeg(), ColorSpace::CreateREC601(), + ColorSpace::CreateREC709()}; + for (const auto& src : common_color_spaces) { + for (const auto& dst : common_color_spaces) { + auto transform = ColorTransform::NewColorTransform(src, dst); + std::string source = "half4 main(half4 color) {\n" + + transform->GetSkShaderSource() + " return color; }"; + SkRuntimeEffect::Result result = SkRuntimeEffect::MakeForColorFilter( + SkString(source.c_str(), source.length()), /*options=*/{}); + EXPECT_NE(result.effect, nullptr); + EXPECT_STREQ(result.errorText.c_str(), ""); + } + } +} + +class TransferTest : public testing::TestWithParam {}; + +TEST_P(TransferTest, basicTest) { + gfx::ColorSpace space_with_transfer(ColorSpace::PrimaryID::BT709, GetParam(), + ColorSpace::MatrixID::RGB, + ColorSpace::RangeID::FULL); + gfx::ColorSpace space_linear( + ColorSpace::PrimaryID::BT709, ColorSpace::TransferID::LINEAR, + ColorSpace::MatrixID::RGB, ColorSpace::RangeID::FULL); + + std::unique_ptr to_linear( + ColorTransform::NewColorTransform(space_with_transfer, space_linear)); + + std::unique_ptr from_linear( + ColorTransform::NewColorTransform(space_linear, space_with_transfer)); + + // The transforms will have 1 or 0 steps (0 for linear). + size_t expected_steps = 1u; + if (GetParam() == ColorSpace::TransferID::LINEAR) + expected_steps = 0u; + EXPECT_EQ(to_linear->NumberOfStepsForTesting(), expected_steps); + EXPECT_EQ(from_linear->NumberOfStepsForTesting(), expected_steps); + + for (float x = 0.0f; x <= 1.0f; x += 1.0f / 128.0f) { + ColorTransform::TriStim tristim(x, x, x); + to_linear->Transform(&tristim, 1); + from_linear->Transform(&tristim, 1); + EXPECT_NEAR(x, tristim.x(), kMathEpsilon); + } +} + +INSTANTIATE_TEST_SUITE_P(ColorSpace, + TransferTest, + testing::ValuesIn(simple_transfers)); + +class ExtendedTransferTest + : public testing::TestWithParam {}; + +TEST_P(ExtendedTransferTest, extendedTest) { + gfx::ColorSpace space_with_transfer(ColorSpace::PrimaryID::BT709, GetParam(), + ColorSpace::MatrixID::RGB, + ColorSpace::RangeID::FULL); + gfx::ColorSpace space_linear( + ColorSpace::PrimaryID::BT709, ColorSpace::TransferID::LINEAR, + ColorSpace::MatrixID::RGB, ColorSpace::RangeID::FULL); + + std::unique_ptr to_linear( + ColorTransform::NewColorTransform(space_with_transfer, space_linear)); + + std::unique_ptr from_linear( + ColorTransform::NewColorTransform(space_linear, space_with_transfer)); + + for (float x = -2.0f; x <= 2.0f; x += 1.0f / 32.0f) { + ColorTransform::TriStim tristim(x, x, x); + to_linear->Transform(&tristim, 1); + from_linear->Transform(&tristim, 1); + EXPECT_NEAR(x, tristim.x(), kMathEpsilon); + } +} + +INSTANTIATE_TEST_SUITE_P(ColorSpace, + ExtendedTransferTest, + testing::ValuesIn(extended_transfers)); + +typedef std::tuple + ColorSpaceTestData; + +class ColorSpaceTest : public testing::TestWithParam { + public: + ColorSpaceTest() + : color_space_(std::get<0>(GetParam()), + std::get<1>(GetParam()), + std::get<2>(GetParam()), + std::get<3>(GetParam())) { + options_.disable_optimizations = std::get<4>(GetParam()); + } + + protected: + ColorSpace color_space_; + ColorTransform::Options options_; +}; + +TEST_P(ColorSpaceTest, testNullTransform) { + std::unique_ptr t( + ColorTransform::NewColorTransform(color_space_, color_space_, options_)); + ColorTransform::TriStim tristim(0.4f, 0.5f, 0.6f); + t->Transform(&tristim, 1); + EXPECT_NEAR(tristim.x(), 0.4f, kMathEpsilon); + EXPECT_NEAR(tristim.y(), 0.5f, kMathEpsilon); + EXPECT_NEAR(tristim.z(), 0.6f, kMathEpsilon); +} + +TEST_P(ColorSpaceTest, toXYZandBack) { + std::unique_ptr t1(ColorTransform::NewColorTransform( + color_space_, ColorSpace::CreateXYZD50(), options_)); + std::unique_ptr t2(ColorTransform::NewColorTransform( + ColorSpace::CreateXYZD50(), color_space_, options_)); + ColorTransform::TriStim tristim(0.4f, 0.5f, 0.6f); + t1->Transform(&tristim, 1); + t2->Transform(&tristim, 1); + EXPECT_NEAR(tristim.x(), 0.4f, kMathEpsilon); + EXPECT_NEAR(tristim.y(), 0.5f, kMathEpsilon); + EXPECT_NEAR(tristim.z(), 0.6f, kMathEpsilon); +} + +INSTANTIATE_TEST_SUITE_P( + A, + ColorSpaceTest, + testing::Combine(testing::ValuesIn(all_primaries), + testing::ValuesIn(simple_transfers), + testing::Values(ColorSpace::MatrixID::BT709), + testing::Values(ColorSpace::RangeID::LIMITED), + testing::ValuesIn(optimizations))); + +INSTANTIATE_TEST_SUITE_P( + B, + ColorSpaceTest, + testing::Combine(testing::Values(ColorSpace::PrimaryID::BT709), + testing::ValuesIn(simple_transfers), + testing::ValuesIn(all_matrices), + testing::ValuesIn(all_ranges), + testing::ValuesIn(optimizations))); + +INSTANTIATE_TEST_SUITE_P( + C, + ColorSpaceTest, + testing::Combine(testing::ValuesIn(all_primaries), + testing::Values(ColorSpace::TransferID::BT709), + testing::ValuesIn(all_matrices), + testing::ValuesIn(all_ranges), + testing::ValuesIn(optimizations))); + +TEST(ColorSpaceTest, ExtendedSRGBScale) { + ColorSpace space_unscaled = ColorSpace::CreateSRGB(); + float scale = 3.14; + skcms_TransferFunction scaled_trfn = + SkTransferFnScaled(*skcms_sRGB_TransferFunction(), scale); + ColorSpace space_scaled(ColorSpace::PrimaryID::BT709, + ColorSpace::TransferID::CUSTOM_HDR, + ColorSpace::MatrixID::RGB, ColorSpace::RangeID::FULL, + nullptr, &scaled_trfn); + ColorSpace space_target(ColorSpace::PrimaryID::BT709, + ColorSpace::TransferID::LINEAR, + ColorSpace::MatrixID::RGB, ColorSpace::RangeID::FULL); + + std::unique_ptr xform_scaled( + ColorTransform::NewColorTransform(space_scaled, space_target)); + std::unique_ptr xform_unscaled( + ColorTransform::NewColorTransform(space_unscaled, space_target)); + + // Make sure that we're testing something in the linear (0.001) and nonlinear + // (the rest) segments of the function. + ColorTransform::TriStim val_scaled(0.001, 0.5, 0.7); + ColorTransform::TriStim val_unscaled = val_scaled; + + xform_scaled->Transform(&val_scaled, 1); + xform_unscaled->Transform(&val_unscaled, 1); + + EXPECT_NEAR(val_scaled.x() / val_unscaled.x(), scale, kMathEpsilon); + EXPECT_NEAR(val_scaled.y() / val_unscaled.y(), scale, kMathEpsilon); + EXPECT_NEAR(val_scaled.z() / val_unscaled.z(), scale, kMathEpsilon); +} + +TEST(ColorSpaceTest, PQSDRWhiteLevel) { + // The PQ function maps |pq_encoded_nits| to |nits|. We mangle it a bit with + // the SDR white level. + float pq_encoded_nits[] = { + 0.485857f, + 0.508078f, + 0.579133f, + }; + float nits[] = {80.f, 100.f, 200.f}; + + for (size_t i = 0; i < 4; ++i) { + // We'll set the SDR white level to the values in |nits| and also the + // default. + ColorSpace hdr10 = + i < 3 ? ColorSpace::CreateHDR10(nits[i]) : ColorSpace::CreateHDR10(); + float white_level = 0; + EXPECT_TRUE(hdr10.GetSDRWhiteLevel(&white_level)); + if (i < 3) + EXPECT_EQ(white_level, nits[i]); + else + EXPECT_EQ(white_level, ColorSpace::kDefaultSDRWhiteLevel); + + // Transform to the same color space, but with the LINEAR_HDR transfer + // function. + ColorSpace target(ColorSpace::PrimaryID::BT2020, + ColorSpace::TransferID::LINEAR_HDR, + ColorSpace::MatrixID::RGB, ColorSpace::RangeID::FULL); + std::unique_ptr xform( + ColorTransform::NewColorTransform(hdr10, target)); + + // Do the transform to the values in |pq_encoded_nits|. + ColorTransform::TriStim val(pq_encoded_nits[0], pq_encoded_nits[1], + pq_encoded_nits[2]); + xform->Transform(&val, 1); + + // The white level should be mapped to 1. + switch (i) { + case 0: + EXPECT_NEAR(val.x(), 1.f, kMathEpsilon); + break; + case 1: + EXPECT_NEAR(val.y(), 1.f, kMathEpsilon); + break; + case 2: + EXPECT_NEAR(val.z(), 1.f, kMathEpsilon); + break; + case 3: + // Check that the default white level is 100 nits. + EXPECT_NEAR(val.y(), 1.f, kMathEpsilon); + break; + } + + // The nit ratios should be preserved by the transform. + EXPECT_NEAR(val.y() / val.x(), nits[1] / nits[0], kMathEpsilon); + EXPECT_NEAR(val.z() / val.x(), nits[2] / nits[0], kMathEpsilon); + + // Test the inverse transform. + std::unique_ptr xform_inv( + ColorTransform::NewColorTransform(target, hdr10)); + xform_inv->Transform(&val, 1); + EXPECT_NEAR(val.x(), pq_encoded_nits[0], kMathEpsilon); + EXPECT_NEAR(val.y(), pq_encoded_nits[1], kMathEpsilon); + EXPECT_NEAR(val.z(), pq_encoded_nits[2], kMathEpsilon); + } +} + +TEST(ColorSpaceTest, HLGSDRWhiteLevel) { + // These values are (1.0f * nits[i] / kDefaultSDRWhiteLevel) converted to + // LINEAR_HDR via the HLG transfer function. + constexpr float hlg_encoded_nits[] = { + 0.447214f, // 0.5 * sqrt(1.0 * 80 / 100) + 0.5f, // 0.5 * sqrt(1.0 * 100 / 100) + 0.65641f, // 0.17883277 * ln(1.0 * 200 / 100 - 0.28466892) + 0.55991073 + }; + constexpr float nits[] = {80.f, 100.f, 200.f}; + + for (size_t i = 0; i < 4; ++i) { + // We'll set the SDR white level to the values in |nits| and also the + // default. + ColorSpace hlg = i < 3 + ? ColorSpace::CreateHLG().GetWithSDRWhiteLevel(nits[i]) + : ColorSpace::CreateHLG(); + float white_level = 0; + EXPECT_TRUE(hlg.GetSDRWhiteLevel(&white_level)); + if (i < 3) + EXPECT_EQ(white_level, nits[i]); + else + EXPECT_EQ(white_level, ColorSpace::kDefaultSDRWhiteLevel); + + // Transform to the same color space, but with the LINEAR_HDR transfer + // function. + ColorSpace target(ColorSpace::PrimaryID::BT2020, + ColorSpace::TransferID::LINEAR_HDR, + ColorSpace::MatrixID::RGB, ColorSpace::RangeID::FULL); + std::unique_ptr xform( + ColorTransform::NewColorTransform(hlg, target)); + + // Do the transform to the values in |hlg_encoded_nits|. + ColorTransform::TriStim val(hlg_encoded_nits[0], hlg_encoded_nits[1], + hlg_encoded_nits[2]); + xform->Transform(&val, 1); + + // Each |hlg_encoded_nits| value should map back to 1.0f after conversion + // via a ColorSpace with the right SDR white level. + switch (i) { + case 0: + EXPECT_NEAR(val.x(), 1.f, kMathEpsilon); + break; + case 1: + EXPECT_NEAR(val.y(), 1.f, kMathEpsilon); + break; + case 2: + EXPECT_NEAR(val.z(), 1.f, kMathEpsilon); + break; + case 3: + // Check that the default white level is 100 nits. + EXPECT_NEAR(val.y(), 1.f, kMathEpsilon); + break; + } + + // The nit ratios should be preserved by the transform. + EXPECT_NEAR(val.y() / val.x(), nits[1] / nits[0], kMathEpsilon); + EXPECT_NEAR(val.z() / val.x(), nits[2] / nits[0], kMathEpsilon); + + // Test the inverse transform. + std::unique_ptr xform_inv( + ColorTransform::NewColorTransform(target, hlg)); + xform_inv->Transform(&val, 1); + EXPECT_NEAR(val.x(), hlg_encoded_nits[0], kMathEpsilon); + EXPECT_NEAR(val.y(), hlg_encoded_nits[1], kMathEpsilon); + EXPECT_NEAR(val.z(), hlg_encoded_nits[2], kMathEpsilon); + } +} + +TEST(ColorSpaceTest, PiecewiseHDR) { + // The sRGB function evaluated at a couple of test points. + const float srgb_x0 = 0.01; + const float srgb_y0 = 0.00077399380805; + const float srgb_x1 = 0.5; + const float srgb_y1 = 0.2140411174732872; + + // Parameters for CreatePiecewiseHDR to test. + const std::vector test_sdr_joints = { + 0.25f, + 0.5f, + 0.75f, + }; + const std::vector test_hdr_levels = { + 1.5f, + 2.0f, + 5.0f, + }; + + // Go through all combinations. + for (float sdr_joint : test_sdr_joints) { + for (float hdr_level : test_hdr_levels) { + ColorSpace hdr = ColorSpace::CreatePiecewiseHDR( + ColorSpace::PrimaryID::BT709, sdr_joint, hdr_level); + ColorSpace linear(ColorSpace::PrimaryID::BT709, + ColorSpace::TransferID::LINEAR_HDR); + std::unique_ptr xform_to( + ColorTransform::NewColorTransform(hdr, linear)); + std::unique_ptr xform_from( + ColorTransform::NewColorTransform(linear, hdr)); + + // We're going to to test both sides of the joint points. Use this + // epsilon, which is much smaller than kMathEpsilon, to make that + // adjustment. + const float kSideEpsilon = kMathEpsilon / 100; + + const size_t kTestPointCount = 8; + const float test_x[kTestPointCount] = { + // Test the linear segment of the sRGB function. + srgb_x0 * sdr_joint, + // Test the exponential segment of the sRGB function. + srgb_x1 * sdr_joint, + // Test epsilon before the HDR joint + sdr_joint - kSideEpsilon, + // Test the HDR joint + sdr_joint, + // Test epsilon after the HDR joint + sdr_joint + kSideEpsilon, + // Test the middle of the linear HDR segment + sdr_joint + 0.5f * (1.f - sdr_joint), + // Test just before the end of the linear HDR segment. + 1.f - kSideEpsilon, + // Test the endpoint of the linear HDR segment. + 1.f, + }; + const float test_y[kTestPointCount] = { + srgb_y0, + srgb_y1, + 1.f - kSideEpsilon, + 1.f, + 1.f + kSideEpsilon, + 0.5f * (1.f + hdr_level), + hdr_level - kSideEpsilon, + hdr_level, + }; + for (size_t i = 0; i < kTestPointCount; ++i) { + ColorTransform::TriStim val; + val.set_x(test_x[i]); + xform_to->Transform(&val, 1); + EXPECT_NEAR(val.x(), test_y[i], kMathEpsilon) + << " test_x[i] is " << test_x[i]; + + val.set_x(test_y[i]); + xform_from->Transform(&val, 1); + EXPECT_NEAR(val.x(), test_x[i], kMathEpsilon) + << " test_y[i] is " << test_y[i]; + } + } + } +} + +} // namespace gfx diff --git a/color_utils.cc b/color_utils.cc new file mode 100644 index 000000000000..06aaaf827863 --- /dev/null +++ b/color_utils.cc @@ -0,0 +1,528 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/color_utils.h" + +#include + +#include +#include + +#include "base/check_op.h" +#include "base/notreached.h" +#include "base/numerics/safe_conversions.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "build/build_config.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/gfx/color_palette.h" + +#if defined(OS_WIN) +#include +#include "skia/ext/skia_utils_win.h" +#endif + +namespace color_utils { + +namespace { + +// The darkest reference color in color_utils. +SkColor g_darkest_color = gfx::kGoogleGrey900; + +// The luminance midpoint for determining if a color is light or dark. This is +// the value where white and g_darkest_color contrast equally. This default +// value is the midpoint given kGoogleGrey900 as the darkest color. +float g_luminance_midpoint = 0.211692036f; + +constexpr float kWhiteLuminance = 1.0f; + +int calcHue(float temp1, float temp2, float hue) { + if (hue < 0.0f) + ++hue; + else if (hue > 1.0f) + --hue; + + float result = temp1; + if (hue * 6.0f < 1.0f) + result = temp1 + (temp2 - temp1) * hue * 6.0f; + else if (hue * 2.0f < 1.0f) + result = temp2; + else if (hue * 3.0f < 2.0f) + result = temp1 + (temp2 - temp1) * (2.0f / 3.0f - hue) * 6.0f; + + return base::ClampRound(result * 255); +} + +// Assumes sRGB. +float Linearize(float eight_bit_component) { + const float component = eight_bit_component / 255.0f; + // The W3C link in the header uses 0.03928 here. See + // https://en.wikipedia.org/wiki/SRGB#Theory_of_the_transformation for + // discussion of why we use this value rather than that one. + return (component <= 0.04045f) ? (component / 12.92f) + : pow((component + 0.055f) / 1.055f, 2.4f); +} + +constexpr size_t kNumGoogleColors = 10; +constexpr SkColor kGrey[kNumGoogleColors] = { + gfx::kGoogleGrey050, gfx::kGoogleGrey100, gfx::kGoogleGrey200, + gfx::kGoogleGrey300, gfx::kGoogleGrey400, gfx::kGoogleGrey500, + gfx::kGoogleGrey600, gfx::kGoogleGrey700, gfx::kGoogleGrey800, + gfx::kGoogleGrey900, +}; + +constexpr SkColor kRed[kNumGoogleColors] = { + gfx::kGoogleRed050, gfx::kGoogleRed100, gfx::kGoogleRed200, + gfx::kGoogleRed300, gfx::kGoogleRed400, gfx::kGoogleRed500, + gfx::kGoogleRed600, gfx::kGoogleRed700, gfx::kGoogleRed800, + gfx::kGoogleRed900, +}; + +constexpr SkColor kOrange[kNumGoogleColors] = { + gfx::kGoogleOrange050, gfx::kGoogleOrange100, gfx::kGoogleOrange200, + gfx::kGoogleOrange300, gfx::kGoogleOrange400, gfx::kGoogleOrange500, + gfx::kGoogleOrange600, gfx::kGoogleOrange700, gfx::kGoogleOrange800, + gfx::kGoogleOrange900, +}; + +constexpr SkColor kYellow[kNumGoogleColors] = { + gfx::kGoogleYellow050, gfx::kGoogleYellow100, gfx::kGoogleYellow200, + gfx::kGoogleYellow300, gfx::kGoogleYellow400, gfx::kGoogleYellow500, + gfx::kGoogleYellow600, gfx::kGoogleYellow700, gfx::kGoogleYellow800, + gfx::kGoogleYellow900, +}; + +constexpr SkColor kGreen[kNumGoogleColors] = { + gfx::kGoogleGreen050, gfx::kGoogleGreen100, gfx::kGoogleGreen200, + gfx::kGoogleGreen300, gfx::kGoogleGreen400, gfx::kGoogleGreen500, + gfx::kGoogleGreen600, gfx::kGoogleGreen700, gfx::kGoogleGreen800, + gfx::kGoogleGreen900, +}; + +constexpr SkColor kBlue[kNumGoogleColors] = { + gfx::kGoogleBlue050, gfx::kGoogleBlue100, gfx::kGoogleBlue200, + gfx::kGoogleBlue300, gfx::kGoogleBlue400, gfx::kGoogleBlue500, + gfx::kGoogleBlue600, gfx::kGoogleBlue700, gfx::kGoogleBlue800, + gfx::kGoogleBlue900, +}; + +constexpr SkColor kPurple[kNumGoogleColors] = { + gfx::kGooglePurple050, gfx::kGooglePurple100, gfx::kGooglePurple200, + gfx::kGooglePurple300, gfx::kGooglePurple400, gfx::kGooglePurple500, + gfx::kGooglePurple600, gfx::kGooglePurple700, gfx::kGooglePurple800, + gfx::kGooglePurple900, +}; + +constexpr SkColor kPink[kNumGoogleColors] = { + gfx::kGooglePink050, gfx::kGooglePink100, gfx::kGooglePink200, + gfx::kGooglePink300, gfx::kGooglePink400, gfx::kGooglePink500, + gfx::kGooglePink600, gfx::kGooglePink700, gfx::kGooglePink800, + gfx::kGooglePink900, +}; + +SkColor PickGoogleColor(const SkColor (&colors)[kNumGoogleColors], + SkColor background_color, + float min_contrast) { + // For dark backgrounds we start at 500 and go down (toward brighter colors). + constexpr size_t kDarkBackgroundStartIndex = 5; + static_assert(kBlue[kDarkBackgroundStartIndex] == gfx::kGoogleBlue500, + "The start index needs to match kGoogleBlue500"); + const float background_luminance = GetRelativeLuminance(background_color); + if (IsDark(background_color)) { + for (size_t i = kDarkBackgroundStartIndex; i > 0; --i) { + if (GetContrastRatio(GetRelativeLuminance(colors[i]), + background_luminance) > min_contrast) { + return colors[i]; + } + } + return colors[0]; + } + + // For light backgrounds we start at 400 and go up (toward darker colors). + constexpr size_t kLightBackgroundStartIndex = 4; + static_assert(kBlue[kLightBackgroundStartIndex] == gfx::kGoogleBlue400, + "The start index needs to match kGoogleBlue400"); + for (size_t i = kLightBackgroundStartIndex; i < kNumGoogleColors - 1; ++i) { + if (GetContrastRatio(GetRelativeLuminance(colors[i]), + background_luminance) > min_contrast) { + return colors[i]; + } + } + return colors[kNumGoogleColors - 1]; +} + +} // namespace + +SkColor PickGoogleColor(SkColor color, + SkColor background_color, + float min_contrast) { + HSL hsl; + SkColorToHSL(color, &hsl); + if (hsl.s < 0.1) { + // Low saturation, let this be a grey. + return PickGoogleColor(kGrey, background_color, min_contrast); + } + + // Map hue to angles for readability. + const float color_angle = hsl.h * 360; + + // Hues in comments below are accent colors from MacOS 11.3.1 light mode as + // point of reference. + // TODO(pbos): Complement this with more Google colors and verify the hue + // ranges, this currently knows about enough colors to pick a corresponding + // color correctly from MacOS accent colors. + // RED: 357.654 + if (color_angle < 20) + return PickGoogleColor(kRed, background_color, min_contrast); + // ORANGE: 28.0687 + if (color_angle < 40) + return PickGoogleColor(kOrange, background_color, min_contrast); + // YELLOW: 44.4156 + if (color_angle < 70) + return PickGoogleColor(kYellow, background_color, min_contrast); + // GREEN: 105.484 + if (color_angle < 160) + return PickGoogleColor(kGreen, background_color, min_contrast); + // BLUE: 214.672 + if (color_angle < 250) + return PickGoogleColor(kBlue, background_color, min_contrast); + // PURPLE: 299.362 + if (color_angle < 310) + return PickGoogleColor(kPurple, background_color, min_contrast); + // PINK: 331.685 + if (color_angle < 345) + return PickGoogleColor(kPink, background_color, min_contrast); + + // End of hue wheel is red. + return PickGoogleColor(kRed, background_color, min_contrast); +} + +float GetContrastRatio(SkColor color_a, SkColor color_b) { + return GetContrastRatio(GetRelativeLuminance(color_a), + GetRelativeLuminance(color_b)); +} + +float GetContrastRatio(float luminance_a, float luminance_b) { + DCHECK_GE(luminance_a, 0.0f); + DCHECK_GE(luminance_b, 0.0f); + luminance_a += 0.05f; + luminance_b += 0.05f; + return (luminance_a > luminance_b) ? (luminance_a / luminance_b) + : (luminance_b / luminance_a); +} + +float GetRelativeLuminance(SkColor color) { + return (0.2126f * Linearize(SkColorGetR(color))) + + (0.7152f * Linearize(SkColorGetG(color))) + + (0.0722f * Linearize(SkColorGetB(color))); +} + +uint8_t GetLuma(SkColor color) { + return base::ClampRound(0.299f * SkColorGetR(color) + + 0.587f * SkColorGetG(color) + + 0.114f * SkColorGetB(color)); +} + +void SkColorToHSL(SkColor c, HSL* hsl) { + float r = SkColorGetR(c) / 255.0f; + float g = SkColorGetG(c) / 255.0f; + float b = SkColorGetB(c) / 255.0f; + float vmax = std::max({r, g, b}); + float vmin = std::min({r, g, b}); + float delta = vmax - vmin; + hsl->l = (vmax + vmin) / 2; + if (SkColorGetR(c) == SkColorGetG(c) && SkColorGetR(c) == SkColorGetB(c)) { + hsl->h = hsl->s = 0; + } else { + float dr = (((vmax - r) / 6.0f) + (delta / 2.0f)) / delta; + float dg = (((vmax - g) / 6.0f) + (delta / 2.0f)) / delta; + float db = (((vmax - b) / 6.0f) + (delta / 2.0f)) / delta; + // We need to compare for the max value because comparing vmax to r, g, or b + // can sometimes result in values overflowing registers. + if (r >= g && r >= b) + hsl->h = db - dg; + else if (g >= r && g >= b) + hsl->h = (1.0f / 3.0f) + dr - db; + else // (b >= r && b >= g) + hsl->h = (2.0f / 3.0f) + dg - dr; + + if (hsl->h < 0.0f) + ++hsl->h; + else if (hsl->h > 1.0f) + --hsl->h; + + hsl->s = delta / ((hsl->l < 0.5f) ? (vmax + vmin) : (2 - vmax - vmin)); + } +} + +SkColor HSLToSkColor(const HSL& hsl, SkAlpha alpha) { + float hue = hsl.h; + float saturation = hsl.s; + float lightness = hsl.l; + + // If there's no color, we don't care about hue and can do everything based on + // brightness. + if (!saturation) { + const uint8_t light = base::ClampRound(lightness * 255); + return SkColorSetARGB(alpha, light, light, light); + } + + float temp2 = (lightness < 0.5f) + ? (lightness * (1.0f + saturation)) + : (lightness + saturation - (lightness * saturation)); + float temp1 = 2.0f * lightness - temp2; + return SkColorSetARGB(alpha, calcHue(temp1, temp2, hue + 1.0f / 3.0f), + calcHue(temp1, temp2, hue), + calcHue(temp1, temp2, hue - 1.0f / 3.0f)); +} + +bool IsWithinHSLRange(const HSL& hsl, + const HSL& lower_bound, + const HSL& upper_bound) { + DCHECK(hsl.h >= 0 && hsl.h <= 1) << hsl.h; + DCHECK(hsl.s >= 0 && hsl.s <= 1) << hsl.s; + DCHECK(hsl.l >= 0 && hsl.l <= 1) << hsl.l; + DCHECK(lower_bound.h < 0 || upper_bound.h < 0 || + (lower_bound.h <= 1 && upper_bound.h <= lower_bound.h + 1)) + << "lower_bound.h: " << lower_bound.h + << ", upper_bound.h: " << upper_bound.h; + DCHECK(lower_bound.s < 0 || upper_bound.s < 0 || + (lower_bound.s <= upper_bound.s && upper_bound.s <= 1)) + << "lower_bound.s: " << lower_bound.s + << ", upper_bound.s: " << upper_bound.s; + DCHECK(lower_bound.l < 0 || upper_bound.l < 0 || + (lower_bound.l <= upper_bound.l && upper_bound.l <= 1)) + << "lower_bound.l: " << lower_bound.l + << ", upper_bound.l: " << upper_bound.l; + + // If the upper hue is >1, the given hue bounds wrap around at 1. + bool matches_hue = upper_bound.h > 1 + ? hsl.h >= lower_bound.h || hsl.h <= upper_bound.h - 1 + : hsl.h >= lower_bound.h && hsl.h <= upper_bound.h; + return (upper_bound.h < 0 || lower_bound.h < 0 || matches_hue) && + (upper_bound.s < 0 || lower_bound.s < 0 || + (hsl.s >= lower_bound.s && hsl.s <= upper_bound.s)) && + (upper_bound.l < 0 || lower_bound.l < 0 || + (hsl.l >= lower_bound.l && hsl.l <= upper_bound.l)); +} + +void MakeHSLShiftValid(HSL* hsl) { + if (hsl->h < 0 || hsl->h > 1) + hsl->h = -1; + if (hsl->s < 0 || hsl->s > 1) + hsl->s = -1; + if (hsl->l < 0 || hsl->l > 1) + hsl->l = -1; +} + +bool IsHSLShiftMeaningful(const HSL& hsl) { + // -1 in any channel has no effect, and 0.5 has no effect for S/L. A shift + // with an effective value in ANY channel is meaningful. + return hsl.h != -1 || (hsl.s != -1 && hsl.s != 0.5) || + (hsl.l != -1 && hsl.l != 0.5); +} + +SkColor HSLShift(SkColor color, const HSL& shift) { + SkAlpha alpha = SkColorGetA(color); + + if (shift.h >= 0 || shift.s >= 0) { + HSL hsl; + SkColorToHSL(color, &hsl); + + // Replace the hue with the tint's hue. + if (shift.h >= 0) + hsl.h = shift.h; + + // Change the saturation. + if (shift.s >= 0) { + if (shift.s <= 0.5f) + hsl.s *= shift.s * 2.0f; + else + hsl.s += (1.0f - hsl.s) * ((shift.s - 0.5f) * 2.0f); + } + + color = HSLToSkColor(hsl, alpha); + } + + if (shift.l < 0) + return color; + + // Lightness shifts in the style of popular image editors aren't actually + // represented in HSL - the L value does have some effect on saturation. + float r = static_cast(SkColorGetR(color)); + float g = static_cast(SkColorGetG(color)); + float b = static_cast(SkColorGetB(color)); + if (shift.l <= 0.5f) { + r *= (shift.l * 2.0f); + g *= (shift.l * 2.0f); + b *= (shift.l * 2.0f); + } else { + r += (255.0f - r) * ((shift.l - 0.5f) * 2.0f); + g += (255.0f - g) * ((shift.l - 0.5f) * 2.0f); + b += (255.0f - b) * ((shift.l - 0.5f) * 2.0f); + } + return SkColorSetARGB(alpha, base::ClampRound(r), + base::ClampRound(g), base::ClampRound(b)); +} + +SkColor AlphaBlend(SkColor foreground, SkColor background, SkAlpha alpha) { + return AlphaBlend(foreground, background, alpha / 255.0f); +} + +SkColor AlphaBlend(SkColor foreground, SkColor background, float alpha) { + DCHECK_GE(alpha, 0.0f); + DCHECK_LE(alpha, 1.0f); + + if (alpha == 0.0f) + return background; + if (alpha == 1.0f) + return foreground; + + int f_alpha = SkColorGetA(foreground); + int b_alpha = SkColorGetA(background); + + float normalizer = f_alpha * alpha + b_alpha * (1.0f - alpha); + if (normalizer == 0.0f) + return SK_ColorTRANSPARENT; + + float f_weight = f_alpha * alpha / normalizer; + float b_weight = b_alpha * (1.0f - alpha) / normalizer; + + float r = + SkColorGetR(foreground) * f_weight + SkColorGetR(background) * b_weight; + float g = + SkColorGetG(foreground) * f_weight + SkColorGetG(background) * b_weight; + float b = + SkColorGetB(foreground) * f_weight + SkColorGetB(background) * b_weight; + + return SkColorSetARGB(base::ClampRound(normalizer), + base::ClampRound(r), base::ClampRound(g), + base::ClampRound(b)); +} + +SkColor GetResultingPaintColor(SkColor foreground, SkColor background) { + return AlphaBlend(SkColorSetA(foreground, SK_AlphaOPAQUE), background, + static_cast(SkColorGetA(foreground))); +} + +bool IsDark(SkColor color) { + return GetRelativeLuminance(color) < g_luminance_midpoint; +} + +SkColor GetColorWithMaxContrast(SkColor color) { + return IsDark(color) ? SK_ColorWHITE : g_darkest_color; +} + +SkColor GetEndpointColorWithMinContrast(SkColor color) { + return IsDark(color) ? g_darkest_color : SK_ColorWHITE; +} + +SkColor BlendTowardMaxContrast(SkColor color, SkAlpha alpha) { + SkAlpha original_alpha = SkColorGetA(color); + SkColor blended_color = AlphaBlend(GetColorWithMaxContrast(color), + SkColorSetA(color, SK_AlphaOPAQUE), alpha); + return SkColorSetA(blended_color, original_alpha); +} + +SkColor PickContrastingColor(SkColor foreground1, + SkColor foreground2, + SkColor background) { + const float background_luminance = GetRelativeLuminance(background); + return (GetContrastRatio(GetRelativeLuminance(foreground1), + background_luminance) >= + GetContrastRatio(GetRelativeLuminance(foreground2), + background_luminance)) ? + foreground1 : foreground2; +} + +BlendResult BlendForMinContrast( + SkColor default_foreground, + SkColor background, + absl::optional high_contrast_foreground, + float contrast_ratio) { + DCHECK_EQ(SkColorGetA(background), SK_AlphaOPAQUE); + default_foreground = GetResultingPaintColor(default_foreground, background); + if (GetContrastRatio(default_foreground, background) >= contrast_ratio) + return {SK_AlphaTRANSPARENT, default_foreground}; + const SkColor target_foreground = GetResultingPaintColor( + high_contrast_foreground.value_or(GetColorWithMaxContrast(background)), + background); + + const float background_luminance = GetRelativeLuminance(background); + + SkAlpha best_alpha = SK_AlphaOPAQUE; + SkColor best_color = target_foreground; + // Use int for inclusive lower bound and exclusive upper bound, reserving + // conversion to SkAlpha for the end (reduces casts). + for (int low = SK_AlphaTRANSPARENT, high = SK_AlphaOPAQUE + 1; low < high;) { + const SkAlpha alpha = (low + high) / 2; + const SkColor color = + AlphaBlend(target_foreground, default_foreground, alpha); + const float luminance = GetRelativeLuminance(color); + const float contrast = GetContrastRatio(luminance, background_luminance); + if (contrast >= contrast_ratio) { + best_alpha = alpha; + best_color = color; + high = alpha; + } else { + low = alpha + 1; + } + } + return {best_alpha, best_color}; +} + +SkColor InvertColor(SkColor color) { + return SkColorSetARGB(SkColorGetA(color), 255 - SkColorGetR(color), + 255 - SkColorGetG(color), 255 - SkColorGetB(color)); +} + +SkColor GetSysSkColor(int which) { +#if defined(OS_WIN) + return skia::COLORREFToSkColor(GetSysColor(which)); +#else + NOTIMPLEMENTED(); + return SK_ColorLTGRAY; +#endif +} + +SkColor DeriveDefaultIconColor(SkColor text_color) { + // Lighten dark colors and brighten light colors. The alpha value here (0x4c) + // is chosen to generate a value close to GoogleGrey700 from GoogleGrey900. + return BlendTowardMaxContrast(text_color, 0x4c); +} + +std::string SkColorToRgbaString(SkColor color) { + // We convert the alpha using NumberToString because StringPrintf will use + // locale specific formatters (e.g., use , instead of . in German). + return base::StringPrintf( + "rgba(%s,%s)", SkColorToRgbString(color).c_str(), + base::NumberToString(SkColorGetA(color) / 255.0).c_str()); +} + +std::string SkColorToRgbString(SkColor color) { + return base::StringPrintf("%d,%d,%d", SkColorGetR(color), SkColorGetG(color), + SkColorGetB(color)); +} + +SkColor SetDarkestColorForTesting(SkColor color) { + const SkColor previous_darkest_color = g_darkest_color; + g_darkest_color = color; + + const float dark_luminance = GetRelativeLuminance(color); + // We want to compute |g_luminance_midpoint| such that + // GetContrastRatio(dark_luminance, g_luminance_midpoint) == + // GetContrastRatio(kWhiteLuminance, g_luminance_midpoint). The formula below + // can be verified by plugging it into how GetContrastRatio() operates. + g_luminance_midpoint = + std::sqrt((dark_luminance + 0.05f) * (kWhiteLuminance + 0.05f)) - 0.05f; + + return previous_darkest_color; +} + +std::tuple GetLuminancesForTesting() { + return std::make_tuple(GetRelativeLuminance(g_darkest_color), + g_luminance_midpoint, kWhiteLuminance); +} + +} // namespace color_utils diff --git a/color_utils.h b/color_utils.h new file mode 100644 index 000000000000..5d5c4309fb74 --- /dev/null +++ b/color_utils.h @@ -0,0 +1,183 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_COLOR_UTILS_H_ +#define UI_GFX_COLOR_UTILS_H_ + +#include +#include + +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "third_party/skia/include/core/SkColor.h" +#include "ui/gfx/gfx_export.h" + +namespace color_utils { + +// Represents an HSL color. +struct HSL { + double h; + double s; + double l; +}; + +// The blend alpha and resulting color when blending to achieve a desired +// contrast raio. +struct BlendResult { + SkAlpha alpha; + SkColor color; +}; + +// The minimum contrast between text and background that is still readable. +// This value is taken from w3c accessibility guidelines. +constexpr float kMinimumReadableContrastRatio = 4.5f; + +// The minimum contrast between button glyphs, focus indicators, large text, or +// other "have to see it but perhaps don't have to read fine detail" cases and +// background. +constexpr float kMinimumVisibleContrastRatio = 3.0f; + +// Determines the contrast ratio of two colors or two relative luminance values +// (as computed by RelativeLuminance()), calculated according to +// http://www.w3.org/TR/WCAG20/#contrast-ratiodef . +GFX_EXPORT float GetContrastRatio(SkColor color_a, SkColor color_b); +GFX_EXPORT float GetContrastRatio(float luminance_a, float luminance_b); + +// The relative luminance of |color|, that is, the weighted sum of the +// linearized RGB components, normalized to 0..1, per BT.709. See +// http://www.w3.org/TR/WCAG20/#relativeluminancedef . +GFX_EXPORT float GetRelativeLuminance(SkColor color); + +// The luma of |color|, that is, the weighted sum of the gamma-compressed R'G'B' +// components, per BT.601, a.k.a. the Y' in Y'UV. See +// https://en.wikipedia.org/wiki/Luma_(video). +GFX_EXPORT uint8_t GetLuma(SkColor color); + +// Note: these transformations assume sRGB as the source color space +GFX_EXPORT void SkColorToHSL(SkColor c, HSL* hsl); +GFX_EXPORT SkColor HSLToSkColor(const HSL& hsl, SkAlpha alpha); + +// Determines whether the given |hsl| falls within the given range for each +// component. All components of |hsl| are expected to be in the range [0, 1]. +// +// If a component is negative in either |lower_bound| or |upper_bound|, that +// component will be ignored. +// +// For hue, the lower bound should be in the range [0, 1] and the upper bound +// should be in the range [(lower bound), (lower bound + 1)]. +// For saturation and value, bounds should be specified in the range [0, 1], +// with the lower bound less than the upper bound. +GFX_EXPORT bool IsWithinHSLRange(const HSL& hsl, + const HSL& lower_bound, + const HSL& upper_bound); + +// Makes |hsl| valid input for HSLShift(). Sets values of hue, saturation +// and lightness which are outside of the valid range [0, 1] to -1. -1 is a +// special value which indicates 'no change'. +GFX_EXPORT void MakeHSLShiftValid(HSL* hsl); + +// Returns whether pasing |hsl| to HSLShift() would have any effect. Assumes +// |hsl| is a valid shift (as defined by MakeHSLShiftValid()). +GFX_EXPORT bool IsHSLShiftMeaningful(const HSL& hsl); + +// HSL-Shift an SkColor. The shift values are in the range of 0-1, with the +// option to specify -1 for 'no change'. The shift values are defined as: +// hsl_shift[0] (hue): The absolute hue value - 0 and 1 map +// to 0 and 360 on the hue color wheel (red). +// hsl_shift[1] (saturation): A saturation shift, with the +// following key values: +// 0 = remove all color. +// 0.5 = leave unchanged. +// 1 = fully saturate the image. +// hsl_shift[2] (lightness): A lightness shift, with the +// following key values: +// 0 = remove all lightness (make all pixels black). +// 0.5 = leave unchanged. +// 1 = full lightness (make all pixels white). +GFX_EXPORT SkColor HSLShift(SkColor color, const HSL& shift); + +// Returns a blend of the supplied colors, ranging from |background| (for +// |alpha| == 0) to |foreground| (for |alpha| == 255). The alpha channels of +// the supplied colors are also taken into account, so the returned color may +// be partially transparent. +GFX_EXPORT SkColor AlphaBlend(SkColor foreground, + SkColor background, + SkAlpha alpha); + +// As above, but with alpha specified as 0..1. +GFX_EXPORT SkColor AlphaBlend(SkColor foreground, + SkColor background, + float alpha); + +// Returns the color that results from painting |foreground| on top of +// |background|. +GFX_EXPORT SkColor GetResultingPaintColor(SkColor foreground, + SkColor background); + +// Returns true if |color| contrasts more with white than the darkest color. +GFX_EXPORT bool IsDark(SkColor color); + +// Returns whichever of white or the darkest available color contrasts more with +// |color|. +GFX_EXPORT SkColor GetColorWithMaxContrast(SkColor color); + +// Returns whichever of white or the darkest available color contrasts less with +// |color|. +GFX_EXPORT SkColor GetEndpointColorWithMinContrast(SkColor color); + +// Blends towards the color with max contrast by |alpha|. The alpha of +// the original color is preserved. +GFX_EXPORT SkColor BlendTowardMaxContrast(SkColor color, SkAlpha alpha); + +// Returns whichever of |foreground1| or |foreground2| has higher contrast with +// |background|. +GFX_EXPORT SkColor PickContrastingColor(SkColor foreground1, + SkColor foreground2, + SkColor background); + +// Alpha-blends |default_foreground| toward either |high_contrast_foreground| +// (if specified) or the color with max contrast with |background| until either +// the result has a contrast ratio against |background| of at least +// |contrast_ratio| or the blend can go no further. Returns the blended color +// and the alpha used to achieve that blend. If |default_foreground| already +// has sufficient contrast, returns an alpha of 0 and color of +// |default_foreground|. +GFX_EXPORT BlendResult BlendForMinContrast( + SkColor default_foreground, + SkColor background, + absl::optional high_contrast_foreground = absl::nullopt, + float contrast_ratio = kMinimumReadableContrastRatio); + +// Invert a color. +GFX_EXPORT SkColor InvertColor(SkColor color); + +// Gets a Windows system color as a SkColor +GFX_EXPORT SkColor GetSysSkColor(int which); + +// Derives a color for icons on a UI surface based on the text color on the same +// surface. +GFX_EXPORT SkColor DeriveDefaultIconColor(SkColor text_color); + +// Gets a Google color that matches the hue of `color` and contrasts well +// enough against `background_color` to meet `min_contrast`. If `color` isn't +// very saturated, grey will be used instead. +GFX_EXPORT SkColor PickGoogleColor(SkColor color, + SkColor background_color, + float min_contrast); + +// Creates an rgba string for an SkColor. For example: 'rgba(255,0,255,0.5)'. +GFX_EXPORT std::string SkColorToRgbaString(SkColor color); + +// Creates an rgb string for an SkColor. For example: '255,0,255'. +GFX_EXPORT std::string SkColorToRgbString(SkColor color); + +// Sets the darkest available color to |color|. Returns the previous darkest +// color. +GFX_EXPORT SkColor SetDarkestColorForTesting(SkColor color); + +// Returns the luminance of the darkest, midpoint, and lightest colors. +GFX_EXPORT std::tuple GetLuminancesForTesting(); + +} // namespace color_utils + +#endif // UI_GFX_COLOR_UTILS_H_ diff --git a/color_utils_unittest.cc b/color_utils_unittest.cc new file mode 100644 index 000000000000..d3d35e1d49f1 --- /dev/null +++ b/color_utils_unittest.cc @@ -0,0 +1,313 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "skia/ext/platform_canvas.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkColorPriv.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/color_palette.h" +#include "ui/gfx/color_utils.h" +#include "ui/gfx/geometry/rect.h" + +namespace color_utils { + +TEST(ColorUtils, SkColorToHSLRed) { + HSL hsl = {0, 0, 0}; + SkColorToHSL(SK_ColorRED, &hsl); + EXPECT_DOUBLE_EQ(hsl.h, 0); + EXPECT_DOUBLE_EQ(hsl.s, 1); + EXPECT_DOUBLE_EQ(hsl.l, 0.5); +} + +TEST(ColorUtils, SkColorToHSLGrey) { + HSL hsl = {0, 0, 0}; + SkColorToHSL(SkColorSetARGB(255, 128, 128, 128), &hsl); + EXPECT_DOUBLE_EQ(hsl.h, 0); + EXPECT_DOUBLE_EQ(hsl.s, 0); + EXPECT_EQ(static_cast(hsl.l * 100), + static_cast(0.5 * 100)); // Accurate to two decimal places. +} + +TEST(ColorUtils, HSLToSkColorWithAlpha) { + SkColor red = SkColorSetARGB(128, 255, 0, 0); + HSL hsl = {0, 1, 0.5}; + SkColor result = HSLToSkColor(hsl, 128); + EXPECT_EQ(SkColorGetA(red), SkColorGetA(result)); + EXPECT_EQ(SkColorGetR(red), SkColorGetR(result)); + EXPECT_EQ(SkColorGetG(red), SkColorGetG(result)); + EXPECT_EQ(SkColorGetB(red), SkColorGetB(result)); +} + +TEST(ColorUtils, RGBtoHSLRoundTrip) { + // Just spot check values near the edges. + for (int r = 0; r < 10; ++r) { + for (int g = 0; g < 10; ++g) { + for (int b = 0; b < 10; ++b) { + SkColor rgb = SkColorSetARGB(255, r, g, b); + HSL hsl = {0, 0, 0}; + SkColorToHSL(rgb, &hsl); + SkColor out = HSLToSkColor(hsl, 255); + EXPECT_EQ(SkColorGetR(out), SkColorGetR(rgb)); + EXPECT_EQ(SkColorGetG(out), SkColorGetG(rgb)); + EXPECT_EQ(SkColorGetB(out), SkColorGetB(rgb)); + } + } + } + for (int r = 240; r < 256; ++r) { + for (int g = 240; g < 256; ++g) { + for (int b = 240; b < 256; ++b) { + SkColor rgb = SkColorSetARGB(255, r, g, b); + HSL hsl = {0, 0, 0}; + SkColorToHSL(rgb, &hsl); + SkColor out = HSLToSkColor(hsl, 255); + EXPECT_EQ(SkColorGetR(out), SkColorGetR(rgb)); + EXPECT_EQ(SkColorGetG(out), SkColorGetG(rgb)); + EXPECT_EQ(SkColorGetB(out), SkColorGetB(rgb)); + } + } + } +} + +TEST(ColorUtils, IsWithinHSLRange) { + HSL hsl = {0.3, 0.4, 0.5}; + HSL lower = {0.2, 0.3, 0.4}; + HSL upper = {0.4, 0.5, 0.6}; + EXPECT_TRUE(IsWithinHSLRange(hsl, lower, upper)); + // Bounds are inclusive. + hsl.h = 0.2; + EXPECT_TRUE(IsWithinHSLRange(hsl, lower, upper)); + hsl.h = 0.4; + EXPECT_TRUE(IsWithinHSLRange(hsl, lower, upper)); + hsl.s = 0.3; + EXPECT_TRUE(IsWithinHSLRange(hsl, lower, upper)); + hsl.s = 0.5; + EXPECT_TRUE(IsWithinHSLRange(hsl, lower, upper)); + hsl.l = 0.4; + EXPECT_TRUE(IsWithinHSLRange(hsl, lower, upper)); + hsl.l = 0.6; + EXPECT_TRUE(IsWithinHSLRange(hsl, lower, upper)); +} + +TEST(ColorUtils, IsWithinHSLRangeHueWrapAround) { + HSL hsl = {0.3, 0.4, 0.5}; + HSL lower = {0.8, -1, -1}; + HSL upper = {1.2, -1, -1}; + hsl.h = 0.1; + EXPECT_TRUE(IsWithinHSLRange(hsl, lower, upper)); + hsl.h = 0.9; + EXPECT_TRUE(IsWithinHSLRange(hsl, lower, upper)); + hsl.h = 0.3; + EXPECT_FALSE(IsWithinHSLRange(hsl, lower, upper)); +} + +TEST(ColorUtils, IsHSLShiftMeaningful) { + HSL noop_all_neg_one{-1.0, -1.0, -1.0}; + HSL noop_s_point_five{-1.0, 0.5, -1.0}; + HSL noop_l_point_five{-1.0, -1.0, 0.5}; + + HSL only_h{0.1, -1.0, -1.0}; + HSL only_s{-1.0, 0.1, -1.0}; + HSL only_l{-1.0, -1.0, 0.1}; + HSL only_hs{0.1, 0.1, -1.0}; + HSL only_hl{0.1, -1.0, 0.1}; + HSL only_sl{-1.0, 0.1, 0.1}; + HSL all_set{0.1, 0.2, 0.3}; + + EXPECT_FALSE(IsHSLShiftMeaningful(noop_all_neg_one)); + EXPECT_FALSE(IsHSLShiftMeaningful(noop_s_point_five)); + EXPECT_FALSE(IsHSLShiftMeaningful(noop_l_point_five)); + + EXPECT_TRUE(IsHSLShiftMeaningful(only_h)); + EXPECT_TRUE(IsHSLShiftMeaningful(only_s)); + EXPECT_TRUE(IsHSLShiftMeaningful(only_l)); + EXPECT_TRUE(IsHSLShiftMeaningful(only_hs)); + EXPECT_TRUE(IsHSLShiftMeaningful(only_hl)); + EXPECT_TRUE(IsHSLShiftMeaningful(only_sl)); + EXPECT_TRUE(IsHSLShiftMeaningful(all_set)); +} + +TEST(ColorUtils, ColorToHSLRegisterSpill) { + // In a opt build on Linux, this was causing a register spill on my laptop + // (Pentium M) when converting from SkColor to HSL. + SkColor input = SkColorSetARGB(255, 206, 154, 89); + HSL hsl = {-1, -1, -1}; + SkColor result = HSLShift(input, hsl); + // |result| should be the same as |input| since we passed in a value meaning + // no color shift. + EXPECT_EQ(SkColorGetA(input), SkColorGetA(result)); + EXPECT_EQ(SkColorGetR(input), SkColorGetR(result)); + EXPECT_EQ(SkColorGetG(input), SkColorGetG(result)); + EXPECT_EQ(SkColorGetB(input), SkColorGetB(result)); +} + +TEST(ColorUtils, AlphaBlend) { + SkColor fore = SkColorSetARGB(255, 200, 200, 200); + SkColor back = SkColorSetARGB(255, 100, 100, 100); + + EXPECT_TRUE(AlphaBlend(fore, back, 1.0f) == fore); + EXPECT_TRUE(AlphaBlend(fore, back, 0.0f) == back); + + // One is fully transparent, result is partially transparent. + back = SkColorSetA(back, 0); + EXPECT_EQ(136U, SkColorGetA(AlphaBlend(fore, back, SkAlpha{136}))); + + // Both are fully transparent, result is fully transparent. + fore = SkColorSetA(fore, 0); + EXPECT_EQ(0U, SkColorGetA(AlphaBlend(fore, back, 1.0f))); +} + +TEST(ColorUtils, SkColorToRgbaString) { + SkColor color = SkColorSetARGB(153, 100, 150, 200); + std::string color_string = SkColorToRgbaString(color); + EXPECT_EQ(color_string, "rgba(100,150,200,0.6)"); +} + +TEST(ColorUtils, SkColorToRgbString) { + SkColor color = SkColorSetARGB(200, 50, 100, 150); + std::string color_string = SkColorToRgbString(color); + EXPECT_EQ(color_string, "50,100,150"); +} + +TEST(ColorUtils, IsDarkDarkestColorChange) { + ASSERT_FALSE(IsDark(SK_ColorLTGRAY)); + const SkColor old_darkest_color = SetDarkestColorForTesting(SK_ColorLTGRAY); + EXPECT_TRUE(IsDark(SK_ColorLTGRAY)); + + SetDarkestColorForTesting(old_darkest_color); +} + +TEST(ColorUtils, MidpointLuminanceMatches) { + const SkColor old_darkest_color = SetDarkestColorForTesting(SK_ColorBLACK); + float darkest, midpoint, lightest; + std::tie(darkest, midpoint, lightest) = GetLuminancesForTesting(); + EXPECT_FLOAT_EQ(GetContrastRatio(darkest, midpoint), + GetContrastRatio(midpoint, lightest)); + + SetDarkestColorForTesting(old_darkest_color); + std::tie(darkest, midpoint, lightest) = GetLuminancesForTesting(); + EXPECT_FLOAT_EQ(GetContrastRatio(darkest, midpoint), + GetContrastRatio(midpoint, lightest)); +} + +TEST(ColorUtils, GetColorWithMaxContrast) { + const SkColor old_darkest_color = SetDarkestColorForTesting(SK_ColorBLACK); + EXPECT_EQ(SK_ColorWHITE, GetColorWithMaxContrast(SK_ColorBLACK)); + EXPECT_EQ(SK_ColorWHITE, + GetColorWithMaxContrast(SkColorSetRGB(0x75, 0x75, 0x75))); + EXPECT_EQ(SK_ColorBLACK, GetColorWithMaxContrast(SK_ColorWHITE)); + EXPECT_EQ(SK_ColorBLACK, + GetColorWithMaxContrast(SkColorSetRGB(0x76, 0x76, 0x76))); + + SetDarkestColorForTesting(old_darkest_color); + EXPECT_EQ(old_darkest_color, GetColorWithMaxContrast(SK_ColorWHITE)); +} + +TEST(ColorUtils, GetEndpointColorWithMinContrast) { + const SkColor old_darkest_color = SetDarkestColorForTesting(SK_ColorBLACK); + EXPECT_EQ(SK_ColorBLACK, GetEndpointColorWithMinContrast(SK_ColorBLACK)); + EXPECT_EQ(SK_ColorBLACK, + GetEndpointColorWithMinContrast(SkColorSetRGB(0x75, 0x75, 0x75))); + EXPECT_EQ(SK_ColorWHITE, GetEndpointColorWithMinContrast(SK_ColorWHITE)); + EXPECT_EQ(SK_ColorWHITE, + GetEndpointColorWithMinContrast(SkColorSetRGB(0x76, 0x76, 0x76))); + + SetDarkestColorForTesting(old_darkest_color); + EXPECT_EQ(old_darkest_color, + GetEndpointColorWithMinContrast(old_darkest_color)); +} + +TEST(ColorUtils, BlendForMinContrast_ForegroundAlreadyMeetsMinimum) { + const auto result = BlendForMinContrast(SK_ColorBLACK, SK_ColorWHITE); + EXPECT_EQ(SK_AlphaTRANSPARENT, result.alpha); + EXPECT_EQ(SK_ColorBLACK, result.color); +} + +TEST(ColorUtils, BlendForMinContrast_BlendDarker) { + const SkColor foreground = SkColorSetRGB(0xAA, 0xAA, 0xAA); + const auto result = BlendForMinContrast(foreground, SK_ColorWHITE); + EXPECT_NE(SK_AlphaTRANSPARENT, result.alpha); + EXPECT_NE(foreground, result.color); + EXPECT_GE(GetContrastRatio(result.color, SK_ColorWHITE), + kMinimumReadableContrastRatio); +} + +TEST(ColorUtils, BlendForMinContrast_BlendLighter) { + const SkColor foreground = SkColorSetRGB(0x33, 0x33, 0x33); + const auto result = BlendForMinContrast(foreground, SK_ColorBLACK); + EXPECT_NE(SK_AlphaTRANSPARENT, result.alpha); + EXPECT_NE(foreground, result.color); + EXPECT_GE(GetContrastRatio(result.color, SK_ColorBLACK), + kMinimumReadableContrastRatio); +} + +TEST(ColorUtils, BlendForMinContrast_StopsAtDarkestColor) { + const SkColor darkest_color = SkColorSetRGB(0x44, 0x44, 0x44); + const SkColor old_darkest_color = SetDarkestColorForTesting(darkest_color); + EXPECT_EQ(darkest_color, BlendForMinContrast(SkColorSetRGB(0x55, 0x55, 0x55), + SkColorSetRGB(0xAA, 0xAA, 0xAA)) + .color); + + SetDarkestColorForTesting(old_darkest_color); +} + +TEST(ColorUtils, BlendForMinContrast_ComputesExpectedOpacities) { + const SkColor source = SkColorSetRGB(0xDE, 0xE1, 0xE6); + const SkColor target = SkColorSetRGB(0xFF, 0xFF, 0xFF); + const SkColor base = source; + SkAlpha alpha = BlendForMinContrast(source, base, target, 1.11f).alpha; + EXPECT_NEAR(alpha / 255.0f, 0.4f, 0.03f); + alpha = BlendForMinContrast(source, base, target, 1.19f).alpha; + EXPECT_NEAR(alpha / 255.0f, 0.65f, 0.03f); + alpha = BlendForMinContrast(source, base, target, 1.13728f).alpha; + EXPECT_NEAR(alpha / 255.0f, 0.45f, 0.03f); +} + +TEST(ColorUtils, BlendTowardMaxContrast_PreservesAlpha) { + SkColor test_colors[] = {SK_ColorBLACK, SK_ColorWHITE, + SK_ColorRED, SK_ColorYELLOW, + SK_ColorMAGENTA, gfx::kGoogleGreen500, + gfx::kGoogleRed050, gfx::kGoogleBlue800}; + SkAlpha test_alphas[] = {SK_AlphaTRANSPARENT, 0x0F, + gfx::kDisabledControlAlpha, 0xDD}; + SkAlpha blend_alpha = 0x7F; + for (const SkColor color : test_colors) { + SkColor opaque_result = + color_utils::BlendTowardMaxContrast(color, blend_alpha); + for (const SkAlpha alpha : test_alphas) { + SkColor input = SkColorSetA(color, alpha); + SkColor result = color_utils::BlendTowardMaxContrast(input, blend_alpha); + // Alpha was preserved. + EXPECT_EQ(SkColorGetA(result), alpha); + // RGB channels unaffected by alpha of input. + EXPECT_EQ(SkColorSetA(result, SK_AlphaOPAQUE), opaque_result); + } + } +} + +TEST(ColorUtils, BlendForMinContrast_MatchesNaiveImplementation) { + constexpr SkColor default_foreground = SkColorSetRGB(0xDE, 0xE1, 0xE6); + constexpr SkColor high_contrast_foreground = SK_ColorWHITE; + constexpr SkColor background = default_foreground; + constexpr float kContrastRatio = 1.11f; + const auto result = BlendForMinContrast( + default_foreground, background, high_contrast_foreground, kContrastRatio); + + // Naive implementation is direct translation of function description. + SkAlpha alpha = SK_AlphaTRANSPARENT; + SkColor color = default_foreground; + for (int i = SK_AlphaTRANSPARENT; i <= SK_AlphaOPAQUE; ++i) { + alpha = static_cast(i); + color = AlphaBlend(high_contrast_foreground, default_foreground, alpha); + if (GetContrastRatio(color, background) >= kContrastRatio) + break; + } + + EXPECT_EQ(alpha, result.alpha); + EXPECT_EQ(color, result.color); +} + +} // namespace color_utils diff --git a/decorated_text.cc b/decorated_text.cc new file mode 100644 index 000000000000..f8b2fd1fac16 --- /dev/null +++ b/decorated_text.cc @@ -0,0 +1,17 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/decorated_text.h" + +namespace gfx { + +DecoratedText::RangedAttribute::RangedAttribute(const gfx::Range& range, + const gfx::Font& font) + : range(range), font(font), strike(false) {} + +DecoratedText::DecoratedText() {} + +DecoratedText::~DecoratedText() {} + +} // namespace gfx diff --git a/decorated_text.h b/decorated_text.h new file mode 100644 index 000000000000..9d53d0547551 --- /dev/null +++ b/decorated_text.h @@ -0,0 +1,45 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_DECORATED_TEXT_H_ +#define UI_GFX_DECORATED_TEXT_H_ + +#include +#include + +#include "ui/gfx/font.h" +#include "ui/gfx/gfx_export.h" +#include "ui/gfx/range/range.h" + +namespace gfx { + +// Encapsulates styling information for some given text. +struct GFX_EXPORT DecoratedText { + // Describes the various text decoration attributes applicable to a given + // range of text. + struct GFX_EXPORT RangedAttribute { + // Disallow default construction of Font, since that's slow. + RangedAttribute() = delete; + RangedAttribute(const Range& range, const Font& font); + + // The range in |text|, this RangedAttribute corresponds to. Should not be + // reversed and should lie within the bounds of |text|. + Range range; + Font font; + bool strike; + }; + + DecoratedText(); + ~DecoratedText(); + + std::u16string text; + + // Vector of RangedAttribute describing styling of non-overlapping ranges + // in |text|. + std::vector attributes; +}; + +} // namespace gfx + +#endif // UI_GFX_DECORATED_TEXT_H_ diff --git a/decorated_text_mac.h b/decorated_text_mac.h new file mode 100644 index 000000000000..ad1aa59b1d70 --- /dev/null +++ b/decorated_text_mac.h @@ -0,0 +1,22 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_DECORATED_TEXT_MAC_H_ +#define UI_GFX_DECORATED_TEXT_MAC_H_ + +#include "ui/gfx/gfx_export.h" + +@class NSAttributedString; + +namespace gfx { + +struct DecoratedText; + +// Returns a NSAttributedString from |decorated_text|. +GFX_EXPORT NSAttributedString* GetAttributedStringFromDecoratedText( + const DecoratedText& decorated_text); + +} // namespace gfx + +#endif // UI_GFX_DECORATED_TEXT_MAC_H_ \ No newline at end of file diff --git a/decorated_text_mac.mm b/decorated_text_mac.mm new file mode 100644 index 000000000000..80942a61f8f6 --- /dev/null +++ b/decorated_text_mac.mm @@ -0,0 +1,51 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "ui/gfx/decorated_text_mac.h" + +#import + +#import "base/mac/scoped_nsobject.h" +#include "base/strings/sys_string_conversions.h" +#include "ui/gfx/decorated_text.h" + +namespace gfx { + +NSAttributedString* GetAttributedStringFromDecoratedText( + const DecoratedText& decorated_text) { + base::scoped_nsobject str( + [[NSMutableAttributedString alloc] + initWithString:base::SysUTF16ToNSString(decorated_text.text)]); + [str beginEditing]; + + NSValue* const line_style = + @(NSUnderlineStyleSingle | NSUnderlinePatternSolid); + + for (const auto& attribute : decorated_text.attributes) { + DCHECK(!attribute.range.is_reversed()); + DCHECK_LE(attribute.range.end(), [str length]); + + NSMutableDictionary* attrs = [NSMutableDictionary dictionary]; + NSRange range = attribute.range.ToNSRange(); + + if (attribute.font.GetNativeFont()) + attrs[NSFontAttributeName] = attribute.font.GetNativeFont(); + + // NSFont does not have underline as an attribute. Hence handle it + // separately. + const bool underline = attribute.font.GetStyle() & gfx::Font::UNDERLINE; + if (underline) + attrs[NSUnderlineStyleAttributeName] = line_style; + + if (attribute.strike) + attrs[NSStrikethroughStyleAttributeName] = line_style; + + [str setAttributes:attrs range:range]; + } + + [str endEditing]; + return str.autorelease(); +} + +} // namespace gfx diff --git a/delegated_ink_metadata.cc b/delegated_ink_metadata.cc new file mode 100644 index 000000000000..eea84b1e3798 --- /dev/null +++ b/delegated_ink_metadata.cc @@ -0,0 +1,24 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/delegated_ink_metadata.h" + +#include + +#include "base/strings/stringprintf.h" + +namespace gfx { + +std::string DelegatedInkMetadata::ToString() const { + std::string str = base::StringPrintf( + "point: %s, diameter: %f, color: %u, timestamp: %" PRId64 + ", presentation_area: %s, frame_time: %" PRId64 ", is_hovering: %d", + point_.ToString().c_str(), diameter_, color_, + timestamp_.since_origin().InMicroseconds(), + presentation_area_.ToString().c_str(), + frame_time_.since_origin().InMicroseconds(), is_hovering_); + return str; +} + +} // namespace gfx diff --git a/delegated_ink_metadata.h b/delegated_ink_metadata.h new file mode 100644 index 000000000000..04b8c7c04a99 --- /dev/null +++ b/delegated_ink_metadata.h @@ -0,0 +1,95 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_DELEGATED_INK_METADATA_H_ +#define UI_GFX_DELEGATED_INK_METADATA_H_ + +#include + +#include "base/time/time.h" +#include "third_party/skia/include/core/SkColor.h" +#include "ui/gfx/geometry/rect_f.h" +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +// This class stores all the metadata that is gathered when the WebAPI +// updateInkTrailStartPoint is called. This metadata flows from blink, +// through cc, and into viz in order to produce a delegated ink trail on the +// end of what was already rendered. +// +// Explainer for the feature: +// https://github.com/WICG/ink-enhancement/blob/master/README.md +class GFX_EXPORT DelegatedInkMetadata { + public: + DelegatedInkMetadata() = default; + DelegatedInkMetadata(const PointF& pt, + double diameter, + SkColor color, + base::TimeTicks timestamp, + const RectF& area, + bool hovering) + : point_(pt), + diameter_(diameter), + color_(color), + timestamp_(timestamp), + presentation_area_(area), + is_hovering_(hovering) {} + DelegatedInkMetadata(const PointF& pt, + double diameter, + SkColor color, + base::TimeTicks timestamp, + const RectF& area, + base::TimeTicks frame_time, + bool hovering) + : point_(pt), + diameter_(diameter), + color_(color), + timestamp_(timestamp), + presentation_area_(area), + frame_time_(frame_time), + is_hovering_(hovering) {} + DelegatedInkMetadata(const DelegatedInkMetadata& other) = default; + DelegatedInkMetadata& operator=(const DelegatedInkMetadata& other) = default; + + const PointF& point() const { return point_; } + double diameter() const { return diameter_; } + SkColor color() const { return color_; } + base::TimeTicks timestamp() const { return timestamp_; } + const RectF& presentation_area() const { return presentation_area_; } + base::TimeTicks frame_time() const { return frame_time_; } + bool is_hovering() const { return is_hovering_; } + + void set_frame_time(base::TimeTicks frame_time) { frame_time_ = frame_time; } + + std::string ToString() const; + + private: + // Location of the pointerevent relative to the root frame. + PointF point_; + + // Width of the trail, in physical pixels. + double diameter_ = 0; + + // Color to draw the ink trail. + SkColor color_ = 0; + + // Timestamp from the pointerevent for the ink point. + base::TimeTicks timestamp_; + + // The rect to clip the ink trail to, defaults to the containing viewport. + RectF presentation_area_; + + // Frame time of the layer tree that this metadata is on. + base::TimeTicks frame_time_; + + // True if the left mouse button is up or if a stylus with hovering + // capabilities is hovering over the screen when updateInkTrailStartPoint is + // called. + bool is_hovering_ = false; +}; + +} // namespace gfx + +#endif // UI_GFX_DELEGATED_INK_METADATA_H_ diff --git a/delegated_ink_point.cc b/delegated_ink_point.cc new file mode 100644 index 000000000000..6240c2765e72 --- /dev/null +++ b/delegated_ink_point.cc @@ -0,0 +1,38 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/delegated_ink_point.h" + +#include + +#include "base/strings/stringprintf.h" +#include "ui/gfx/delegated_ink_metadata.h" + +namespace gfx { + +bool DelegatedInkPoint::MatchesDelegatedInkMetadata( + const DelegatedInkMetadata* metadata) const { + // The maximum difference allowed when comparing a DelegatedInkPoint |point_| + // to the |point_| on a DelegatedInkMetadata. Some precision loss can occur + // when moving between coordinate spaces in the browser and renderer, + // particularly when the device scale factor is not a whole number. This can + // result in a DelegatedInkMetadata and DelegatedInkPoint having been created + // from the same point, but having a very small difference. When this occurs, + // we can safely ignore that they are slightly different and use the point for + // a delegated ink trail anyway, since it is a very small difference and will + // only be visible for a single frame. + constexpr float kEpsilon = 0.05f; + + return metadata && timestamp_ == metadata->timestamp() && + point_.IsWithinDistance(metadata->point(), kEpsilon); +} + +std::string DelegatedInkPoint::ToString() const { + return base::StringPrintf("point: %s, timestamp: %" PRId64 ", pointer_id: %d", + point_.ToString().c_str(), + timestamp_.since_origin().InMicroseconds(), + pointer_id_); +} + +} // namespace gfx diff --git a/delegated_ink_point.h b/delegated_ink_point.h new file mode 100644 index 000000000000..92d117827237 --- /dev/null +++ b/delegated_ink_point.h @@ -0,0 +1,68 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_DELEGATED_INK_POINT_H_ +#define UI_GFX_DELEGATED_INK_POINT_H_ + +#include +#include + +#include "base/time/time.h" +#include "mojo/public/cpp/bindings/struct_traits.h" +#include "ui/gfx/geometry/point_f.h" +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +class DelegatedInkMetadata; +namespace mojom { +class DelegatedInkPointDataView; +} // namespace mojom + +// This class stores the information required to draw a single point of a +// delegated ink trail. When the WebAPI |updateInkTrailStartPoint| is called, +// the renderer requests that the browser begin sending these to viz. Viz +// will collect them, and then during |DrawAndSwap| will use the +// DelegatedInkPoints that have arrived from the browser along with the +// DelegatedInkMetadata that the renderer sent to draw a delegated ink trail on +// the screen, connected to the end of the already rendered ink stroke. +// +// Explainer for the feature: +// https://github.com/WICG/ink-enhancement/blob/master/README.md +class GFX_EXPORT DelegatedInkPoint { + public: + DelegatedInkPoint() = default; + DelegatedInkPoint(const PointF& pt, + base::TimeTicks timestamp, + int32_t pointer_id = std::numeric_limits::min()) + : point_(pt), timestamp_(timestamp), pointer_id_(pointer_id) {} + + const PointF& point() const { return point_; } + base::TimeTicks timestamp() const { return timestamp_; } + int32_t pointer_id() const { return pointer_id_; } + std::string ToString() const; + + bool MatchesDelegatedInkMetadata(const DelegatedInkMetadata* metadata) const; + + private: + friend struct mojo::StructTraits; + + // Location of the input event relative to the root window in device pixels. + // Scale is device scale factor at time of input. + PointF point_; + + // Timestamp from the input event. + base::TimeTicks timestamp_; + + // Pointer ID from the input event. Used to store all DelegatedInkPoints from + // the same source together in viz so that they are all candidates for a + // single delegated ink trail and DelegatedInkPoints from other sources are + // not. + int32_t pointer_id_; +}; + +} // namespace gfx + +#endif // UI_GFX_DELEGATED_INK_POINT_H_ diff --git a/delegated_ink_unittest.cc b/delegated_ink_unittest.cc new file mode 100644 index 000000000000..455d4bf57d77 --- /dev/null +++ b/delegated_ink_unittest.cc @@ -0,0 +1,83 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/delegated_ink_metadata.h" +#include "ui/gfx/delegated_ink_point.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace gfx { + +// Test confirms that DelegatedInkPoint and DelegatedInkMetadata will both +// report that they match each other if the timestamp and point are the same, +// regardless of any other members. +TEST(DelegatedInkTest, PointAndMetadataMatch) { + const gfx::PointF point(10, 40); + const base::TimeTicks timestamp = base::TimeTicks::Now(); + + DelegatedInkPoint ink_point(point, timestamp, /*pointer_id=*/1); + DelegatedInkMetadata metadata(point, /*diameter=*/10, SK_ColorBLACK, + timestamp, gfx::RectF(10, 10, 100, 100), + /*hovering=*/false); + + EXPECT_TRUE(ink_point.MatchesDelegatedInkMetadata(&metadata)); + + DelegatedInkPoint point_no_pointer_id(point, timestamp); + + EXPECT_TRUE(point_no_pointer_id.MatchesDelegatedInkMetadata(&metadata)); + + DelegatedInkMetadata metadata_with_frame_time( + point, /*diameter=*/7.777f, SK_ColorCYAN, timestamp, + gfx::RectF(6, 14, 240, 307), base::TimeTicks::Now(), /*hovering=*/true); + + EXPECT_TRUE(ink_point.MatchesDelegatedInkMetadata(&metadata_with_frame_time)); + EXPECT_TRUE(point_no_pointer_id.MatchesDelegatedInkMetadata( + &metadata_with_frame_time)); +} + +// Confirms that if the timestamps are the same and point locations are within +// |kEpsilon|, then they will be considered matching. +TEST(DelegatedInkTest, PointAndMetadataAreClose) { + const base::TimeTicks timestamp = base::TimeTicks::Now(); + const gfx::PointF point_location(34, 95.002f); + const gfx::PointF metadata_location(33.994f, 94.9524f); + + DelegatedInkPoint ink_point(point_location, timestamp); + DelegatedInkMetadata metadata( + metadata_location, /*diameter=*/3.5f, SK_ColorRED, timestamp, + gfx::RectF(0, 3, 14.6f, 78.2f), /*hovering=*/false); + + EXPECT_TRUE(ink_point.MatchesDelegatedInkMetadata(&metadata)); +} + +// Confirms that if timestamps or points are different (or the metadata is null) +// the DelegatedInkPoint and DelegatedInkMetadata are not considered matching. +TEST(DelegatedInkTest, PointAndMetadataDoNotMatch) { + const base::TimeTicks timestamp = base::TimeTicks::Now(); + const gfx::PointF point_location(34, 95.002f); + const gfx::PointF metadata_location_close(33.994f, 94.9523f); + + DelegatedInkPoint ink_point(point_location, timestamp); + EXPECT_FALSE(ink_point.MatchesDelegatedInkMetadata(nullptr)); + + DelegatedInkMetadata close_metadata( + metadata_location_close, /*diameter=*/5, SK_ColorWHITE, timestamp, + gfx::RectF(40, 30.6f, 43, 7.2f), /*hovering=*/true); + EXPECT_FALSE(ink_point.MatchesDelegatedInkMetadata(&close_metadata)); + + const gfx::PointF metadata_location_far(23.789f, 20); + DelegatedInkMetadata far_metadata( + metadata_location_far, /*diameter=*/1, SK_ColorYELLOW, timestamp, + gfx::RectF(12.44f, 3, 1000, 1000.1f), /*hovering=*/false); + EXPECT_FALSE(ink_point.MatchesDelegatedInkMetadata(&far_metadata)); + + DelegatedInkMetadata metadata_different_timestamp( + point_location, /*diameter=*/10, SK_ColorGREEN, + timestamp + base::Milliseconds(10), gfx::RectF(0, 0, 0, 0), + /*hovering*/ true); + EXPECT_FALSE( + ink_point.MatchesDelegatedInkMetadata(&metadata_different_timestamp)); +} + +} // namespace gfx diff --git a/display_color_spaces.cc b/display_color_spaces.cc new file mode 100644 index 000000000000..a5b8e14ed903 --- /dev/null +++ b/display_color_spaces.cc @@ -0,0 +1,202 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/display_color_spaces.h" + +#include "build/build_config.h" +#include "build/chromeos_buildflags.h" + +namespace gfx { + +namespace { + +const ContentColorUsage kAllColorUsages[] = { + ContentColorUsage::kSRGB, + ContentColorUsage::kWideColorGamut, + ContentColorUsage::kHDR, +}; + +gfx::BufferFormat DefaultBufferFormat() { + // ChromeOS expects the default buffer format be BGRA_8888 in several places. + // https://crbug.com/1057501, https://crbug.com/1073237 +#if BUILDFLAG(IS_CHROMEOS_ASH) + return gfx::BufferFormat::BGRA_8888; +#else + return gfx::BufferFormat::RGBA_8888; +#endif +} + +size_t GetIndex(ContentColorUsage color_usage, bool needs_alpha) { + switch (color_usage) { + case ContentColorUsage::kSRGB: + return 0 + needs_alpha; + case ContentColorUsage::kWideColorGamut: + return 2 + needs_alpha; + case ContentColorUsage::kHDR: + return 4 + needs_alpha; + } +} + +} // namespace + +DisplayColorSpaces::DisplayColorSpaces() { + for (auto& color_space : color_spaces_) + color_space = gfx::ColorSpace::CreateSRGB(); + for (auto& buffer_format : buffer_formats_) + buffer_format = DefaultBufferFormat(); +} + +DisplayColorSpaces::DisplayColorSpaces(const gfx::DisplayColorSpaces&) = + default; + +DisplayColorSpaces& DisplayColorSpaces::operator=( + const gfx::DisplayColorSpaces&) = default; + +DisplayColorSpaces::DisplayColorSpaces(const gfx::ColorSpace& c) + : DisplayColorSpaces() { + if (!c.IsValid()) + return; + for (auto& color_space : color_spaces_) + color_space = c; +} + +DisplayColorSpaces::DisplayColorSpaces(const ColorSpace& c, BufferFormat f) { + for (auto& color_space : color_spaces_) + color_space = c.IsValid() ? c : gfx::ColorSpace::CreateSRGB(); + for (auto& buffer_format : buffer_formats_) + buffer_format = f; +} + +void DisplayColorSpaces::SetOutputBufferFormats( + gfx::BufferFormat buffer_format_no_alpha, + gfx::BufferFormat buffer_format_needs_alpha) { + for (const auto& color_usage : kAllColorUsages) { + size_t i_no_alpha = GetIndex(color_usage, false); + size_t i_needs_alpha = GetIndex(color_usage, true); + buffer_formats_[i_no_alpha] = buffer_format_no_alpha; + buffer_formats_[i_needs_alpha] = buffer_format_needs_alpha; + } +} + +void DisplayColorSpaces::SetOutputColorSpaceAndBufferFormat( + ContentColorUsage color_usage, + bool needs_alpha, + const gfx::ColorSpace& color_space, + gfx::BufferFormat buffer_format) { + size_t i = GetIndex(color_usage, needs_alpha); + color_spaces_[i] = color_space; + buffer_formats_[i] = buffer_format; +} + +ColorSpace DisplayColorSpaces::GetOutputColorSpace( + ContentColorUsage color_usage, + bool needs_alpha) const { + return color_spaces_[GetIndex(color_usage, needs_alpha)]; +} + +BufferFormat DisplayColorSpaces::GetOutputBufferFormat( + ContentColorUsage color_usage, + bool needs_alpha) const { + return buffer_formats_[GetIndex(color_usage, needs_alpha)]; +} + +gfx::ColorSpace DisplayColorSpaces::GetRasterColorSpace() const { + return GetOutputColorSpace(ContentColorUsage::kHDR, false /* needs_alpha */); +} + +gfx::ColorSpace DisplayColorSpaces::GetCompositingColorSpace( + bool needs_alpha, + ContentColorUsage color_usage) const { + gfx::ColorSpace result = GetOutputColorSpace(color_usage, needs_alpha); + if (!result.IsSuitableForBlending()) + result = gfx::ColorSpace::CreateExtendedSRGB(); + return result; +} + +bool DisplayColorSpaces::SupportsHDR() const { + return GetOutputColorSpace(ContentColorUsage::kHDR, false).IsHDR() || + GetOutputColorSpace(ContentColorUsage::kHDR, true).IsHDR(); +} + +ColorSpace DisplayColorSpaces::GetScreenInfoColorSpace() const { + return GetOutputColorSpace(ContentColorUsage::kHDR, false /* needs_alpha */); +} + +void DisplayColorSpaces::ToStrings( + std::vector* out_names, + std::vector* out_color_spaces, + std::vector* out_buffer_formats) const { + // The names of the configurations. + const char* config_names[kConfigCount] = { + "sRGB/no-alpha", "sRGB/alpha", "WCG/no-alpha", + "WCG/alpha", "HDR/no-alpha", "HDR/alpha", + }; + // Names for special configuration subsets (e.g, all sRGB, all WCG, etc). + constexpr size_t kSpecialConfigCount = 5; + const char* special_config_names[kSpecialConfigCount] = { + "sRGB", "WCG", "SDR", "HDR", "all", + }; + const size_t special_config_indices[kSpecialConfigCount][2] = { + {0, 2}, {2, 4}, {0, 4}, {4, 6}, {0, 6}, + }; + + // We don't want to take up 6 lines (one for each config) if we don't need to. + // To avoid this, build up half-open intervals [i, j) which have the same + // color space and buffer formats, and group them together. The above "special + // configs" give groups that have a common name. + size_t i = 0; + size_t j = 0; + while (i != kConfigCount) { + // Keep growing the interval [i, j) until entry j is different, or past the + // end. + if (color_spaces_[i] == color_spaces_[j] && + buffer_formats_[i] == buffer_formats_[j] && j != kConfigCount) { + j += 1; + continue; + } + + // Populate the name for the group from the "special config" names. + std::string name; + for (size_t k = 0; k < kSpecialConfigCount; ++k) { + if (i == special_config_indices[k][0] && + j == special_config_indices[k][1]) { + name = special_config_names[k]; + break; + } + } + // If that didn't work, just list the configs. + if (name.empty()) { + for (size_t k = i; k < j; ++k) { + name += std::string(config_names[k]); + if (k != j - 1) + name += ","; + } + } + + // Add an entry, and continue with the interval [j, j). + out_names->push_back(name); + out_buffer_formats->push_back(buffer_formats_[i]); + out_color_spaces->push_back(color_spaces_[i]); + i = j; + }; +} + +bool DisplayColorSpaces::operator==(const DisplayColorSpaces& other) const { + for (size_t i = 0; i < kConfigCount; ++i) { + if (color_spaces_[i] != other.color_spaces_[i]) + return false; + if (buffer_formats_[i] != other.buffer_formats_[i]) + return false; + } + if (sdr_white_level_ != other.sdr_white_level_) + return false; + + return true; +} + +bool DisplayColorSpaces::operator!=(const DisplayColorSpaces& other) const { + return !(*this == other); +} + +} // namespace gfx diff --git a/display_color_spaces.h b/display_color_spaces.h new file mode 100644 index 000000000000..85d3d8e861b9 --- /dev/null +++ b/display_color_spaces.h @@ -0,0 +1,138 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_DISPLAY_COLOR_SPACES_H_ +#define UI_GFX_DISPLAY_COLOR_SPACES_H_ + +#include +#include + +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/gfx/buffer_types.h" +#include "ui/gfx/color_space.h" +#include "ui/gfx/color_space_export.h" +#include "ui/gfx/hdr_static_metadata.h" + +namespace mojo { +template +struct StructTraits; +} // namespace mojo + +namespace gfx { + +namespace mojom { +class DisplayColorSpacesDataView; +} // namespace mojom + +// The values are set so std::max() can be used to find the widest. +enum class ContentColorUsage : uint8_t { + // These values are histogrammed over time; do not change their ordinal + // values. When deleting a color usage replace it with a dummy value; when + // adding a color usage, do so at the bottom (and update kMaxValue). + kSRGB = 0, + kWideColorGamut = 1, + kHDR = 2, + kMaxValue = kHDR, +}; + +// This structure is used by a display::Display to specify the color space that +// should be used to display content of various types. This lives in here, as +// opposed to in ui/display because it is used directly by components/viz. +class COLOR_SPACE_EXPORT DisplayColorSpaces { + public: + static constexpr size_t kConfigCount = 6; + + // Initialize as sRGB-only. + DisplayColorSpaces(); + DisplayColorSpaces(const DisplayColorSpaces& display_color_space); + DisplayColorSpaces& operator=(const DisplayColorSpaces& display_color_space); + + // Initialize as |color_space| for all settings. If |color_space| is the + // default (invalid) color space, then initialize to sRGB. The BufferFormat + // will be set to a default value (BGRA_8888 or RGBA_8888) depending on + // build configuration. + explicit DisplayColorSpaces(const ColorSpace& color_space); + + // Initialize as |color_space| and |buffer_format| for all settings. If + // |color_space| is the default (invalid) color space, then initialize to + // sRGB. + DisplayColorSpaces(const ColorSpace& color_space, BufferFormat buffer_format); + + // Set the color space and buffer format for the final output surface when the + // specified content is being displayed. + void SetOutputColorSpaceAndBufferFormat(ContentColorUsage color_usage, + bool needs_alpha, + const gfx::ColorSpace& color_space, + gfx::BufferFormat buffer_format); + + // Set the buffer format for all color usages to |buffer_format_no_alpha| when + // alpha is not needed and |buffer_format_with_alpha| when alpha is needed. + void SetOutputBufferFormats(gfx::BufferFormat buffer_format_no_alpha, + gfx::BufferFormat buffer_format_with_alpha); + + // Retrieve parameters for a specific usage and alpha. + ColorSpace GetOutputColorSpace(ContentColorUsage color_usage, + bool needs_alpha) const; + BufferFormat GetOutputBufferFormat(ContentColorUsage color_usage, + bool needs_alpha) const; + + // Set the custom SDR white level, in nits. This is a non-default value only + // on Windows. + void SetSDRWhiteLevel(float sdr_white_level) { + sdr_white_level_ = sdr_white_level; + } + float GetSDRWhiteLevel() const { return sdr_white_level_; } + + void set_hdr_static_metadata( + absl::optional hdr_static_metadata) { + hdr_static_metadata_ = hdr_static_metadata; + } + const absl::optional& hdr_static_metadata() const { + return hdr_static_metadata_; + } + + // TODO(https://crbug.com/1116870): These helper functions exist temporarily + // to handle the transition of display::ScreenInfo off of ColorSpace. All + // calls to these functions are to be eliminated. + ColorSpace GetScreenInfoColorSpace() const; + + // Return the color space that should be used for rasterization. + // TODO: This will eventually need to take a ContentColorUsage. + gfx::ColorSpace GetRasterColorSpace() const; + + // Return the color space in which compositing (and, in particular, blending, + // should be performed). This space may not (on Windows) be suitable for + // output. + gfx::ColorSpace GetCompositingColorSpace(bool needs_alpha, + ContentColorUsage color_usage) const; + + // Return true if the HDR color spaces are, indeed, HDR. + bool SupportsHDR() const; + + // Output as a vector of strings. This is a helper function for printing in + // about:gpu. All output vectors will be the same length. Each entry will be + // the configuration name, its buffer format, and its color space. + void ToStrings(std::vector* out_names, + std::vector* out_color_spaces, + std::vector* out_buffer_formats) const; + + bool operator==(const DisplayColorSpaces& other) const; + bool operator!=(const DisplayColorSpaces& other) const; + + private: + // Serialization of DisplayColorSpaces directly accesses members. + friend struct IPC::ParamTraits; + friend struct mojo::StructTraits; + + gfx::ColorSpace color_spaces_[kConfigCount]; + gfx::BufferFormat buffer_formats_[kConfigCount]; + float sdr_white_level_ = ColorSpace::kDefaultSDRWhiteLevel; + // By definition this only applies to ContentColorUsage::kHDR. + absl::optional hdr_static_metadata_; +}; + +} // namespace gfx + +#endif // UI_GFX_DISPLAY_COLOR_SPACES_H_ diff --git a/extension_set.cc b/extension_set.cc new file mode 100644 index 000000000000..d61e6007fe8a --- /dev/null +++ b/extension_set.cc @@ -0,0 +1,27 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/extension_set.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" + +namespace gfx { + +ExtensionSet MakeExtensionSet(const base::StringPiece& extensions_string) { + return ExtensionSet(SplitStringPiece( + extensions_string, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)); +} + +bool HasExtension(const ExtensionSet& extension_set, + const base::StringPiece& extension) { + return extension_set.find(extension) != extension_set.end(); +} + +std::string MakeExtensionString(const ExtensionSet& extension_set) { + std::vector extension_list(extension_set.begin(), + extension_set.end()); + return base::JoinString(extension_list, " "); +} + +} // namespace gfx diff --git a/extension_set.h b/extension_set.h new file mode 100644 index 000000000000..3a699f4defb2 --- /dev/null +++ b/extension_set.h @@ -0,0 +1,32 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_EXTENSION_SET_H_ +#define UI_GFX_EXTENSION_SET_H_ + +#include "base/containers/flat_set.h" +#include "base/strings/string_piece.h" +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +using ExtensionSet = base::flat_set; + +GFX_EXPORT ExtensionSet +MakeExtensionSet(const base::StringPiece& extensions_string); + +GFX_EXPORT bool HasExtension(const ExtensionSet& extension_set, + const base::StringPiece& extension); + +template +inline bool HasExtension(const ExtensionSet& extension_set, + const char (&extension)[N]) { + return HasExtension(extension_set, base::StringPiece(extension, N - 1)); +} + +GFX_EXPORT std::string MakeExtensionString(const ExtensionSet& extension_set); + +} // namespace gfx + +#endif // UI_GFX_EXTENSION_SET_H_ diff --git a/favicon_size.cc b/favicon_size.cc new file mode 100644 index 000000000000..d0ba48a4ccdd --- /dev/null +++ b/favicon_size.cc @@ -0,0 +1,25 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/favicon_size.h" + +namespace gfx { + +const int kFaviconSize = 16; + +void CalculateFaviconTargetSize(int* width, int* height) { + if (*width > kFaviconSize || *height > kFaviconSize) { + // Too big, resize it maintaining the aspect ratio. + float aspect_ratio = static_cast(*width) / + static_cast(*height); + *height = kFaviconSize; + *width = static_cast(aspect_ratio * *height); + if (*width > kFaviconSize) { + *width = kFaviconSize; + *height = static_cast(*width / aspect_ratio); + } + } +} + +} // namespace gfx diff --git a/favicon_size.h b/favicon_size.h new file mode 100644 index 000000000000..ad51a9bdb005 --- /dev/null +++ b/favicon_size.h @@ -0,0 +1,22 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_FAVICON_SIZE_H_ +#define UI_GFX_FAVICON_SIZE_H_ + +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +// Size (along each axis) of the favicon. +GFX_EXPORT extern const int kFaviconSize; + +// If the width or height is bigger than the favicon size, a new width/height +// is calculated and returned in width/height that maintains the aspect +// ratio of the supplied values. +GFX_EXPORT void CalculateFaviconTargetSize(int* width, int* height); + +} // namespace gfx + +#endif // UI_GFX_FAVICON_SIZE_H_ diff --git a/font.cc b/font.cc new file mode 100644 index 000000000000..50a071a20fa0 --- /dev/null +++ b/font.cc @@ -0,0 +1,122 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/font.h" + +#include + +#include "base/strings/utf_string_conversions.h" +#include "build/build_config.h" +#include "ui/gfx/platform_font.h" + +namespace gfx { + +//////////////////////////////////////////////////////////////////////////////// +// Font, public: + +Font::Font() : platform_font_(PlatformFont::CreateDefault()) { +} + +Font::Font(const Font& other) : platform_font_(other.platform_font_) { +} + +Font& Font::operator=(const Font& other) { + platform_font_ = other.platform_font_; + return *this; +} + +#if defined(OS_APPLE) +Font::Font(NativeFont native_font) + : platform_font_(PlatformFont::CreateFromNativeFont(native_font)) { +} +#endif + +Font::Font(PlatformFont* platform_font) : platform_font_(platform_font) { +} + +Font::Font(const std::string& font_name, int font_size) + : platform_font_(PlatformFont::CreateFromNameAndSize(font_name, + font_size)) { +} + +Font::~Font() { +} + +Font Font::Derive(int size_delta, int style, Font::Weight weight) const { + if (size_delta == 0 && style == GetStyle() && weight == GetWeight()) + return *this; + + return platform_font_->DeriveFont(size_delta, style, weight); +} + +int Font::GetHeight() const { + return platform_font_->GetHeight(); +} + +int Font::GetBaseline() const { + return platform_font_->GetBaseline(); +} + +int Font::GetCapHeight() const { + return platform_font_->GetCapHeight(); +} + +int Font::GetExpectedTextWidth(int length) const { + return platform_font_->GetExpectedTextWidth(length); +} + +int Font::GetStyle() const { + return platform_font_->GetStyle(); +} + +const std::string& Font::GetFontName() const { + return platform_font_->GetFontName(); +} + +std::string Font::GetActualFontName() const { + return platform_font_->GetActualFontName(); +} + +int Font::GetFontSize() const { + return platform_font_->GetFontSize(); +} + +Font::Weight Font::GetWeight() const { + return platform_font_->GetWeight(); +} + +const FontRenderParams& Font::GetFontRenderParams() const { + return platform_font_->GetFontRenderParams(); +} + +#if defined(OS_APPLE) +NativeFont Font::GetNativeFont() const { + return platform_font_->GetNativeFont(); +} +#endif + +#ifndef NDEBUG +std::ostream& operator<<(std::ostream& stream, const Font::Weight weight) { + return stream << static_cast(weight); +} +#endif + +Font::Weight FontWeightFromInt(int weight) { + static const Font::Weight weights[] = { + Font::Weight::INVALID, Font::Weight::THIN, Font::Weight::EXTRA_LIGHT, + Font::Weight::LIGHT, Font::Weight::NORMAL, Font::Weight::MEDIUM, + Font::Weight::SEMIBOLD, Font::Weight::BOLD, Font::Weight::EXTRA_BOLD, + Font::Weight::BLACK}; + + const Font::Weight* next_bigger_weight = std::lower_bound( + std::begin(weights), std::end(weights), weight, + [](const Font::Weight& a, const int& b) { + return static_cast::type>(a) < b; + }); + if (next_bigger_weight != std::end(weights)) + return *next_bigger_weight; + return Font::Weight::INVALID; +} + +} // namespace gfx diff --git a/font.h b/font.h new file mode 100644 index 000000000000..f797b0d87b3e --- /dev/null +++ b/font.h @@ -0,0 +1,144 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_FONT_H_ +#define UI_GFX_FONT_H_ + +#include + +#include "base/memory/ref_counted.h" +#include "build/build_config.h" +#include "ui/gfx/gfx_export.h" +#include "ui/gfx/native_widget_types.h" + +namespace gfx { + +struct FontRenderParams; +class PlatformFont; + +// Font provides a wrapper around an underlying font. Copy and assignment +// operators are explicitly allowed, and cheap. +// +// Figure of font metrics: +// +--------+-------------------+------------------+ +// | | | internal leading | +// | | ascent (baseline) +------------------+ +// | height | | cap height | +// | |-------------------+------------------+ +// | | descent (height - baseline) | +// +--------+--------------------------------------+ +class GFX_EXPORT Font { + public: + // The following constants indicate the font style. + enum FontStyle { + NORMAL = 0, + ITALIC = 1, + UNDERLINE = 2, + }; + + // Standard font weights as used in Pango and Windows. The values must match + // https://msdn.microsoft.com/en-us/library/system.windows.fontweights(v=vs.110).aspx + enum class Weight { + INVALID = -1, + THIN = 100, + EXTRA_LIGHT = 200, + LIGHT = 300, + NORMAL = 400, + MEDIUM = 500, + SEMIBOLD = 600, + BOLD = 700, + EXTRA_BOLD = 800, + BLACK = 900, + }; + + // Creates a font with the default name and style. + Font(); + + // Creates a font that is a clone of another font object. + Font(const Font& other); + Font& operator=(const Font& other); + +#if defined(OS_APPLE) + // Creates a font from the specified native font. + explicit Font(NativeFont native_font); +#endif + + // Constructs a Font object with the specified PlatformFont object. The Font + // object takes ownership of the PlatformFont object. + explicit Font(PlatformFont* platform_font); + + // Creates a font with the specified name in UTF-8 and size in pixels. + Font(const std::string& font_name, int font_size); + + ~Font(); + + // Returns a new Font derived from the existing font. + // |size_delta| is the size in pixels to add to the current font. For example, + // a value of 5 results in a font 5 pixels bigger than this font. + // The style parameter specifies the new style for the font, and is a + // bitmask of the values: ITALIC and UNDERLINE. + Font Derive(int size_delta, int style, Font::Weight weight) const; + + // Returns the number of vertical pixels needed to display characters from + // the specified font. This may include some leading, i.e. height may be + // greater than just ascent + descent. Specifically, the Windows and Mac + // implementations include leading and the Linux one does not. This may + // need to be revisited in the future. + int GetHeight() const; + + // Returns the font weight. + Font::Weight GetWeight() const; + + // Returns the baseline, or ascent, of the font. + int GetBaseline() const; + + // Returns the cap height of the font. + int GetCapHeight() const; + + // Returns the expected number of horizontal pixels needed to display the + // specified length of characters. Call gfx::GetStringWidth() to retrieve the + // actual number. + int GetExpectedTextWidth(int length) const; + + // Returns the style of the font. + int GetStyle() const; + + // Returns the specified font name in UTF-8, without font mapping. + const std::string& GetFontName() const; + + // Returns the actually used font name in UTF-8 after font mapping. + std::string GetActualFontName() const; + + // Returns the font size in pixels. + int GetFontSize() const; + + // Returns an object describing how the font should be rendered. + const FontRenderParams& GetFontRenderParams() const; + +#if defined(OS_APPLE) + // Returns the native font handle. + // Lifetime lore: + // Mac: The object is owned by the system and should not be released. + NativeFont GetNativeFont() const; +#endif + + // Raw access to the underlying platform font implementation. + PlatformFont* platform_font() const { return platform_font_.get(); } + + private: + // Wrapped platform font implementation. + scoped_refptr platform_font_; +}; + +#ifndef NDEBUG +GFX_EXPORT std::ostream& operator<<(std::ostream& stream, + const Font::Weight weight); +#endif + +// Returns the Font::Weight that matches |weight| or the next bigger one. +GFX_EXPORT Font::Weight FontWeightFromInt(int weight); + +} // namespace gfx + +#endif // UI_GFX_FONT_H_ diff --git a/font_fallback.h b/font_fallback.h new file mode 100644 index 000000000000..001ec40eb986 --- /dev/null +++ b/font_fallback.h @@ -0,0 +1,32 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_FONT_FALLBACK_H_ +#define UI_GFX_FONT_FALLBACK_H_ + +#include +#include + +#include "build/build_config.h" +#include "ui/gfx/font.h" +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +class Font; + +// Given a font, returns the fonts that are suitable for fallback. +GFX_EXPORT std::vector GetFallbackFonts(const Font& font); + +// Finds a fallback font to render the specified |text| with respect to an +// initial |font|. Returns the resulting font via out param |result|. Returns +// |true| if a fallback font was found. +bool GFX_EXPORT GetFallbackFont(const Font& font, + const std::string& locale, + base::StringPiece16 text, + Font* result); + +} // namespace gfx + +#endif // UI_GFX_FONT_FALLBACK_H_ diff --git a/font_fallback_linux.cc b/font_fallback_linux.cc new file mode 100644 index 000000000000..5f3240c330c2 --- /dev/null +++ b/font_fallback_linux.cc @@ -0,0 +1,531 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/font_fallback_linux.h" + +#include + +#include +#include +#include + +#include "base/containers/mru_cache.h" +#include "base/files/file_path.h" +#include "base/lazy_instance.h" +#include "base/memory/ptr_util.h" +#include "base/no_destructor.h" +#include "base/trace_event/trace_event.h" +#include "third_party/icu/source/common/unicode/uchar.h" +#include "third_party/icu/source/common/unicode/utf16.h" +#include "third_party/skia/include/core/SkFontMgr.h" +#include "ui/gfx/font.h" +#include "ui/gfx/font_fallback.h" +#include "ui/gfx/linux/fontconfig_util.h" +#include "ui/gfx/platform_font.h" + +namespace gfx { + +namespace { + +const char kFontFormatTrueType[] = "TrueType"; +const char kFontFormatCFF[] = "CFF"; + +bool IsValidFontFromPattern(FcPattern* pattern) { + // Ignore any bitmap fonts users may still have installed from last + // century. + if (!IsFontScalable(pattern)) + return false; + + // Take only supported font formats on board. + std::string format = GetFontFormat(pattern); + if (format != kFontFormatTrueType && format != kFontFormatCFF) + return false; + + // Ignore any fonts FontConfig knows about, but that we don't have + // permission to read. + base::FilePath font_path = GetFontPath(pattern); + if (font_path.empty() || access(font_path.AsUTF8Unsafe().c_str(), R_OK)) + return false; + + return true; +} + +// This class uniquely identified a typeface. A typeface can be identified by +// its file path and it's ttc index. +class TypefaceCacheKey { + public: + TypefaceCacheKey(const base::FilePath& font_path, int ttc_index) + : font_path_(font_path), ttc_index_(ttc_index) {} + TypefaceCacheKey(const TypefaceCacheKey&) = default; + TypefaceCacheKey& operator=(const TypefaceCacheKey&) = default; + + const base::FilePath& font_path() const { return font_path_; } + int ttc_index() const { return ttc_index_; } + + bool operator<(const TypefaceCacheKey& other) const { + return std::tie(ttc_index_, font_path_) < + std::tie(other.ttc_index_, other.font_path_); + } + + private: + base::FilePath font_path_; + int ttc_index_; +}; + +// Returns a SkTypeface for a given font path and ttc_index. The typeface is +// cached to avoid reloading the font from file. SkTypeface is not caching +// these requests. +sk_sp GetSkTypefaceFromPathAndIndex(const base::FilePath& font_path, + int ttc_index) { + using TypefaceCache = std::map>; + static base::NoDestructor typeface_cache; + + if (font_path.empty()) + return nullptr; + + TypefaceCache* cache = typeface_cache.get(); + TypefaceCacheKey key(font_path, ttc_index); + TypefaceCache::iterator entry = cache->find(key); + if (entry != cache->end()) + return sk_sp(entry->second); + + sk_sp font_mgr = SkFontMgr::RefDefault(); + std::string filename = font_path.AsUTF8Unsafe(); + sk_sp typeface = + font_mgr->makeFromFile(filename.c_str(), ttc_index); + (*cache)[key] = typeface; + + return sk_sp(typeface); +} + +// Implements a fallback font cache over FontConfig API. +// +// A MRU cache is kept from a font to its potential fallback fonts. +// The key (e.g. FallbackFontEntry) contains the font for which +// fallback font must be returned. +// +// For each key, the cache is keeping a set (e.g. FallbackFontEntries) of +// potential fallback font (e.g. FallbackFontEntry). Each fallback font entry +// contains the supported codepoints (e.g. charset). The fallback font returned +// by GetFallbackFont(...) depends on the input text and is using the charset +// to determine the best candidate. +class FallbackFontKey { + public: + FallbackFontKey(std::string locale, Font font) + : locale_(locale), font_(font) {} + + FallbackFontKey(const FallbackFontKey&) = default; + + FallbackFontKey& operator=(const FallbackFontKey&) = delete; + + ~FallbackFontKey() = default; + + bool operator<(const FallbackFontKey& other) const { + if (font_.GetFontSize() != other.font_.GetFontSize()) + return font_.GetFontSize() < other.font_.GetFontSize(); + if (font_.GetStyle() != other.font_.GetStyle()) + return font_.GetStyle() < other.font_.GetStyle(); + if (font_.GetFontName() != other.font_.GetFontName()) + return font_.GetFontName() < other.font_.GetFontName(); + return locale_ < other.locale_; + } + + private: + std::string locale_; + Font font_; +}; + +class FallbackFontEntry { + public: + FallbackFontEntry(const base::FilePath& font_path, + int ttc_index, + FontRenderParams font_params, + FcCharSet* charset) + : font_path_(font_path), + ttc_index_(ttc_index), + font_params_(font_params), + charset_(FcCharSetCopy(charset)) {} + + FallbackFontEntry(const FallbackFontEntry& other) + : font_path_(other.font_path_), + ttc_index_(other.ttc_index_), + font_params_(other.font_params_), + charset_(FcCharSetCopy(other.charset_)) {} + + FallbackFontEntry& operator=(const FallbackFontEntry&) = delete; + + ~FallbackFontEntry() { FcCharSetDestroy(charset_); } + + const base::FilePath& font_path() const { return font_path_; } + int ttc_index() const { return ttc_index_; } + FontRenderParams font_params() const { return font_params_; } + + // Returns whether the fallback font support the codepoint. + bool HasGlyphForCharacter(UChar32 c) const { + return FcCharSetHasChar(charset_, static_cast(c)); + } + + private: + // Font identity fields. + base::FilePath font_path_; + int ttc_index_; + + // Font rendering parameters. + FontRenderParams font_params_; + + // Font code points coverage. + FcCharSet* charset_; +}; + +using FallbackFontEntries = std::vector; +using FallbackFontEntriesCache = + base::MRUCache; + +// The fallback font cache is a mapping from a font to the potential fallback +// fonts with their codepoint coverage. +FallbackFontEntriesCache* GetFallbackFontEntriesCacheInstance() { + constexpr int kFallbackFontCacheSize = 256; + static base::NoDestructor cache( + kFallbackFontCacheSize); + return cache.get(); +} + +// The fallback fonts cache is a mapping from a font family name to its +// potential fallback fonts. +using FallbackFontList = std::vector; +using FallbackFontListCache = base::MRUCache; + +FallbackFontListCache* GetFallbackFontListCacheInstance() { + constexpr int kFallbackCacheSize = 64; + static base::NoDestructor fallback_cache( + kFallbackCacheSize); + return fallback_cache.get(); +} + +} // namespace + +size_t GetFallbackFontEntriesCacheSizeForTesting() { + return GetFallbackFontEntriesCacheInstance()->size(); +} + +size_t GetFallbackFontListCacheSizeForTesting() { + return GetFallbackFontListCacheInstance()->size(); +} + +void ClearAllFontFallbackCachesForTesting() { + GetFallbackFontEntriesCacheInstance()->Clear(); + GetFallbackFontListCacheInstance()->Clear(); +} + +bool GetFallbackFont(const Font& font, + const std::string& locale, + base::StringPiece16 text, + Font* result) { + TRACE_EVENT0("fonts", "gfx::GetFallbackFont"); + + // The text passed must be at least length 1. + if (text.empty()) + return false; + + FallbackFontEntriesCache* cache = GetFallbackFontEntriesCacheInstance(); + FallbackFontKey key(locale, font); + FallbackFontEntriesCache::iterator cache_entry = cache->Get(key); + + // The cache entry for this font is missing, build it. + if (cache_entry == cache->end()) { + ScopedFcPattern pattern(FcPatternCreate()); + + // Add pattern for family name. + std::string font_family = font.GetFontName(); + FcPatternAddString(pattern.get(), FC_FAMILY, + reinterpret_cast(font_family.c_str())); + + // Prefer scalable font. + FcPatternAddBool(pattern.get(), FC_SCALABLE, FcTrue); + + // Add pattern for locale. + FcPatternAddString(pattern.get(), FC_LANG, + reinterpret_cast(locale.c_str())); + + // Add pattern for font style. + if ((font.GetStyle() & gfx::Font::ITALIC) != 0) + FcPatternAddInteger(pattern.get(), FC_SLANT, FC_SLANT_ITALIC); + + // Match a font fallback. + FcConfig* config = GetGlobalFontConfig(); + FcConfigSubstitute(config, pattern.get(), FcMatchPattern); + FcDefaultSubstitute(pattern.get()); + + FallbackFontEntries fallback_font_entries; + FcResult fc_result; + FcFontSet* fonts = + FcFontSort(config, pattern.get(), FcTrue, nullptr, &fc_result); + if (fonts) { + // Add each potential fallback font returned by font-config to the + // set of fallback fonts and keep track of their codepoints coverage. + for (int i = 0; i < fonts->nfont; ++i) { + FcPattern* current_font = fonts->fonts[i]; + if (!IsValidFontFromPattern(current_font)) + continue; + + // Retrieve the font identity fields. + base::FilePath font_path = GetFontPath(current_font); + int font_ttc_index = GetFontTtcIndex(current_font); + + // Retrieve the charset of the current font. + FcCharSet* char_set = nullptr; + fc_result = FcPatternGetCharSet(current_font, FC_CHARSET, 0, &char_set); + if (fc_result != FcResultMatch || char_set == nullptr) + continue; + + // Retrieve the font render params. + FontRenderParams font_params; + GetFontRenderParamsFromFcPattern(current_font, &font_params); + + fallback_font_entries.push_back(FallbackFontEntry( + font_path, font_ttc_index, font_params, char_set)); + } + FcFontSetDestroy(fonts); + } + + cache_entry = cache->Put(key, std::move(fallback_font_entries)); + } + + // Try each font in the cache to find the one with the highest coverage. + size_t fewest_missing_glyphs = text.length() + 1; + const FallbackFontEntry* prefered_entry = nullptr; + + for (const auto& entry : cache_entry->second) { + // Validate that every character has a known glyph in the font. + size_t missing_glyphs = 0; + size_t matching_glyphs = 0; + size_t i = 0; + while (i < text.length()) { + UChar32 c = 0; + U16_NEXT(text.data(), i, text.length(), c); + if (entry.HasGlyphForCharacter(c)) { + ++matching_glyphs; + } else { + ++missing_glyphs; + } + } + + if (matching_glyphs > 0 && missing_glyphs < fewest_missing_glyphs) { + fewest_missing_glyphs = missing_glyphs; + prefered_entry = &entry; + } + + // The font has coverage for the given text and is a valid fallback font. + if (missing_glyphs == 0) + break; + } + + // No fonts can be used as font fallback. + if (!prefered_entry) + return false; + + sk_sp typeface = GetSkTypefaceFromPathAndIndex( + prefered_entry->font_path(), prefered_entry->ttc_index()); + // The file can't be parsed (e.g. corrupt). This font can't be used as a + // fallback font. + if (!typeface) + return false; + + Font fallback_font(PlatformFont::CreateFromSkTypeface( + typeface, font.GetFontSize(), prefered_entry->font_params())); + + *result = fallback_font; + return true; +} + +std::vector GetFallbackFonts(const Font& font) { + TRACE_EVENT0("fonts", "gfx::GetFallbackFonts"); + + std::string font_family = font.GetFontName(); + + // Lookup in the cache for already processed family. + FallbackFontListCache* font_cache = GetFallbackFontListCacheInstance(); + auto cached_fallback_fonts = font_cache->Get(font_family); + if (cached_fallback_fonts != font_cache->end()) { + // Already in cache. + return cached_fallback_fonts->second; + } + + // Retrieve the font fallbacks for a given family name. + FallbackFontList fallback_fonts; + FcPattern* pattern = FcPatternCreate(); + FcPatternAddString(pattern, FC_FAMILY, + reinterpret_cast(font_family.c_str())); + + FcConfig* config = GetGlobalFontConfig(); + if (FcConfigSubstitute(config, pattern, FcMatchPattern) == FcTrue) { + FcDefaultSubstitute(pattern); + FcResult result; + FcFontSet* fonts = FcFontSort(config, pattern, FcTrue, nullptr, &result); + if (fonts) { + std::set fallback_names; + for (int i = 0; i < fonts->nfont; ++i) { + std::string name_str = GetFontName(fonts->fonts[i]); + if (name_str.empty()) + continue; + + // FontConfig returns multiple fonts with the same family name and + // different configurations. Check to prevent duplicate family names. + if (fallback_names.insert(name_str).second) + fallback_fonts.push_back(Font(name_str, 13)); + } + FcFontSetDestroy(fonts); + } + } + FcPatternDestroy(pattern); + + // Store the font fallbacks to the cache. + font_cache->Put(font_family, fallback_fonts); + + return fallback_fonts; +} + +namespace { + +class CachedFont { + public: + // Note: We pass the charset explicitly as callers + // should not create CachedFont entries without knowing + // that the FcPattern contains a valid charset. + CachedFont(FcPattern* pattern, FcCharSet* char_set) + : supported_characters_(char_set) { + DCHECK(pattern); + DCHECK(char_set); + fallback_font_.name = GetFontName(pattern); + fallback_font_.filepath = GetFontPath(pattern); + fallback_font_.ttc_index = GetFontTtcIndex(pattern); + fallback_font_.is_bold = IsFontBold(pattern); + fallback_font_.is_italic = IsFontItalic(pattern); + } + + const FallbackFontData& fallback_font() const { return fallback_font_; } + + bool HasGlyphForCharacter(UChar32 c) const { + return supported_characters_ && FcCharSetHasChar(supported_characters_, c); + } + + private: + FallbackFontData fallback_font_; + // supported_characters_ is owned by the parent + // FcFontSet and should never be freed. + FcCharSet* supported_characters_; +}; + +class CachedFontSet { + public: + // CachedFontSet takes ownership of the passed FcFontSet. + static std::unique_ptr CreateForLocale( + const std::string& locale) { + FcFontSet* font_set = CreateFcFontSetForLocale(locale); + return base::WrapUnique(new CachedFontSet(font_set)); + } + + CachedFontSet(const CachedFontSet&) = delete; + CachedFontSet& operator=(const CachedFontSet&) = delete; + + ~CachedFontSet() { + fallback_list_.clear(); + FcFontSetDestroy(font_set_); + } + + bool GetFallbackFontForChar(UChar32 c, FallbackFontData* fallback_font) { + TRACE_EVENT0("fonts", "gfx::CachedFontSet::GetFallbackFontForChar"); + + for (const auto& cached_font : fallback_list_) { + if (cached_font.HasGlyphForCharacter(c)) { + *fallback_font = cached_font.fallback_font(); + return true; + } + } + return false; + } + + private: + static FcFontSet* CreateFcFontSetForLocale(const std::string& locale) { + FcPattern* pattern = FcPatternCreate(); + + if (!locale.empty()) { + // FcChar* is unsigned char* so we have to cast. + FcPatternAddString(pattern, FC_LANG, + reinterpret_cast(locale.c_str())); + } + + FcPatternAddBool(pattern, FC_SCALABLE, FcTrue); + + FcConfigSubstitute(0, pattern, FcMatchPattern); + FcDefaultSubstitute(pattern); + + if (locale.empty()) + FcPatternDel(pattern, FC_LANG); + + // The result parameter returns if any fonts were found. + // We already handle 0 fonts correctly, so we ignore the param. + FcResult result; + FcFontSet* font_set = FcFontSort(0, pattern, 0, 0, &result); + FcPatternDestroy(pattern); + + // The caller will take ownership of this FcFontSet. + return font_set; + } + + CachedFontSet(FcFontSet* font_set) : font_set_(font_set) { + FillFallbackList(); + } + + void FillFallbackList() { + TRACE_EVENT0("fonts", "gfx::CachedFontSet::FillFallbackList"); + + DCHECK(fallback_list_.empty()); + if (!font_set_) + return; + + for (int i = 0; i < font_set_->nfont; ++i) { + FcPattern* pattern = font_set_->fonts[i]; + + if (!IsValidFontFromPattern(pattern)) + continue; + + // Make sure this font can tell us what characters it has glyphs for. + FcCharSet* char_set; + if (FcPatternGetCharSet(pattern, FC_CHARSET, 0, &char_set) != + FcResultMatch) + continue; + + fallback_list_.emplace_back(pattern, char_set); + } + } + + FcFontSet* font_set_; // Owned by this object. + // CachedFont has a FcCharset* which points into the FcFontSet. + // If the FcFontSet is ever destroyed, the fallback list + // must be cleared first. + std::vector fallback_list_; +}; + +typedef std::map> FontSetCache; +base::LazyInstance::Leaky g_font_sets_by_locale = + LAZY_INSTANCE_INITIALIZER; + +} // namespace + +FallbackFontData::FallbackFontData() = default; +FallbackFontData::FallbackFontData(const FallbackFontData& other) = default; +FallbackFontData& FallbackFontData::operator=(const FallbackFontData& other) = + default; + +bool GetFallbackFontForChar(UChar32 c, + const std::string& locale, + FallbackFontData* fallback_font) { + auto& cached_font_set = g_font_sets_by_locale.Get()[locale]; + if (!cached_font_set) + cached_font_set = CachedFontSet::CreateForLocale(locale); + return cached_font_set->GetFallbackFontForChar(c, fallback_font); +} + +} // namespace gfx diff --git a/font_fallback_linux.h b/font_fallback_linux.h new file mode 100644 index 000000000000..7a768690ab9d --- /dev/null +++ b/font_fallback_linux.h @@ -0,0 +1,47 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_FONT_FALLBACK_LINUX_H_ +#define UI_GFX_FONT_FALLBACK_LINUX_H_ + +#include + +#include "base/files/file_path.h" +#include "third_party/icu/source/common/unicode/uchar.h" +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +// Exposed fallback font caches methods for testing. +GFX_EXPORT size_t GetFallbackFontEntriesCacheSizeForTesting(); +GFX_EXPORT size_t GetFallbackFontListCacheSizeForTesting(); +GFX_EXPORT void ClearAllFontFallbackCachesForTesting(); + +struct GFX_EXPORT FallbackFontData { + std::string name; + base::FilePath filepath; + int fontconfig_interface_id = 0; + int ttc_index = 0; + bool is_bold = false; + bool is_italic = false; + + FallbackFontData(); + FallbackFontData(const FallbackFontData& other); + FallbackFontData& operator=(const FallbackFontData& other); +}; + +// Return a font family which provides a glyph for the Unicode code point +// specified by character. +// c: an UTF-32 code point +// preferred_locale: preferred locale identifier for |c| +// (e.g. "en", "ja", "zh-CN") +// +// Return whether the request was successful or not. +GFX_EXPORT bool GetFallbackFontForChar(UChar32 c, + const std::string& preferred_locale, + FallbackFontData* fallback_font); + +} // namespace gfx + +#endif // UI_GFX_FONT_FALLBACK_LINUX_H_ diff --git a/font_fallback_linux_unittest.cc b/font_fallback_linux_unittest.cc new file mode 100644 index 000000000000..ec156cfc9f7d --- /dev/null +++ b/font_fallback_linux_unittest.cc @@ -0,0 +1,106 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/font_fallback_linux.h" + +#include "base/files/file_path.h" +#include "base/strings/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/font.h" +#include "ui/gfx/font_fallback.h" + +namespace gfx { + +namespace { +const char kDefaultApplicationLocale[] = "us-en"; +const char kFrenchApplicationLocale[] = "ca-fr"; +} // namespace + +class FontFallbackLinuxTest : public testing::Test { + public: + void SetUp() override { + // Clear the font fallback caches. + ClearAllFontFallbackCachesForTesting(); + } +}; + +// If the Type 1 Symbol.pfb font is installed, it is returned as fallback font +// for the PUA character 0xf6db. This test ensures we're not returning Type 1 +// fonts as fallback. +TEST_F(FontFallbackLinuxTest, NoType1InFallbackFonts) { + FallbackFontData font_fallback_data; + if (GetFallbackFontForChar(0xf6db, std::string(), &font_fallback_data)) { + std::string extension = font_fallback_data.filepath.Extension(); + if (!extension.empty()) + EXPECT_NE(extension, ".pfb"); + } +} + +TEST_F(FontFallbackLinuxTest, GetFallbackFont) { + Font base_font; + + Font fallback_font_cjk; + EXPECT_TRUE(GetFallbackFont(base_font, kDefaultApplicationLocale, u"⻩", + &fallback_font_cjk)); + EXPECT_EQ(fallback_font_cjk.GetFontName(), "Noto Sans CJK JP"); + + Font fallback_font_khmer; + EXPECT_TRUE(GetFallbackFont(base_font, kDefaultApplicationLocale, u"ឨឮឡ", + &fallback_font_khmer)); + EXPECT_EQ(fallback_font_khmer.GetFontName(), "Noto Sans Khmer"); +} + +TEST_F(FontFallbackLinuxTest, GetFallbackFontCache) { + EXPECT_EQ(0U, GetFallbackFontEntriesCacheSizeForTesting()); + + Font base_font; + Font fallback_font; + EXPECT_TRUE(GetFallbackFont(base_font, kDefaultApplicationLocale, u"⻩", + &fallback_font)); + EXPECT_EQ(1U, GetFallbackFontEntriesCacheSizeForTesting()); + + // Second call should not increase the cache size. + EXPECT_TRUE(GetFallbackFont(base_font, kDefaultApplicationLocale, u"⻩", + &fallback_font)); + EXPECT_EQ(1U, GetFallbackFontEntriesCacheSizeForTesting()); + + // Third call with a different code point in the same font, should not + // increase the cache size. + EXPECT_TRUE(GetFallbackFont(base_font, kDefaultApplicationLocale, u"⻪", + &fallback_font)); + EXPECT_EQ(1U, GetFallbackFontEntriesCacheSizeForTesting()); + + // A different locale should trigger an new cache entry. + EXPECT_TRUE(GetFallbackFont(base_font, kFrenchApplicationLocale, u"⻩", + &fallback_font)); + EXPECT_EQ(2U, GetFallbackFontEntriesCacheSizeForTesting()); + + // The fallbackfonts cache should not be affected. + EXPECT_EQ(0U, GetFallbackFontListCacheSizeForTesting()); +} + +TEST_F(FontFallbackLinuxTest, Fallbacks) { + EXPECT_EQ(0U, GetFallbackFontListCacheSizeForTesting()); + + Font default_font("sans", 13); + std::vector fallbacks = GetFallbackFonts(default_font); + EXPECT_FALSE(fallbacks.empty()); + EXPECT_EQ(1U, GetFallbackFontListCacheSizeForTesting()); + + // The first fallback should be 'DejaVu Sans' which is the default linux + // fonts. The fonts on linux are mock with test_fonts (see + // third_party/tests_font). + if (!fallbacks.empty()) + EXPECT_EQ(fallbacks[0].GetFontName(), "DejaVu Sans"); + + // Second lookup should not increase the cache size. + fallbacks = GetFallbackFonts(default_font); + EXPECT_FALSE(fallbacks.empty()); + EXPECT_EQ(1U, GetFallbackFontListCacheSizeForTesting()); + + // The fallbackfont cache should not be affected. + EXPECT_EQ(0U, GetFallbackFontEntriesCacheSizeForTesting()); +} + +} // namespace gfx diff --git a/font_fallback_mac.mm b/font_fallback_mac.mm new file mode 100644 index 000000000000..aee22499ca20 --- /dev/null +++ b/font_fallback_mac.mm @@ -0,0 +1,95 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/font_fallback.h" + +#include +#import + +#include "base/i18n/char_iterator.h" +#include "base/mac/foundation_util.h" +#import "base/mac/mac_util.h" +#include "base/mac/scoped_cftyperef.h" +#import "base/strings/sys_string_conversions.h" +#include "base/trace_event/trace_event.h" +#include "third_party/icu/source/common/unicode/uchar.h" +#include "ui/gfx/font.h" +#include "ui/gfx/font_fallback_skia_impl.h" +#include "ui/gfx/platform_font.h" + +namespace gfx { + +namespace { + +bool TextSequenceHasEmoji(base::StringPiece16 text) { + for (base::i18n::UTF16CharIterator iter(text); !iter.end(); iter.Advance()) { + const UChar32 codepoint = iter.get(); + if (u_hasBinaryProperty(codepoint, UCHAR_EMOJI)) + return true; + } + return false; +} + +} // namespace + +std::vector GetFallbackFonts(const Font& font) { + DCHECK(font.GetNativeFont()); + // On Mac "There is a system default cascade list (which is polymorphic, based + // on the user's language setting and current font)" - CoreText Programming + // Guide. + NSArray* languages = [[NSUserDefaults standardUserDefaults] + stringArrayForKey:@"AppleLanguages"]; + CFArrayRef languages_cf = base::mac::NSToCFCast(languages); + base::ScopedCFTypeRef cascade_list( + CTFontCopyDefaultCascadeListForLanguages( + static_cast(font.GetNativeFont()), languages_cf)); + + std::vector fallback_fonts; + if (!cascade_list) + return fallback_fonts; // This should only happen for an invalid |font|. + + const CFIndex fallback_count = CFArrayGetCount(cascade_list); + for (CFIndex i = 0; i < fallback_count; ++i) { + CTFontDescriptorRef descriptor = + base::mac::CFCastStrict( + CFArrayGetValueAtIndex(cascade_list, i)); + base::ScopedCFTypeRef fallback_font( + CTFontCreateWithFontDescriptor(descriptor, 0.0, nullptr)); + if (fallback_font.get()) + fallback_fonts.push_back(Font(static_cast(fallback_font.get()))); + } + + if (fallback_fonts.empty()) + return std::vector(1, font); + + return fallback_fonts; +} + +bool GetFallbackFont(const Font& font, + const std::string& locale, + base::StringPiece16 text, + Font* result) { + TRACE_EVENT0("fonts", "gfx::GetFallbackFont"); + + if (TextSequenceHasEmoji(text)) { + *result = Font("Apple Color Emoji", font.GetFontSize()); + return true; + } + + sk_sp fallback_typeface = + GetSkiaFallbackTypeface(font, locale, text); + + if (!fallback_typeface) + return false; + + // Fallback needs to keep the exact SkTypeface, as re-matching the font using + // family name and styling information loses access to the underlying platform + // font handles and is not guaranteed to result in the correct typeface, see + // https://crbug.com/1003829 + *result = Font(PlatformFont::CreateFromSkTypeface( + std::move(fallback_typeface), font.GetFontSize(), absl::nullopt)); + return true; +} + +} // namespace gfx diff --git a/font_fallback_mac_unittest.cc b/font_fallback_mac_unittest.cc new file mode 100644 index 000000000000..dbb1724194a5 --- /dev/null +++ b/font_fallback_mac_unittest.cc @@ -0,0 +1,76 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/font_fallback.h" + +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/font.h" + +namespace gfx { + +namespace { +const char kDefaultApplicationLocale[] = "us-en"; +} // namespace + +// A targeted test for GetFallbackFonts on Mac. It uses a system API that +// only became publicly available in the 10.8 SDK. This test is to ensure it +// behaves sensibly on all supported OS versions. +TEST(FontFallbackMacTest, GetFallbackFonts) { + Font font("Arial", 12); + std::vector fallback_fonts = GetFallbackFonts(font); + // If there is only one fallback, it means the only fallback is the font + // itself. + EXPECT_LT(1u, fallback_fonts.size()); +} + +// Sanity check GetFallbackFont() behavior on Mac. This test makes assumptions +// about font properties and availability on specific macOS versions. +TEST(FontFallbackMacTest, GetFallbackFont) { + Font arial("Helvetica", 12); + const std::u16string ascii = u"abc"; + const std::u16string hebrew = u"\x5d0\x5d1\x5d2"; + const std::u16string emoji = u"😋"; + + Font fallback; + EXPECT_TRUE( + GetFallbackFont(arial, kDefaultApplicationLocale, hebrew, &fallback)); + EXPECT_EQ("Lucida Grande", fallback.GetFontName()); + EXPECT_TRUE( + GetFallbackFont(arial, kDefaultApplicationLocale, emoji, &fallback)); + EXPECT_EQ("Apple Color Emoji", fallback.GetFontName()); +} + +TEST(FontFallbackMacTest, GetFallbackFontForEmoji) { + static struct { + const char* test_name; + const wchar_t* text; + } kEmojiTests[] = { + {"aries", L"\u2648"}, + {"candle", L"\U0001F56F"}, + {"anchor", L"\u2693"}, + {"grinning_face", L"\U0001F600"}, + {"flag_andorra", L"\U0001F1E6\U0001F1E9"}, + {"woman_man_hands_light", L"\U0001F46B\U0001F3FB"}, + {"hole_text", L"\U0001F573\uFE0E"}, + {"hole_emoji", L"\U0001F573\uFE0F"}, + {"man_judge_medium", L"\U0001F468\U0001F3FD\u200D\u2696\uFE0F"}, + {"woman_turban", L"\U0001F473\u200D\u2640\uFE0F"}, + {"rainbow_flag", L"\U0001F3F3\uFE0F\u200D\U0001F308"}, + {"eye_bubble", L"\U0001F441\uFE0F\u200D\U0001F5E8\uFE0F"}, + }; + + Font font; + for (const auto& test : kEmojiTests) { + SCOPED_TRACE( + base::StringPrintf("GetFallbackFontForEmoji [%s]", test.test_name)); + Font fallback; + EXPECT_TRUE(GetFallbackFont(font, kDefaultApplicationLocale, + base::WideToUTF16(test.text), &fallback)); + EXPECT_EQ("Apple Color Emoji", fallback.GetFontName()); + } +} + +} // namespace gfx diff --git a/font_fallback_skia.cc b/font_fallback_skia.cc new file mode 100644 index 000000000000..29a5cc0216c8 --- /dev/null +++ b/font_fallback_skia.cc @@ -0,0 +1,49 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/font_fallback.h" + +#include +#include + +#include "base/strings/utf_string_conversions.h" +#include "base/trace_event/trace_event.h" +#include "build/build_config.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/gfx/font.h" +#include "ui/gfx/font_fallback_skia_impl.h" +#include "ui/gfx/font_render_params.h" +#include "ui/gfx/platform_font.h" + +namespace gfx { + +std::vector GetFallbackFonts(const Font& font) { + return std::vector(); +} + +bool GetFallbackFont(const Font& font, + const std::string& locale, + base::StringPiece16 text, + Font* result) { + TRACE_EVENT0("fonts", "gfx::GetFallbackFont"); + + if (text.empty()) + return false; + + sk_sp fallback_typeface = + GetSkiaFallbackTypeface(font, locale, text); + + if (!fallback_typeface) + return false; + + // Fallback needs to keep the exact SkTypeface, as re-matching the font using + // family name and styling information loses access to the underlying platform + // font handles and is not guaranteed to result in the correct typeface, see + // https://crbug.com/1003829 + *result = Font(PlatformFont::CreateFromSkTypeface( + std::move(fallback_typeface), font.GetFontSize(), absl::nullopt)); + return true; +} + +} // namespace gfx diff --git a/font_fallback_skia_impl.cc b/font_fallback_skia_impl.cc new file mode 100644 index 000000000000..a2eb422ab05f --- /dev/null +++ b/font_fallback_skia_impl.cc @@ -0,0 +1,160 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/font_fallback_skia_impl.h" + +#include +#include + +#include "third_party/icu/source/common/unicode/normalizer2.h" +#include "third_party/icu/source/common/unicode/uchar.h" +#include "third_party/icu/source/common/unicode/utf16.h" +#include "third_party/skia/include/core/SkFontMgr.h" +#include "third_party/skia/include/core/SkTypeface.h" + +namespace gfx { + +namespace { + +// Returns true when the codepoint has an unicode decomposition and store +// the decomposed string into |output|. +bool UnicodeDecomposeCodepoint(UChar32 codepoint, icu::UnicodeString* output) { + static const icu::Normalizer2* normalizer = nullptr; + + UErrorCode error = U_ZERO_ERROR; + if (!normalizer) { + normalizer = icu::Normalizer2::getNFDInstance(error); + if (U_FAILURE(error)) + return false; + DCHECK(normalizer); + } + + return normalizer->getDecomposition(codepoint, *output); +} + +// Extracts every codepoint and its decomposed codepoints from unicode +// decomposition. Inserts in |codepoints| the set of codepoints in |text|. +void RetrieveCodepointsAndDecomposedCodepoints(base::StringPiece16 text, + std::set* codepoints) { + size_t offset = 0; + while (offset < text.length()) { + UChar32 codepoint; + U16_NEXT(text.data(), offset, text.length(), codepoint); + + if (codepoints->insert(codepoint).second) { + // For each codepoint, add the decomposed codepoints. + icu::UnicodeString decomposed_text; + if (UnicodeDecomposeCodepoint(codepoint, &decomposed_text)) { + for (int i = 0; i < decomposed_text.length(); ++i) { + codepoints->insert(decomposed_text[i]); + } + } + } + } +} + +// Returns the amount of codepoint in |text| without a glyph representation in +// |typeface|. A codepoint is present if there is a corresponding glyph in +// typeface, or if there are glyphs for each of its decomposed codepoints. +size_t ComputeMissingGlyphsForGivenTypeface(base::StringPiece16 text, + sk_sp typeface) { + // Validate that every character has a known glyph in the font. + size_t missing_glyphs = 0; + size_t i = 0; + while (i < text.length()) { + UChar32 codepoint; + U16_NEXT(text.data(), i, text.length(), codepoint); + + // The glyph is present in the font. + if (typeface->unicharToGlyph(codepoint) != 0) + continue; + + // Do not count missing codepoints when they are ignorable as they will be + // ignored by the shaping engine. + if (u_hasBinaryProperty(codepoint, UCHAR_DEFAULT_IGNORABLE_CODE_POINT)) + continue; + + // No glyph is present in the font for the codepoint. Try the decomposed + // codepoints instead. + icu::UnicodeString decomposed_text; + if (UnicodeDecomposeCodepoint(codepoint, &decomposed_text) && + !decomposed_text.isEmpty()) { + // Check that every decomposed codepoint is in the font. + bool every_codepoint_found = true; + for (int offset = 0; offset < decomposed_text.length(); ++offset) { + if (typeface->unicharToGlyph(decomposed_text[offset]) == 0) { + every_codepoint_found = false; + break; + } + } + + // The decomposed codepoints can be mapped to glyphs by the font. + if (every_codepoint_found) + continue; + } + + // The current glyphs can't be find. + ++missing_glyphs; + } + + return missing_glyphs; +} + +} // namespace + +sk_sp GetSkiaFallbackTypeface(const Font& template_font, + const std::string& locale, + base::StringPiece16 text) { + if (text.empty()) + return nullptr; + + sk_sp font_mgr(SkFontMgr::RefDefault()); + + const char* bcp47_locales[] = {locale.c_str()}; + int num_locales = locale.empty() ? 0 : 1; + const char** locales = locale.empty() ? nullptr : bcp47_locales; + + const int font_weight = (template_font.GetWeight() == Font::Weight::INVALID) + ? static_cast(Font::Weight::NORMAL) + : static_cast(template_font.GetWeight()); + const bool italic = (template_font.GetStyle() & Font::ITALIC) != 0; + SkFontStyle skia_style( + font_weight, SkFontStyle::kNormal_Width, + italic ? SkFontStyle::kItalic_Slant : SkFontStyle::kUpright_Slant); + + std::set tested_typeface; + sk_sp fallback_typeface; + size_t fewest_missing_glyphs = text.length() + 1; + + // Retrieve the set of codepoints (or unicode decomposed codepoints) from + // the input text. + std::set codepoints; + RetrieveCodepointsAndDecomposedCodepoints(text, &codepoints); + + // Determine which fallback font is given the fewer missing glyphs. + for (UChar32 codepoint : codepoints) { + sk_sp typeface(font_mgr->matchFamilyStyleCharacter( + template_font.GetFontName().c_str(), skia_style, locales, num_locales, + codepoint)); + // If the typeface is not found or was already tested, skip it. + if (!typeface || !tested_typeface.insert(typeface->uniqueID()).second) + continue; + + // Validate that every codepoint has a known glyph in the font. + size_t missing_glyphs = + ComputeMissingGlyphsForGivenTypeface(text, typeface); + if (missing_glyphs < fewest_missing_glyphs) { + fewest_missing_glyphs = missing_glyphs; + fallback_typeface = typeface; + } + + // The font is a valid fallback font for the given text. + if (missing_glyphs == 0) + break; + } + + return fallback_typeface; +} + +} // namespace gfx diff --git a/font_fallback_skia_impl.h b/font_fallback_skia_impl.h new file mode 100644 index 000000000000..1908deef5f6a --- /dev/null +++ b/font_fallback_skia_impl.h @@ -0,0 +1,23 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_FONT_FALLBACK_SKIA_IMPL_H_ +#define UI_GFX_FONT_FALLBACK_SKIA_IMPL_H_ + +#include + +#include "base/strings/string_piece.h" +#include "ui/gfx/font.h" + +#include "third_party/skia/include/core/SkRefCnt.h" +#include "third_party/skia/include/core/SkTypeface.h" + +namespace gfx { + +sk_sp GetSkiaFallbackTypeface(const Font& template_font, + const std::string& locale, + base::StringPiece16 text); +} + +#endif // UI_GFX_FONT_FALLBACK_SKIA_IMPL_H_ diff --git a/font_fallback_skia_unittest.cc b/font_fallback_skia_unittest.cc new file mode 100644 index 000000000000..01bd3f255cec --- /dev/null +++ b/font_fallback_skia_unittest.cc @@ -0,0 +1,96 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/font_fallback.h" + +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" +#include "build/build_config.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace gfx { + +namespace { + +const wchar_t* kFallbackFontTests[] = { + L"\u0540\u0541", // Armenian, + L"\u0631\u0632", // Arabic + L"\u0915\u093f", // Devanagari + L"\u5203\u5204", // CJK Unified Ideograph +}; + +const char kDefaultApplicationLocale[] = "us-en"; + +} // namespace + +TEST(FontFallbackSkiaTest, EmptyStringFallback) { + Font base_font; + Font fallback_font; + bool result = GetFallbackFont(base_font, kDefaultApplicationLocale, + base::StringPiece16(), &fallback_font); + EXPECT_FALSE(result); +} + +TEST(FontFallbackSkiaTest, FontFallback) { + for (const auto* test : kFallbackFontTests) { + Font base_font; + Font fallback_font; + std::u16string text = base::WideToUTF16(test); + + if (!GetFallbackFont(base_font, kDefaultApplicationLocale, text, + &fallback_font)) { + ADD_FAILURE() << "Font fallback failed: '" << text << "'"; + } + } +} + +#if !defined(OS_ANDROID) && !defined(OS_FUCHSIA) +// TODO(sergeyu): Fuchsia doesn't not support locale for font fallbacks. +// TODO(etienneb): Android doesn't allow locale override, unless the language +// is added in the system UI. +TEST(FontFallbackSkiaTest, CJKLocaleFallback) { + // Han unification is an effort to map multiple character sets of the CJK + // languages into a single set of unified characters. Han characters are a + // common feature of written Chinese (hanzi), Japanese (kanji), and Korean + // (hanja). The same text will be rendered using a different font based on + // locale. + const std::u16string kCJKTest = u"\u8AA4\u904E\u9AA8"; + Font base_font; + + Font fallback_font_zh_cn; + Font fallback_font_zh_tw; + Font fallback_font_zh_hk; + EXPECT_TRUE( + GetFallbackFont(base_font, "zh-CN", kCJKTest, &fallback_font_zh_cn)); + EXPECT_TRUE( + GetFallbackFont(base_font, "zh-TW", kCJKTest, &fallback_font_zh_tw)); + EXPECT_TRUE( + GetFallbackFont(base_font, "zh-HK", kCJKTest, &fallback_font_zh_hk)); + EXPECT_EQ(fallback_font_zh_cn.GetFontName(), + fallback_font_zh_tw.GetFontName()); + EXPECT_EQ(fallback_font_zh_cn.GetFontName(), + fallback_font_zh_hk.GetFontName()); + + Font fallback_font_ja; + Font fallback_font_ja_jp; + EXPECT_TRUE(GetFallbackFont(base_font, "ja", kCJKTest, &fallback_font_ja)); + EXPECT_TRUE( + GetFallbackFont(base_font, "ja-JP", kCJKTest, &fallback_font_ja_jp)); + EXPECT_EQ(fallback_font_ja.GetFontName(), fallback_font_ja_jp.GetFontName()); + + Font fallback_font_ko; + Font fallback_font_ko_kr; + EXPECT_TRUE(GetFallbackFont(base_font, "ko", kCJKTest, &fallback_font_ko)); + EXPECT_TRUE( + GetFallbackFont(base_font, "ko-KR", kCJKTest, &fallback_font_ko_kr)); + EXPECT_EQ(fallback_font_ko.GetFontName(), fallback_font_ko_kr.GetFontName()); + + // The three fonts must not be the same. + EXPECT_NE(fallback_font_zh_cn.GetFontName(), fallback_font_ja.GetFontName()); + EXPECT_NE(fallback_font_zh_cn.GetFontName(), fallback_font_ko.GetFontName()); + EXPECT_NE(fallback_font_ja.GetFontName(), fallback_font_ko.GetFontName()); +} +#endif // !defined(OS_ANDROID) && !defined(OS_FUCHSIA) + +} // namespace gfx diff --git a/font_fallback_unittest.cc b/font_fallback_unittest.cc new file mode 100644 index 000000000000..236ba7c1ddcf --- /dev/null +++ b/font_fallback_unittest.cc @@ -0,0 +1,276 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/font_fallback_win.h" + +#include + +#include "base/cxx17_backports.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/test/task_environment.h" +#include "build/build_config.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/icu/source/common/unicode/uchar.h" +#include "third_party/icu/source/common/unicode/uscript.h" +#include "third_party/icu/source/common/unicode/utf16.h" +#include "third_party/skia/include/core/SkTypeface.h" +#include "ui/gfx/platform_font.h" +#include "ui/gfx/test/font_fallback_test_data.h" + +#if defined(OS_WIN) +#include "base/win/windows_version.h" +#endif + +namespace gfx { + +namespace { + +// Options to parameterized unittests. +struct FallbackFontTestOption { + bool ignore_get_fallback_failure = false; + bool skip_code_point_validation = false; + bool skip_fallback_fonts_validation = false; +}; + +const FallbackFontTestOption default_fallback_option = {false, false, false}; +// Options for tests that does not validate the GetFallbackFont(...) parameters. +const FallbackFontTestOption untested_fallback_option = {true, true, true}; + +struct BaseFontTestOption { + const char* family_name = nullptr; + int delta = 0; + int style = 0; + Font::Weight weight = Font::Weight::NORMAL; +}; + +constexpr BaseFontTestOption default_base_font; +constexpr BaseFontTestOption styled_font = {nullptr, 1, Font::FontStyle::ITALIC, + Font::Weight::BOLD}; +constexpr BaseFontTestOption sans_font = {"sans", 2}; + +using FallbackFontTestParamInfo = std:: + tuple; + +class GetFallbackFontTest + : public ::testing::TestWithParam { + public: + GetFallbackFontTest() = default; + + GetFallbackFontTest(const GetFallbackFontTest&) = delete; + GetFallbackFontTest& operator=(const GetFallbackFontTest&) = delete; + + static std::string ParamInfoToString( + ::testing::TestParamInfo param_info) { + const FallbackFontTestCase& test_case = std::get<0>(param_info.param); + const BaseFontTestOption& base_font_option = std::get<2>(param_info.param); + + std::string font_option; + if (base_font_option.family_name) + font_option += std::string("F") + base_font_option.family_name; + if (base_font_option.delta || base_font_option.style || + base_font_option.style) { + font_option += + base::StringPrintf("_d%ds%dw%d", base_font_option.delta, + base_font_option.style, base_font_option.weight); + } + + std::string language_tag = test_case.language_tag; + base::RemoveChars(language_tag, "-", &language_tag); + return std::string("S") + uscript_getName(test_case.script) + "L" + + language_tag + font_option; + } + + void SetUp() override { + std::tie(test_case_, test_option_, base_font_option_) = GetParam(); + } + + protected: + bool GetFallbackFont(const Font& font, + const std::string& language_tag, + Font* result) { + return gfx::GetFallbackFont(font, language_tag, test_case_.text, result); + } + + bool EnsuresScriptSupportCodePoints(const std::u16string& text, + UScriptCode script, + const std::string& script_name) { + size_t i = 0; + while (i < text.length()) { + UChar32 code_point; + U16_NEXT(text.c_str(), i, text.size(), code_point); + if (!uscript_hasScript(code_point, script)) { + // Retrieve the appropriate script + UErrorCode script_error; + UScriptCode codepoint_script = + uscript_getScript(code_point, &script_error); + + ADD_FAILURE() << "CodePoint U+" << std::hex << code_point + << " is not part of the script '" << script_name + << "'. Script '" << uscript_getName(codepoint_script) + << "' detected."; + return false; + } + } + return true; + } + + bool DoesFontSupportCodePoints(Font font, const std::u16string& text) { + sk_sp skia_face = font.platform_font()->GetNativeSkTypeface(); + if (!skia_face) { + ADD_FAILURE() << "Cannot create typeface for '" << font.GetFontName() + << "'."; + return false; + } + + size_t i = 0; + const SkGlyphID kUnsupportedGlyph = 0; + while (i < text.length()) { + UChar32 code_point; + U16_NEXT(text.c_str(), i, text.size(), code_point); + SkGlyphID glyph_id = skia_face->unicharToGlyph(code_point); + if (glyph_id == kUnsupportedGlyph) + return false; + } + return true; + } + + FallbackFontTestCase test_case_; + FallbackFontTestOption test_option_; + BaseFontTestOption base_font_option_; + std::string script_name_; + + private: + // Needed to bypass DCHECK in GetFallbackFont. + base::test::TaskEnvironment task_environment_{ + base::test::TaskEnvironment::MainThreadType::UI}; +}; + +} // namespace + +// This test ensures the font fallback work correctly. It will ensures that +// 1) The script supports the text +// 2) The input font does not already support the text +// 3) The call to GetFallbackFont() succeed +// 4) The fallback font has a glyph for every character of the text +// +// The previous checks can be activated or deactivated through the class +// FallbackFontTestOption (e.g. test_option_). +TEST_P(GetFallbackFontTest, GetFallbackFont) { + // Default system font. + Font base_font; + // Apply font options to the base font. + if (base_font_option_.family_name) + base_font = Font(base_font_option_.family_name, base_font.GetFontSize()); + if (base_font_option_.delta != 0 || base_font_option_.style != 0 || + base_font_option_.weight != gfx::Font::Weight::NORMAL) { + base_font = + base_font.Derive(base_font_option_.delta, base_font_option_.style, + base_font_option_.weight); + } + +#if defined(OS_WIN) + // Skip testing this call to GetFallbackFont on older windows versions. Some + // fonts only got introduced on windows 10 and the test will fail on previous + // versions. + const bool is_win10 = base::win::GetVersion() >= base::win::Version::WIN10; + if (test_case_.is_win10 && !is_win10) + return; +#endif + + // Retrieve the name of the current script. + script_name_ = uscript_getName(test_case_.script); + + // Validate that tested characters are part of the script. + if (!test_option_.skip_code_point_validation && + !EnsuresScriptSupportCodePoints(test_case_.text, test_case_.script, + script_name_)) { + return; + } + + // The default font already support it, do not try to find a fallback font. + if (DoesFontSupportCodePoints(base_font, test_case_.text)) + return; + + // Retrieve the fallback font. + Font fallback_font; + bool result = + GetFallbackFont(base_font, test_case_.language_tag, &fallback_font); + if (!result) { + if (!test_option_.ignore_get_fallback_failure) + ADD_FAILURE() << "GetFallbackFont failed for '" << script_name_ << "'"; + return; + } + + // Ensure the fallback font is a part of the validation fallback fonts list. + if (!test_option_.skip_fallback_fonts_validation) { + bool valid = std::find(test_case_.fallback_fonts.begin(), + test_case_.fallback_fonts.end(), + fallback_font.GetFontName()) != + test_case_.fallback_fonts.end(); + if (!valid) { + ADD_FAILURE() << "GetFallbackFont failed for '" << script_name_ + << "' invalid fallback font: " + << fallback_font.GetFontName() + << " not among valid options: " + << base::JoinString(test_case_.fallback_fonts, ", "); + return; + } + } + + // Ensure that glyphs exists in the fallback font. + if (!DoesFontSupportCodePoints(fallback_font, test_case_.text)) { + ADD_FAILURE() << "Font '" << fallback_font.GetFontName() + << "' does not matched every CodePoints."; + return; + } +} + +// Produces a font test case for every script. +std::vector GetSampleFontTestCases() { + std::vector result; + + const unsigned int script_max = u_getIntPropertyMaxValue(UCHAR_SCRIPT) + 1; + for (unsigned int i = 0; i < script_max; i++) { + const UScriptCode script = static_cast(i); + + // Make a sample text to test the script. + char16_t text[8]; + UErrorCode errorCode = U_ZERO_ERROR; + int text_length = + uscript_getSampleString(script, text, base::size(text), &errorCode); + if (text_length <= 0 || errorCode != U_ZERO_ERROR) + continue; + + FallbackFontTestCase test_case(script, "", text, {}); + result.push_back(test_case); + } + return result; +} + +// Ensures that the default fallback font gives known results. The test +// is validating that a known fallback font is given for a given text and font. +INSTANTIATE_TEST_SUITE_P( + KnownExpectedFonts, + GetFallbackFontTest, + testing::Combine( + testing::ValuesIn(kGetFontFallbackTests), + testing::Values(default_fallback_option), + testing::Values(default_base_font, styled_font, sans_font)), + GetFallbackFontTest::ParamInfoToString); + +// Ensures that font fallback functions are working properly for any string +// (strings from any script). The test doesn't enforce the functions to +// give a fallback font. The accepted behaviors are: +// 1) The fallback function failed and doesn't provide a fallback. +// 2) The fallback function succeeded and the font supports every glyphs. +INSTANTIATE_TEST_SUITE_P( + Glyphs, + GetFallbackFontTest, + testing::Combine(testing::ValuesIn(GetSampleFontTestCases()), + testing::Values(untested_fallback_option), + testing::Values(default_base_font)), + GetFallbackFontTest::ParamInfoToString); + +} // namespace gfx diff --git a/font_fallback_win.cc b/font_fallback_win.cc new file mode 100644 index 000000000000..753b5112330a --- /dev/null +++ b/font_fallback_win.cc @@ -0,0 +1,246 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/font_fallback_win.h" + +#include +#include + +#include "base/macros.h" +#include "base/memory/singleton.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/task/current_thread.h" +#include "base/trace_event/trace_event.h" +#include "base/win/registry.h" +#include "ui/gfx/font.h" +#include "ui/gfx/font_fallback.h" +#include "ui/gfx/font_fallback_skia_impl.h" +#include "ui/gfx/platform_font.h" + +namespace gfx { + +namespace { + +// Queries the registry to get a mapping from font filenames to font names. +void QueryFontsFromRegistry(std::map* map) { + const wchar_t* kFonts = + L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"; + + base::win::RegistryValueIterator it(HKEY_LOCAL_MACHINE, kFonts); + for (; it.Valid(); ++it) { + const std::string filename = + base::ToLowerASCII(base::WideToUTF8(it.Value())); + (*map)[filename] = base::WideToUTF8(it.Name()); + } +} + +// Fills |font_names| with a list of font families found in the font file at +// |filename|. Takes in a |font_map| from font filename to font families, which +// is filled-in by querying the registry, if empty. +void GetFontNamesFromFilename(const std::string& filename, + std::map* font_map, + std::vector* font_names) { + if (font_map->empty()) + QueryFontsFromRegistry(font_map); + + std::map::const_iterator it = + font_map->find(base::ToLowerASCII(filename)); + if (it == font_map->end()) + return; + + internal::ParseFontFamilyString(it->second, font_names); +} + +// Returns true if |text| contains only ASCII digits. +bool ContainsOnlyDigits(const std::string& text) { + return text.find_first_not_of("0123456789") == std::u16string::npos; +} + +// Appends a Font with the given |name| and |size| to |fonts| unless the last +// entry is already a font with that name. +void AppendFont(const std::string& name, int size, std::vector* fonts) { + if (fonts->empty() || fonts->back().GetFontName() != name) + fonts->push_back(Font(name, size)); +} + +// Queries the registry to get a list of linked fonts for |font|. +void QueryLinkedFontsFromRegistry(const Font& font, + std::map* font_map, + std::vector* linked_fonts) { + const wchar_t* kSystemLink = + L"Software\\Microsoft\\Windows NT\\CurrentVersion\\FontLink\\SystemLink"; + + base::win::RegKey key; + if (FAILED(key.Open(HKEY_LOCAL_MACHINE, kSystemLink, KEY_READ))) + return; + + const std::wstring original_font_name = base::UTF8ToWide(font.GetFontName()); + std::vector values; + if (FAILED(key.ReadValues(original_font_name.c_str(), &values))) { + key.Close(); + return; + } + + std::string filename; + std::string font_name; + for (size_t i = 0; i < values.size(); ++i) { + internal::ParseFontLinkEntry( + base::WideToUTF8(values[i]), &filename, &font_name); + + // If the font name is present, add that directly, otherwise add the + // font names corresponding to the filename. + if (!font_name.empty()) { + AppendFont(font_name, font.GetFontSize(), linked_fonts); + } else if (!filename.empty()) { + std::vector filename_fonts; + GetFontNamesFromFilename(filename, font_map, &filename_fonts); + for (const std::string& filename_font : filename_fonts) + AppendFont(filename_font, font.GetFontSize(), linked_fonts); + } + } + + key.Close(); +} + +// CachedFontLinkSettings is a singleton cache of the Windows font settings +// from the registry. It maintains a cached view of the registry's list of +// system fonts and their font link chains. +class CachedFontLinkSettings { + public: + static CachedFontLinkSettings* GetInstance(); + + CachedFontLinkSettings(const CachedFontLinkSettings&) = delete; + CachedFontLinkSettings& operator=(const CachedFontLinkSettings&) = delete; + + // Returns the linked fonts list correspond to |font|. Returned value will + // never be null. + const std::vector* GetLinkedFonts(const Font& font); + + private: + friend struct base::DefaultSingletonTraits; + + CachedFontLinkSettings(); + virtual ~CachedFontLinkSettings(); + + // Map of system fonts, from file names to font families. + std::map cached_system_fonts_; + + // Map from font names to vectors of linked fonts. + std::map > cached_linked_fonts_; +}; + +// static +CachedFontLinkSettings* CachedFontLinkSettings::GetInstance() { + return base::Singleton< + CachedFontLinkSettings, + base::LeakySingletonTraits>::get(); +} + +const std::vector* CachedFontLinkSettings::GetLinkedFonts( + const Font& font) { + const std::string& font_name = font.GetFontName(); + std::map >::const_iterator it = + cached_linked_fonts_.find(font_name); + if (it != cached_linked_fonts_.end()) + return &it->second; + + std::vector* linked_fonts = &cached_linked_fonts_[font_name]; + QueryLinkedFontsFromRegistry(font, &cached_system_fonts_, linked_fonts); + return linked_fonts; +} + +CachedFontLinkSettings::CachedFontLinkSettings() { +} + +CachedFontLinkSettings::~CachedFontLinkSettings() { +} + +} // namespace + +namespace internal { + +void ParseFontLinkEntry(const std::string& entry, + std::string* filename, + std::string* font_name) { + std::vector parts = base::SplitString( + entry, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); + filename->clear(); + font_name->clear(); + if (parts.size() > 0) + *filename = parts[0]; + // The second entry may be the font name or the first scaling factor, if the + // entry does not contain a font name. If it contains only digits, assume it + // is a scaling factor. + if (parts.size() > 1 && !ContainsOnlyDigits(parts[1])) + *font_name = parts[1]; +} + +void ParseFontFamilyString(const std::string& family, + std::vector* font_names) { + // The entry is comma separated, having the font filename as the first value + // followed optionally by the font family name and a pair of integer scaling + // factors. + // TODO(asvitkine): Should we support these scaling factors? + *font_names = base::SplitString(family, "&", base::TRIM_WHITESPACE, + base::SPLIT_WANT_ALL); + if (!font_names->empty()) { + const size_t index = font_names->back().find('('); + if (index != std::string::npos) { + font_names->back().resize(index); + base::TrimWhitespaceASCII(font_names->back(), base::TRIM_TRAILING, + &font_names->back()); + } + } +} + +} // namespace internal + +std::vector GetFallbackFonts(const Font& font) { + TRACE_EVENT0("fonts", "gfx::GetFallbackFonts"); + std::string font_family = font.GetFontName(); + CachedFontLinkSettings* font_link = CachedFontLinkSettings::GetInstance(); + // GetLinkedFonts doesn't care about the font size, so we always pass 10. + return *font_link->GetLinkedFonts(Font(font_family, 10)); +} + +bool GetFallbackFont(const Font& font, + const std::string& locale, + base::StringPiece16 text, + Font* result) { + TRACE_EVENT0("fonts", "gfx::GetFallbackFont"); + // Creating a DirectWrite font fallback can be expensive. It's ok in the + // browser process because we can use the shared system fallback, but in the + // renderer this can cause hangs. Code that needs font fallback in the + // renderer should instead use the font proxy. + DCHECK(base::CurrentUIThread::IsSet()); + + // The text passed must be at least length 1. + if (text.empty()) + return false; + + // Check that we have at least as much text as was claimed. If we have less + // text than expected then DirectWrite will become confused and crash. This + // shouldn't happen, but crbug.com/624905 shows that it happens sometimes. + constexpr char16_t kNulCharacter = '\0'; + if (text.find(kNulCharacter) != base::StringPiece16::npos) + return false; + + sk_sp fallback_typeface = + GetSkiaFallbackTypeface(font, locale, text); + + if (!fallback_typeface) + return false; + + // Fallback needs to keep the exact SkTypeface, as re-matching the font using + // family name and styling information loses access to the underlying platform + // font handles and is not guaranteed to result in the correct typeface, see + // https://crbug.com/1003829 + *result = Font(PlatformFont::CreateFromSkTypeface( + std::move(fallback_typeface), font.GetFontSize(), absl::nullopt)); + return true; +} + +} // namespace gfx diff --git a/font_fallback_win.h b/font_fallback_win.h new file mode 100644 index 000000000000..bf6c74534511 --- /dev/null +++ b/font_fallback_win.h @@ -0,0 +1,40 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_FONT_FALLBACK_WIN_H_ +#define UI_GFX_FONT_FALLBACK_WIN_H_ + +#include + +#include +#include + +#include "base/macros.h" +#include "ui/gfx/font.h" +#include "ui/gfx/font_fallback.h" + +namespace gfx { + +// Internals of font_fallback_win.cc exposed for testing. +namespace internal { + +// Parses comma separated SystemLink |entry|, per the format described here: +// http://msdn.microsoft.com/en-us/goglobal/bb688134.aspx +// +// Sets |filename| and |font_name| respectively. If a field is not present +// or could not be parsed, the corresponding parameter will be cleared. +void GFX_EXPORT ParseFontLinkEntry(const std::string& entry, + std::string* filename, + std::string* font_name); + +// Parses a font |family| in the format "FamilyFoo & FamilyBar (TrueType)". +// Splits by '&' and strips off the trailing parenthesized expression. +void GFX_EXPORT ParseFontFamilyString(const std::string& family, + std::vector* font_names); + +} // namespace internal + +} // namespace gfx + +#endif // UI_GFX_FONT_FALLBACK_WIN_H_ diff --git a/font_fallback_win_unittest.cc b/font_fallback_win_unittest.cc new file mode 100644 index 000000000000..b3b68b32ef3e --- /dev/null +++ b/font_fallback_win_unittest.cc @@ -0,0 +1,145 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/font_fallback_win.h" + +#include "base/cxx17_backports.h" +#include "base/macros.h" +#include "base/test/task_environment.h" +#include "base/win/windows_version.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace gfx { + +namespace { + +const char kDefaultApplicationLocale[] = "us-en"; + +class FontFallbackWinTest : public testing::Test { + public: + FontFallbackWinTest() = default; + + FontFallbackWinTest(const FontFallbackWinTest&) = delete; + FontFallbackWinTest& operator=(const FontFallbackWinTest&) = delete; + + private: + // Needed to bypass DCHECK in GetFallbackFont. + base::test::TaskEnvironment task_environment_{ + base::test::TaskEnvironment::MainThreadType::UI}; +}; + +} // namespace + +TEST_F(FontFallbackWinTest, ParseFontLinkEntry) { + std::string file; + std::string font; + + internal::ParseFontLinkEntry("TAHOMA.TTF", &file, &font); + EXPECT_EQ("TAHOMA.TTF", file); + EXPECT_EQ("", font); + + internal::ParseFontLinkEntry("MSGOTHIC.TTC,MS UI Gothic", &file, &font); + EXPECT_EQ("MSGOTHIC.TTC", file); + EXPECT_EQ("MS UI Gothic", font); + + internal::ParseFontLinkEntry("MALGUN.TTF,128,96", &file, &font); + EXPECT_EQ("MALGUN.TTF", file); + EXPECT_EQ("", font); + + internal::ParseFontLinkEntry("MEIRYO.TTC,Meiryo,128,85", &file, &font); + EXPECT_EQ("MEIRYO.TTC", file); + EXPECT_EQ("Meiryo", font); +} + +TEST_F(FontFallbackWinTest, ParseFontFamilyString) { + std::vector font_names; + + internal::ParseFontFamilyString("Times New Roman (TrueType)", &font_names); + ASSERT_EQ(1U, font_names.size()); + EXPECT_EQ("Times New Roman", font_names[0]); + font_names.clear(); + + internal::ParseFontFamilyString("Cambria & Cambria Math (TrueType)", + &font_names); + ASSERT_EQ(2U, font_names.size()); + EXPECT_EQ("Cambria", font_names[0]); + EXPECT_EQ("Cambria Math", font_names[1]); + font_names.clear(); + + internal::ParseFontFamilyString( + "Meiryo & Meiryo Italic & Meiryo UI & Meiryo UI Italic (TrueType)", + &font_names); + ASSERT_EQ(4U, font_names.size()); + EXPECT_EQ("Meiryo", font_names[0]); + EXPECT_EQ("Meiryo Italic", font_names[1]); + EXPECT_EQ("Meiryo UI", font_names[2]); + EXPECT_EQ("Meiryo UI Italic", font_names[3]); +} + +TEST_F(FontFallbackWinTest, EmptyStringFallback) { + Font base_font; + Font fallback_font; + bool result = GetFallbackFont(base_font, kDefaultApplicationLocale, + base::StringPiece16(), &fallback_font); + EXPECT_FALSE(result); +} + +TEST_F(FontFallbackWinTest, NulTerminatedStringPiece) { + Font base_font; + Font fallback_font; + // Multiple ending NUL characters. + const char16_t kTest1[] = {0x0540, 0x0541, 0, 0, 0}; + EXPECT_FALSE(GetFallbackFont(base_font, kDefaultApplicationLocale, + base::StringPiece16(kTest1, base::size(kTest1)), + &fallback_font)); + // No ending NUL character. + const char16_t kTest2[] = {0x0540, 0x0541}; + EXPECT_TRUE(GetFallbackFont(base_font, kDefaultApplicationLocale, + base::StringPiece16(kTest2, base::size(kTest2)), + &fallback_font)); + + // NUL only characters. + const char16_t kTest3[] = {0, 0, 0}; + EXPECT_FALSE(GetFallbackFont(base_font, kDefaultApplicationLocale, + base::StringPiece16(kTest3, base::size(kTest3)), + &fallback_font)); +} + +TEST_F(FontFallbackWinTest, CJKLocaleFallback) { + // The uniscribe fallback used by win7 does not support locale. + if (base::win::GetVersion() < base::win::Version::WIN10) + return; + + // Han unification is an effort to map multiple character sets of the CJK + // languages into a single set of unified characters. Han characters are a + // common feature of written Chinese (hanzi), Japanese (kanji), and Korean + // (hanja). The same text will be rendered using a different font based on + // locale. + const char16_t kCJKTest[] = u"\u8AA4\u904E\u9AA8"; + Font base_font; + Font fallback_font; + + EXPECT_TRUE(GetFallbackFont(base_font, "zh-CN", kCJKTest, &fallback_font)); + EXPECT_EQ(fallback_font.GetFontName(), "Microsoft YaHei UI"); + + EXPECT_TRUE(GetFallbackFont(base_font, "zh-TW", kCJKTest, &fallback_font)); + EXPECT_EQ(fallback_font.GetFontName(), "Microsoft JhengHei UI"); + + EXPECT_TRUE(GetFallbackFont(base_font, "zh-HK", kCJKTest, &fallback_font)); + EXPECT_EQ(fallback_font.GetFontName(), "Microsoft JhengHei UI"); + + EXPECT_TRUE(GetFallbackFont(base_font, "ja", kCJKTest, &fallback_font)); + EXPECT_EQ(fallback_font.GetFontName(), "Yu Gothic UI"); + + EXPECT_TRUE(GetFallbackFont(base_font, "ja-JP", kCJKTest, &fallback_font)); + EXPECT_EQ(fallback_font.GetFontName(), "Yu Gothic UI"); + + EXPECT_TRUE(GetFallbackFont(base_font, "ko", kCJKTest, &fallback_font)); + EXPECT_EQ(fallback_font.GetFontName(), "Malgun Gothic"); + + EXPECT_TRUE(GetFallbackFont(base_font, "ko-KR", kCJKTest, &fallback_font)); + EXPECT_EQ(fallback_font.GetFontName(), "Malgun Gothic"); +} + +} // namespace gfx diff --git a/font_list.cc b/font_list.cc new file mode 100644 index 000000000000..4155b8aba459 --- /dev/null +++ b/font_list.cc @@ -0,0 +1,255 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/font_list.h" + +#include + +#include "base/lazy_instance.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "third_party/skia/include/core/SkFontMgr.h" +#include "third_party/skia/include/core/SkTypeface.h" +#include "ui/gfx/font_list_impl.h" + +namespace { + +// Font description of the default font set. +base::LazyInstance::Leaky g_default_font_description = + LAZY_INSTANCE_INITIALIZER; + +// The default instance of gfx::FontListImpl. +base::LazyInstance>::Leaky g_default_impl = + LAZY_INSTANCE_INITIALIZER; +bool g_default_impl_initialized = false; + +bool IsFontFamilyAvailable(const std::string& family, SkFontMgr* fontManager) { +#if defined(OS_LINUX) || defined(OS_CHROMEOS) + return !!fontManager->legacyMakeTypeface(family.c_str(), SkFontStyle()); +#else + sk_sp set(fontManager->matchFamily(family.c_str())); + return set && set->count(); +#endif +} + +} // namespace + +namespace gfx { + +// static +bool FontList::ParseDescription(const std::string& description, + std::vector* families_out, + int* style_out, + int* size_pixels_out, + Font::Weight* weight_out) { + DCHECK(families_out); + DCHECK(style_out); + DCHECK(size_pixels_out); + DCHECK(weight_out); + + *families_out = base::SplitString( + description, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); + if (families_out->empty()) + return false; + for (auto& family : *families_out) + base::TrimWhitespaceASCII(family, base::TRIM_ALL, &family); + + // The last item is "[STYLE1] [STYLE2] [...] SIZE". + std::vector styles = base::SplitString( + families_out->back(), base::kWhitespaceASCII, + base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY); + families_out->pop_back(); + if (styles.empty()) + return false; + + // The size takes the form "px". + std::string size_string = styles.back(); + styles.pop_back(); + if (!base::EndsWith(size_string, "px", base::CompareCase::SENSITIVE)) + return false; + size_string.resize(size_string.size() - 2); + if (!base::StringToInt(size_string, size_pixels_out) || + *size_pixels_out <= 0) + return false; + + // Font supports ITALIC and weights; underline is supported via RenderText. + *style_out = Font::NORMAL; + *weight_out = Font::Weight::NORMAL; + for (const auto& style_string : styles) { + if (style_string == "Italic") + *style_out |= Font::ITALIC; + else if (style_string == "Thin") + *weight_out = Font::Weight::THIN; + else if (style_string == "Ultra-Light") + *weight_out = Font::Weight::EXTRA_LIGHT; + else if (style_string == "Light") + *weight_out = Font::Weight::LIGHT; + else if (style_string == "Normal") + *weight_out = Font::Weight::NORMAL; + else if (style_string == "Medium") + *weight_out = Font::Weight::MEDIUM; + else if (style_string == "Semi-Bold") + *weight_out = Font::Weight::SEMIBOLD; + else if (style_string == "Bold") + *weight_out = Font::Weight::BOLD; + else if (style_string == "Ultra-Bold") + *weight_out = Font::Weight::EXTRA_BOLD; + else if (style_string == "Heavy") + *weight_out = Font::Weight::BLACK; + else + return false; + } + + return true; +} + +FontList::FontList() : impl_(GetDefaultImpl()) {} + +FontList::FontList(const FontList& other) : impl_(other.impl_) {} + +FontList::FontList(const std::string& font_description_string) + : impl_(new FontListImpl(font_description_string)) {} + +FontList::FontList(const std::vector& font_names, + int font_style, + int font_size, + Font::Weight font_weight) + : impl_(new FontListImpl(font_names, font_style, font_size, font_weight)) {} + +FontList::FontList(const std::vector& fonts) + : impl_(new FontListImpl(fonts)) {} + +FontList::FontList(const Font& font) : impl_(new FontListImpl(font)) {} + +FontList::~FontList() {} + +FontList& FontList::operator=(const FontList& other) { + impl_ = other.impl_; + return *this; +} + +// static +void FontList::SetDefaultFontDescription(const std::string& font_description) { + // The description string must end with "px" for size in pixel, or must be + // the empty string, which specifies to use a single default font. + DCHECK(font_description.empty() || + base::EndsWith(font_description, "px", base::CompareCase::SENSITIVE)); + + g_default_font_description.Get() = font_description; + g_default_impl_initialized = false; +} + +FontList FontList::Derive(int size_delta, + int font_style, + Font::Weight weight) const { + return FontList(impl_->Derive(size_delta, font_style, weight)); +} + +FontList FontList::DeriveWithSizeDelta(int size_delta) const { + return Derive(size_delta, GetFontStyle(), GetFontWeight()); +} + +FontList FontList::DeriveWithStyle(int font_style) const { + return Derive(0, font_style, GetFontWeight()); +} + +FontList FontList::DeriveWithWeight(Font::Weight weight) const { + return Derive(0, GetFontStyle(), weight); +} + +FontList FontList::DeriveWithHeightUpperBound(int height) const { + FontList font_list(*this); + for (int font_size = font_list.GetFontSize(); font_size > 1; --font_size) { + const int internal_leading = + font_list.GetBaseline() - font_list.GetCapHeight(); + // Some platforms don't support getting the cap height, and simply return + // the entire font ascent from GetCapHeight(). Centering the ascent makes + // the font look too low, so if GetCapHeight() returns the ascent, center + // the entire font height instead. + const int space = + height - ((internal_leading != 0) ? + font_list.GetCapHeight() : font_list.GetHeight()); + const int y_offset = space / 2 - internal_leading; + const int space_at_bottom = height - (y_offset + font_list.GetHeight()); + if ((y_offset >= 0) && (space_at_bottom >= 0)) + break; + font_list = font_list.DeriveWithSizeDelta(-1); + } + return font_list; +} + +int FontList::GetHeight() const { + return impl_->GetHeight(); +} + +int FontList::GetBaseline() const { + return impl_->GetBaseline(); +} + +int FontList::GetCapHeight() const { + return impl_->GetCapHeight(); +} + +int FontList::GetExpectedTextWidth(int length) const { + return impl_->GetExpectedTextWidth(length); +} + +int FontList::GetFontStyle() const { + return impl_->GetFontStyle(); +} + +int FontList::GetFontSize() const { + return impl_->GetFontSize(); +} + +Font::Weight FontList::GetFontWeight() const { + return impl_->GetFontWeight(); +} + +const std::vector& FontList::GetFonts() const { + return impl_->GetFonts(); +} + +const Font& FontList::GetPrimaryFont() const { + return impl_->GetPrimaryFont(); +} + +FontList::FontList(FontListImpl* impl) : impl_(impl) {} + +// static +const scoped_refptr& FontList::GetDefaultImpl() { + // SetDefaultFontDescription() must be called and the default font description + // must be set earlier than any call of this function. + DCHECK(g_default_font_description.IsCreated()) + << "SetDefaultFontDescription has not been called."; + + if (!g_default_impl_initialized) { + g_default_impl.Get() = + g_default_font_description.Get().empty() ? + new FontListImpl(Font()) : + new FontListImpl(g_default_font_description.Get()); + g_default_impl_initialized = true; + } + + return g_default_impl.Get(); +} + +// static +std::string FontList::FirstAvailableOrFirst(const std::string& font_name_list) { + std::vector families = base::SplitString( + font_name_list, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); + if (families.empty()) + return std::string(); + if (families.size() == 1) + return families[0]; + sk_sp fm(SkFontMgr::RefDefault()); + for (const auto& family : families) { + if (IsFontFamilyAvailable(family, fm.get())) + return family; + } + return families[0]; +} + +} // namespace gfx diff --git a/font_list.h b/font_list.h new file mode 100644 index 000000000000..a0d96fae610e --- /dev/null +++ b/font_list.h @@ -0,0 +1,187 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_FONT_LIST_H_ +#define UI_GFX_FONT_LIST_H_ + +#include +#include + +#include "base/memory/ref_counted.h" +#include "ui/gfx/font.h" +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +class FontListImpl; + +// FontList represents a list of fonts and provides metrics which are common +// across the fonts. FontList is copyable and quite cheap to copy. +// +// The format of font description strings is a subset of that used by Pango, as +// described at +// http://developer.gnome.org/pango/stable/pango-Fonts.html#pango-font-description-from-string +// +// Pango font description strings should not be passed directly into FontLists. +// +// The format is ",[STYLES] ", where: +// - FONT_FAMILY_LIST is a comma-separated list of font family names, +// - STYLES is an optional space-separated list of style names (case-sensitive +// "Italic" "Ultra-Light" "Light" "Normal" "Semi-Bold" "Bold" "Ultra-Bold" +// "Heavy" are supported), and +// - SIZE is an integer font size in pixels with the suffix "px" +// +// Here are examples of valid font description strings: +// - "Arial, Helvetica, Italic Semi-Bold 14px" +// - "Arial, 14px" +class GFX_EXPORT FontList { + public: + // Parses a FontList description string into |families_out|, |style_out| (a + // bitfield of gfx::Font::Style values), |size_pixels_out| and |weight_out|. + // Returns true if the string is properly-formed. + static bool ParseDescription(const std::string& description, + std::vector* families_out, + int* style_out, + int* size_pixels_out, + Font::Weight* weight_out); + + // Creates a font list with default font names, size and style, which are + // specified by SetDefaultFontDescription(). + FontList(); + + // Creates a font list that is a clone of another font list. + FontList(const FontList& other); + + // Creates a font list from a string representing font names, styles, and + // size. + explicit FontList(const std::string& font_description_string); + + // Creates a font list from font names, styles, size and weight. + FontList(const std::vector& font_names, + int font_style, + int font_size, + Font::Weight font_weight); + + // Creates a font list from a Font vector. + // All fonts in this vector should have the same style and size. + explicit FontList(const std::vector& fonts); + + // Creates a font list from a Font. + explicit FontList(const Font& font); + + ~FontList(); + + // Copies the given font list into this object. + FontList& operator=(const FontList& other); + + // Sets the description string for default FontList construction. If it's + // empty, FontList will initialize using the default Font constructor. + // + // The client code must call this function before any call of the default + // constructor. This should be done on the UI thread. + // + // ui::ResourceBundle may call this function more than once when UI language + // is changed. + // + // Unit Tests should use ScopedDefaultFontDescription instead of calling this + // directly, to avoid leaving the default font description in an unexpected + // state for tests that run in the same process. + static void SetDefaultFontDescription(const std::string& font_description); + + // Returns a new FontList with the same font names but resized and the given + // style and weight. |size_delta| is the size in pixels to add to the current + // font size. |font_style| specifies the new style, which is a bitmask of the + // values: Font::ITALIC and Font::UNDERLINE. |weight| is the requested font + // weight. + FontList Derive(int size_delta, + int font_style, + Font::Weight weight) const; + + // Returns a new FontList with the same font names and style but resized. + // |size_delta| is the size in pixels to add to the current font size. + FontList DeriveWithSizeDelta(int size_delta) const; + + // Returns a new FontList with the same font names, weight and size but the + // given style. |font_style| specifies the new style, which is a bitmask of + // the values: Font::ITALIC and Font::UNDERLINE. + FontList DeriveWithStyle(int font_style) const; + + // Returns a new FontList with the same font name, size and style but with + // the given weight. + FontList DeriveWithWeight(Font::Weight weight) const; + + // Shrinks the font size until the font list fits within |height| while + // having its cap height vertically centered. Returns a new FontList with + // the correct height. + // + // The expected layout: + // +--------+-----------------------------------------------+------------+ + // | | y offset | space | + // | +--------+-------------------+------------------+ above | + // | | | | internal leading | cap height | + // | box | font | ascent (baseline) +------------------+------------+ + // | height | height | | cap height | + // | | |-------------------+------------------+------------+ + // | | | descent (height - baseline) | space | + // | +--------+--------------------------------------+ below | + // | | space at bottom | cap height | + // +--------+-----------------------------------------------+------------+ + // Goal: + // center of box height == center of cap height + // (i.e. space above cap height == space below cap height) + // Restrictions: + // y offset >= 0 + // space at bottom >= 0 + // (i.e. Entire font must be visible inside the box.) + FontList DeriveWithHeightUpperBound(int height) const; + + // Returns the height of this font list, which is max(ascent) + max(descent) + // for all the fonts in the font list. + int GetHeight() const; + + // Returns the baseline of this font list, which is max(baseline) for all the + // fonts in the font list. + int GetBaseline() const; + + // Returns the cap height of this font list. + // Currently returns the cap height of the primary font. + int GetCapHeight() const; + + // Returns the expected number of horizontal pixels needed to display the + // specified length of characters. Call GetStringWidth() to retrieve the + // actual number. + int GetExpectedTextWidth(int length) const; + + // Returns the |Font::FontStyle| style flags for this font list. + int GetFontStyle() const; + + // Returns the font size in pixels. + int GetFontSize() const; + + // Returns the font weight. + Font::Weight GetFontWeight() const; + + // Returns the Font vector. + const std::vector& GetFonts() const; + + // Returns the first font in the list. + const Font& GetPrimaryFont() const; + + // Returns the first available font name. If there is no available font, + // returns the first font name. Empty entries are ignored. + // Used by Blink and webui to pick the primary standard/serif/sans/fixed/etc. + // fonts from region-specific IDS lists. + static std::string FirstAvailableOrFirst(const std::string& font_name_list); + + private: + explicit FontList(FontListImpl* impl); + + static const scoped_refptr& GetDefaultImpl(); + + scoped_refptr impl_; +}; + +} // namespace gfx + +#endif // UI_GFX_FONT_LIST_H_ diff --git a/font_list_impl.cc b/font_list_impl.cc new file mode 100644 index 000000000000..d805ee18b287 --- /dev/null +++ b/font_list_impl.cc @@ -0,0 +1,242 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/font_list_impl.h" + +#include + +#include + +#include "base/check_op.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "ui/gfx/font_list.h" + +namespace gfx { +namespace { + +// Returns a font description from |families|, |style|, and |size_pixels|. +std::string BuildDescription(const std::vector& families, + int style, + int size_pixels, + Font::Weight weight) { + std::string description = base::JoinString(families, ","); + description += ","; + + if (style & Font::ITALIC) + description += "Italic "; + switch (weight) { + case Font::Weight::THIN: + description += "Thin "; + break; + case Font::Weight::EXTRA_LIGHT: + description += "Ultra-Light "; + break; + case Font::Weight::LIGHT: + description += "Light "; + break; + case Font::Weight::MEDIUM: + description += "Medium "; + break; + case Font::Weight::SEMIBOLD: + description += "Semi-Bold "; + break; + case Font::Weight::BOLD: + description += "Bold "; + break; + case Font::Weight::EXTRA_BOLD: + description += "Ultra-Bold "; + break; + case Font::Weight::BLACK: + description += "Heavy "; + break; + case Font::Weight::NORMAL: + case Font::Weight::INVALID: + break; + } + + description += base::NumberToString(size_pixels); + description += "px"; + + return description; +} + +} // namespace + +FontListImpl::FontListImpl(const std::string& font_description_string) + : font_description_string_(font_description_string), + common_height_(-1), + common_baseline_(-1), + font_style_(-1), + font_size_(-1), + font_weight_(Font::Weight::INVALID) { + DCHECK(!font_description_string.empty()); + // DCHECK description string ends with "px" for size in pixel. + DCHECK(base::EndsWith(font_description_string, "px", + base::CompareCase::SENSITIVE)); +} + +FontListImpl::FontListImpl(const std::vector& font_names, + int font_style, + int font_size, + Font::Weight font_weight) + : font_description_string_( + BuildDescription(font_names, font_style, font_size, font_weight)), + common_height_(-1), + common_baseline_(-1), + font_style_(font_style), + font_size_(font_size), + font_weight_(font_weight) { + DCHECK(!font_names.empty()); + DCHECK(!font_names[0].empty()); +} + +FontListImpl::FontListImpl(const std::vector& fonts) + : fonts_(fonts), + common_height_(-1), + common_baseline_(-1), + font_style_(-1), + font_size_(-1), + font_weight_(Font::Weight::INVALID) { + DCHECK(!fonts.empty()); + font_style_ = fonts[0].GetStyle(); + font_size_ = fonts[0].GetFontSize(); + font_weight_ = fonts[0].GetWeight(); +#if DCHECK_IS_ON() + for (size_t i = 1; i < fonts.size(); ++i) { + DCHECK_EQ(fonts[i].GetStyle(), font_style_); + DCHECK_EQ(fonts[i].GetFontSize(), font_size_); + } +#endif +} + +FontListImpl::FontListImpl(const Font& font) + : common_height_(-1), + common_baseline_(-1), + font_style_(-1), + font_size_(-1), + font_weight_(Font::Weight::INVALID) { + fonts_.push_back(font); +} + +FontListImpl* FontListImpl::Derive(int size_delta, + int font_style, + Font::Weight weight) const { + // If there is a font vector, derive from that. + if (!fonts_.empty()) { + std::vector fonts = fonts_; + for (size_t i = 0; i < fonts.size(); ++i) + fonts[i] = fonts[i].Derive(size_delta, font_style, weight); + return new FontListImpl(fonts); + } + + // Otherwise, parse the font description string to derive from it. + std::vector font_names; + int old_size; + int old_style; + Font::Weight old_weight; + CHECK(FontList::ParseDescription(font_description_string_, &font_names, + &old_style, &old_size, &old_weight)); + const int size = std::max(1, old_size + size_delta); + return new FontListImpl(font_names, font_style, size, weight); +} + +int FontListImpl::GetHeight() const { + if (common_height_ == -1) + CacheCommonFontHeightAndBaseline(); + return common_height_; +} + +int FontListImpl::GetBaseline() const { + if (common_baseline_ == -1) + CacheCommonFontHeightAndBaseline(); + return common_baseline_; +} + +int FontListImpl::GetCapHeight() const { + // Assume the primary font is used to render Latin characters. + return GetPrimaryFont().GetCapHeight(); +} + +int FontListImpl::GetExpectedTextWidth(int length) const { + // Rely on the primary font metrics for the time being. + return GetPrimaryFont().GetExpectedTextWidth(length); +} + +int FontListImpl::GetFontStyle() const { + if (font_style_ == -1) + CacheFontStyleAndSize(); + return font_style_; +} + +int FontListImpl::GetFontSize() const { + if (font_size_ == -1) + CacheFontStyleAndSize(); + return font_size_; +} + +Font::Weight FontListImpl::GetFontWeight() const { + if (font_weight_ == Font::Weight::INVALID) + CacheFontStyleAndSize(); + return font_weight_; +} + +const std::vector& FontListImpl::GetFonts() const { + if (fonts_.empty()) { + DCHECK(!font_description_string_.empty()); + + std::vector font_names; + // It's possible that Font::UNDERLINE is specified and it's already + // stored in |font_style_| but |font_description_string_| doesn't have the + // underline info. So we should respect |font_style_| as long as it's + // valid. + int style = 0; + CHECK(FontList::ParseDescription(font_description_string_, &font_names, + &style, &font_size_, &font_weight_)); + if (font_style_ == -1) + font_style_ = style; + for (size_t i = 0; i < font_names.size(); ++i) { + DCHECK(!font_names[i].empty()); + + Font font(font_names[i], font_size_); + if (font_style_ == Font::NORMAL && font_weight_ == Font::Weight::NORMAL) + fonts_.push_back(font); + else + fonts_.push_back(font.Derive(0, font_style_, font_weight_)); + } + } + return fonts_; +} + +const Font& FontListImpl::GetPrimaryFont() const { + return GetFonts()[0]; +} + +FontListImpl::~FontListImpl() {} + +void FontListImpl::CacheCommonFontHeightAndBaseline() const { + int ascent = 0; + int descent = 0; + const std::vector& fonts = GetFonts(); + for (auto i = fonts.begin(); i != fonts.end(); ++i) { + ascent = std::max(ascent, i->GetBaseline()); + descent = std::max(descent, i->GetHeight() - i->GetBaseline()); + } + common_height_ = ascent + descent; + common_baseline_ = ascent; +} + +void FontListImpl::CacheFontStyleAndSize() const { + if (!fonts_.empty()) { + font_style_ = fonts_[0].GetStyle(); + font_size_ = fonts_[0].GetFontSize(); + font_weight_ = fonts_[0].GetWeight(); + } else { + std::vector font_names; + CHECK(FontList::ParseDescription(font_description_string_, &font_names, + &font_style_, &font_size_, &font_weight_)); + } +} + +} // namespace gfx diff --git a/font_list_impl.h b/font_list_impl.h new file mode 100644 index 000000000000..d405288195c7 --- /dev/null +++ b/font_list_impl.h @@ -0,0 +1,126 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_FONT_LIST_IMPL_H_ +#define UI_GFX_FONT_LIST_IMPL_H_ + +#include +#include + +#include "base/memory/ref_counted.h" +#include "ui/gfx/font.h" + +namespace gfx { + +// FontListImpl is designed to provide the implementation of FontList and +// intended to be used only from FontList. You must not use this class +// directly. +// +// FontListImpl represents a list of fonts either in the form of Font vector or +// in the form of a string representing font names, styles, and size. +// +// FontListImpl could be initialized either way without conversion to the other +// form. The conversion to the other form is done only when asked to get the +// other form. +// +// For the format of font description string, see font_list.h for details. +class FontListImpl : public base::RefCounted { + public: + // Creates a font list from a string representing font names, styles, and + // size. + explicit FontListImpl(const std::string& font_description_string); + + // Creates a font list from font names, styles, size and weight. + FontListImpl(const std::vector& font_names, + int font_style, + int font_size, + Font::Weight font_weight); + + // Creates a font list from a Font vector. + // All fonts in this vector should have the same style and size. + explicit FontListImpl(const std::vector& fonts); + + // Creates a font list from a Font. + explicit FontListImpl(const Font& font); + + // Returns a new FontListImpl with the same font names but resized and the + // given style and weight. |size_delta| is the size in pixels to add to the + // current font size. |font_style| specifies the new style, which is a + // bitmask of the values: Font::ITALIC and Font::UNDERLINE. + FontListImpl* Derive(int size_delta, + int font_style, + Font::Weight weight) const; + + // Returns the height of this font list, which is max(ascent) + max(descent) + // for all the fonts in the font list. + int GetHeight() const; + + // Returns the baseline of this font list, which is max(baseline) for all the + // fonts in the font list. + int GetBaseline() const; + + // Returns the cap height of this font list. + // Currently returns the cap height of the primary font. + int GetCapHeight() const; + + // Returns the expected number of horizontal pixels needed to display the + // specified length of characters. Call GetStringWidth() to retrieve the + // actual number. + int GetExpectedTextWidth(int length) const; + + // Returns the |Font::FontStyle| style flags for this font list. + int GetFontStyle() const; + + // Returns the font size in pixels. + int GetFontSize() const; + + // Returns the font weight. + Font::Weight GetFontWeight() const; + + // Returns the Font vector. + const std::vector& GetFonts() const; + + // Returns the first font in the list. + const Font& GetPrimaryFont() const; + + private: + friend class base::RefCounted; + + ~FontListImpl(); + + // Extracts common font height and baseline into |common_height_| and + // |common_baseline_|. + void CacheCommonFontHeightAndBaseline() const; + + // Extracts font style and size into |font_style_| and |font_size_|. + void CacheFontStyleAndSize() const; + + // A vector of Font. If FontListImpl is constructed with font description + // string, |fonts_| is not initialized during construction. Instead, it is + // computed lazily when user asked to get the font vector. + mutable std::vector fonts_; + + // A string representing font names, styles, and sizes. + // Please refer to the comments before class declaration for details on string + // format. + // If FontListImpl is constructed with a vector of font, + // |font_description_string_| is not initialized during construction. Instead, + // it is computed lazily when user asked to get the font description string. + // + // TODO(derat): Remove laziness so that this can be removed. + mutable std::string font_description_string_; + + // The cached common height and baseline of the fonts in the font list. + mutable int common_height_; + mutable int common_baseline_; + + // Cached font style and size. + mutable int font_style_; + mutable int font_size_; + mutable Font::Weight font_weight_; +}; + +} // namespace gfx + +#endif // UI_GFX_FONT_LIST_IMPL_H_ diff --git a/font_list_unittest.cc b/font_list_unittest.cc new file mode 100644 index 000000000000..405bdd90df45 --- /dev/null +++ b/font_list_unittest.cc @@ -0,0 +1,307 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/font_list.h" + +#include +#include +#include + +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "build/build_config.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/font_names_testing.h" + +namespace gfx { + +namespace { + +// Helper function for comparing fonts for equality. +std::string FontToString(const Font& font) { + std::string font_string = font.GetFontName(); + font_string += "|"; + font_string += base::NumberToString(font.GetFontSize()); + int style = font.GetStyle(); + if (style & Font::ITALIC) + font_string += "|italic"; + if (style & Font::UNDERLINE) + font_string += "|underline"; + auto weight = font.GetWeight(); + if (weight == Font::Weight::BLACK) + font_string += "|black"; + else if (weight == Font::Weight::BOLD) + font_string += "|bold"; + else if (weight == Font::Weight::EXTRA_BOLD) + font_string += "|extrabold"; + else if (weight == Font::Weight::EXTRA_LIGHT) + font_string += "|extralight"; + else if (weight == Font::Weight::LIGHT) + font_string += "|light"; + else if (weight == Font::Weight::MEDIUM) + font_string += "|medium"; + else if (weight == Font::Weight::NORMAL) + font_string += "|normal"; + else if (weight == Font::Weight::SEMIBOLD) + font_string += "|semibold"; + else if (weight == Font::Weight::THIN) + font_string += "|thin"; + return font_string; +} + +} // namespace + +TEST(FontListTest, ParseDescription) { + std::vector families; + int style = Font::NORMAL; + int size_pixels = 0; + Font::Weight weight = Font::Weight::NORMAL; + + // Parse a well-formed description containing styles and a size. + EXPECT_TRUE(FontList::ParseDescription("Arial,Helvetica,Bold Italic 12px", + &families, &style, &size_pixels, + &weight)); + ASSERT_EQ(2U, families.size()); + EXPECT_EQ("Arial", families[0]); + EXPECT_EQ("Helvetica", families[1]); + EXPECT_EQ(Font::ITALIC, style); + EXPECT_EQ(Font::Weight::BOLD, weight); + EXPECT_EQ(12, size_pixels); + + // Whitespace should be removed. + EXPECT_TRUE(FontList::ParseDescription(" Verdana , Italic Bold 10px ", + &families, &style, &size_pixels, + &weight)); + ASSERT_EQ(1U, families.size()); + EXPECT_EQ("Verdana", families[0]); + EXPECT_EQ(Font::ITALIC, style); + EXPECT_EQ(Font::Weight::BOLD, weight); + EXPECT_EQ(10, size_pixels); + + // Invalid descriptions should be rejected. + EXPECT_FALSE( + FontList::ParseDescription("", &families, &style, &size_pixels, &weight)); + EXPECT_FALSE(FontList::ParseDescription("Arial", &families, &style, + &size_pixels, &weight)); + EXPECT_FALSE(FontList::ParseDescription("Arial,12", &families, &style, + &size_pixels, &weight)); + EXPECT_FALSE(FontList::ParseDescription("Arial 12px", &families, &style, + &size_pixels, &weight)); + EXPECT_FALSE(FontList::ParseDescription("Arial,12px,", &families, &style, + &size_pixels, &weight)); + EXPECT_FALSE(FontList::ParseDescription("Arial,0px", &families, &style, + &size_pixels, &weight)); + EXPECT_FALSE(FontList::ParseDescription("Arial,-1px", &families, &style, + &size_pixels, &weight)); + EXPECT_FALSE(FontList::ParseDescription("Arial,foo 12px", &families, &style, + &size_pixels, &weight)); +} + +TEST(FontListTest, Fonts_FromDescString) { + // Test init from font name size string. + FontList font_list = FontList("arial, Courier New, 13px"); + const std::vector& fonts = font_list.GetFonts(); + ASSERT_EQ(2U, fonts.size()); + EXPECT_EQ("arial|13|normal", FontToString(fonts[0])); + EXPECT_EQ("Courier New|13|normal", FontToString(fonts[1])); +} + +TEST(FontListTest, Fonts_FromDescStringInFlexibleFormat) { + // Test init from font name size string with flexible format. + FontList font_list = FontList(" arial , Courier New , 13px"); + const std::vector& fonts = font_list.GetFonts(); + ASSERT_EQ(2U, fonts.size()); + EXPECT_EQ("arial|13|normal", FontToString(fonts[0])); + EXPECT_EQ("Courier New|13|normal", FontToString(fonts[1])); +} + +TEST(FontListTest, Fonts_FromDescStringWithStyleInFlexibleFormat) { + // Test init from font name style size string with flexible format. + FontList font_list = FontList( + " arial , Courier New , Bold " + " Italic 13px"); + const std::vector& fonts = font_list.GetFonts(); + ASSERT_EQ(2U, fonts.size()); + EXPECT_EQ("arial|13|italic|bold", FontToString(fonts[0])); + EXPECT_EQ("Courier New|13|italic|bold", FontToString(fonts[1])); +} + +TEST(FontListTest, Fonts_FromFont) { + // Test init from Font. + Font font("Arial", 8); + FontList font_list = FontList(font); + const std::vector& fonts = font_list.GetFonts(); + ASSERT_EQ(1U, fonts.size()); + EXPECT_EQ("Arial|8|normal", FontToString(fonts[0])); +} + +TEST(FontListTest, Fonts_FromFontWithNonNormalStyle) { + // Test init from Font with non-normal style. + Font font("Arial", 8); + FontList font_list(font.Derive(2, Font::NORMAL, Font::Weight::BOLD)); + std::vector fonts = font_list.GetFonts(); + ASSERT_EQ(1U, fonts.size()); + EXPECT_EQ("Arial|10|bold", FontToString(fonts[0])); + + font_list = FontList(font.Derive(-2, Font::ITALIC, Font::Weight::NORMAL)); + fonts = font_list.GetFonts(); + ASSERT_EQ(1U, fonts.size()); + EXPECT_EQ("Arial|6|italic|normal", FontToString(fonts[0])); +} + +TEST(FontListTest, Fonts_FromFontVector) { + // Test init from Font vector. + Font font("Arial", 8); + Font font_1("Courier New", 10); + std::vector input_fonts; + input_fonts.push_back(font.Derive(0, Font::NORMAL, Font::Weight::BOLD)); + input_fonts.push_back(font_1.Derive(-2, Font::NORMAL, Font::Weight::BOLD)); + FontList font_list = FontList(input_fonts); + const std::vector& fonts = font_list.GetFonts(); + ASSERT_EQ(2U, fonts.size()); + EXPECT_EQ("Arial|8|bold", FontToString(fonts[0])); + EXPECT_EQ("Courier New|8|bold", FontToString(fonts[1])); +} + +TEST(FontListTest, FontDescString_GetStyle) { + FontList font_list = FontList("Arial,Sans serif, 8px"); + EXPECT_EQ(Font::NORMAL, font_list.GetFontStyle()); + EXPECT_EQ(Font::Weight::NORMAL, font_list.GetFontWeight()); + + font_list = FontList("Arial,Sans serif,Bold 8px"); + EXPECT_EQ(Font::NORMAL, font_list.GetFontStyle()); + EXPECT_EQ(Font::Weight::BOLD, font_list.GetFontWeight()); + + font_list = FontList("Arial,Sans serif,Italic 8px"); + EXPECT_EQ(Font::ITALIC, font_list.GetFontStyle()); + EXPECT_EQ(Font::Weight::NORMAL, font_list.GetFontWeight()); + + font_list = FontList("Arial,Italic Bold 8px"); + EXPECT_EQ(Font::ITALIC, font_list.GetFontStyle()); + EXPECT_EQ(Font::Weight::BOLD, font_list.GetFontWeight()); +} + +TEST(FontListTest, Fonts_GetStyle) { + std::vector fonts; + fonts.push_back(Font("Arial", 8)); + fonts.push_back(Font("Sans serif", 8)); + FontList font_list = FontList(fonts); + EXPECT_EQ(Font::NORMAL, font_list.GetFontStyle()); + fonts[0] = fonts[0].Derive(0, Font::ITALIC, Font::Weight::BOLD); + fonts[1] = fonts[1].Derive(0, Font::ITALIC, Font::Weight::BOLD); + font_list = FontList(fonts); + EXPECT_EQ(Font::ITALIC, font_list.GetFontStyle()); + EXPECT_EQ(Font::Weight::BOLD, font_list.GetFontWeight()); +} + +TEST(FontListTest, Fonts_Derive) { + std::vector fonts; + fonts.push_back(Font("Arial", 8)); + fonts.push_back(Font("Courier New", 8)); + FontList font_list = FontList(fonts); + + FontList derived = font_list.Derive(5, Font::ITALIC, Font::Weight::BOLD); + const std::vector& derived_fonts = derived.GetFonts(); + + EXPECT_EQ(2U, derived_fonts.size()); + EXPECT_EQ("Arial|13|italic|bold", FontToString(derived_fonts[0])); + EXPECT_EQ("Courier New|13|italic|bold", FontToString(derived_fonts[1])); + + derived = font_list.Derive(5, Font::UNDERLINE, Font::Weight::BOLD); + const std::vector& underline_fonts = derived.GetFonts(); + + EXPECT_EQ(2U, underline_fonts.size()); + EXPECT_EQ("Arial|13|underline|bold", FontToString(underline_fonts[0])); + EXPECT_EQ("Courier New|13|underline|bold", FontToString(underline_fonts[1])); +} + +TEST(FontListTest, Fonts_DeriveWithSizeDelta) { + std::vector fonts; + fonts.push_back( + Font("Arial", 18).Derive(0, Font::ITALIC, Font::Weight::NORMAL)); + fonts.push_back(Font("Courier New", 18) + .Derive(0, Font::ITALIC, Font::Weight::NORMAL)); + FontList font_list = FontList(fonts); + + FontList derived = font_list.DeriveWithSizeDelta(-5); + const std::vector& derived_fonts = derived.GetFonts(); + + EXPECT_EQ(2U, derived_fonts.size()); + EXPECT_EQ("Arial|13|italic|normal", FontToString(derived_fonts[0])); + EXPECT_EQ("Courier New|13|italic|normal", FontToString(derived_fonts[1])); +} + +TEST(FontListTest, Fonts_GetHeight_GetBaseline) { + // If a font list has only one font, the height and baseline must be the same. + Font font1(kTestFontName, 16); + ASSERT_EQ(base::ToLowerASCII(kTestFontName), + base::ToLowerASCII(font1.GetActualFontName())); + FontList font_list1(std::string(kTestFontName) + ", 16px"); + EXPECT_EQ(font1.GetHeight(), font_list1.GetHeight()); + EXPECT_EQ(font1.GetBaseline(), font_list1.GetBaseline()); + + // If there are two different fonts, the font list returns the max value + // for the baseline (ascent) and height. + // NOTE: On most platforms, kCJKFontName has different metrics than + // kTestFontName, but on Android it does not. + Font font2(kCJKFontName, 16); + ASSERT_EQ(base::ToLowerASCII(kCJKFontName), + base::ToLowerASCII(font2.GetActualFontName())); + std::vector fonts; + fonts.push_back(font1); + fonts.push_back(font2); + FontList font_list_mix(fonts); + // ascent of FontList == max(ascent of Fonts) + EXPECT_EQ(std::max(font1.GetBaseline(), font2.GetBaseline()), + font_list_mix.GetBaseline()); + // descent of FontList == max(descent of Fonts) + EXPECT_EQ(std::max(font1.GetHeight() - font1.GetBaseline(), + font2.GetHeight() - font2.GetBaseline()), + font_list_mix.GetHeight() - font_list_mix.GetBaseline()); +} + +TEST(FontListTest, Fonts_DeriveWithHeightUpperBound) { + std::vector fonts; + + fonts.push_back(Font("Arial", 18)); + fonts.push_back(Font("Sans serif", 18)); + fonts.push_back(Font(kSymbolFontName, 18)); + FontList font_list = FontList(fonts); + + // A smaller upper bound should derive a font list with a smaller height. + const int height_1 = font_list.GetHeight() - 5; + FontList derived_1 = font_list.DeriveWithHeightUpperBound(height_1); + EXPECT_LE(derived_1.GetHeight(), height_1); + EXPECT_LT(derived_1.GetHeight(), font_list.GetHeight()); + EXPECT_LT(derived_1.GetFontSize(), font_list.GetFontSize()); + + // A larger upper bound should not change the height of the font list. + const int height_2 = font_list.GetHeight() + 5; + FontList derived_2 = font_list.DeriveWithHeightUpperBound(height_2); + EXPECT_LE(derived_2.GetHeight(), height_2); + EXPECT_EQ(font_list.GetHeight(), derived_2.GetHeight()); + EXPECT_EQ(font_list.GetFontSize(), derived_2.GetFontSize()); +} + +TEST(FontListTest, FirstAvailableOrFirst) { + EXPECT_TRUE(FontList::FirstAvailableOrFirst("").empty()); + EXPECT_TRUE(FontList::FirstAvailableOrFirst(std::string()).empty()); + + EXPECT_EQ("Arial", FontList::FirstAvailableOrFirst("Arial")); + EXPECT_EQ("not exist", FontList::FirstAvailableOrFirst("not exist")); + + EXPECT_EQ("Arial", FontList::FirstAvailableOrFirst("Arial, not exist")); + EXPECT_EQ("Arial", FontList::FirstAvailableOrFirst("not exist, Arial")); + EXPECT_EQ("Arial", + FontList::FirstAvailableOrFirst("not exist, Arial, not exist")); + + EXPECT_EQ("not exist", + FontList::FirstAvailableOrFirst("not exist, not exist 2")); + + EXPECT_EQ("Arial", FontList::FirstAvailableOrFirst(", not exist, Arial")); + EXPECT_EQ("not exist", + FontList::FirstAvailableOrFirst(", not exist, not exist")); +} + +} // namespace gfx diff --git a/font_names_testing.cc b/font_names_testing.cc new file mode 100644 index 000000000000..86e4abb739a7 --- /dev/null +++ b/font_names_testing.cc @@ -0,0 +1,53 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/font_names_testing.h" + +#include "build/build_config.h" + +namespace gfx { + +/* +Reference for fonts available on Android: + +Jelly Bean: + https://android.googlesource.com/platform/frameworks/base/+/jb-release/data/fonts/system_fonts.xml +KitKat: + https://android.googlesource.com/platform/frameworks/base/+/kitkat-release/data/fonts/system_fonts.xml +master: + https://android.googlesource.com/platform/frameworks/base/+/master/data/fonts/fonts.xml + +Note that we have to support the full range from JellyBean to the latest +dessert. +*/ + +#if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_FUCHSIA) +const char kTestFontName[] = "Arimo"; +#elif defined(OS_ANDROID) +const char kTestFontName[] = "sans-serif"; +#else +const char kTestFontName[] = "Arial"; +#endif + +#if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_FUCHSIA) +const char kSymbolFontName[] = "DejaVu Sans"; +#elif defined(OS_ANDROID) +const char kSymbolFontName[] = "monospace"; +#elif defined(OS_WIN) +const char kSymbolFontName[] = "Segoe UI Symbol"; +#else +const char kSymbolFontName[] = "Symbol"; +#endif + +#if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_FUCHSIA) +const char kCJKFontName[] = "Noto Sans CJK JP"; +#elif defined(OS_ANDROID) +const char kCJKFontName[] = "serif"; +#elif defined(OS_APPLE) +const char kCJKFontName[] = "Heiti SC"; +#else +const char kCJKFontName[] = "SimSun"; +#endif + +} // namespace gfx diff --git a/font_names_testing.h b/font_names_testing.h new file mode 100644 index 000000000000..b4f7a9a69f83 --- /dev/null +++ b/font_names_testing.h @@ -0,0 +1,16 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_FONT_NAMES_TESTING_H_ +#define UI_GFX_FONT_NAMES_TESTING_H_ + +namespace gfx { + +extern const char kTestFontName[]; +extern const char kSymbolFontName[]; +extern const char kCJKFontName[]; + +} // end namespace gfx + +#endif // UI_GFX_FONT_NAMES_TESTING_H_ diff --git a/font_render_params.cc b/font_render_params.cc new file mode 100644 index 000000000000..31799c8674f6 --- /dev/null +++ b/font_render_params.cc @@ -0,0 +1,43 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/font_render_params.h" + +#include "base/notreached.h" + +namespace gfx { + +// static +SkPixelGeometry FontRenderParams::SubpixelRenderingToSkiaPixelGeometry( + FontRenderParams::SubpixelRendering subpixel_rendering) { + switch (subpixel_rendering) { + case gfx::FontRenderParams::SUBPIXEL_RENDERING_NONE: + return kRGB_H_SkPixelGeometry; // why not kUnknown_SkPixelGeometry ?? + case gfx::FontRenderParams::SUBPIXEL_RENDERING_RGB: + return kRGB_H_SkPixelGeometry; + case gfx::FontRenderParams::SUBPIXEL_RENDERING_VRGB: + return kRGB_V_SkPixelGeometry; + case gfx::FontRenderParams::SUBPIXEL_RENDERING_BGR: + return kBGR_H_SkPixelGeometry; + case gfx::FontRenderParams::SUBPIXEL_RENDERING_VBGR: + return kBGR_V_SkPixelGeometry; + } + + NOTREACHED(); + return kRGB_H_SkPixelGeometry; +} + +FontRenderParamsQuery::FontRenderParamsQuery() + : pixel_size(0), + point_size(0), + style(-1), + weight(Font::Weight::INVALID), + device_scale_factor(0) {} + +FontRenderParamsQuery::FontRenderParamsQuery( + const FontRenderParamsQuery& other) = default; + +FontRenderParamsQuery::~FontRenderParamsQuery() {} + +} // namespace gfx diff --git a/font_render_params.h b/font_render_params.h new file mode 100644 index 000000000000..96508f213f48 --- /dev/null +++ b/font_render_params.h @@ -0,0 +1,131 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_FONT_RENDER_PARAMS_H_ +#define UI_GFX_FONT_RENDER_PARAMS_H_ + +#include +#include + +#include "build/build_config.h" +#include "device/vr/buildflags/buildflags.h" +#include "third_party/skia/include/core/SkSurfaceProps.h" +#include "ui/gfx/font.h" +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +// A collection of parameters describing how text should be rendered on Linux. +struct GFX_EXPORT FontRenderParams { + bool operator==(const FontRenderParams& other) const { + return antialiasing == other.antialiasing && + subpixel_positioning == other.subpixel_positioning && + autohinter == other.autohinter && use_bitmaps == other.use_bitmaps && + hinting == other.hinting && + subpixel_rendering == other.subpixel_rendering; + } + + // Level of hinting to be applied. + enum Hinting { + HINTING_NONE = 0, + HINTING_SLIGHT, + HINTING_MEDIUM, + HINTING_FULL, + + HINTING_MAX = HINTING_FULL, + }; + + // Different subpixel orders to be used for subpixel rendering. + enum SubpixelRendering { + SUBPIXEL_RENDERING_NONE = 0, + SUBPIXEL_RENDERING_RGB, + SUBPIXEL_RENDERING_BGR, + SUBPIXEL_RENDERING_VRGB, + SUBPIXEL_RENDERING_VBGR, + + SUBPIXEL_RENDERING_MAX = SUBPIXEL_RENDERING_VBGR, + }; + + // Antialiasing (grayscale if |subpixel_rendering| is SUBPIXEL_RENDERING_NONE + // and RGBA otherwise). + bool antialiasing = true; + + // Should subpixel positioning (i.e. fractional X positions for glyphs) be + // used? + // TODO(derat): Remove this; we don't set it in the browser and mostly ignore + // it in Blink: http://crbug.com/396659 + bool subpixel_positioning = true; + + // Should FreeType's autohinter be used (as opposed to Freetype's bytecode + // interpreter, which uses fonts' own hinting instructions)? + bool autohinter = false; + + // Should embedded bitmaps in fonts should be used? + bool use_bitmaps = false; + + // Hinting level. + Hinting hinting = HINTING_MEDIUM; + + // Whether subpixel rendering should be used or not, and if so, the display's + // subpixel order. + SubpixelRendering subpixel_rendering = SUBPIXEL_RENDERING_NONE; + + static SkPixelGeometry SubpixelRenderingToSkiaPixelGeometry( + SubpixelRendering subpixel_rendering); +}; + +// A query used to determine the appropriate FontRenderParams. +struct GFX_EXPORT FontRenderParamsQuery { + FontRenderParamsQuery(); + FontRenderParamsQuery(const FontRenderParamsQuery& other); + ~FontRenderParamsQuery(); + + bool is_empty() const { + return families.empty() && pixel_size <= 0 && point_size <= 0 && style < 0; + } + + // Requested font families, or empty if unset. + std::vector families; + + // Font size in pixels or points, or 0 if unset. + int pixel_size; + int point_size; + + // Font::FontStyle bit field, or -1 if unset. + int style; + + // Weight of the font. Weight::NORMAL by default. + Font::Weight weight; + + // The device scale factor of the display, or 0 if unset. + float device_scale_factor; +}; + +// Returns the appropriate parameters for rendering the font described by +// |query|. If |family_out| is non-NULL, it will be updated to contain the +// recommended font family from |query.families|. +GFX_EXPORT FontRenderParams GetFontRenderParams( + const FontRenderParamsQuery& query, + std::string* family_out); + +#if defined(OS_LINUX) || defined(OS_CHROMEOS) +// Clears GetFontRenderParams()'s cache. Intended to be called by tests that are +// changing Fontconfig's configuration. +GFX_EXPORT void ClearFontRenderParamsCacheForTest(); +#endif + +// Gets the device scale factor to query the FontRenderParams. +GFX_EXPORT float GetFontRenderParamsDeviceScaleFactor(); + +#if defined(OS_WIN) || defined(OS_LINUX) || defined(OS_CHROMEOS) || \ + defined(OS_ANDROID) || defined(OS_FUCHSIA) +// Sets the device scale factor for FontRenderParams to decide +// if it should enable subpixel positioning. +GFX_EXPORT void SetFontRenderParamsDeviceScaleFactor( + float device_scale_factor); +#endif + +} // namespace gfx + +#endif // UI_GFX_FONT_RENDER_PARAMS_H_ diff --git a/font_render_params_linux.cc b/font_render_params_linux.cc new file mode 100644 index 000000000000..7e42a2e0e2b8 --- /dev/null +++ b/font_render_params_linux.cc @@ -0,0 +1,273 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/font_render_params.h" + +#include +#include +#include + +#include + +#include "base/command_line.h" +#include "base/containers/mru_cache.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/synchronization/lock.h" +#include "base/trace_event/trace_event.h" +#include "build/build_config.h" +#include "build/chromeos_buildflags.h" +#include "ui/gfx/font.h" +#include "ui/gfx/linux/fontconfig_util.h" +#include "ui/gfx/skia_font_delegate.h" +#include "ui/gfx/switches.h" + +namespace gfx { + +namespace { + +int FontWeightToFCWeight(Font::Weight weight) { + const int weight_number = static_cast(weight); + if (weight_number <= (static_cast(Font::Weight::THIN) + + static_cast(Font::Weight::EXTRA_LIGHT)) / + 2) + return FC_WEIGHT_THIN; + else if (weight_number <= (static_cast(Font::Weight::EXTRA_LIGHT) + + static_cast(Font::Weight::LIGHT)) / + 2) + return FC_WEIGHT_ULTRALIGHT; + else if (weight_number <= (static_cast(Font::Weight::LIGHT) + + static_cast(Font::Weight::NORMAL)) / + 2) + return FC_WEIGHT_LIGHT; + else if (weight_number <= (static_cast(Font::Weight::NORMAL) + + static_cast(Font::Weight::MEDIUM)) / + 2) + return FC_WEIGHT_NORMAL; + else if (weight_number <= (static_cast(Font::Weight::MEDIUM) + + static_cast(Font::Weight::SEMIBOLD)) / + 2) + return FC_WEIGHT_MEDIUM; + else if (weight_number <= (static_cast(Font::Weight::SEMIBOLD) + + static_cast(Font::Weight::BOLD)) / + 2) + return FC_WEIGHT_DEMIBOLD; + else if (weight_number <= (static_cast(Font::Weight::BOLD) + + static_cast(Font::Weight::EXTRA_BOLD)) / + 2) + return FC_WEIGHT_BOLD; + else if (weight_number <= (static_cast(Font::Weight::EXTRA_BOLD) + + static_cast(Font::Weight::BLACK)) / + 2) + return FC_WEIGHT_ULTRABOLD; + else + return FC_WEIGHT_BLACK; +} + +// A device scale factor used to determine if subpixel positioning +// should be used. +float device_scale_factor_ = 1.0f; + +// Number of recent GetFontRenderParams() results to cache. +const size_t kCacheSize = 256; + +// Cached result from a call to GetFontRenderParams(). +struct QueryResult { + QueryResult(const FontRenderParams& params, const std::string& family) + : params(params), + family(family) { + } + ~QueryResult() {} + + FontRenderParams params; + std::string family; +}; + +// Keyed by hashes of FontRenderParamQuery structs from +// HashFontRenderParamsQuery(). +typedef base::HashingMRUCache Cache; + +// A cache and the lock that must be held while accessing it. +// GetFontRenderParams() is called by both the UI thread and the sandbox IPC +// thread. +struct SynchronizedCache { + SynchronizedCache() : cache(kCacheSize) {} + + base::Lock lock; + Cache cache; +}; + +base::LazyInstance::Leaky g_synchronized_cache = + LAZY_INSTANCE_INITIALIZER; + +// Queries Fontconfig for rendering settings and updates |params_out| and +// |family_out| (if non-NULL). Returns false on failure. +bool QueryFontconfig(const FontRenderParamsQuery& query, + FontRenderParams* params_out, + std::string* family_out) { + TRACE_EVENT0("fonts", "gfx::QueryFontconfig"); + + ScopedFcPattern query_pattern(FcPatternCreate()); + CHECK(query_pattern); + + FcPatternAddBool(query_pattern.get(), FC_SCALABLE, FcTrue); + + for (auto it = query.families.begin(); it != query.families.end(); ++it) { + FcPatternAddString(query_pattern.get(), + FC_FAMILY, reinterpret_cast(it->c_str())); + } + if (query.pixel_size > 0) + FcPatternAddDouble(query_pattern.get(), FC_PIXEL_SIZE, query.pixel_size); + if (query.point_size > 0) + FcPatternAddInteger(query_pattern.get(), FC_SIZE, query.point_size); + if (query.style >= 0) { + FcPatternAddInteger(query_pattern.get(), FC_SLANT, + (query.style & Font::ITALIC) ? FC_SLANT_ITALIC : FC_SLANT_ROMAN); + } + if (query.weight != Font::Weight::INVALID) { + FcPatternAddInteger(query_pattern.get(), FC_WEIGHT, + FontWeightToFCWeight(query.weight)); + } + + FcConfig* config = GetGlobalFontConfig(); + FcConfigSubstitute(config, query_pattern.get(), FcMatchPattern); + FcDefaultSubstitute(query_pattern.get()); + + ScopedFcPattern result_pattern; + if (query.is_empty()) { + // If the query was empty, call FcConfigSubstituteWithPat() to get a + // non-family- or size-specific configuration so it can be used as the + // default. + result_pattern.reset(FcPatternDuplicate(query_pattern.get())); + if (!result_pattern) + return false; + FcPatternDel(result_pattern.get(), FC_FAMILY); + FcPatternDel(result_pattern.get(), FC_PIXEL_SIZE); + FcPatternDel(result_pattern.get(), FC_SIZE); + FcConfigSubstituteWithPat(config, result_pattern.get(), query_pattern.get(), + FcMatchFont); + } else { + TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("fonts"), "FcFontMatch"); + FcResult result; + result_pattern.reset(FcFontMatch(config, query_pattern.get(), &result)); + if (!result_pattern) + return false; + } + DCHECK(result_pattern); + + if (family_out) { + FcChar8* family = NULL; + FcPatternGetString(result_pattern.get(), FC_FAMILY, 0, &family); + if (family) + family_out->assign(reinterpret_cast(family)); + } + + if (params_out) + GetFontRenderParamsFromFcPattern(result_pattern.get(), params_out); + + return true; +} + +// Serialize |query| into a string value suitable for use as a cache key. +std::string GetFontRenderParamsQueryKey(const FontRenderParamsQuery& query) { + return base::StringPrintf( + "%d|%d|%d|%d|%s|%f", query.pixel_size, query.point_size, query.style, + static_cast(query.weight), + base::JoinString(query.families, ",").c_str(), query.device_scale_factor); +} + +} // namespace + +FontRenderParams GetFontRenderParams(const FontRenderParamsQuery& query, + std::string* family_out) { + TRACE_EVENT0("fonts", "gfx::GetFontRenderParams"); + + FontRenderParamsQuery actual_query(query); + if (actual_query.device_scale_factor == 0) + actual_query.device_scale_factor = device_scale_factor_; + + std::string query_key = GetFontRenderParamsQueryKey(actual_query); + SynchronizedCache* synchronized_cache = g_synchronized_cache.Pointer(); + + { + // Try to find a cached result so Fontconfig doesn't need to be queried. + base::AutoLock lock(synchronized_cache->lock); + Cache::const_iterator it = synchronized_cache->cache.Get(query_key); + if (it != synchronized_cache->cache.end()) { + DVLOG(1) << "Returning cached params for " << query_key; + const QueryResult& result = it->second; + if (family_out) + *family_out = result.family; + return result.params; + } + } + + DVLOG(1) << "Computing params for " << query_key; + if (family_out) + family_out->clear(); + + // Start with the delegate's settings, but let Fontconfig have the final say. + FontRenderParams params; + const SkiaFontDelegate* delegate = SkiaFontDelegate::instance(); + if (delegate) + params = delegate->GetDefaultFontRenderParams(); + QueryFontconfig(actual_query, ¶ms, family_out); + if (!params.antialiasing) { + // Cairo forces full hinting when antialiasing is disabled, since anything + // less than that looks awful; do the same here. Requesting subpixel + // rendering or positioning doesn't make sense either. + params.hinting = FontRenderParams::HINTING_FULL; + params.subpixel_rendering = FontRenderParams::SUBPIXEL_RENDERING_NONE; + params.subpixel_positioning = false; + } else if (!base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kDisableFontSubpixelPositioning)) { +#if !BUILDFLAG(IS_CHROMEOS_ASH) + params.subpixel_positioning = actual_query.device_scale_factor > 1.0f; +#else + // We want to enable subpixel positioning for fractional dsf. + params.subpixel_positioning = + std::abs(std::round(actual_query.device_scale_factor) - + actual_query.device_scale_factor) > + std::numeric_limits::epsilon(); +#endif // !BUILDFLAG(IS_CHROMEOS_ASH) + + // To enable subpixel positioning, we need to disable hinting. + if (params.subpixel_positioning) + params.hinting = FontRenderParams::HINTING_NONE; + } + + // Use the first family from the list if Fontconfig didn't suggest a family. + if (family_out && family_out->empty() && !actual_query.families.empty()) + *family_out = actual_query.families[0]; + + { + // Store the result. It's fine if this overwrites a result that was cached + // by a different thread in the meantime; the values should be identical. + base::AutoLock lock(synchronized_cache->lock); + synchronized_cache->cache.Put( + query_key, + QueryResult(params, family_out ? *family_out : std::string())); + } + + return params; +} + +void ClearFontRenderParamsCacheForTest() { + SynchronizedCache* synchronized_cache = g_synchronized_cache.Pointer(); + base::AutoLock lock(synchronized_cache->lock); + synchronized_cache->cache.Clear(); +} + +float GetFontRenderParamsDeviceScaleFactor() { + return device_scale_factor_; +} + +void SetFontRenderParamsDeviceScaleFactor(float device_scale_factor) { + device_scale_factor_ = device_scale_factor; +} + +} // namespace gfx diff --git a/font_render_params_linux_unittest.cc b/font_render_params_linux_unittest.cc new file mode 100644 index 000000000000..240e68b39fbf --- /dev/null +++ b/font_render_params_linux_unittest.cc @@ -0,0 +1,462 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/font_render_params.h" + +#include + +#include "base/check_op.h" +#include "base/files/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/macros.h" +#include "base/notreached.h" +#include "base/strings/stringprintf.h" +#include "build/build_config.h" +#include "build/chromeos_buildflags.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/test_fonts/fontconfig_util_linux.h" +#include "ui/gfx/font.h" +#include "ui/gfx/linux/fontconfig_util.h" +#include "ui/gfx/skia_font_delegate.h" + +namespace gfx { + +namespace { + +// Strings appearing at the beginning and end of Fontconfig XML files. +const char kFontconfigFileHeader[] = + "\n" + "\n" + "\n"; +const char kFontconfigFileFooter[] = ""; + +// Strings appearing at the beginning and end of Fontconfig stanzas. +const char kFontconfigMatchFontHeader[] = " \n"; +const char kFontconfigMatchPatternHeader[] = " \n"; +const char kFontconfigMatchFooter[] = " \n"; + +// Implementation of SkiaFontDelegate that returns a canned FontRenderParams +// struct. This is used to isolate tests from the system's local configuration. +class TestFontDelegate : public SkiaFontDelegate { + public: + TestFontDelegate() {} + + TestFontDelegate(const TestFontDelegate&) = delete; + TestFontDelegate& operator=(const TestFontDelegate&) = delete; + + ~TestFontDelegate() override {} + + void set_params(const FontRenderParams& params) { params_ = params; } + + FontRenderParams GetDefaultFontRenderParams() const override { + return params_; + } + void GetDefaultFontDescription(std::string* family_out, + int* size_pixels_out, + int* style_out, + Font::Weight* weight_out, + FontRenderParams* params_out) const override { + NOTIMPLEMENTED(); + } + + private: + FontRenderParams params_; +}; + +// Loads XML-formatted |data| into the current font configuration. +bool LoadConfigDataIntoFontconfig(const std::string& data) { + FcConfig* config = GetGlobalFontConfig(); + constexpr FcBool kComplain = FcTrue; + return FcConfigParseAndLoadFromMemory( + config, reinterpret_cast(data.c_str()), kComplain); +} + +// Returns a Fontconfig stanza. +std::string CreateFontconfigEditStanza(const std::string& name, + const std::string& type, + const std::string& value) { + return base::StringPrintf( + " \n" + " <%s>%s\n" + " \n", + name.c_str(), type.c_str(), value.c_str(), type.c_str()); +} + +// Returns a Fontconfig stanza. +std::string CreateFontconfigTestStanza(const std::string& name, + const std::string& op, + const std::string& type, + const std::string& value) { + return base::StringPrintf( + " \n" + " <%s>%s\n" + " \n", + name.c_str(), op.c_str(), type.c_str(), value.c_str(), type.c_str()); +} + +// Returns a Fontconfig stanza. +std::string CreateFontconfigAliasStanza(const std::string& original_family, + const std::string& preferred_family) { + return base::StringPrintf( + " \n" + " %s\n" + " %s\n" + " \n", + original_family.c_str(), preferred_family.c_str()); +} + +} // namespace + +class FontRenderParamsTest : public testing::Test { + public: + FontRenderParamsTest() { + original_font_delegate_ = SkiaFontDelegate::instance(); + SkiaFontDelegate::SetInstance(&test_font_delegate_); + ClearFontRenderParamsCacheForTest(); + + // Create a new fontconfig configuration and load the default fonts + // configuration. The default test config file is produced in the build + // folder under /etc/fonts/fonts.conf and the loaded tests fonts + // are under /test_fonts. + override_config_ = FcConfigCreate(); + FcBool parse_success = + FcConfigParseAndLoad(override_config_, nullptr, FcTrue); + DCHECK_NE(parse_success, FcFalse); + FcBool load_success = FcConfigBuildFonts(override_config_); + DCHECK_NE(load_success, FcFalse); + + original_config_ = GetGlobalFontConfig(); + OverrideGlobalFontConfigForTesting(override_config_); + } + + FontRenderParamsTest(const FontRenderParamsTest&) = delete; + FontRenderParamsTest& operator=(const FontRenderParamsTest&) = delete; + + ~FontRenderParamsTest() override { + OverrideGlobalFontConfigForTesting(original_config_); + FcConfigDestroy(override_config_); + + SkiaFontDelegate::SetInstance( + const_cast(original_font_delegate_)); + } + + protected: + const SkiaFontDelegate* original_font_delegate_; + TestFontDelegate test_font_delegate_; + + FcConfig* override_config_ = nullptr; + FcConfig* original_config_ = nullptr; +}; + +TEST_F(FontRenderParamsTest, Default) { + ASSERT_TRUE(LoadConfigDataIntoFontconfig( + std::string(kFontconfigFileHeader) + + // Specify the desired defaults via a font match rather than a pattern + // match (since this is the style generally used in + // /etc/fonts/conf.d). + kFontconfigMatchFontHeader + + CreateFontconfigEditStanza("antialias", "bool", "true") + + CreateFontconfigEditStanza("autohint", "bool", "true") + + CreateFontconfigEditStanza("hinting", "bool", "true") + + CreateFontconfigEditStanza("hintstyle", "const", "hintslight") + + CreateFontconfigEditStanza("rgba", "const", "rgb") + + kFontconfigMatchFooter + + // Add a font match for Arimo. Since it specifies a family, it + // shouldn't take effect when querying default settings. + kFontconfigMatchFontHeader + + CreateFontconfigTestStanza("family", "eq", "string", "Arimo") + + CreateFontconfigEditStanza("antialias", "bool", "true") + + CreateFontconfigEditStanza("autohint", "bool", "false") + + CreateFontconfigEditStanza("hinting", "bool", "true") + + CreateFontconfigEditStanza("hintstyle", "const", "hintfull") + + CreateFontconfigEditStanza("rgba", "const", "none") + + kFontconfigMatchFooter + + // Add font matches for fonts between 10 and 20 points or pixels. + // Since they specify sizes, they also should not affect the defaults. + kFontconfigMatchFontHeader + + CreateFontconfigTestStanza("size", "more_eq", "double", "10.0") + + CreateFontconfigTestStanza("size", "less_eq", "double", "20.0") + + CreateFontconfigEditStanza("antialias", "bool", "false") + + kFontconfigMatchFooter + kFontconfigMatchFontHeader + + CreateFontconfigTestStanza("pixel_size", "more_eq", "double", + "10.0") + + CreateFontconfigTestStanza("pixel_size", "less_eq", "double", + "20.0") + + CreateFontconfigEditStanza("antialias", "bool", "false") + + kFontconfigMatchFooter + kFontconfigFileFooter)); + + FontRenderParams params = GetFontRenderParams( + FontRenderParamsQuery(), NULL); + EXPECT_TRUE(params.antialiasing); + EXPECT_TRUE(params.autohinter); + EXPECT_TRUE(params.use_bitmaps); + EXPECT_EQ(FontRenderParams::HINTING_SLIGHT, params.hinting); + EXPECT_FALSE(params.subpixel_positioning); + EXPECT_EQ(FontRenderParams::SUBPIXEL_RENDERING_RGB, + params.subpixel_rendering); +} + +TEST_F(FontRenderParamsTest, Size) { + ASSERT_TRUE(LoadConfigDataIntoFontconfig( + std::string(kFontconfigFileHeader) + kFontconfigMatchPatternHeader + + CreateFontconfigEditStanza("antialias", "bool", "true") + + CreateFontconfigEditStanza("hinting", "bool", "true") + + CreateFontconfigEditStanza("hintstyle", "const", "hintfull") + + CreateFontconfigEditStanza("rgba", "const", "none") + + kFontconfigMatchFooter + kFontconfigMatchPatternHeader + + CreateFontconfigTestStanza("pixelsize", "less_eq", "double", "10") + + CreateFontconfigEditStanza("antialias", "bool", "false") + + kFontconfigMatchFooter + kFontconfigMatchPatternHeader + + CreateFontconfigTestStanza("size", "more_eq", "double", "20") + + CreateFontconfigEditStanza("hintstyle", "const", "hintslight") + + CreateFontconfigEditStanza("rgba", "const", "rgb") + + kFontconfigMatchFooter + kFontconfigFileFooter)); + + // The defaults should be used when the supplied size isn't matched by the + // second or third blocks. + FontRenderParamsQuery query; + query.pixel_size = 12; + FontRenderParams params = GetFontRenderParams(query, NULL); + EXPECT_TRUE(params.antialiasing); + EXPECT_EQ(FontRenderParams::HINTING_FULL, params.hinting); + EXPECT_EQ(FontRenderParams::SUBPIXEL_RENDERING_NONE, + params.subpixel_rendering); + + query.pixel_size = 10; + params = GetFontRenderParams(query, NULL); + EXPECT_FALSE(params.antialiasing); + EXPECT_EQ(FontRenderParams::HINTING_FULL, params.hinting); + EXPECT_EQ(FontRenderParams::SUBPIXEL_RENDERING_NONE, + params.subpixel_rendering); + + query.pixel_size = 0; + query.point_size = 20; + params = GetFontRenderParams(query, NULL); + EXPECT_TRUE(params.antialiasing); + EXPECT_EQ(FontRenderParams::HINTING_SLIGHT, params.hinting); + EXPECT_EQ(FontRenderParams::SUBPIXEL_RENDERING_RGB, + params.subpixel_rendering); +} + +TEST_F(FontRenderParamsTest, Style) { + // Load a config that disables subpixel rendering for bold text and disables + // hinting for italic text. + ASSERT_TRUE(LoadConfigDataIntoFontconfig( + std::string(kFontconfigFileHeader) + kFontconfigMatchPatternHeader + + CreateFontconfigEditStanza("antialias", "bool", "true") + + CreateFontconfigEditStanza("hinting", "bool", "true") + + CreateFontconfigEditStanza("hintstyle", "const", "hintslight") + + CreateFontconfigEditStanza("rgba", "const", "rgb") + + kFontconfigMatchFooter + kFontconfigMatchPatternHeader + + CreateFontconfigTestStanza("weight", "eq", "const", "bold") + + CreateFontconfigEditStanza("rgba", "const", "none") + + kFontconfigMatchFooter + kFontconfigMatchPatternHeader + + CreateFontconfigTestStanza("slant", "eq", "const", "italic") + + CreateFontconfigEditStanza("hinting", "bool", "false") + + kFontconfigMatchFooter + kFontconfigFileFooter)); + + FontRenderParamsQuery query; + query.style = Font::NORMAL; + FontRenderParams params = GetFontRenderParams(query, NULL); + EXPECT_EQ(FontRenderParams::HINTING_SLIGHT, params.hinting); + EXPECT_EQ(FontRenderParams::SUBPIXEL_RENDERING_RGB, + params.subpixel_rendering); + + query.weight = Font::Weight::BOLD; + params = GetFontRenderParams(query, NULL); + EXPECT_EQ(FontRenderParams::HINTING_SLIGHT, params.hinting); + EXPECT_EQ(FontRenderParams::SUBPIXEL_RENDERING_NONE, + params.subpixel_rendering); + + query.weight = Font::Weight::NORMAL; + query.style = Font::ITALIC; + params = GetFontRenderParams(query, NULL); + EXPECT_EQ(FontRenderParams::HINTING_NONE, params.hinting); + EXPECT_EQ(FontRenderParams::SUBPIXEL_RENDERING_RGB, + params.subpixel_rendering); + + query.weight = Font::Weight::BOLD; + query.style = Font::ITALIC; + params = GetFontRenderParams(query, NULL); + EXPECT_EQ(FontRenderParams::HINTING_NONE, params.hinting); + EXPECT_EQ(FontRenderParams::SUBPIXEL_RENDERING_NONE, + params.subpixel_rendering); +} + +TEST_F(FontRenderParamsTest, Scalable) { + // Load a config that only enables antialiasing for scalable fonts. + ASSERT_TRUE(LoadConfigDataIntoFontconfig( + std::string(kFontconfigFileHeader) + kFontconfigMatchPatternHeader + + CreateFontconfigEditStanza("antialias", "bool", "false") + + kFontconfigMatchFooter + kFontconfigMatchPatternHeader + + CreateFontconfigTestStanza("scalable", "eq", "bool", "true") + + CreateFontconfigEditStanza("antialias", "bool", "true") + + kFontconfigMatchFooter + kFontconfigFileFooter)); + + // Check that we specifically ask how scalable fonts should be rendered. + FontRenderParams params = GetFontRenderParams( + FontRenderParamsQuery(), NULL); + EXPECT_TRUE(params.antialiasing); +} + +TEST_F(FontRenderParamsTest, UseBitmaps) { + // Load a config that enables embedded bitmaps for fonts <= 10 pixels. + ASSERT_TRUE(LoadConfigDataIntoFontconfig( + std::string(kFontconfigFileHeader) + kFontconfigMatchPatternHeader + + CreateFontconfigEditStanza("embeddedbitmap", "bool", "false") + + kFontconfigMatchFooter + kFontconfigMatchPatternHeader + + CreateFontconfigTestStanza("pixelsize", "less_eq", "double", "10") + + CreateFontconfigEditStanza("embeddedbitmap", "bool", "true") + + kFontconfigMatchFooter + kFontconfigFileFooter)); + + FontRenderParamsQuery query; + FontRenderParams params = GetFontRenderParams(query, NULL); + EXPECT_FALSE(params.use_bitmaps); + + query.pixel_size = 5; + params = GetFontRenderParams(query, NULL); + EXPECT_TRUE(params.use_bitmaps); +} + +TEST_F(FontRenderParamsTest, ForceFullHintingWhenAntialiasingIsDisabled) { + // Load a config that disables antialiasing and hinting while requesting + // subpixel rendering. + ASSERT_TRUE(LoadConfigDataIntoFontconfig( + std::string(kFontconfigFileHeader) + kFontconfigMatchPatternHeader + + CreateFontconfigEditStanza("antialias", "bool", "false") + + CreateFontconfigEditStanza("hinting", "bool", "false") + + CreateFontconfigEditStanza("hintstyle", "const", "hintnone") + + CreateFontconfigEditStanza("rgba", "const", "rgb") + + kFontconfigMatchFooter + kFontconfigFileFooter)); + + // Full hinting should be forced. See the comment in GetFontRenderParams() for + // more information. + FontRenderParams params = GetFontRenderParams( + FontRenderParamsQuery(), NULL); + EXPECT_FALSE(params.antialiasing); + EXPECT_EQ(FontRenderParams::HINTING_FULL, params.hinting); + EXPECT_EQ(FontRenderParams::SUBPIXEL_RENDERING_NONE, + params.subpixel_rendering); + EXPECT_FALSE(params.subpixel_positioning); +} + +TEST_F(FontRenderParamsTest, ForceSubpixelPositioning) { + { + FontRenderParams params = + GetFontRenderParams(FontRenderParamsQuery(), NULL); + EXPECT_TRUE(params.antialiasing); + EXPECT_FALSE(params.subpixel_positioning); + SetFontRenderParamsDeviceScaleFactor(1.0f); + } + ClearFontRenderParamsCacheForTest(); + SetFontRenderParamsDeviceScaleFactor(1.25f); + // Subpixel positioning should be forced. + { + FontRenderParams params = + GetFontRenderParams(FontRenderParamsQuery(), NULL); + EXPECT_TRUE(params.antialiasing); + EXPECT_TRUE(params.subpixel_positioning); + SetFontRenderParamsDeviceScaleFactor(1.0f); + } + ClearFontRenderParamsCacheForTest(); + SetFontRenderParamsDeviceScaleFactor(2.f); + // Subpixel positioning should be forced on non-Chrome-OS. + { + FontRenderParams params = + GetFontRenderParams(FontRenderParamsQuery(), nullptr); + EXPECT_TRUE(params.antialiasing); +#if !BUILDFLAG(IS_CHROMEOS_ASH) + EXPECT_TRUE(params.subpixel_positioning); +#else + // Integral scale factor does not require subpixel positioning. + EXPECT_FALSE(params.subpixel_positioning); +#endif // !BUILDFLAG(IS_CHROMEOS_ASH) + SetFontRenderParamsDeviceScaleFactor(1.0f); + } +} + +TEST_F(FontRenderParamsTest, OnlySetConfiguredValues) { + // Configure the SkiaFontDelegate (which queries GtkSettings on desktop + // Linux) to request subpixel rendering. + FontRenderParams system_params; + system_params.subpixel_rendering = FontRenderParams::SUBPIXEL_RENDERING_RGB; + test_font_delegate_.set_params(system_params); + + // Load a Fontconfig config that enables antialiasing but doesn't say anything + // about subpixel rendering. + ASSERT_TRUE(LoadConfigDataIntoFontconfig( + std::string(kFontconfigFileHeader) + kFontconfigMatchPatternHeader + + CreateFontconfigEditStanza("antialias", "bool", "true") + + kFontconfigMatchFooter + kFontconfigFileFooter)); + + // The subpixel rendering setting from the delegate should make it through. + FontRenderParams params = GetFontRenderParams( + FontRenderParamsQuery(), NULL); + EXPECT_EQ(system_params.subpixel_rendering, params.subpixel_rendering); +} + +TEST_F(FontRenderParamsTest, NoFontconfigMatch) { + // A default configuration was set up globally. Reset it to a blank config. + FcConfig* blank = FcConfigCreate(); + OverrideGlobalFontConfigForTesting(blank); + + FontRenderParams system_params; + system_params.antialiasing = true; + system_params.hinting = FontRenderParams::HINTING_MEDIUM; + system_params.subpixel_rendering = FontRenderParams::SUBPIXEL_RENDERING_RGB; + test_font_delegate_.set_params(system_params); + + FontRenderParamsQuery query; + query.families.push_back("Arimo"); + query.families.push_back("Times New Roman"); + query.pixel_size = 10; + std::string suggested_family; + FontRenderParams params = GetFontRenderParams(query, &suggested_family); + + // The system params and the first requested family should be returned. + EXPECT_EQ(system_params.antialiasing, params.antialiasing); + EXPECT_EQ(system_params.hinting, params.hinting); + EXPECT_EQ(system_params.subpixel_rendering, params.subpixel_rendering); + EXPECT_EQ(query.families[0], suggested_family); + + OverrideGlobalFontConfigForTesting(override_config_); + FcConfigDestroy(blank); +} + +TEST_F(FontRenderParamsTest, MissingFamily) { + // With Arimo and Verdana installed, request (in order) Helvetica, Arimo, and + // Verdana and check that Arimo is returned. + FontRenderParamsQuery query; + query.families.push_back("Helvetica"); + query.families.push_back("Arimo"); + query.families.push_back("Verdana"); + std::string suggested_family; + GetFontRenderParams(query, &suggested_family); + EXPECT_EQ("Arimo", suggested_family); +} + +TEST_F(FontRenderParamsTest, SubstituteFamily) { + // Configure Fontconfig to use Tinos for both Helvetica and Arimo. + ASSERT_TRUE(LoadConfigDataIntoFontconfig( + std::string(kFontconfigFileHeader) + + CreateFontconfigAliasStanza("Helvetica", "Tinos") + + kFontconfigMatchPatternHeader + + CreateFontconfigTestStanza("family", "eq", "string", "Arimo") + + CreateFontconfigEditStanza("family", "string", "Tinos") + + kFontconfigMatchFooter + kFontconfigFileFooter)); + + FontRenderParamsQuery query; + query.families.push_back("Helvetica"); + std::string suggested_family; + GetFontRenderParams(query, &suggested_family); + EXPECT_EQ("Tinos", suggested_family); + + query.families.clear(); + query.families.push_back("Arimo"); + suggested_family.clear(); + GetFontRenderParams(query, &suggested_family); + EXPECT_EQ("Tinos", suggested_family); +} + +} // namespace gfx diff --git a/font_render_params_mac.cc b/font_render_params_mac.cc new file mode 100644 index 000000000000..03bf988c8065 --- /dev/null +++ b/font_render_params_mac.cc @@ -0,0 +1,42 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/font_render_params.h" + +#include "base/macros.h" +#include "base/notreached.h" + +namespace gfx { + +namespace { + +// Returns params that match SkiaTextRenderer's default render settings. +FontRenderParams LoadDefaults() { + FontRenderParams params; + params.antialiasing = true; + params.autohinter = false; + params.use_bitmaps = true; + params.subpixel_rendering = FontRenderParams::SUBPIXEL_RENDERING_RGB; + params.subpixel_positioning = true; + params.hinting = FontRenderParams::HINTING_MEDIUM; + + return params; +} + +} // namespace + +FontRenderParams GetFontRenderParams(const FontRenderParamsQuery& query, + std::string* family_out) { + if (family_out) + NOTIMPLEMENTED(); + // TODO: Query the OS for font render settings instead of returning defaults. + static const gfx::FontRenderParams params(LoadDefaults()); + return params; +} + +float GetFontRenderParamsDeviceScaleFactor() { + return 1.0; +} + +} // namespace gfx diff --git a/font_render_params_skia.cc b/font_render_params_skia.cc new file mode 100644 index 000000000000..7e8edf7dcfa8 --- /dev/null +++ b/font_render_params_skia.cc @@ -0,0 +1,54 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/font_render_params.h" + +#include "base/macros.h" +#include "base/notreached.h" + +namespace gfx { + +namespace { + +// Returns the system's default settings. +FontRenderParams LoadDefaults() { + FontRenderParams params; + params.antialiasing = true; + params.autohinter = true; + params.use_bitmaps = true; + params.subpixel_rendering = FontRenderParams::SUBPIXEL_RENDERING_NONE; + + // Use subpixel text positioning to keep consistent character spacing when + // the page is scaled by a fractional factor. + params.subpixel_positioning = true; + // Slight hinting renders much better than normal hinting on Android. + params.hinting = FontRenderParams::HINTING_SLIGHT; + + return params; +} + +// A device scale factor used to determine if subpixel positioning +// should be used. +float device_scale_factor_ = 1.0f; + +} // namespace + +FontRenderParams GetFontRenderParams(const FontRenderParamsQuery& query, + std::string* family_out) { + if (family_out) + NOTIMPLEMENTED(); + // Customized font rendering settings are not supported, only defaults. + static const gfx::FontRenderParams params(LoadDefaults()); + return params; +} + +float GetFontRenderParamsDeviceScaleFactor() { + return device_scale_factor_; +} + +void SetFontRenderParamsDeviceScaleFactor(float device_scale_factor) { + device_scale_factor_ = device_scale_factor; +} + +} // namespace gfx diff --git a/font_render_params_win.cc b/font_render_params_win.cc new file mode 100644 index 000000000000..1369f8a4fdff --- /dev/null +++ b/font_render_params_win.cc @@ -0,0 +1,120 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/font_render_params.h" + +#include + +#include "base/bind.h" +#include "base/callback_helpers.h" +#include "base/files/file_path.h" +#include "base/macros.h" +#include "base/memory/singleton.h" +#include "base/win/registry.h" +#include "ui/gfx/win/singleton_hwnd_observer.h" + +namespace gfx { + +namespace { + +FontRenderParams::SubpixelRendering GetSubpixelRenderingGeometry() { + DISPLAY_DEVICE display_device = {sizeof(DISPLAY_DEVICE)}; + for (int i = 0; EnumDisplayDevices(nullptr, i, &display_device, 0); ++i) { + // TODO(scottmg): We only support the primary device currently. + if (display_device.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) { + base::FilePath trimmed = + base::FilePath(display_device.DeviceName).BaseName(); + base::win::RegKey key( + HKEY_LOCAL_MACHINE, + (L"SOFTWARE\\Microsoft\\Avalon.Graphics\\" + trimmed.value()).c_str(), + KEY_READ); + DWORD pixel_structure; + if (key.ReadValueDW(L"PixelStructure", &pixel_structure) == + ERROR_SUCCESS) { + if (pixel_structure == 1) + return FontRenderParams::SUBPIXEL_RENDERING_RGB; + if (pixel_structure == 2) + return FontRenderParams::SUBPIXEL_RENDERING_BGR; + } + break; + } + } + + // No explicit ClearType settings, default to RGB. + return FontRenderParams::SUBPIXEL_RENDERING_RGB; +} + +// Caches font render params and updates them on system notifications. +class CachedFontRenderParams { + public: + static CachedFontRenderParams* GetInstance() { + return base::Singleton::get(); + } + + CachedFontRenderParams(const CachedFontRenderParams&) = delete; + CachedFontRenderParams& operator=(const CachedFontRenderParams&) = delete; + + const FontRenderParams& GetParams() { + if (params_) + return *params_; + + params_ = std::make_unique(); + params_->antialiasing = false; + params_->subpixel_positioning = false; + params_->autohinter = false; + params_->use_bitmaps = false; + params_->hinting = FontRenderParams::HINTING_MEDIUM; + params_->subpixel_rendering = FontRenderParams::SUBPIXEL_RENDERING_NONE; + + BOOL enabled = false; + if (SystemParametersInfo(SPI_GETFONTSMOOTHING, 0, &enabled, 0) && enabled) { + params_->antialiasing = true; + params_->subpixel_positioning = true; + + UINT type = 0; + if (SystemParametersInfo(SPI_GETFONTSMOOTHINGTYPE, 0, &type, 0) && + type == FE_FONTSMOOTHINGCLEARTYPE) { + params_->subpixel_rendering = GetSubpixelRenderingGeometry(); + } + } + singleton_hwnd_observer_ = + std::make_unique(base::BindRepeating( + &CachedFontRenderParams::OnWndProc, base::Unretained(this))); + return *params_; + } + + private: + friend struct base::DefaultSingletonTraits; + + CachedFontRenderParams() {} + ~CachedFontRenderParams() {} + + void OnWndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { + if (message == WM_SETTINGCHANGE) { + // TODO(khushalsagar): This should trigger an update to the + // renderer and gpu processes, where the params are cached. + params_.reset(); + singleton_hwnd_observer_.reset(nullptr); + } + } + + std::unique_ptr params_; + std::unique_ptr singleton_hwnd_observer_; +}; + +} // namespace + +FontRenderParams GetFontRenderParams(const FontRenderParamsQuery& query, + std::string* family_out) { + if (family_out) + NOTIMPLEMENTED(); + // Customized font rendering settings are not supported, only defaults. + return CachedFontRenderParams::GetInstance()->GetParams(); +} + +float GetFontRenderParamsDeviceScaleFactor() { + return 1.; +} + +} // namespace gfx diff --git a/font_unittest.cc b/font_unittest.cc new file mode 100644 index 000000000000..c71929d041b1 --- /dev/null +++ b/font_unittest.cc @@ -0,0 +1,181 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/font.h" + +#include + +#include "base/macros.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "build/build_config.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/font_names_testing.h" + +#if defined(OS_WIN) +#include "ui/gfx/system_fonts_win.h" +#endif + +namespace gfx { +namespace { + +class FontTest : public testing::Test { + public: + FontTest() = default; + + FontTest(const FontTest&) = delete; + FontTest& operator=(const FontTest&) = delete; + + protected: + void SetUp() override { +#if defined(OS_WIN) + // System fonts is keeping a cache of loaded system fonts. These fonts are + // scaled based on global callbacks configured on startup. The tests in this + // file are testing these callbacks and need to be sure we cleared the + // global state to avoid flaky tests. + win::ResetSystemFontsForTesting(); +#endif + } +}; + +TEST_F(FontTest, DefaultFont) { + Font cf; + EXPECT_EQ(cf.GetStyle(), Font::NORMAL); + EXPECT_EQ(cf.GetWeight(), Font::Weight::NORMAL); + // Ensures that font metrics are generated. Some fonts backends do not provide + // some metrics (e.g. DWrite do not produce average character width). + EXPECT_GT(cf.GetFontSize(), 0); + EXPECT_GT(cf.GetHeight(), 0); + EXPECT_GT(cf.GetBaseline(), 0); + EXPECT_GT(cf.GetCapHeight(), 0); + EXPECT_GT(cf.GetExpectedTextWidth(1), 0); +} + +TEST_F(FontTest, LoadArial) { + Font cf(kTestFontName, 16); +#if defined(OS_APPLE) + EXPECT_TRUE(cf.GetNativeFont()); +#endif + EXPECT_EQ(cf.GetStyle(), Font::NORMAL); + EXPECT_EQ(cf.GetFontSize(), 16); + EXPECT_EQ(cf.GetFontName(), kTestFontName); + EXPECT_EQ(base::ToLowerASCII(kTestFontName), + base::ToLowerASCII(cf.GetActualFontName())); +} + +TEST_F(FontTest, LoadArialBold) { + Font cf(kTestFontName, 16); + Font bold(cf.Derive(0, Font::NORMAL, Font::Weight::BOLD)); +#if defined(OS_APPLE) + EXPECT_TRUE(bold.GetNativeFont()); +#endif + EXPECT_EQ(bold.GetStyle(), Font::NORMAL); + EXPECT_EQ(bold.GetWeight(), Font::Weight::BOLD); + EXPECT_EQ(base::ToLowerASCII(kTestFontName), + base::ToLowerASCII(cf.GetActualFontName())); +} + +TEST_F(FontTest, Ascent) { + Font cf(kTestFontName, 16); + EXPECT_GT(cf.GetBaseline(), 2); + EXPECT_LE(cf.GetBaseline(), 22); +} + +TEST_F(FontTest, Height) { + Font cf(kTestFontName, 16); + EXPECT_GE(cf.GetHeight(), 16); + // TODO(akalin): Figure out why height is so large on Linux. + EXPECT_LE(cf.GetHeight(), 26); +} + +TEST_F(FontTest, CapHeight) { + Font cf(kTestFontName, 16); + EXPECT_GT(cf.GetCapHeight(), 0); + EXPECT_GT(cf.GetCapHeight(), cf.GetHeight() / 2); + EXPECT_LT(cf.GetCapHeight(), cf.GetBaseline()); +} + +TEST_F(FontTest, AvgWidths) { + Font cf(kTestFontName, 16); + EXPECT_EQ(cf.GetExpectedTextWidth(0), 0); + EXPECT_GT(cf.GetExpectedTextWidth(1), cf.GetExpectedTextWidth(0)); + EXPECT_GT(cf.GetExpectedTextWidth(2), cf.GetExpectedTextWidth(1)); + EXPECT_GT(cf.GetExpectedTextWidth(3), cf.GetExpectedTextWidth(2)); +} + +// Check that fonts used for testing are installed and enabled. On Mac +// fonts may be installed but still need enabling in Font Book.app. +// http://crbug.com/347429 +TEST_F(FontTest, GetActualFontName) { + Font arial(kTestFontName, 16); + EXPECT_EQ(base::ToLowerASCII(kTestFontName), + base::ToLowerASCII(arial.GetActualFontName())) + << "********\n" + << "Your test environment seems to be missing Arial font, which is " + << "needed for unittests. Check if Arial font is installed.\n" + << "********"; + Font symbol(kSymbolFontName, 16); + EXPECT_EQ(base::ToLowerASCII(kSymbolFontName), + base::ToLowerASCII(symbol.GetActualFontName())) + << "********\n" + << "Your test environment seems to be missing the " << kSymbolFontName + << " font, which is " + << "needed for unittests. Check if " << kSymbolFontName + << " font is installed.\n" + << "********"; + + const char* const invalid_font_name = "no_such_font_name"; + Font fallback_font(invalid_font_name, 16); + EXPECT_NE(invalid_font_name, + base::ToLowerASCII(fallback_font.GetActualFontName())); +} + +TEST_F(FontTest, DeriveFont) { + Font cf(kTestFontName, 8); + const int kSizeDelta = 2; + Font cf_underlined = + cf.Derive(0, cf.GetStyle() | gfx::Font::UNDERLINE, cf.GetWeight()); + Font cf_underlined_resized = cf_underlined.Derive( + kSizeDelta, cf_underlined.GetStyle(), cf_underlined.GetWeight()); + EXPECT_EQ(cf.GetStyle() | gfx::Font::UNDERLINE, + cf_underlined_resized.GetStyle()); + EXPECT_EQ(cf.GetFontSize() + kSizeDelta, cf_underlined_resized.GetFontSize()); + EXPECT_EQ(cf.GetWeight(), cf_underlined_resized.GetWeight()); +} + +#if defined(OS_WIN) +TEST_F(FontTest, DeriveResizesIfSizeTooSmall) { + Font cf(kTestFontName, 8); + gfx::win::SetGetMinimumFontSizeCallback([] { return 5; }); + + Font derived_font = cf.Derive(-4, cf.GetStyle(), cf.GetWeight()); + EXPECT_EQ(5, derived_font.GetFontSize()); +} + +TEST_F(FontTest, DeriveKeepsOriginalSizeIfHeightOk) { + Font cf(kTestFontName, 8); + gfx::win::SetGetMinimumFontSizeCallback([] { return 5; }); + + Font derived_font = cf.Derive(-2, cf.GetStyle(), cf.GetWeight()); + EXPECT_EQ(6, derived_font.GetFontSize()); +} +#endif // defined(OS_WIN) + +TEST_F(FontTest, WeightConversion) { + struct WeightMatchExpectation { + int weight; + Font::Weight enum_value; + } expectations[] = { + {-10, Font::Weight::INVALID}, {-1, Font::Weight::INVALID}, + {0, Font::Weight::THIN}, {1, Font::Weight::THIN}, + {100, Font::Weight::THIN}, {350, Font::Weight::NORMAL}, + {400, Font::Weight::NORMAL}, {899, Font::Weight::BLACK}, + {900, Font::Weight::BLACK}, {901, Font::Weight::INVALID}}; + for (const auto& expectation : expectations) { + EXPECT_EQ(FontWeightFromInt(expectation.weight), expectation.enum_value); + } +} + +} // namespace +} // namespace gfx diff --git a/font_util.cc b/font_util.cc new file mode 100644 index 000000000000..faf6dd7b2d67 --- /dev/null +++ b/font_util.cc @@ -0,0 +1,38 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/font_util.h" + +#include "build/build_config.h" + +#if defined(OS_LINUX) || defined(OS_CHROMEOS) +#include +#include "ui/gfx/linux/fontconfig_util.h" +#endif + +#if defined(OS_WIN) +#include "ui/gfx/win/direct_write.h" +#endif + +namespace gfx { + +void InitializeFonts() { + // Implicit initialization can cause a long delay on the first rendering if + // the font cache has to be regenerated for some reason. Doing it explicitly + // here helps in cases where the browser process is starting up in the + // background (resources have not yet been granted to cast) since it prevents + // the long delay the user would have seen on first rendering. + +#if defined(OS_LINUX) || defined(OS_CHROMEOS) + // Ensures the config is created on this thread. + FcConfig* config = GetGlobalFontConfig(); + DCHECK(config); +#endif // defined(OS_LINUX) || defined(OS_CHROMEOS) + +#if defined(OS_WIN) + gfx::win::InitializeDirectWrite(); +#endif // OS_WIN +} + +} // namespace gfx diff --git a/font_util.h b/font_util.h new file mode 100644 index 000000000000..0ceac62fbf19 --- /dev/null +++ b/font_util.h @@ -0,0 +1,17 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_FONT_UTIL_H_ +#define UI_GFX_FONT_UTIL_H_ + +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +// Initialize the library fonts. +GFX_EXPORT void InitializeFonts(); + +} // namespace gfx + +#endif // UI_GFX_FONT_UTIL_H_ diff --git a/gdi_util.cc b/gdi_util.cc new file mode 100644 index 000000000000..abfc3cb124d5 --- /dev/null +++ b/gdi_util.cc @@ -0,0 +1,57 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/gdi_util.h" + +#include + +#include +#include + +#include "skia/ext/skia_utils_win.h" + +namespace gfx { + +void CreateBitmapV4HeaderForARGB888(int width, + int height, + BITMAPV4HEADER* hdr) { + // Because bmp v4 header is just an extension, we just create a v3 header and + // copy the bits over to the v4 header. + BITMAPINFOHEADER header_v3; + skia::CreateBitmapHeaderForXRGB888(width, height, &header_v3); + memset(hdr, 0, sizeof(BITMAPV4HEADER)); + memcpy(hdr, &header_v3, sizeof(BITMAPINFOHEADER)); + + // Correct the size of the header and fill in the mask values. + hdr->bV4Size = sizeof(BITMAPV4HEADER); + hdr->bV4RedMask = 0x00ff0000; + hdr->bV4GreenMask = 0x0000ff00; + hdr->bV4BlueMask = 0x000000ff; + hdr->bV4AlphaMask = 0xff000000; +} + +float CalculatePageScale(HDC dc, int page_width, int page_height) { + int dc_width = GetDeviceCaps(dc, HORZRES); + int dc_height = GetDeviceCaps(dc, VERTRES); + + // If page fits DC - no scaling needed. + if (dc_width >= page_width && dc_height >= page_height) + return 1.0; + + float x_factor = + static_cast(dc_width) / static_cast(page_width); + float y_factor = + static_cast(dc_height) / static_cast(page_height); + return std::min(x_factor, y_factor); +} + +// Apply scaling to the DC. +bool ScaleDC(HDC dc, float scale_factor) { + SetGraphicsMode(dc, GM_ADVANCED); + XFORM xform = {0}; + xform.eM11 = xform.eM22 = scale_factor; + return !!ModifyWorldTransform(dc, &xform, MWT_LEFTMULTIPLY); +} + +} // namespace gfx diff --git a/gdi_util.h b/gdi_util.h new file mode 100644 index 000000000000..5b9af51bcf58 --- /dev/null +++ b/gdi_util.h @@ -0,0 +1,31 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GDI_UTIL_H_ +#define UI_GFX_GDI_UTIL_H_ + +#include + +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +// Creates a BITMAPV4HEADER structure given the bitmap's size. You probably +// only need to use BMP V4 if you need transparency (alpha channel). This +// function sets the AlphaMask to 0xff000000, meaning this header is for a +// 32-bits-per-pixel ARGB8888 bitmap. +GFX_EXPORT void CreateBitmapV4HeaderForARGB888(int width, + int height, + BITMAPV4HEADER* hdr); + +// Calculate scale to fit an entire page on DC. +GFX_EXPORT float CalculatePageScale(HDC dc, int page_width, int page_height); + +// Apply scaling to the DC. +GFX_EXPORT bool ScaleDC(HDC dc, float scale_factor); + +} // namespace gfx + +#endif // UI_GFX_GDI_UTIL_H_ diff --git a/generic_shared_memory_id.cc b/generic_shared_memory_id.cc new file mode 100644 index 000000000000..e9ed1c9b8b49 --- /dev/null +++ b/generic_shared_memory_id.cc @@ -0,0 +1,21 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/generic_shared_memory_id.h" + +#include "base/format_macros.h" +#include "base/strings/stringprintf.h" + +namespace gfx { + +base::trace_event::MemoryAllocatorDumpGuid +GetGenericSharedGpuMemoryGUIDForTracing( + uint64_t tracing_process_id, + GenericSharedMemoryId generic_shared_memory_id) { + return base::trace_event::MemoryAllocatorDumpGuid( + base::StringPrintf("genericsharedmemory-x-process/%" PRIx64 "/%d", + tracing_process_id, generic_shared_memory_id.id)); +} + +} // namespace gfx diff --git a/generic_shared_memory_id.h b/generic_shared_memory_id.h new file mode 100644 index 000000000000..39c53e0d3c76 --- /dev/null +++ b/generic_shared_memory_id.h @@ -0,0 +1,72 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GENERIC_SHARED_MEMORY_ID_H_ +#define UI_GFX_GENERIC_SHARED_MEMORY_ID_H_ + +#include +#include + +#include + +#include "base/hash/hash.h" +#include "base/trace_event/memory_allocator_dump.h" +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +// Defines an ID type which is used across all types of shared memory +// allocations in content/. This ID type is in ui/gfx, as components outside +// content/ may need to hold an ID (but should not generate one). +class GFX_EXPORT GenericSharedMemoryId { + public: + int id; + + // Invalid ID is -1 to match semantics of base::AtomicSequenceNumber. + GenericSharedMemoryId() : id(-1) {} + explicit GenericSharedMemoryId(int id) : id(id) {} + GenericSharedMemoryId(const GenericSharedMemoryId& other) = default; + GenericSharedMemoryId& operator=(const GenericSharedMemoryId& other) = + default; + + bool is_valid() const { return id >= 0; } + + bool operator==(const GenericSharedMemoryId& other) const { + return id == other.id; + } + + bool operator<(const GenericSharedMemoryId& other) const { + return id < other.id; + } +}; + +// Generates GUID which can be used to trace shared memory using its +// GenericSharedMemoryId. +GFX_EXPORT base::trace_event::MemoryAllocatorDumpGuid +GetGenericSharedGpuMemoryGUIDForTracing( + uint64_t tracing_process_id, + GenericSharedMemoryId generic_shared_memory_id); + +} // namespace gfx + +namespace std { + +template <> +struct hash { + size_t operator()(gfx::GenericSharedMemoryId key) const { + return std::hash()(key.id); + } +}; + +template +struct hash> { + size_t operator()( + const std::pair& pair) const { + return base::HashInts(pair.first.id, pair.second); + } +}; + +} // namespace std + +#endif // UI_GFX_GENERIC_SHARED_MEMORY_ID_H_ diff --git a/geometry/BUILD.gn b/geometry/BUILD.gn new file mode 100644 index 000000000000..abc446c8a1c6 --- /dev/null +++ b/geometry/BUILD.gn @@ -0,0 +1,101 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +component("geometry") { + sources = [ + "../gfx_export.h", + "angle_conversions.h", + "axis_transform2d.cc", + "axis_transform2d.h", + "box_f.cc", + "box_f.h", + "cubic_bezier.cc", + "cubic_bezier.h", + "dip_util.cc", + "dip_util.h", + "geometry_export.h", + "insets.cc", + "insets.h", + "insets_conversions.cc", + "insets_conversions.h", + "insets_f.cc", + "insets_f.h", + "matrix3_f.cc", + "matrix3_f.h", + "point.cc", + "point.h", + "point3_f.cc", + "point3_f.h", + "point_conversions.cc", + "point_conversions.h", + "point_f.cc", + "point_f.h", + "quad_f.cc", + "quad_f.h", + "quaternion.cc", + "quaternion.h", + "rect.cc", + "rect.h", + "rect_conversions.cc", + "rect_conversions.h", + "rect_f.cc", + "rect_f.h", + "resize_utils.cc", + "resize_utils.h", + "rounded_corners_f.cc", + "rounded_corners_f.h", + "size.cc", + "size.h", + "size_conversions.cc", + "size_conversions.h", + "size_f.cc", + "size_f.h", + "vector2d.cc", + "vector2d.h", + "vector2d_conversions.cc", + "vector2d_conversions.h", + "vector2d_f.cc", + "vector2d_f.h", + "vector3d_f.cc", + "vector3d_f.h", + ] + + defines = [ "GEOMETRY_IMPLEMENTATION" ] + + deps = [ "//base" ] + + if (!is_debug) { + configs -= [ "//build/config/compiler:default_optimization" ] + configs += [ "//build/config/compiler:optimize_max" ] + } +} + +component("geometry_skia") { + sources = [ + "geometry_skia_export.h", + "mask_filter_info.cc", + "mask_filter_info.h", + "rrect_f.cc", + "rrect_f.h", + "rrect_f_builder.cc", + "rrect_f_builder.h", + "skia_conversions.cc", + "skia_conversions.h", + "transform.cc", + "transform.h", + "transform_operation.cc", + "transform_operation.h", + "transform_operations.cc", + "transform_operations.h", + "transform_util.cc", + "transform_util.h", + ] + configs += [ "//build/config/compiler:wexit_time_destructors" ] + public_deps = [ + ":geometry", + "//base", + "//skia", + ] + defines = [ "GEOMETRY_SKIA_IMPLEMENTATION" ] +} diff --git a/geometry/OWNERS b/geometry/OWNERS new file mode 100644 index 000000000000..3aaf7a7587ee --- /dev/null +++ b/geometry/OWNERS @@ -0,0 +1,5 @@ +danakj@chromium.org +vollick@chromium.org + +per-file cubic_bezier*=ajuma@chromium.org +per-file box*=ajuma@chromium.org diff --git a/geometry/angle_conversions.h b/geometry/angle_conversions.h new file mode 100644 index 000000000000..876ad5c5ac4d --- /dev/null +++ b/geometry/angle_conversions.h @@ -0,0 +1,29 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_ANGLE_CONVERSIONS_H_ +#define UI_GFX_GEOMETRY_ANGLE_CONVERSIONS_H_ + +#include "base/numerics/math_constants.h" +#include "ui/gfx/geometry/geometry_export.h" + +namespace gfx { + +GEOMETRY_EXPORT constexpr double DegToRad(double deg) { + return deg * base::kPiDouble / 180.0; +} +GEOMETRY_EXPORT constexpr float DegToRad(float deg) { + return deg * base::kPiFloat / 180.0f; +} + +GEOMETRY_EXPORT constexpr double RadToDeg(double rad) { + return rad * 180.0 / base::kPiDouble; +} +GEOMETRY_EXPORT constexpr float RadToDeg(float rad) { + return rad * 180.0f / base::kPiFloat; +} + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_ANGLE_CONVERSIONS_H_ diff --git a/geometry/axis_transform2d.cc b/geometry/axis_transform2d.cc new file mode 100644 index 000000000000..fb2bb423401d --- /dev/null +++ b/geometry/axis_transform2d.cc @@ -0,0 +1,16 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/axis_transform2d.h" + +#include "base/strings/stringprintf.h" + +namespace gfx { + +std::string AxisTransform2d::ToString() const { + return base::StringPrintf("[%s, %s]", scale_.ToString().c_str(), + translation_.ToString().c_str()); +} + +} // namespace gfx \ No newline at end of file diff --git a/geometry/axis_transform2d.h b/geometry/axis_transform2d.h new file mode 100644 index 000000000000..3e74fca46eb9 --- /dev/null +++ b/geometry/axis_transform2d.h @@ -0,0 +1,144 @@ +// Copyright (c) 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_AXIS_TRANSFORM2D_H_ +#define UI_GFX_GEOMETRY_AXIS_TRANSFORM2D_H_ + +#include "base/check_op.h" +#include "ui/gfx/geometry/geometry_export.h" +#include "ui/gfx/geometry/rect_f.h" +#include "ui/gfx/geometry/vector2d_f.h" + +namespace gfx { + +// This class implements the subset of 2D linear transforms that only +// translation and uniform scaling are allowed. +// Internally this is stored as a vector for pre-scale, and another vector +// for post-translation. The class constructor and member accessor follows +// the same convention, but a scalar scale factor is also accepted. +class GEOMETRY_EXPORT AxisTransform2d { + public: + constexpr AxisTransform2d() = default; + constexpr AxisTransform2d(float scale, const Vector2dF& translation) + : scale_(scale, scale), translation_(translation) {} + constexpr AxisTransform2d(const Vector2dF& scale, + const Vector2dF& translation) + : scale_(scale), translation_(translation) {} + + bool operator==(const AxisTransform2d& other) const { + return scale_ == other.scale_ && translation_ == other.translation_; + } + bool operator!=(const AxisTransform2d& other) const { + return !(*this == other); + } + + void PreScale(const Vector2dF& scale) { scale_.Scale(scale.x(), scale.y()); } + void PostScale(const Vector2dF& scale) { + scale_.Scale(scale.x(), scale.y()); + translation_.Scale(scale.x(), scale.y()); + } + void PreTranslate(const Vector2dF& translation) { + translation_ += ScaleVector2d(translation, scale_.x(), scale_.y()); + } + void PostTranslate(const Vector2dF& translation) { + translation_ += translation; + } + + void PreConcat(const AxisTransform2d& pre) { + PreTranslate(pre.translation_); + PreScale(pre.scale_); + } + void PostConcat(const AxisTransform2d& post) { + PostScale(post.scale_); + PostTranslate(post.translation_); + } + + void Invert() { + DCHECK(scale_.x()); + DCHECK(scale_.y()); + scale_ = Vector2dF(1.f / scale_.x(), 1.f / scale_.y()); + translation_.Scale(-scale_.x(), -scale_.y()); + } + + PointF MapPoint(const PointF& p) const { + return ScalePoint(p, scale_.x(), scale_.y()) + translation_; + } + PointF InverseMapPoint(const PointF& p) const { + return ScalePoint(p - translation_, 1.f / scale_.x(), 1.f / scale_.y()); + } + + RectF MapRect(const RectF& r) const { + DCHECK_GE(scale_.x(), 0.f); + DCHECK_GE(scale_.y(), 0.f); + return ScaleRect(r, scale_.x(), scale_.y()) + translation_; + } + RectF InverseMapRect(const RectF& r) const { + DCHECK_GT(scale_.x(), 0.f); + DCHECK_GT(scale_.y(), 0.f); + return ScaleRect(r - translation_, 1.f / scale_.x(), 1.f / scale_.y()); + } + + const Vector2dF& scale() const { return scale_; } + const Vector2dF& translation() const { return translation_; } + + std::string ToString() const; + + private: + // Scale is applied before translation, i.e. + // this->Transform(p) == scale_ * p + translation_ + Vector2dF scale_{1.f, 1.f}; + Vector2dF translation_; +}; + +inline AxisTransform2d PreScaleAxisTransform2d(const AxisTransform2d& t, + float scale) { + AxisTransform2d result(t); + result.PreScale(Vector2dF(scale, scale)); + return result; +} + +inline AxisTransform2d PostScaleAxisTransform2d(const AxisTransform2d& t, + float scale) { + AxisTransform2d result(t); + result.PostScale(Vector2dF(scale, scale)); + return result; +} + +inline AxisTransform2d PreTranslateAxisTransform2d( + const AxisTransform2d& t, + const Vector2dF& translation) { + AxisTransform2d result(t); + result.PreTranslate(translation); + return result; +} + +inline AxisTransform2d PostTranslateAxisTransform2d( + const AxisTransform2d& t, + const Vector2dF& translation) { + AxisTransform2d result(t); + result.PostTranslate(translation); + return result; +} + +inline AxisTransform2d ConcatAxisTransform2d(const AxisTransform2d& post, + const AxisTransform2d& pre) { + AxisTransform2d result(post); + result.PreConcat(pre); + return result; +} + +inline AxisTransform2d InvertAxisTransform2d(const AxisTransform2d& t) { + AxisTransform2d result = t; + result.Invert(); + return result; +} + +// This is declared here for use in gtest-based unit tests but is defined in +// the //ui/gfx:test_support target. Depend on that to use this in your unit +// test. This should not be used in production code - call ToString() instead. +void PrintTo(const AxisTransform2d&, ::std::ostream* os); + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_AXIS_TRANSFORM2D_H_ diff --git a/geometry/axis_transform2d_unittest.cc b/geometry/axis_transform2d_unittest.cc new file mode 100644 index 000000000000..6bee3da50a4a --- /dev/null +++ b/geometry/axis_transform2d_unittest.cc @@ -0,0 +1,91 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/axis_transform2d.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/test/gfx_util.h" + +namespace gfx { +namespace { + +TEST(AxisTransform2dTest, Mapping) { + AxisTransform2d t(1.25f, Vector2dF(3.75f, 55.f)); + + PointF p(150.f, 100.f); + EXPECT_EQ(PointF(191.25f, 180.f), t.MapPoint(p)); + EXPECT_POINTF_EQ(PointF(117.f, 36.f), t.InverseMapPoint(p)); + + RectF r(150.f, 100.f, 22.5f, 37.5f); + EXPECT_EQ(RectF(191.25f, 180.f, 28.125f, 46.875f), t.MapRect(r)); + EXPECT_RECTF_EQ(RectF(117.f, 36.f, 18.f, 30.f), t.InverseMapRect(r)); +} + +TEST(AxisTransform2dTest, Scaling) { + { + AxisTransform2d t(1.25f, Vector2dF(3.75f, 55.f)); + EXPECT_EQ(AxisTransform2d(1.5625f, Vector2dF(3.75f, 55.f)), + PreScaleAxisTransform2d(t, 1.25)); + t.PreScale(Vector2dF(1.25f, 1.25f)); + EXPECT_EQ(AxisTransform2d(1.5625f, Vector2dF(3.75f, 55.f)), t); + } + + { + AxisTransform2d t(1.25f, Vector2dF(3.75f, 55.f)); + EXPECT_EQ(AxisTransform2d(1.5625f, Vector2dF(4.6875f, 68.75f)), + PostScaleAxisTransform2d(t, 1.25)); + t.PostScale(Vector2dF(1.25f, 1.25f)); + EXPECT_EQ(AxisTransform2d(1.5625f, Vector2dF(4.6875f, 68.75f)), t); + } +} + +TEST(AxisTransform2dTest, Translating) { + Vector2dF tr(3.f, -5.f); + { + AxisTransform2d t(1.25f, Vector2dF(3.75f, 55.f)); + EXPECT_EQ(AxisTransform2d(1.25f, Vector2dF(7.5f, 48.75f)), + PreTranslateAxisTransform2d(t, tr)); + t.PreTranslate(tr); + EXPECT_EQ(AxisTransform2d(1.25f, Vector2dF(7.5f, 48.75f)), t); + } + + { + AxisTransform2d t(1.25f, Vector2dF(3.75f, 55.f)); + EXPECT_EQ(AxisTransform2d(1.25f, Vector2dF(6.75f, 50.f)), + PostTranslateAxisTransform2d(t, tr)); + t.PostTranslate(tr); + EXPECT_EQ(AxisTransform2d(1.25f, Vector2dF(6.75f, 50.f)), t); + } +} + +TEST(AxisTransform2dTest, Concat) { + AxisTransform2d pre(1.25f, Vector2dF(3.75f, 55.f)); + AxisTransform2d post(0.5f, Vector2dF(10.f, 5.f)); + AxisTransform2d expectation(0.625f, Vector2dF(11.875f, 32.5f)); + EXPECT_EQ(expectation, ConcatAxisTransform2d(post, pre)); + + AxisTransform2d post_concat = pre; + post_concat.PostConcat(post); + EXPECT_EQ(expectation, post_concat); + + AxisTransform2d pre_concat = post; + pre_concat.PreConcat(pre); + EXPECT_EQ(expectation, pre_concat); +} + +TEST(AxisTransform2dTest, Inverse) { + AxisTransform2d t(1.25f, Vector2dF(3.75f, 55.f)); + AxisTransform2d inv_inplace = t; + inv_inplace.Invert(); + AxisTransform2d inv_out_of_place = InvertAxisTransform2d(t); + + EXPECT_AXIS_TRANSFORM2D_EQ(inv_inplace, inv_out_of_place); + EXPECT_AXIS_TRANSFORM2D_EQ(AxisTransform2d(), + ConcatAxisTransform2d(t, inv_inplace)); + EXPECT_AXIS_TRANSFORM2D_EQ(AxisTransform2d(), + ConcatAxisTransform2d(inv_inplace, t)); +} + +} // namespace +} // namespace gfx diff --git a/geometry/box_f.cc b/geometry/box_f.cc new file mode 100644 index 000000000000..d942b70eb5f1 --- /dev/null +++ b/geometry/box_f.cc @@ -0,0 +1,70 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/box_f.h" + +#include + +#include "base/check_op.h" +#include "base/strings/stringprintf.h" + +namespace gfx { + +std::string BoxF::ToString() const { + return base::StringPrintf("%s %fx%fx%f", + origin().ToString().c_str(), + width_, + height_, + depth_); +} + +bool BoxF::IsEmpty() const { + return (width_ == 0 && height_ == 0) || + (width_ == 0 && depth_ == 0) || + (height_ == 0 && depth_ == 0); +} + +void BoxF::ExpandTo(const Point3F& min, const Point3F& max) { + DCHECK_LE(min.x(), max.x()); + DCHECK_LE(min.y(), max.y()); + DCHECK_LE(min.z(), max.z()); + + float min_x = std::min(x(), min.x()); + float min_y = std::min(y(), min.y()); + float min_z = std::min(z(), min.z()); + float max_x = std::max(right(), max.x()); + float max_y = std::max(bottom(), max.y()); + float max_z = std::max(front(), max.z()); + + origin_.SetPoint(min_x, min_y, min_z); + width_ = max_x - min_x; + height_ = max_y - min_y; + depth_ = max_z - min_z; +} + +void BoxF::Union(const BoxF& box) { + if (IsEmpty()) { + *this = box; + return; + } + if (box.IsEmpty()) + return; + ExpandTo(box); +} + +void BoxF::ExpandTo(const Point3F& point) { + ExpandTo(point, point); +} + +void BoxF::ExpandTo(const BoxF& box) { + ExpandTo(box.origin(), gfx::Point3F(box.right(), box.bottom(), box.front())); +} + +BoxF UnionBoxes(const BoxF& a, const BoxF& b) { + BoxF result = a; + result.Union(b); + return result; +} + +} // namespace gfx diff --git a/geometry/box_f.h b/geometry/box_f.h new file mode 100644 index 000000000000..443174d3eb61 --- /dev/null +++ b/geometry/box_f.h @@ -0,0 +1,160 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_BOX_F_H_ +#define UI_GFX_GEOMETRY_BOX_F_H_ + +#include +#include + +#include "ui/gfx/geometry/point3_f.h" +#include "ui/gfx/geometry/vector3d_f.h" + +namespace gfx { + +// A 3d version of gfx::RectF, with the positive z-axis pointed towards +// the camera. +class GEOMETRY_EXPORT BoxF { + public: + constexpr BoxF() : BoxF(0, 0, 0) {} + constexpr BoxF(float width, float height, float depth) + : BoxF(0, 0, 0, width, height, depth) {} + constexpr BoxF(float x, + float y, + float z, + float width, + float height, + float depth) + : BoxF(Point3F(x, y, z), width, height, depth) {} + constexpr BoxF(const Point3F& origin, float width, float height, float depth) + : origin_(origin), + width_(width >= 0 ? width : 0), + height_(height >= 0 ? height : 0), + depth_(depth >= 0 ? depth : 0) {} + + // Scales all three axes by the given scale. + void Scale(float scale) { + Scale(scale, scale, scale); + } + + // Scales each axis by the corresponding given scale. + void Scale(float x_scale, float y_scale, float z_scale) { + origin_.Scale(x_scale, y_scale, z_scale); + set_size(width_ * x_scale, height_ * y_scale, depth_ * z_scale); + } + + // Moves the box by the specified distance in each dimension. + void operator+=(const Vector3dF& offset) { + origin_ += offset; + } + + // Returns true if the box has no interior points. + bool IsEmpty() const; + + // Computes the union of this box with the given box. The union is the + // smallest box that contains both boxes. + void Union(const BoxF& box); + + std::string ToString() const; + + constexpr float x() const { return origin_.x(); } + void set_x(float x) { origin_.set_x(x); } + + constexpr float y() const { return origin_.y(); } + void set_y(float y) { origin_.set_y(y); } + + constexpr float z() const { return origin_.z(); } + void set_z(float z) { origin_.set_z(z); } + + constexpr float width() const { return width_; } + void set_width(float width) { width_ = width < 0 ? 0 : width; } + + constexpr float height() const { return height_; } + void set_height(float height) { height_ = height < 0 ? 0 : height; } + + constexpr float depth() const { return depth_; } + void set_depth(float depth) { depth_ = depth < 0 ? 0 : depth; } + + constexpr float right() const { return x() + width(); } + constexpr float bottom() const { return y() + height(); } + constexpr float front() const { return z() + depth(); } + + void set_size(float width, float height, float depth) { + width_ = width < 0 ? 0 : width; + height_ = height < 0 ? 0 : height; + depth_ = depth < 0 ? 0 : depth; + } + + constexpr const Point3F& origin() const { return origin_; } + void set_origin(const Point3F& origin) { origin_ = origin; } + + // Expands |this| to contain the given point, if necessary. Please note, even + // if |this| is empty, after the function |this| will continue to contain its + // |origin_|. + void ExpandTo(const Point3F& point); + + // Expands |this| to contain the given box, if necessary. Please note, even + // if |this| is empty, after the function |this| will continue to contain its + // |origin_|. + void ExpandTo(const BoxF& box); + + private: + // Expands the box to contain the two given points. It is required that each + // component of |min| is less than or equal to the corresponding component in + // |max|. Precisely, what this function does is ensure that after the function + // completes, |this| contains origin_, min, max, and origin_ + (width_, + // height_, depth_), even if the box is empty. Emptiness checks are handled in + // the public function Union. + void ExpandTo(const Point3F& min, const Point3F& max); + + Point3F origin_; + float width_; + float height_; + float depth_; +}; + +GEOMETRY_EXPORT BoxF UnionBoxes(const BoxF& a, const BoxF& b); + +inline BoxF ScaleBox(const BoxF& b, + float x_scale, + float y_scale, + float z_scale) { + return BoxF(b.x() * x_scale, + b.y() * y_scale, + b.z() * z_scale, + b.width() * x_scale, + b.height() * y_scale, + b.depth() * z_scale); +} + +inline BoxF ScaleBox(const BoxF& b, float scale) { + return ScaleBox(b, scale, scale, scale); +} + +inline bool operator==(const BoxF& a, const BoxF& b) { + return a.origin() == b.origin() && a.width() == b.width() && + a.height() == b.height() && a.depth() == b.depth(); +} + +inline bool operator!=(const BoxF& a, const BoxF& b) { + return !(a == b); +} + +inline BoxF operator+(const BoxF& b, const Vector3dF& v) { + return BoxF(b.x() + v.x(), + b.y() + v.y(), + b.z() + v.z(), + b.width(), + b.height(), + b.depth()); +} + +// This is declared here for use in gtest-based unit tests but is defined in +// the //ui/gfx:test_support target. Depend on that to use this in your unit +// test. This should not be used in production code - call ToString() instead. +void PrintTo(const BoxF& box, ::std::ostream* os); + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_BOX_F_H_ diff --git a/geometry/box_unittest.cc b/geometry/box_unittest.cc new file mode 100644 index 000000000000..990fccc24977 --- /dev/null +++ b/geometry/box_unittest.cc @@ -0,0 +1,175 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/geometry/box_f.h" + +namespace gfx { + +TEST(BoxTest, Constructors) { + EXPECT_EQ(BoxF(0.f, 0.f, 0.f, 0.f, 0.f, 0.f).ToString(), + BoxF().ToString()); + EXPECT_EQ(BoxF(0.f, 0.f, 0.f, -3.f, -5.f, -7.f).ToString(), + BoxF().ToString()); + + EXPECT_EQ(BoxF(0.f, 0.f, 0.f, 3.f, 5.f, 7.f).ToString(), + BoxF(3.f, 5.f, 7.f).ToString()); + EXPECT_EQ(BoxF(0.f, 0.f, 0.f, 0.f, 0.f, 0.f).ToString(), + BoxF(-3.f, -5.f, -7.f).ToString()); + + EXPECT_EQ(BoxF(2.f, 4.f, 6.f, 3.f, 5.f, 7.f).ToString(), + BoxF(Point3F(2.f, 4.f, 6.f), 3.f, 5.f, 7.f).ToString()); + EXPECT_EQ(BoxF(2.f, 4.f, 6.f, 0.f, 0.f, 0.f).ToString(), + BoxF(Point3F(2.f, 4.f, 6.f), -3.f, -5.f, -7.f).ToString()); +} + +TEST(BoxTest, IsEmpty) { + EXPECT_TRUE(BoxF(0.f, 0.f, 0.f, 0.f, 0.f, 0.f).IsEmpty()); + EXPECT_TRUE(BoxF(1.f, 2.f, 3.f, 0.f, 0.f, 0.f).IsEmpty()); + + EXPECT_TRUE(BoxF(0.f, 0.f, 0.f, 2.f, 0.f, 0.f).IsEmpty()); + EXPECT_TRUE(BoxF(1.f, 2.f, 3.f, 2.f, 0.f, 0.f).IsEmpty()); + EXPECT_TRUE(BoxF(0.f, 0.f, 0.f, 0.f, 2.f, 0.f).IsEmpty()); + EXPECT_TRUE(BoxF(1.f, 2.f, 3.f, 0.f, 2.f, 0.f).IsEmpty()); + EXPECT_TRUE(BoxF(0.f, 0.f, 0.f, 0.f, 0.f, 2.f).IsEmpty()); + EXPECT_TRUE(BoxF(1.f, 2.f, 3.f, 0.f, 0.f, 2.f).IsEmpty()); + + EXPECT_FALSE(BoxF(0.f, 0.f, 0.f, 0.f, 2.f, 2.f).IsEmpty()); + EXPECT_FALSE(BoxF(1.f, 2.f, 3.f, 0.f, 2.f, 2.f).IsEmpty()); + EXPECT_FALSE(BoxF(0.f, 0.f, 0.f, 2.f, 0.f, 2.f).IsEmpty()); + EXPECT_FALSE(BoxF(1.f, 2.f, 3.f, 2.f, 0.f, 2.f).IsEmpty()); + EXPECT_FALSE(BoxF(0.f, 0.f, 0.f, 2.f, 2.f, 0.f).IsEmpty()); + EXPECT_FALSE(BoxF(1.f, 2.f, 3.f, 2.f, 2.f, 0.f).IsEmpty()); + + EXPECT_FALSE(BoxF(0.f, 0.f, 0.f, 2.f, 2.f, 2.f).IsEmpty()); + EXPECT_FALSE(BoxF(1.f, 2.f, 3.f, 2.f, 2.f, 2.f).IsEmpty()); +} + +TEST(BoxTest, Union) { + BoxF empty_box; + BoxF box1(0.f, 0.f, 0.f, 1.f, 1.f, 1.f); + BoxF box2(0.f, 0.f, 0.f, 4.f, 6.f, 8.f); + BoxF box3(3.f, 4.f, 5.f, 6.f, 4.f, 0.f); + + EXPECT_EQ(empty_box.ToString(), UnionBoxes(empty_box, empty_box).ToString()); + EXPECT_EQ(box1.ToString(), UnionBoxes(empty_box, box1).ToString()); + EXPECT_EQ(box1.ToString(), UnionBoxes(box1, empty_box).ToString()); + EXPECT_EQ(box2.ToString(), UnionBoxes(empty_box, box2).ToString()); + EXPECT_EQ(box2.ToString(), UnionBoxes(box2, empty_box).ToString()); + EXPECT_EQ(box3.ToString(), UnionBoxes(empty_box, box3).ToString()); + EXPECT_EQ(box3.ToString(), UnionBoxes(box3, empty_box).ToString()); + + // box_1 is contained in box_2 + EXPECT_EQ(box2.ToString(), UnionBoxes(box1, box2).ToString()); + EXPECT_EQ(box2.ToString(), UnionBoxes(box2, box1).ToString()); + + // box_1 and box_3 are disjoint + EXPECT_EQ(BoxF(0.f, 0.f, 0.f, 9.f, 8.f, 5.f).ToString(), + UnionBoxes(box1, box3).ToString()); + EXPECT_EQ(BoxF(0.f, 0.f, 0.f, 9.f, 8.f, 5.f).ToString(), + UnionBoxes(box3, box1).ToString()); + + // box_2 and box_3 intersect, but neither contains the other + EXPECT_EQ(BoxF(0.f, 0.f, 0.f, 9.f, 8.f, 8.f).ToString(), + UnionBoxes(box2, box3).ToString()); + EXPECT_EQ(BoxF(0.f, 0.f, 0.f, 9.f, 8.f, 8.f).ToString(), + UnionBoxes(box3, box2).ToString()); +} + +TEST(BoxTest, ExpandTo) { + BoxF box1; + BoxF box2(0.f, 0.f, 0.f, 1.f, 1.f, 1.f); + BoxF box3(1.f, 1.f, 1.f, 0.f, 0.f, 0.f); + + Point3F point1(0.5f, 0.5f, 0.5f); + Point3F point2(-0.5f, -0.5f, -0.5f); + + BoxF expected1_1(0.f, 0.f, 0.f, 0.5f, 0.5f, 0.5f); + BoxF expected1_2(-0.5f, -0.5f, -0.5f, 1.f, 1.f, 1.f); + + BoxF expected2_1 = box2; + BoxF expected2_2(-0.5f, -0.5f, -0.5f, 1.5f, 1.5f, 1.5f); + + BoxF expected3_1(0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f); + BoxF expected3_2(-0.5f, -0.5f, -0.5f, 1.5f, 1.5f, 1.5f); + + box1.ExpandTo(point1); + EXPECT_EQ(expected1_1.ToString(), box1.ToString()); + box1.ExpandTo(point2); + EXPECT_EQ(expected1_2.ToString(), box1.ToString()); + + box2.ExpandTo(point1); + EXPECT_EQ(expected2_1.ToString(), box2.ToString()); + box2.ExpandTo(point2); + EXPECT_EQ(expected2_2.ToString(), box2.ToString()); + + box3.ExpandTo(point1); + EXPECT_EQ(expected3_1.ToString(), box3.ToString()); + box3.ExpandTo(point2); + EXPECT_EQ(expected3_2.ToString(), box3.ToString()); +} + +TEST(BoxTest, Scale) { + BoxF box1(2.f, 3.f, 4.f, 5.f, 6.f, 7.f); + + EXPECT_EQ(BoxF().ToString(), ScaleBox(box1, 0.f).ToString()); + EXPECT_EQ(box1.ToString(), ScaleBox(box1, 1.f).ToString()); + EXPECT_EQ(BoxF(4.f, 12.f, 24.f, 10.f, 24.f, 42.f).ToString(), + ScaleBox(box1, 2.f, 4.f, 6.f).ToString()); + + BoxF box2 = box1; + box2.Scale(0.f); + EXPECT_EQ(BoxF().ToString(), box2.ToString()); + + box2 = box1; + box2.Scale(1.f); + EXPECT_EQ(box1.ToString(), box2.ToString()); + + box2.Scale(2.f, 4.f, 6.f); + EXPECT_EQ(BoxF(4.f, 12.f, 24.f, 10.f, 24.f, 42.f).ToString(), + box2.ToString()); +} + +TEST(BoxTest, Equals) { + EXPECT_TRUE(BoxF() == BoxF()); + EXPECT_TRUE(BoxF(2.f, 3.f, 4.f, 6.f, 8.f, 10.f) == + BoxF(2.f, 3.f, 4.f, 6.f, 8.f, 10.f)); + EXPECT_FALSE(BoxF() == BoxF(0.f, 0.f, 0.f, 0.f, 0.f, 1.f)); + EXPECT_FALSE(BoxF() == BoxF(0.f, 0.f, 0.f, 0.f, 1.f, 0.f)); + EXPECT_FALSE(BoxF() == BoxF(0.f, 0.f, 0.f, 1.f, 0.f, 0.f)); + EXPECT_FALSE(BoxF() == BoxF(0.f, 0.f, 1.f, 0.f, 0.f, 0.f)); + EXPECT_FALSE(BoxF() == BoxF(0.f, 1.f, 0.f, 0.f, 0.f, 0.f)); + EXPECT_FALSE(BoxF() == BoxF(1.f, 0.f, 0.f, 0.f, 0.f, 0.f)); +} + +TEST(BoxTest, NotEquals) { + EXPECT_FALSE(BoxF() != BoxF()); + EXPECT_FALSE(BoxF(2.f, 3.f, 4.f, 6.f, 8.f, 10.f) != + BoxF(2.f, 3.f, 4.f, 6.f, 8.f, 10.f)); + EXPECT_TRUE(BoxF() != BoxF(0.f, 0.f, 0.f, 0.f, 0.f, 1.f)); + EXPECT_TRUE(BoxF() != BoxF(0.f, 0.f, 0.f, 0.f, 1.f, 0.f)); + EXPECT_TRUE(BoxF() != BoxF(0.f, 0.f, 0.f, 1.f, 0.f, 0.f)); + EXPECT_TRUE(BoxF() != BoxF(0.f, 0.f, 1.f, 0.f, 0.f, 0.f)); + EXPECT_TRUE(BoxF() != BoxF(0.f, 1.f, 0.f, 0.f, 0.f, 0.f)); + EXPECT_TRUE(BoxF() != BoxF(1.f, 0.f, 0.f, 0.f, 0.f, 0.f)); +} + + +TEST(BoxTest, Offset) { + BoxF box1(2.f, 3.f, 4.f, 5.f, 6.f, 7.f); + + EXPECT_EQ(box1.ToString(), (box1 + Vector3dF(0.f, 0.f, 0.f)).ToString()); + EXPECT_EQ(BoxF(3.f, 1.f, 0.f, 5.f, 6.f, 7.f).ToString(), + (box1 + Vector3dF(1.f, -2.f, -4.f)).ToString()); + + BoxF box2 = box1; + box2 += Vector3dF(0.f, 0.f, 0.f); + EXPECT_EQ(box1.ToString(), box2.ToString()); + + box2 += Vector3dF(1.f, -2.f, -4.f); + EXPECT_EQ(BoxF(3.f, 1.f, 0.f, 5.f, 6.f, 7.f).ToString(), + box2.ToString()); +} + +} // namespace gfx diff --git a/geometry/cubic_bezier.cc b/geometry/cubic_bezier.cc new file mode 100644 index 000000000000..ccd297bbd116 --- /dev/null +++ b/geometry/cubic_bezier.cc @@ -0,0 +1,259 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/cubic_bezier.h" + +#include +#include + +#include "base/check_op.h" +#include "base/cxx17_backports.h" + +namespace gfx { + +namespace { + +const int kMaxNewtonIterations = 4; + +} // namespace + +static const double kBezierEpsilon = 1e-7; + +CubicBezier::CubicBezier(double p1x, double p1y, double p2x, double p2y) { + InitCoefficients(p1x, p1y, p2x, p2y); + InitGradients(p1x, p1y, p2x, p2y); + InitRange(p1y, p2y); + InitSpline(); +} + +CubicBezier::CubicBezier(const CubicBezier& other) = default; + +void CubicBezier::InitCoefficients(double p1x, + double p1y, + double p2x, + double p2y) { + // Calculate the polynomial coefficients, implicit first and last control + // points are (0,0) and (1,1). + cx_ = 3.0 * p1x; + bx_ = 3.0 * (p2x - p1x) - cx_; + ax_ = 1.0 - cx_ - bx_; + + cy_ = 3.0 * p1y; + by_ = 3.0 * (p2y - p1y) - cy_; + ay_ = 1.0 - cy_ - by_; + +#ifndef NDEBUG + // Bezier curves with x-coordinates outside the range [0,1] for internal + // control points may have multiple values for t for a given value of x. + // In this case, calls to SolveCurveX may produce ambiguous results. + monotonically_increasing_ = p1x >= 0 && p1x <= 1 && p2x >= 0 && p2x <= 1; +#endif +} + +void CubicBezier::InitGradients(double p1x, + double p1y, + double p2x, + double p2y) { + // End-point gradients are used to calculate timing function results + // outside the range [0, 1]. + // + // There are four possibilities for the gradient at each end: + // (1) the closest control point is not horizontally coincident with regard to + // (0, 0) or (1, 1). In this case the line between the end point and + // the control point is tangent to the bezier at the end point. + // (2) the closest control point is coincident with the end point. In + // this case the line between the end point and the far control + // point is tangent to the bezier at the end point. + // (3) both internal control points are coincident with an endpoint. There + // are two special case that fall into this category: + // CubicBezier(0, 0, 0, 0) and CubicBezier(1, 1, 1, 1). Both are + // equivalent to linear. + // (4) the closest control point is horizontally coincident with the end + // point, but vertically distinct. In this case the gradient at the + // end point is Infinite. However, this causes issues when + // interpolating. As a result, we break down to a simple case of + // 0 gradient under these conditions. + + if (p1x > 0) + start_gradient_ = p1y / p1x; + else if (!p1y && p2x > 0) + start_gradient_ = p2y / p2x; + else if (!p1y && !p2y) + start_gradient_ = 1; + else + start_gradient_ = 0; + + if (p2x < 1) + end_gradient_ = (p2y - 1) / (p2x - 1); + else if (p2y == 1 && p1x < 1) + end_gradient_ = (p1y - 1) / (p1x - 1); + else if (p2y == 1 && p1y == 1) + end_gradient_ = 1; + else + end_gradient_ = 0; +} + +// This works by taking taking the derivative of the cubic bezier, on the y +// axis. We can then solve for where the derivative is zero to find the min +// and max distance along the line. We the have to solve those in terms of time +// rather than distance on the x-axis +void CubicBezier::InitRange(double p1y, double p2y) { + range_min_ = 0; + range_max_ = 1; + if (0 <= p1y && p1y < 1 && 0 <= p2y && p2y <= 1) + return; + + const double epsilon = kBezierEpsilon; + + // Represent the function's derivative in the form at^2 + bt + c + // as in sampleCurveDerivativeY. + // (Technically this is (dy/dt)*(1/3), which is suitable for finding zeros + // but does not actually give the slope of the curve.) + const double a = 3.0 * ay_; + const double b = 2.0 * by_; + const double c = cy_; + + // Check if the derivative is constant. + if (std::abs(a) < epsilon && std::abs(b) < epsilon) + return; + + // Zeros of the function's derivative. + double t1 = 0; + double t2 = 0; + + if (std::abs(a) < epsilon) { + // The function's derivative is linear. + t1 = -c / b; + } else { + // The function's derivative is a quadratic. We find the zeros of this + // quadratic using the quadratic formula. + double discriminant = b * b - 4 * a * c; + if (discriminant < 0) + return; + double discriminant_sqrt = sqrt(discriminant); + t1 = (-b + discriminant_sqrt) / (2 * a); + t2 = (-b - discriminant_sqrt) / (2 * a); + } + + double sol1 = 0; + double sol2 = 0; + + // If the solution is in the range [0,1] then we include it, otherwise we + // ignore it. + + // An interesting fact about these beziers is that they are only + // actually evaluated in [0,1]. After that we take the tangent at that point + // and linearly project it out. + if (0 < t1 && t1 < 1) + sol1 = SampleCurveY(t1); + + if (0 < t2 && t2 < 1) + sol2 = SampleCurveY(t2); + + range_min_ = std::min({range_min_, sol1, sol2}); + range_max_ = std::max({range_max_, sol1, sol2}); +} + +void CubicBezier::InitSpline() { + double delta_t = 1.0 / (CUBIC_BEZIER_SPLINE_SAMPLES - 1); + for (int i = 0; i < CUBIC_BEZIER_SPLINE_SAMPLES; i++) { + spline_samples_[i] = SampleCurveX(i * delta_t); + } +} + +double CubicBezier::GetDefaultEpsilon() { + return kBezierEpsilon; +} + +double CubicBezier::SolveCurveX(double x, double epsilon) const { + DCHECK_GE(x, 0.0); + DCHECK_LE(x, 1.0); + + double t0; + double t1; + double t2 = x; + double x2; + double d2; + int i; + +#ifndef NDEBUG + DCHECK(monotonically_increasing_); +#endif + + // Linear interpolation of spline curve for initial guess. + double delta_t = 1.0 / (CUBIC_BEZIER_SPLINE_SAMPLES - 1); + for (i = 1; i < CUBIC_BEZIER_SPLINE_SAMPLES; i++) { + if (x <= spline_samples_[i]) { + t1 = delta_t * i; + t0 = t1 - delta_t; + t2 = t0 + (t1 - t0) * (x - spline_samples_[i - 1]) / + (spline_samples_[i] - spline_samples_[i - 1]); + break; + } + } + + // Perform a few iterations of Newton's method -- normally very fast. + // See https://en.wikipedia.org/wiki/Newton%27s_method. + double newton_epsilon = std::min(kBezierEpsilon, epsilon); + for (i = 0; i < kMaxNewtonIterations; i++) { + x2 = SampleCurveX(t2) - x; + if (fabs(x2) < newton_epsilon) + return t2; + d2 = SampleCurveDerivativeX(t2); + if (fabs(d2) < kBezierEpsilon) + break; + t2 = t2 - x2 / d2; + } + if (fabs(x2) < epsilon) + return t2; + + // Fall back to the bisection method for reliability. + while (t0 < t1) { + x2 = SampleCurveX(t2); + if (fabs(x2 - x) < epsilon) + return t2; + if (x > x2) + t0 = t2; + else + t1 = t2; + t2 = (t1 + t0) * .5; + } + + // Failure. + return t2; +} + +double CubicBezier::Solve(double x) const { + return SolveWithEpsilon(x, kBezierEpsilon); +} + +double CubicBezier::SlopeWithEpsilon(double x, double epsilon) const { + x = base::clamp(x, 0.0, 1.0); + double t = SolveCurveX(x, epsilon); + double dx = SampleCurveDerivativeX(t); + double dy = SampleCurveDerivativeY(t); + return dy / dx; +} + +double CubicBezier::Slope(double x) const { + return SlopeWithEpsilon(x, kBezierEpsilon); +} + +double CubicBezier::GetX1() const { + return cx_ / 3.0; +} + +double CubicBezier::GetY1() const { + return cy_ / 3.0; +} + +double CubicBezier::GetX2() const { + return (bx_ + cx_) / 3.0 + GetX1(); +} + +double CubicBezier::GetY2() const { + return (by_ + cy_) / 3.0 + GetY1(); +} + +} // namespace gfx diff --git a/geometry/cubic_bezier.h b/geometry/cubic_bezier.h new file mode 100644 index 000000000000..5709888aa92a --- /dev/null +++ b/geometry/cubic_bezier.h @@ -0,0 +1,107 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_CUBIC_BEZIER_H_ +#define UI_GFX_GEOMETRY_CUBIC_BEZIER_H_ + +#include "base/macros.h" +#include "ui/gfx/geometry/geometry_export.h" + +namespace gfx { + +#define CUBIC_BEZIER_SPLINE_SAMPLES 11 + +class GEOMETRY_EXPORT CubicBezier { + public: + CubicBezier(double p1x, double p1y, double p2x, double p2y); + CubicBezier(const CubicBezier& other); + + CubicBezier& operator=(const CubicBezier&) = delete; + + double SampleCurveX(double t) const { + // `ax t^3 + bx t^2 + cx t' expanded using Horner's rule. + return ((ax_ * t + bx_) * t + cx_) * t; + } + + double SampleCurveY(double t) const { + return ((ay_ * t + by_) * t + cy_) * t; + } + + double SampleCurveDerivativeX(double t) const { + return (3.0 * ax_ * t + 2.0 * bx_) * t + cx_; + } + + double SampleCurveDerivativeY(double t) const { + return (3.0 * ay_ * t + 2.0 * by_) * t + cy_; + } + + static double GetDefaultEpsilon(); + + // Given an x value, find a parametric value it came from. + // x must be in [0, 1] range. Doesn't use gradients. + double SolveCurveX(double x, double epsilon) const; + + // Evaluates y at the given x with default epsilon. + double Solve(double x) const; + // Evaluates y at the given x. The epsilon parameter provides a hint as to the + // required accuracy and is not guaranteed. Uses gradients if x is + // out of [0, 1] range. + double SolveWithEpsilon(double x, double epsilon) const { + if (x < 0.0) + return 0.0 + start_gradient_ * x; + if (x > 1.0) + return 1.0 + end_gradient_ * (x - 1.0); + return SampleCurveY(SolveCurveX(x, epsilon)); + } + + // Returns an approximation of dy/dx at the given x with default epsilon. + double Slope(double x) const; + // Returns an approximation of dy/dx at the given x. + // Clamps x to range [0, 1]. + double SlopeWithEpsilon(double x, double epsilon) const; + + // These getters are used rarely. We reverse compute them from coefficients. + // See CubicBezier::InitCoefficients. The speed has been traded for memory. + double GetX1() const; + double GetY1() const; + double GetX2() const; + double GetY2() const; + + // Gets the bezier's minimum y value in the interval [0, 1]. + double range_min() const { return range_min_; } + // Gets the bezier's maximum y value in the interval [0, 1]. + double range_max() const { return range_max_; } + + private: + void InitCoefficients(double p1x, double p1y, double p2x, double p2y); + void InitGradients(double p1x, double p1y, double p2x, double p2y); + void InitRange(double p1y, double p2y); + void InitSpline(); + + double ax_; + double bx_; + double cx_; + + double ay_; + double by_; + double cy_; + + double start_gradient_; + double end_gradient_; + + double range_min_; + double range_max_; + + double spline_samples_[CUBIC_BEZIER_SPLINE_SAMPLES]; + +#ifndef NDEBUG + // Guard against attempted to solve for t given x in the event that the curve + // may have multiple values for t for some values of x in [0, 1]. + bool monotonically_increasing_; +#endif +}; + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_CUBIC_BEZIER_H_ diff --git a/geometry/cubic_bezier_unittest.cc b/geometry/cubic_bezier_unittest.cc new file mode 100644 index 000000000000..bf86f417b7ec --- /dev/null +++ b/geometry/cubic_bezier_unittest.cc @@ -0,0 +1,273 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/cubic_bezier.h" + +#include + +#include "testing/gtest/include/gtest/gtest.h" + +namespace gfx { +namespace { + +TEST(CubicBezierTest, Basic) { + CubicBezier function(0.25, 0.0, 0.75, 1.0); + + double epsilon = 0.00015; + + EXPECT_NEAR(function.Solve(0), 0, epsilon); + EXPECT_NEAR(function.Solve(0.05), 0.01136, epsilon); + EXPECT_NEAR(function.Solve(0.1), 0.03978, epsilon); + EXPECT_NEAR(function.Solve(0.15), 0.079780, epsilon); + EXPECT_NEAR(function.Solve(0.2), 0.12803, epsilon); + EXPECT_NEAR(function.Solve(0.25), 0.18235, epsilon); + EXPECT_NEAR(function.Solve(0.3), 0.24115, epsilon); + EXPECT_NEAR(function.Solve(0.35), 0.30323, epsilon); + EXPECT_NEAR(function.Solve(0.4), 0.36761, epsilon); + EXPECT_NEAR(function.Solve(0.45), 0.43345, epsilon); + EXPECT_NEAR(function.Solve(0.5), 0.5, epsilon); + EXPECT_NEAR(function.Solve(0.6), 0.63238, epsilon); + EXPECT_NEAR(function.Solve(0.65), 0.69676, epsilon); + EXPECT_NEAR(function.Solve(0.7), 0.75884, epsilon); + EXPECT_NEAR(function.Solve(0.75), 0.81764, epsilon); + EXPECT_NEAR(function.Solve(0.8), 0.87196, epsilon); + EXPECT_NEAR(function.Solve(0.85), 0.92021, epsilon); + EXPECT_NEAR(function.Solve(0.9), 0.96021, epsilon); + EXPECT_NEAR(function.Solve(0.95), 0.98863, epsilon); + EXPECT_NEAR(function.Solve(1), 1, epsilon); + + CubicBezier basic_use(0.5, 1.0, 0.5, 1.0); + EXPECT_EQ(0.875, basic_use.Solve(0.5)); + + CubicBezier overshoot(0.5, 2.0, 0.5, 2.0); + EXPECT_EQ(1.625, overshoot.Solve(0.5)); + + CubicBezier undershoot(0.5, -1.0, 0.5, -1.0); + EXPECT_EQ(-0.625, undershoot.Solve(0.5)); +} + +// Tests that solving the bezier works with knots with y not in (0, 1). +TEST(CubicBezierTest, UnclampedYValues) { + CubicBezier function(0.5, -1.0, 0.5, 2.0); + + double epsilon = 0.00015; + + EXPECT_NEAR(function.Solve(0.0), 0.0, epsilon); + EXPECT_NEAR(function.Solve(0.05), -0.08954, epsilon); + EXPECT_NEAR(function.Solve(0.1), -0.15613, epsilon); + EXPECT_NEAR(function.Solve(0.15), -0.19641, epsilon); + EXPECT_NEAR(function.Solve(0.2), -0.20651, epsilon); + EXPECT_NEAR(function.Solve(0.25), -0.18232, epsilon); + EXPECT_NEAR(function.Solve(0.3), -0.11992, epsilon); + EXPECT_NEAR(function.Solve(0.35), -0.01672, epsilon); + EXPECT_NEAR(function.Solve(0.4), 0.12660, epsilon); + EXPECT_NEAR(function.Solve(0.45), 0.30349, epsilon); + EXPECT_NEAR(function.Solve(0.5), 0.50000, epsilon); + EXPECT_NEAR(function.Solve(0.55), 0.69651, epsilon); + EXPECT_NEAR(function.Solve(0.6), 0.87340, epsilon); + EXPECT_NEAR(function.Solve(0.65), 1.01672, epsilon); + EXPECT_NEAR(function.Solve(0.7), 1.11992, epsilon); + EXPECT_NEAR(function.Solve(0.75), 1.18232, epsilon); + EXPECT_NEAR(function.Solve(0.8), 1.20651, epsilon); + EXPECT_NEAR(function.Solve(0.85), 1.19641, epsilon); + EXPECT_NEAR(function.Solve(0.9), 1.15613, epsilon); + EXPECT_NEAR(function.Solve(0.95), 1.08954, epsilon); + EXPECT_NEAR(function.Solve(1.0), 1.0, epsilon); +} + +TEST(CubicBezierTest, Range) { + double epsilon = 0.00015; + + // Derivative is a constant. + std::unique_ptr function = + std::make_unique(0.25, (1.0 / 3.0), 0.75, (2.0 / 3.0)); + EXPECT_EQ(0, function->range_min()); + EXPECT_EQ(1, function->range_max()); + + // Derivative is linear. + function = std::make_unique(0.25, -0.5, 0.75, (-1.0 / 6.0)); + EXPECT_NEAR(function->range_min(), -0.225, epsilon); + EXPECT_EQ(1, function->range_max()); + + // Derivative has no real roots. + function = std::make_unique(0.25, 0.25, 0.75, 0.5); + EXPECT_EQ(0, function->range_min()); + EXPECT_EQ(1, function->range_max()); + + // Derivative has exactly one real root. + function = std::make_unique(0.0, 1.0, 1.0, 0.0); + EXPECT_EQ(0, function->range_min()); + EXPECT_EQ(1, function->range_max()); + + // Derivative has one root < 0 and one root > 1. + function = std::make_unique(0.25, 0.1, 0.75, 0.9); + EXPECT_EQ(0, function->range_min()); + EXPECT_EQ(1, function->range_max()); + + // Derivative has two roots in [0,1]. + function = std::make_unique(0.25, 2.5, 0.75, 0.5); + EXPECT_EQ(0, function->range_min()); + EXPECT_NEAR(function->range_max(), 1.28818, epsilon); + function = std::make_unique(0.25, 0.5, 0.75, -1.5); + EXPECT_NEAR(function->range_min(), -0.28818, epsilon); + EXPECT_EQ(1, function->range_max()); + + // Derivative has one root < 0 and one root in [0,1]. + function = std::make_unique(0.25, 0.1, 0.75, 1.5); + EXPECT_EQ(0, function->range_min()); + EXPECT_NEAR(function->range_max(), 1.10755, epsilon); + + // Derivative has one root in [0,1] and one root > 1. + function = std::make_unique(0.25, -0.5, 0.75, 0.9); + EXPECT_NEAR(function->range_min(), -0.10755, epsilon); + EXPECT_EQ(1, function->range_max()); + + // Derivative has two roots < 0. + function = std::make_unique(0.25, 0.3, 0.75, 0.633); + EXPECT_EQ(0, function->range_min()); + EXPECT_EQ(1, function->range_max()); + + // Derivative has two roots > 1. + function = std::make_unique(0.25, 0.367, 0.75, 0.7); + EXPECT_EQ(0.f, function->range_min()); + EXPECT_EQ(1.f, function->range_max()); +} + +TEST(CubicBezierTest, Slope) { + CubicBezier function(0.25, 0.0, 0.75, 1.0); + + double epsilon = 0.00015; + + EXPECT_NEAR(function.Slope(-0.1), 0, epsilon); + EXPECT_NEAR(function.Slope(0), 0, epsilon); + EXPECT_NEAR(function.Slope(0.05), 0.42170, epsilon); + EXPECT_NEAR(function.Slope(0.1), 0.69778, epsilon); + EXPECT_NEAR(function.Slope(0.15), 0.89121, epsilon); + EXPECT_NEAR(function.Slope(0.2), 1.03184, epsilon); + EXPECT_NEAR(function.Slope(0.25), 1.13576, epsilon); + EXPECT_NEAR(function.Slope(0.3), 1.21239, epsilon); + EXPECT_NEAR(function.Slope(0.35), 1.26751, epsilon); + EXPECT_NEAR(function.Slope(0.4), 1.30474, epsilon); + EXPECT_NEAR(function.Slope(0.45), 1.32628, epsilon); + EXPECT_NEAR(function.Slope(0.5), 1.33333, epsilon); + EXPECT_NEAR(function.Slope(0.55), 1.32628, epsilon); + EXPECT_NEAR(function.Slope(0.6), 1.30474, epsilon); + EXPECT_NEAR(function.Slope(0.65), 1.26751, epsilon); + EXPECT_NEAR(function.Slope(0.7), 1.21239, epsilon); + EXPECT_NEAR(function.Slope(0.75), 1.13576, epsilon); + EXPECT_NEAR(function.Slope(0.8), 1.03184, epsilon); + EXPECT_NEAR(function.Slope(0.85), 0.89121, epsilon); + EXPECT_NEAR(function.Slope(0.9), 0.69778, epsilon); + EXPECT_NEAR(function.Slope(0.95), 0.42170, epsilon); + EXPECT_NEAR(function.Slope(1), 0, epsilon); + EXPECT_NEAR(function.Slope(1.1), 0, epsilon); +} + +TEST(CubicBezierTest, InputOutOfRange) { + CubicBezier simple(0.5, 1.0, 0.5, 1.0); + EXPECT_EQ(-2.0, simple.Solve(-1.0)); + EXPECT_EQ(1.0, simple.Solve(2.0)); + + CubicBezier at_edge_of_range(0.5, 1.0, 0.5, 1.0); + EXPECT_EQ(0.0, at_edge_of_range.Solve(0.0)); + EXPECT_EQ(1.0, at_edge_of_range.Solve(1.0)); + + CubicBezier large_epsilon(0.5, 1.0, 0.5, 1.0); + EXPECT_EQ(-2.0, large_epsilon.SolveWithEpsilon(-1.0, 1.0)); + EXPECT_EQ(1.0, large_epsilon.SolveWithEpsilon(2.0, 1.0)); + + CubicBezier coincident_endpoints(0.0, 0.0, 1.0, 1.0); + EXPECT_EQ(-1.0, coincident_endpoints.Solve(-1.0)); + EXPECT_EQ(2.0, coincident_endpoints.Solve(2.0)); + + CubicBezier vertical_gradient(0.0, 1.0, 1.0, 0.0); + EXPECT_EQ(0.0, vertical_gradient.Solve(-1.0)); + EXPECT_EQ(1.0, vertical_gradient.Solve(2.0)); + + CubicBezier vertical_trailing_gradient(0.5, 0.0, 1.0, 0.5); + EXPECT_EQ(0.0, vertical_trailing_gradient.Solve(-1.0)); + EXPECT_EQ(1.0, vertical_trailing_gradient.Solve(2.0)); + + CubicBezier distinct_endpoints(0.1, 0.2, 0.8, 0.8); + EXPECT_EQ(-2.0, distinct_endpoints.Solve(-1.0)); + EXPECT_EQ(2.0, distinct_endpoints.Solve(2.0)); + + CubicBezier coincident_leading_endpoint(0.0, 0.0, 0.5, 1.0); + EXPECT_EQ(-2.0, coincident_leading_endpoint.Solve(-1.0)); + EXPECT_EQ(1.0, coincident_leading_endpoint.Solve(2.0)); + + CubicBezier coincident_trailing_endpoint(1.0, 0.5, 1.0, 1.0); + EXPECT_EQ(-0.5, coincident_trailing_endpoint.Solve(-1.0)); + EXPECT_EQ(1.0, coincident_trailing_endpoint.Solve(2.0)); + + // Two special cases with three coincident points. Both are equivalent to + // linear. + CubicBezier all_zeros(0.0, 0.0, 0.0, 0.0); + EXPECT_EQ(-1.0, all_zeros.Solve(-1.0)); + EXPECT_EQ(2.0, all_zeros.Solve(2.0)); + + CubicBezier all_ones(1.0, 1.0, 1.0, 1.0); + EXPECT_EQ(-1.0, all_ones.Solve(-1.0)); + EXPECT_EQ(2.0, all_ones.Solve(2.0)); +} + +TEST(CubicBezierTest, GetPoints) { + double epsilon = 0.00015; + + CubicBezier cubic1(0.1, 0.2, 0.8, 0.9); + EXPECT_NEAR(0.1, cubic1.GetX1(), epsilon); + EXPECT_NEAR(0.2, cubic1.GetY1(), epsilon); + EXPECT_NEAR(0.8, cubic1.GetX2(), epsilon); + EXPECT_NEAR(0.9, cubic1.GetY2(), epsilon); + + CubicBezier cubic_zero(0, 0, 0, 0); + EXPECT_NEAR(0, cubic_zero.GetX1(), epsilon); + EXPECT_NEAR(0, cubic_zero.GetY1(), epsilon); + EXPECT_NEAR(0, cubic_zero.GetX2(), epsilon); + EXPECT_NEAR(0, cubic_zero.GetY2(), epsilon); + + CubicBezier cubic_one(1, 1, 1, 1); + EXPECT_NEAR(1, cubic_one.GetX1(), epsilon); + EXPECT_NEAR(1, cubic_one.GetY1(), epsilon); + EXPECT_NEAR(1, cubic_one.GetX2(), epsilon); + EXPECT_NEAR(1, cubic_one.GetY2(), epsilon); + + CubicBezier cubic_oor(-0.5, -1.5, 1.5, -1.6); + EXPECT_NEAR(-0.5, cubic_oor.GetX1(), epsilon); + EXPECT_NEAR(-1.5, cubic_oor.GetY1(), epsilon); + EXPECT_NEAR(1.5, cubic_oor.GetX2(), epsilon); + EXPECT_NEAR(-1.6, cubic_oor.GetY2(), epsilon); +} + +void validateSolver(const CubicBezier& cubic_bezier) { + const double epsilon = 1e-7; + const double precision = 1e-5; + for (double t = 0; t <= 1; t += 0.05) { + double x = cubic_bezier.SampleCurveX(t); + double root = cubic_bezier.SolveCurveX(x, epsilon); + EXPECT_NEAR(t, root, precision); + } +} + +TEST(CubicBezierTest, CommonEasingFunctions) { + validateSolver(CubicBezier(0.25, 0.1, 0.25, 1)); // ease + validateSolver(CubicBezier(0.42, 0, 1, 1)); // ease-in + validateSolver(CubicBezier(0, 0, 0.58, 1)); // ease-out + validateSolver(CubicBezier(0.42, 0, 0.58, 1)); // ease-in-out +} + +TEST(CubicBezierTest, LinearEquivalentBeziers) { + validateSolver(CubicBezier(0.0, 0.0, 0.0, 0.0)); + validateSolver(CubicBezier(1.0, 1.0, 1.0, 1.0)); +} + +TEST(CubicBezierTest, ControlPointsOutsideUnitSquare) { + validateSolver(CubicBezier(0.3, 1.5, 0.8, 1.5)); + validateSolver(CubicBezier(0.4, -0.8, 0.7, 1.7)); + validateSolver(CubicBezier(0.7, -2.0, 1.0, -1.5)); + validateSolver(CubicBezier(0, 4, 1, -3)); +} + +} // namespace +} // namespace gfx diff --git a/geometry/dip_util.cc b/geometry/dip_util.cc new file mode 100644 index 000000000000..11a15f740401 --- /dev/null +++ b/geometry/dip_util.cc @@ -0,0 +1,169 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/dip_util.h" + +#include "base/numerics/safe_conversions.h" +#include "build/build_config.h" +#include "ui/gfx/geometry/insets.h" +#include "ui/gfx/geometry/insets_f.h" +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/point_f.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/rect_conversions.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/geometry/size_conversions.h" + +namespace gfx { + +#if defined(OS_MAC) +// Returns true if the floating point value is holding an integer, modulo +// floating point error. The value `f` can be safely converted to its integer +// form with base::ClampRound(). +static bool IsIntegerInFloat(float f) { + return std::abs(f - base::ClampRound(f)) < 0.01f; +} +#endif + +PointF ConvertPointToDips(const Point& point_in_pixels, + float device_scale_factor) { +#if defined(OS_MAC) + // Device scale factor on MacOSX is always an integer. + DCHECK(IsIntegerInFloat(device_scale_factor)); +#endif + return ScalePoint(PointF(point_in_pixels), 1.f / device_scale_factor); +} + +PointF ConvertPointToDips(const PointF& point_in_pixels, + float device_scale_factor) { +#if defined(OS_MAC) + // Device scale factor on MacOSX is always an integer. + DCHECK(IsIntegerInFloat(device_scale_factor)); +#endif + return ScalePoint(point_in_pixels, 1.f / device_scale_factor); +} + +PointF ConvertPointToPixels(const Point& point_in_dips, + float device_scale_factor) { +#if defined(OS_MAC) + // Device scale factor on MacOSX is always an integer. + DCHECK(IsIntegerInFloat(device_scale_factor)); +#endif + return ScalePoint(PointF(point_in_dips), device_scale_factor); +} + +PointF ConvertPointToPixels(const PointF& point_in_dips, + float device_scale_factor) { +#if defined(OS_MAC) + // Device scale factor on MacOSX is always an integer. + DCHECK(IsIntegerInFloat(device_scale_factor)); +#endif + return ScalePoint(point_in_dips, device_scale_factor); +} + +SizeF ConvertSizeToDips(const Size& size_in_pixels, float device_scale_factor) { +#if defined(OS_MAC) + // Device scale factor on MacOSX is always an integer. + DCHECK(IsIntegerInFloat(device_scale_factor)); +#endif + return ScaleSize(SizeF(size_in_pixels), 1.f / device_scale_factor); +} + +SizeF ConvertSizeToDips(const SizeF& size_in_pixels, + float device_scale_factor) { +#if defined(OS_MAC) + // Device scale factor on MacOSX is always an integer. + DCHECK(IsIntegerInFloat(device_scale_factor)); +#endif + return ScaleSize(size_in_pixels, 1.f / device_scale_factor); +} + +SizeF ConvertSizeToPixels(const Size& size_in_dips, float device_scale_factor) { +#if defined(OS_MAC) + // Device scale factor on MacOSX is always an integer. + DCHECK(IsIntegerInFloat(device_scale_factor)); +#endif + return ScaleSize(SizeF(size_in_dips), device_scale_factor); +} + +SizeF ConvertSizeToPixels(const SizeF& size_in_dips, + float device_scale_factor) { +#if defined(OS_MAC) + // Device scale factor on MacOSX is always an integer. + DCHECK(IsIntegerInFloat(device_scale_factor)); +#endif + return ScaleSize(size_in_dips, device_scale_factor); +} + +RectF ConvertRectToDips(const Rect& rect_in_pixels, float device_scale_factor) { +#if defined(OS_MAC) + // Device scale factor on MacOSX is always an integer. + DCHECK(IsIntegerInFloat(device_scale_factor)); +#endif + return ScaleRect(RectF(rect_in_pixels), 1.f / device_scale_factor); +} + +RectF ConvertRectToDips(const RectF& rect_in_pixels, + float device_scale_factor) { +#if defined(OS_MAC) + // Device scale factor on MacOSX is always an integer. + DCHECK(IsIntegerInFloat(device_scale_factor)); +#endif + return ScaleRect(rect_in_pixels, 1.f / device_scale_factor); +} + +RectF ConvertRectToPixels(const Rect& rect_in_dips, float device_scale_factor) { +#if defined(OS_MAC) + // Device scale factor on MacOSX is always an integer. + DCHECK(IsIntegerInFloat(device_scale_factor)); +#endif + return ScaleRect(RectF(rect_in_dips), device_scale_factor); +} + +RectF ConvertRectToPixels(const RectF& rect_in_dips, + float device_scale_factor) { +#if defined(OS_MAC) + // Device scale factor on MacOSX is always an integer. + DCHECK(IsIntegerInFloat(device_scale_factor)); +#endif + return ScaleRect(rect_in_dips, device_scale_factor); +} + +InsetsF ConvertInsetsToDips(const gfx::Insets& insets_in_pixels, + float device_scale_factor) { +#if defined(OS_MAC) + // Device scale factor on MacOSX is always an integer. + DCHECK(IsIntegerInFloat(device_scale_factor)); +#endif + return ScaleInsets(InsetsF(insets_in_pixels), 1.f / device_scale_factor); +} + +InsetsF ConvertInsetsToDips(const gfx::InsetsF& insets_in_pixels, + float device_scale_factor) { +#if defined(OS_MAC) + // Device scale factor on MacOSX is always an integer. + DCHECK(IsIntegerInFloat(device_scale_factor)); +#endif + return ScaleInsets(insets_in_pixels, 1.f / device_scale_factor); +} + +InsetsF ConvertInsetsToPixels(const gfx::Insets& insets_in_dips, + float device_scale_factor) { +#if defined(OS_MAC) + // Device scale factor on MacOSX is always an integer. + DCHECK(IsIntegerInFloat(device_scale_factor)); +#endif + return ScaleInsets(InsetsF(insets_in_dips), device_scale_factor); +} + +InsetsF ConvertInsetsToPixels(const gfx::InsetsF& insets_in_dips, + float device_scale_factor) { +#if defined(OS_MAC) + // Device scale factor on MacOSX is always an integer. + DCHECK(IsIntegerInFloat(device_scale_factor)); +#endif + return ScaleInsets(insets_in_dips, device_scale_factor); +} + +} // namespace gfx diff --git a/geometry/dip_util.h b/geometry/dip_util.h new file mode 100644 index 000000000000..cc542a833bf8 --- /dev/null +++ b/geometry/dip_util.h @@ -0,0 +1,83 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_DIP_UTIL_H_ +#define UI_GFX_GEOMETRY_DIP_UTIL_H_ + +#include "ui/gfx/geometry/geometry_export.h" + +namespace gfx { + +class Insets; +class InsetsF; +class Point; +class PointF; +class Rect; +class RectF; +class Size; +class SizeF; + +// This file contains helper functions to move between DIPs (device-independent +// pixels) and physical pixels, by multiplying or dividing by device scale +// factor. These help show the intent of the caller by naming the operation, +// instead of directly performing a scale operation. More complicated +// transformations between coordinate spaces than DIP<->physical pixels should +// be done via more explicit means. +// +// Note that functions that receive integer values will convert them to floating +// point values, which can itself be a lossy operation for large integers. The +// intention of these methods is to be used for UI values which are relatively +// small. + +GEOMETRY_EXPORT gfx::PointF ConvertPointToDips( + const gfx::Point& point_in_pixels, + float device_scale_factor); +GEOMETRY_EXPORT gfx::PointF ConvertPointToDips( + const gfx::PointF& point_in_pixels, + float device_scale_factor); + +GEOMETRY_EXPORT gfx::PointF ConvertPointToPixels( + const gfx::Point& point_in_dips, + float device_scale_factor); +GEOMETRY_EXPORT gfx::PointF ConvertPointToPixels( + const gfx::PointF& point_in_dips, + float device_scale_factor); + +GEOMETRY_EXPORT gfx::SizeF ConvertSizeToDips(const gfx::Size& size_in_pixels, + float device_scale_factor); +GEOMETRY_EXPORT gfx::SizeF ConvertSizeToDips(const gfx::SizeF& size_in_pixels, + float device_scale_factor); + +GEOMETRY_EXPORT gfx::SizeF ConvertSizeToPixels(const gfx::Size& size_in_dips, + float device_scale_factor); +GEOMETRY_EXPORT gfx::SizeF ConvertSizeToPixels(const gfx::SizeF& size_in_dips, + float device_scale_factor); + +GEOMETRY_EXPORT gfx::RectF ConvertRectToDips(const gfx::Rect& rect_in_pixels, + float device_scale_factor); +GEOMETRY_EXPORT gfx::RectF ConvertRectToDips(const gfx::RectF& rect_in_pixels, + float device_scale_factor); + +GEOMETRY_EXPORT gfx::RectF ConvertRectToPixels(const gfx::Rect& rect_in_dips, + float device_scale_factor); +GEOMETRY_EXPORT gfx::RectF ConvertRectToPixels(const gfx::RectF& rect_in_dips, + float device_scale_factor); + +GEOMETRY_EXPORT gfx::InsetsF ConvertInsetsToDips( + const gfx::Insets& insets_in_pixels, + float device_scale_factor); +GEOMETRY_EXPORT gfx::InsetsF ConvertInsetsToDips( + const gfx::InsetsF& insets_in_pixels, + float device_scale_factor); + +GEOMETRY_EXPORT gfx::InsetsF ConvertInsetsToPixels( + const gfx::Insets& insets_in_dips, + float device_scale_factor); +GEOMETRY_EXPORT gfx::InsetsF ConvertInsetsToPixels( + const gfx::InsetsF& insets_in_dips, + float device_scale_factor); + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_DIP_UTIL_H_ diff --git a/geometry/geometry_export.h b/geometry/geometry_export.h new file mode 100644 index 000000000000..5e687875eb96 --- /dev/null +++ b/geometry/geometry_export.h @@ -0,0 +1,29 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_GEOMETRY_EXPORT_H_ +#define UI_GFX_GEOMETRY_GEOMETRY_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(GEOMETRY_IMPLEMENTATION) +#define GEOMETRY_EXPORT __declspec(dllexport) +#else +#define GEOMETRY_EXPORT __declspec(dllimport) +#endif // defined(GEOMETRY_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(GEOMETRY_IMPLEMENTATION) +#define GEOMETRY_EXPORT __attribute__((visibility("default"))) +#else +#define GEOMETRY_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define GEOMETRY_EXPORT +#endif + +#endif // UI_GFX_GEOMETRY_GEOMETRY_EXPORT_H_ diff --git a/geometry/geometry_skia_export.h b/geometry/geometry_skia_export.h new file mode 100644 index 000000000000..b7cda339f79d --- /dev/null +++ b/geometry/geometry_skia_export.h @@ -0,0 +1,29 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_GEOMETRY_SKIA_EXPORT_H_ +#define UI_GFX_GEOMETRY_GEOMETRY_SKIA_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(GEOMETRY_SKIA_IMPLEMENTATION) +#define GEOMETRY_SKIA_EXPORT __declspec(dllexport) +#else +#define GEOMETRY_SKIA_EXPORT __declspec(dllimport) +#endif // defined(GEOMETRY_SKIA_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(GEOMETRY_SKIA_IMPLEMENTATION) +#define GEOMETRY_SKIA_EXPORT __attribute__((visibility("default"))) +#else +#define GEOMETRY_SKIA_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define GEOMETRY_SKIA_EXPORT +#endif + +#endif // UI_GFX_GEOMETRY_GEOMETRY_SKIA_EXPORT_H_ diff --git a/geometry/insets.cc b/geometry/insets.cc new file mode 100644 index 000000000000..0e706e1dc876 --- /dev/null +++ b/geometry/insets.cc @@ -0,0 +1,66 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/insets.h" + +#include "base/strings/stringprintf.h" +#include "ui/gfx/geometry/insets_conversions.h" +#include "ui/gfx/geometry/insets_f.h" +#include "ui/gfx/geometry/vector2d.h" + +namespace gfx { + +std::string Insets::ToString() const { + // Print members in the same order of the constructor parameters. + return base::StringPrintf("%d,%d,%d,%d", top(), left(), bottom(), right()); +} + +Insets Insets::Offset(const gfx::Vector2d& vector) const { + return gfx::Insets(base::ClampAdd(top(), vector.y()), + base::ClampAdd(left(), vector.x()), + base::ClampSub(bottom(), vector.y()), + base::ClampSub(right(), vector.x())); +} + +Insets ScaleToCeiledInsets(const Insets& insets, float x_scale, float y_scale) { + if (x_scale == 1.f && y_scale == 1.f) + return insets; + return ToCeiledInsets(ScaleInsets(gfx::InsetsF(insets), x_scale, y_scale)); +} + +Insets ScaleToCeiledInsets(const Insets& insets, float scale) { + if (scale == 1.f) + return insets; + return ToCeiledInsets(ScaleInsets(gfx::InsetsF(insets), scale)); +} + +Insets ScaleToFlooredInsets(const Insets& insets, + float x_scale, + float y_scale) { + if (x_scale == 1.f && y_scale == 1.f) + return insets; + return ToFlooredInsets(ScaleInsets(gfx::InsetsF(insets), x_scale, y_scale)); +} + +Insets ScaleToFlooredInsets(const Insets& insets, float scale) { + if (scale == 1.f) + return insets; + return ToFlooredInsets(ScaleInsets(gfx::InsetsF(insets), scale)); +} + +Insets ScaleToRoundedInsets(const Insets& insets, + float x_scale, + float y_scale) { + if (x_scale == 1.f && y_scale == 1.f) + return insets; + return ToRoundedInsets(ScaleInsets(gfx::InsetsF(insets), x_scale, y_scale)); +} + +Insets ScaleToRoundedInsets(const Insets& insets, float scale) { + if (scale == 1.f) + return insets; + return ToRoundedInsets(ScaleInsets(gfx::InsetsF(insets), scale)); +} + +} // namespace gfx diff --git a/geometry/insets.h b/geometry/insets.h new file mode 100644 index 000000000000..9d83dca0f300 --- /dev/null +++ b/geometry/insets.h @@ -0,0 +1,199 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_INSETS_H_ +#define UI_GFX_GEOMETRY_INSETS_H_ + +#include + +#include "base/numerics/clamped_math.h" +#include "ui/gfx/geometry/geometry_export.h" +#include "ui/gfx/geometry/insets_f.h" +#include "ui/gfx/geometry/size.h" + +namespace gfx { + +class Vector2d; + +// Represents the widths of the four borders or margins of an unspecified +// rectangle. An Insets stores the thickness of the top, left, bottom and right +// edges, without storing the actual size and position of the rectangle itself. +// +// This can be used to represent a space within a rectangle, by "shrinking" the +// rectangle by the inset amount on all four sides. Alternatively, it can +// represent a border that has a different thickness on each side. +class GEOMETRY_EXPORT Insets { + public: + constexpr Insets() : top_(0), left_(0), bottom_(0), right_(0) {} + constexpr explicit Insets(int all) + : top_(all), + left_(all), + bottom_(GetClampedValue(all, all)), + right_(GetClampedValue(all, all)) {} + constexpr explicit Insets(int vertical, int horizontal) + : top_(vertical), + left_(horizontal), + bottom_(GetClampedValue(vertical, vertical)), + right_(GetClampedValue(horizontal, horizontal)) {} + constexpr Insets(int top, int left, int bottom, int right) + : top_(top), + left_(left), + bottom_(GetClampedValue(top, bottom)), + right_(GetClampedValue(left, right)) {} + + constexpr int top() const { return top_; } + constexpr int left() const { return left_; } + constexpr int bottom() const { return bottom_; } + constexpr int right() const { return right_; } + + // Returns the total width taken up by the insets, which is the sum of the + // left and right insets. + constexpr int width() const { return left_ + right_; } + + // Returns the total height taken up by the insets, which is the sum of the + // top and bottom insets. + constexpr int height() const { return top_ + bottom_; } + + // Returns the sum of the left and right insets as the width, the sum of the + // top and bottom insets as the height. + constexpr Size size() const { return Size(width(), height()); } + + // Returns true if the insets are empty. + bool IsEmpty() const { return width() == 0 && height() == 0; } + + void set_top(int top) { + top_ = top; + bottom_ = GetClampedValue(top_, bottom_); + } + void set_left(int left) { + left_ = left; + right_ = GetClampedValue(left_, right_); + } + void set_bottom(int bottom) { bottom_ = GetClampedValue(top_, bottom); } + void set_right(int right) { right_ = GetClampedValue(left_, right); } + + void Set(int top, int left, int bottom, int right) { + top_ = top; + left_ = left; + bottom_ = GetClampedValue(top_, bottom); + right_ = GetClampedValue(left_, right); + } + + bool operator==(const Insets& insets) const { + return top_ == insets.top_ && left_ == insets.left_ && + bottom_ == insets.bottom_ && right_ == insets.right_; + } + + bool operator!=(const Insets& insets) const { + return !(*this == insets); + } + + void operator+=(const Insets& insets) { + top_ = base::ClampAdd(top_, insets.top_); + left_ = base::ClampAdd(left_, insets.left_); + bottom_ = GetClampedValue(top_, base::ClampAdd(bottom_, insets.bottom_)); + right_ = GetClampedValue(left_, base::ClampAdd(right_, insets.right_)); + } + + void operator-=(const Insets& insets) { + top_ = base::ClampSub(top_, insets.top_); + left_ = base::ClampSub(left_, insets.left_); + bottom_ = GetClampedValue(top_, base::ClampSub(bottom_, insets.bottom_)); + right_ = GetClampedValue(left_, base::ClampSub(right_, insets.right_)); + } + + Insets operator-() const { + return Insets(-base::MakeClampedNum(top_), -base::MakeClampedNum(left_), + -base::MakeClampedNum(bottom_), + -base::MakeClampedNum(right_)); + } + + // Adjusts the vertical and horizontal dimensions by the values described in + // |vector|. Offsetting insets before applying to a rectangle would be + // equivalent to offseting the rectangle then applying the insets. + Insets Offset(const gfx::Vector2d& vector) const; + + operator InsetsF() const { + return InsetsF(static_cast(top()), static_cast(left()), + static_cast(bottom()), static_cast(right())); + } + + // Returns a string representation of the insets. + std::string ToString() const; + + private: + int top_; + int left_; + int bottom_; + int right_; + + // See ui/gfx/geometry/rect.h + // Returns true iff a+b would overflow max int. + static constexpr bool AddWouldOverflow(int a, int b) { + // In this function, GCC tries to make optimizations that would only work if + // max - a wouldn't overflow but it isn't smart enough to notice that a > 0. + // So cast everything to unsigned to avoid this. As it is guaranteed that + // max - a and b are both already positive, the cast is a noop. + // + // This is intended to be: a > 0 && max - a < b + return a > 0 && b > 0 && + static_cast(std::numeric_limits::max() - a) < + static_cast(b); + } + + // Returns true iff a+b would underflow min int. + static constexpr bool AddWouldUnderflow(int a, int b) { + return a < 0 && b < 0 && std::numeric_limits::min() - a > b; + } + + // Clamp the right/bottom to avoid integer over/underflow in width() and + // height(). This returns the right/bottom given a top_or_left and a + // bottom_or_right. + // TODO(enne): this should probably use base::ClampAdd, but that + // function is not a constexpr. + static constexpr int GetClampedValue(int top_or_left, int bottom_or_right) { + if (AddWouldOverflow(top_or_left, bottom_or_right)) { + return std::numeric_limits::max() - top_or_left; + } else if (AddWouldUnderflow(top_or_left, bottom_or_right)) { + // If |top_or_left| and |bottom_or_right| are both negative, + // adds |top_or_left| to prevent underflow by subtracting it. + return std::numeric_limits::min() - top_or_left; + } else { + return bottom_or_right; + } + } +}; + +// This is declared here for use in gtest-based unit tests but is defined in +// the //ui/gfx:test_support target. Depend on that to use this in your unit +// test. This should not be used in production code - call ToString() instead. +void PrintTo(const Insets& point, ::std::ostream* os); + +inline Insets operator+(Insets lhs, const Insets& rhs) { + lhs += rhs; + return lhs; +} + +inline Insets operator-(Insets lhs, const Insets& rhs) { + lhs -= rhs; + return lhs; +} + +// Helper methods to scale a gfx::Insets to a new gfx::Insets. +GEOMETRY_EXPORT Insets ScaleToCeiledInsets(const Insets& insets, + float x_scale, + float y_scale); +GEOMETRY_EXPORT Insets ScaleToCeiledInsets(const Insets& insets, float scale); +GEOMETRY_EXPORT Insets ScaleToFlooredInsets(const Insets& insets, + float x_scale, + float y_scale); +GEOMETRY_EXPORT Insets ScaleToFlooredInsets(const Insets& insets, float scale); +GEOMETRY_EXPORT Insets ScaleToRoundedInsets(const Insets& insets, + float x_scale, + float y_scale); +GEOMETRY_EXPORT Insets ScaleToRoundedInsets(const Insets& insets, float scale); + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_INSETS_H_ diff --git a/geometry/insets_conversions.cc b/geometry/insets_conversions.cc new file mode 100644 index 000000000000..e41a600b5e55 --- /dev/null +++ b/geometry/insets_conversions.cc @@ -0,0 +1,31 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/insets_conversions.h" + +#include "base/numerics/safe_conversions.h" +#include "ui/gfx/geometry/insets.h" +#include "ui/gfx/geometry/insets_f.h" + +namespace gfx { + +Insets ToFlooredInsets(const InsetsF& insets) { + return Insets(base::ClampFloor(insets.top()), base::ClampFloor(insets.left()), + base::ClampFloor(insets.bottom()), + base::ClampFloor(insets.right())); +} + +Insets ToCeiledInsets(const InsetsF& insets) { + return Insets(base::ClampCeil(insets.top()), base::ClampCeil(insets.left()), + base::ClampCeil(insets.bottom()), + base::ClampCeil(insets.right())); +} + +Insets ToRoundedInsets(const InsetsF& insets) { + return Insets(base::ClampRound(insets.top()), base::ClampRound(insets.left()), + base::ClampRound(insets.bottom()), + base::ClampRound(insets.right())); +} + +} // namespace gfx diff --git a/geometry/insets_conversions.h b/geometry/insets_conversions.h new file mode 100644 index 000000000000..613515046c7d --- /dev/null +++ b/geometry/insets_conversions.h @@ -0,0 +1,25 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_INSETS_CONVERSIONS_H_ +#define UI_GFX_GEOMETRY_INSETS_CONVERSIONS_H_ + +#include "ui/gfx/geometry/geometry_export.h" + +namespace gfx { +class Insets; +class InsetsF; + +// Returns an Insets with each component from the input InsetsF floored. +GEOMETRY_EXPORT Insets ToFlooredInsets(const InsetsF& insets); + +// Returns an Insets with each component from the input InsetsF ceiled. +GEOMETRY_EXPORT Insets ToCeiledInsets(const InsetsF& insets); + +// Returns a Point with each component from the input PointF rounded. +GEOMETRY_EXPORT Insets ToRoundedInsets(const InsetsF& insets); + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_INSETS_CONVERSIONS_H_ diff --git a/geometry/insets_f.cc b/geometry/insets_f.cc new file mode 100644 index 000000000000..c1bc27ec5245 --- /dev/null +++ b/geometry/insets_f.cc @@ -0,0 +1,16 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/insets_f.h" + +#include "base/strings/stringprintf.h" + +namespace gfx { + +std::string InsetsF::ToString() const { + // Print members in the same order of the constructor parameters. + return base::StringPrintf("%f,%f,%f,%f", top(), left(), bottom(), right()); +} + +} // namespace gfx diff --git a/geometry/insets_f.h b/geometry/insets_f.h new file mode 100644 index 000000000000..3d9380cf4a0a --- /dev/null +++ b/geometry/insets_f.h @@ -0,0 +1,120 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_INSETS_F_H_ +#define UI_GFX_GEOMETRY_INSETS_F_H_ + +#include + +#include "ui/gfx/geometry/geometry_export.h" + +namespace gfx { + +// A floating point version of gfx::Insets. +class GEOMETRY_EXPORT InsetsF { + public: + constexpr InsetsF() : top_(0.f), left_(0.f), bottom_(0.f), right_(0.f) {} + constexpr explicit InsetsF(float all) + : top_(all), left_(all), bottom_(all), right_(all) {} + constexpr InsetsF(float vertical, float horizontal) + : top_(vertical), + left_(horizontal), + bottom_(vertical), + right_(horizontal) {} + constexpr InsetsF(float top, float left, float bottom, float right) + : top_(top), left_(left), bottom_(bottom), right_(right) {} + + constexpr float top() const { return top_; } + constexpr float left() const { return left_; } + constexpr float bottom() const { return bottom_; } + constexpr float right() const { return right_; } + + // Returns the total width taken up by the insets, which is the sum of the + // left and right insets. + constexpr float width() const { return left_ + right_; } + + // Returns the total height taken up by the insets, which is the sum of the + // top and bottom insets. + constexpr float height() const { return top_ + bottom_; } + + // Returns true if the insets are empty. + bool IsEmpty() const { return width() == 0.f && height() == 0.f; } + + void Set(float top, float left, float bottom, float right) { + top_ = top; + left_ = left; + bottom_ = bottom; + right_ = right; + } + + bool operator==(const InsetsF& insets) const { + return top_ == insets.top_ && left_ == insets.left_ && + bottom_ == insets.bottom_ && right_ == insets.right_; + } + + bool operator!=(const InsetsF& insets) const { + return !(*this == insets); + } + + void operator+=(const InsetsF& insets) { + top_ += insets.top_; + left_ += insets.left_; + bottom_ += insets.bottom_; + right_ += insets.right_; + } + + void operator-=(const InsetsF& insets) { + top_ -= insets.top_; + left_ -= insets.left_; + bottom_ -= insets.bottom_; + right_ -= insets.right_; + } + + InsetsF operator-() const { + return InsetsF(-top_, -left_, -bottom_, -right_); + } + + InsetsF Scale(float scale) const { + return InsetsF(scale * top(), scale * left(), scale * bottom(), + scale * right()); + } + + // Returns a string representation of the insets. + std::string ToString() const; + + private: + float top_; + float left_; + float bottom_; + float right_; +}; + +// This is declared here for use in gtest-based unit tests but is defined in +// the //ui/gfx:test_support target. Depend on that to use this in your unit +// test. This should not be used in production code - call ToString() instead. +void PrintTo(const InsetsF& point, ::std::ostream* os); + +inline InsetsF ScaleInsets(const InsetsF& i, float scale) { + return InsetsF(i.top() * scale, i.left() * scale, i.bottom() * scale, + i.right() * scale); +} + +inline InsetsF ScaleInsets(const InsetsF& i, float x_scale, float y_scale) { + return InsetsF(i.top() * y_scale, i.left() * x_scale, i.bottom() * y_scale, + i.right() * x_scale); +} + +inline InsetsF operator+(InsetsF lhs, const InsetsF& rhs) { + lhs += rhs; + return lhs; +} + +inline InsetsF operator-(InsetsF lhs, const InsetsF& rhs) { + lhs -= rhs; + return lhs; +} + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_INSETS_F_H_ diff --git a/geometry/insets_unittest.cc b/geometry/insets_unittest.cc new file mode 100644 index 000000000000..83ef9107ad57 --- /dev/null +++ b/geometry/insets_unittest.cc @@ -0,0 +1,336 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/insets.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/geometry/insets_f.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/geometry/vector2d.h" + +TEST(InsetsTest, InsetsDefault) { + gfx::Insets insets; + EXPECT_EQ(0, insets.top()); + EXPECT_EQ(0, insets.left()); + EXPECT_EQ(0, insets.bottom()); + EXPECT_EQ(0, insets.right()); + EXPECT_EQ(0, insets.width()); + EXPECT_EQ(0, insets.height()); + EXPECT_TRUE(insets.IsEmpty()); +} + +TEST(InsetsTest, Insets) { + gfx::Insets insets(1, 2, 3, 4); + EXPECT_EQ(1, insets.top()); + EXPECT_EQ(2, insets.left()); + EXPECT_EQ(3, insets.bottom()); + EXPECT_EQ(4, insets.right()); + EXPECT_EQ(6, insets.width()); // Left + right. + EXPECT_EQ(4, insets.height()); // Top + bottom. + EXPECT_FALSE(insets.IsEmpty()); +} + +TEST(InsetsTest, SetTop) { + gfx::Insets insets(1); + insets.set_top(2); + EXPECT_EQ(gfx::Insets(2, 1, 1, 1), insets); +} + +TEST(InsetsTest, SetBottom) { + gfx::Insets insets(1); + insets.set_bottom(2); + EXPECT_EQ(gfx::Insets(1, 1, 2, 1), insets); +} + +TEST(InsetsTest, SetLeft) { + gfx::Insets insets(1); + insets.set_left(2); + EXPECT_EQ(gfx::Insets(1, 2, 1, 1), insets); +} + +TEST(InsetsTest, SetRight) { + gfx::Insets insets(1); + insets.set_right(2); + EXPECT_EQ(gfx::Insets(1, 1, 1, 2), insets); +} + +TEST(InsetsTest, Set) { + gfx::Insets insets; + insets.Set(1, 2, 3, 4); + EXPECT_EQ(1, insets.top()); + EXPECT_EQ(2, insets.left()); + EXPECT_EQ(3, insets.bottom()); + EXPECT_EQ(4, insets.right()); +} + +TEST(InsetsTest, Operators) { + gfx::Insets insets; + insets.Set(1, 2, 3, 4); + insets += gfx::Insets(5, 6, 7, 8); + EXPECT_EQ(6, insets.top()); + EXPECT_EQ(8, insets.left()); + EXPECT_EQ(10, insets.bottom()); + EXPECT_EQ(12, insets.right()); + + insets -= gfx::Insets(-1, 0, 1, 2); + EXPECT_EQ(7, insets.top()); + EXPECT_EQ(8, insets.left()); + EXPECT_EQ(9, insets.bottom()); + EXPECT_EQ(10, insets.right()); + + insets = gfx::Insets(10, 10, 10, 10) + gfx::Insets(5, 5, 0, -20); + EXPECT_EQ(15, insets.top()); + EXPECT_EQ(15, insets.left()); + EXPECT_EQ(10, insets.bottom()); + EXPECT_EQ(-10, insets.right()); + + insets = gfx::Insets(10, 10, 10, 10) - gfx::Insets(5, 5, 0, -20); + EXPECT_EQ(5, insets.top()); + EXPECT_EQ(5, insets.left()); + EXPECT_EQ(10, insets.bottom()); + EXPECT_EQ(30, insets.right()); +} + +TEST(InsetsFTest, Operators) { + gfx::InsetsF insets; + insets.Set(1.f, 2.5f, 3.3f, 4.1f); + insets += gfx::InsetsF(5.8f, 6.7f, 7.6f, 8.5f); + EXPECT_FLOAT_EQ(6.8f, insets.top()); + EXPECT_FLOAT_EQ(9.2f, insets.left()); + EXPECT_FLOAT_EQ(10.9f, insets.bottom()); + EXPECT_FLOAT_EQ(12.6f, insets.right()); + + insets -= gfx::InsetsF(-1.f, 0, 1.1f, 2.2f); + EXPECT_FLOAT_EQ(7.8f, insets.top()); + EXPECT_FLOAT_EQ(9.2f, insets.left()); + EXPECT_FLOAT_EQ(9.8f, insets.bottom()); + EXPECT_FLOAT_EQ(10.4f, insets.right()); + + insets = gfx::InsetsF(10, 10.1f, 10.01f, 10.001f) + + gfx::InsetsF(5.5f, 5.f, 0, -20.2f); + EXPECT_FLOAT_EQ(15.5f, insets.top()); + EXPECT_FLOAT_EQ(15.1f, insets.left()); + EXPECT_FLOAT_EQ(10.01f, insets.bottom()); + EXPECT_FLOAT_EQ(-10.199f, insets.right()); + + insets = gfx::InsetsF(10, 10.1f, 10.01f, 10.001f) - + gfx::InsetsF(5.5f, 5.f, 0, -20.2f); + EXPECT_FLOAT_EQ(4.5f, insets.top()); + EXPECT_FLOAT_EQ(5.1f, insets.left()); + EXPECT_FLOAT_EQ(10.01f, insets.bottom()); + EXPECT_FLOAT_EQ(30.201f, insets.right()); +} + +TEST(InsetsTest, Equality) { + gfx::Insets insets1; + insets1.Set(1, 2, 3, 4); + gfx::Insets insets2; + // Test operator== and operator!=. + EXPECT_FALSE(insets1 == insets2); + EXPECT_TRUE(insets1 != insets2); + + insets2.Set(1, 2, 3, 4); + EXPECT_TRUE(insets1 == insets2); + EXPECT_FALSE(insets1 != insets2); +} + +TEST(InsetsTest, ToString) { + gfx::Insets insets(1, 2, 3, 4); + EXPECT_EQ("1,2,3,4", insets.ToString()); +} + +TEST(InsetsTest, Offset) { + const gfx::Insets insets(1, 2, 3, 4); + const gfx::Rect rect(5, 6, 7, 8); + const gfx::Vector2d vector(9, 10); + + // Whether you inset then offset the rect, offset then inset the rect, or + // offset the insets then apply to the rect, the outcome should be the same. + gfx::Rect inset_first = rect; + inset_first.Inset(insets); + inset_first.Offset(vector); + + gfx::Rect offset_first = rect; + offset_first.Offset(vector); + offset_first.Inset(insets); + + gfx::Rect inset_by_offset = rect; + inset_by_offset.Inset(insets.Offset(vector)); + + EXPECT_EQ(inset_first, offset_first); + EXPECT_EQ(inset_by_offset, inset_first); +} + +TEST(InsetsTest, Scale) { + gfx::Insets in(7, 5); + + gfx::InsetsF testf = gfx::ScaleInsets(in, 2.5f, 3.5f); + EXPECT_EQ(gfx::InsetsF(24.5f, 12.5f), testf); + testf = gfx::ScaleInsets(in, 2.5f); + EXPECT_EQ(gfx::InsetsF(17.5f, 12.5f), testf); + + gfx::Insets test = gfx::ScaleToFlooredInsets(in, 2.5f, 3.5f); + EXPECT_EQ(gfx::Insets(24, 12), test); + test = gfx::ScaleToFlooredInsets(in, 2.5f); + EXPECT_EQ(gfx::Insets(17, 12), test); + + test = gfx::ScaleToCeiledInsets(in, 2.5f, 3.5f); + EXPECT_EQ(gfx::Insets(25, 13), test); + test = gfx::ScaleToCeiledInsets(in, 2.5f); + EXPECT_EQ(gfx::Insets(18, 13), test); + + test = gfx::ScaleToRoundedInsets(in, 2.49f, 3.49f); + EXPECT_EQ(gfx::Insets(24, 12), test); + test = gfx::ScaleToRoundedInsets(in, 2.49f); + EXPECT_EQ(gfx::Insets(17, 12), test); + + test = gfx::ScaleToRoundedInsets(in, 2.5f, 3.5f); + EXPECT_EQ(gfx::Insets(25, 13), test); + test = gfx::ScaleToRoundedInsets(in, 2.5f); + EXPECT_EQ(gfx::Insets(18, 13), test); +} + +TEST(InsetsTest, ScaleNegative) { + gfx::Insets in(-7, -5); + + gfx::InsetsF testf = gfx::ScaleInsets(in, 2.5f, 3.5f); + EXPECT_EQ(gfx::InsetsF(-24.5f, -12.5f), testf); + testf = gfx::ScaleInsets(in, 2.5f); + EXPECT_EQ(gfx::InsetsF(-17.5f, -12.5f), testf); + + gfx::Insets test = gfx::ScaleToFlooredInsets(in, 2.5f, 3.5f); + EXPECT_EQ(gfx::Insets(-25, -13), test); + test = gfx::ScaleToFlooredInsets(in, 2.5f); + EXPECT_EQ(gfx::Insets(-18, -13), test); + + test = gfx::ScaleToCeiledInsets(in, 2.5f, 3.5f); + EXPECT_EQ(gfx::Insets(-24, -12), test); + test = gfx::ScaleToCeiledInsets(in, 2.5f); + EXPECT_EQ(gfx::Insets(-17, -12), test); + + test = gfx::ScaleToRoundedInsets(in, 2.49f, 3.49f); + EXPECT_EQ(gfx::Insets(-24, -12), test); + test = gfx::ScaleToRoundedInsets(in, 2.49f); + EXPECT_EQ(gfx::Insets(-17, -12), test); + + test = gfx::ScaleToRoundedInsets(in, 2.5f, 3.5f); + EXPECT_EQ(gfx::Insets(-25, -13), test); + test = gfx::ScaleToRoundedInsets(in, 2.5f); + EXPECT_EQ(gfx::Insets(-18, -13), test); +} + +TEST(InsetsTest, IntegerOverflow) { + constexpr int int_min = std::numeric_limits::min(); + constexpr int int_max = std::numeric_limits::max(); + + gfx::Insets width_height_test(int_max); + EXPECT_EQ(int_max, width_height_test.width()); + EXPECT_EQ(int_max, width_height_test.height()); + + gfx::Insets plus_test(int_max); + plus_test += gfx::Insets(int_max); + EXPECT_EQ(gfx::Insets(int_max), plus_test); + + gfx::Insets negation_test = -gfx::Insets(int_min); + EXPECT_EQ(gfx::Insets(int_max), negation_test); + + gfx::Insets scale_test(int_max); + scale_test = gfx::ScaleToRoundedInsets(scale_test, 2.f); + EXPECT_EQ(gfx::Insets(int_max), scale_test); +} + +TEST(InsetsTest, IntegerUnderflow) { + constexpr int int_min = std::numeric_limits::min(); + constexpr int int_max = std::numeric_limits::max(); + + gfx::Insets width_height_test = gfx::Insets(int_min); + EXPECT_EQ(int_min, width_height_test.width()); + EXPECT_EQ(int_min, width_height_test.height()); + + gfx::Insets minus_test(int_min); + minus_test -= gfx::Insets(int_max); + EXPECT_EQ(gfx::Insets(int_min), minus_test); + + gfx::Insets scale_test = gfx::Insets(int_min); + scale_test = gfx::ScaleToRoundedInsets(scale_test, 2.f); + EXPECT_EQ(gfx::Insets(int_min), scale_test); +} + +TEST(InsetsTest, IntegerOverflowSetVariants) { + constexpr int int_max = std::numeric_limits::max(); + + gfx::Insets set_test(20); + set_test.set_top(int_max); + EXPECT_EQ(int_max, set_test.top()); + EXPECT_EQ(0, set_test.bottom()); + + set_test.set_left(int_max); + EXPECT_EQ(int_max, set_test.left()); + EXPECT_EQ(0, set_test.right()); + + set_test = gfx::Insets(30); + set_test.set_bottom(int_max); + EXPECT_EQ(int_max - 30, set_test.bottom()); + EXPECT_EQ(30, set_test.top()); + + set_test.set_right(int_max); + EXPECT_EQ(int_max - 30, set_test.right()); + EXPECT_EQ(30, set_test.left()); +} + +TEST(InsetsTest, IntegerUnderflowSetVariants) { + constexpr int int_min = std::numeric_limits::min(); + + gfx::Insets set_test(-20); + set_test.set_top(int_min); + EXPECT_EQ(int_min, set_test.top()); + EXPECT_EQ(0, set_test.bottom()); + + set_test.set_left(int_min); + EXPECT_EQ(int_min, set_test.left()); + EXPECT_EQ(0, set_test.right()); + + set_test = gfx::Insets(-30); + set_test.set_bottom(int_min); + EXPECT_EQ(int_min + 30, set_test.bottom()); + EXPECT_EQ(-30, set_test.top()); + + set_test.set_right(int_min); + EXPECT_EQ(int_min + 30, set_test.right()); + EXPECT_EQ(-30, set_test.left()); +} + +TEST(InsetsTest, IntegerOverflowSet) { + constexpr int int_max = std::numeric_limits::max(); + + gfx::Insets set_all_test; + set_all_test.Set(10, 20, int_max, int_max); + EXPECT_EQ(gfx::Insets(10, 20, int_max - 10, int_max - 20), set_all_test); +} + +TEST(InsetsTest, IntegerOverflowOffset) { + constexpr int int_max = std::numeric_limits::max(); + + const gfx::Vector2d max_vector(int_max, int_max); + gfx::Insets insets(1, 2, 3, 4); + gfx::Insets offset_test = insets.Offset(max_vector); + EXPECT_EQ(gfx::Insets(int_max, int_max, 3 - int_max, 4 - int_max), + offset_test); +} + +TEST(InsetsTest, IntegerUnderflowOffset) { + constexpr int int_min = std::numeric_limits::min(); + + const gfx::Vector2d min_vector(int_min, int_min); + gfx::Insets insets(-10); + gfx::Insets offset_test = insets.Offset(min_vector); + EXPECT_EQ(gfx::Insets(int_min, int_min, -10 - int_min, -10 - int_min), + offset_test); +} + +TEST(InsetsTest, Size) { + gfx::Insets insets(1, 2, 3, 4); + EXPECT_EQ(gfx::Size(6, 4), insets.size()); +} diff --git a/geometry/mask_filter_info.cc b/geometry/mask_filter_info.cc new file mode 100644 index 000000000000..9757acdefb59 --- /dev/null +++ b/geometry/mask_filter_info.cc @@ -0,0 +1,23 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/mask_filter_info.h" + +#include + +#include "ui/gfx/geometry/transform.h" + +namespace gfx { + +bool MaskFilterInfo::Transform(const gfx::Transform& transform) { + return rounded_corner_bounds_.IsEmpty() + ? false + : transform.TransformRRectF(&rounded_corner_bounds_); +} + +std::string MaskFilterInfo::ToString() const { + return "MaskFilterInfo{" + rounded_corner_bounds_.ToString() + "}"; +} + +} // namespace gfx diff --git a/geometry/mask_filter_info.h b/geometry/mask_filter_info.h new file mode 100644 index 000000000000..c796b4945bd8 --- /dev/null +++ b/geometry/mask_filter_info.h @@ -0,0 +1,64 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_MASK_FILTER_INFO_H_ +#define UI_GFX_GEOMETRY_MASK_FILTER_INFO_H_ + +#include "ui/gfx/geometry/geometry_skia_export.h" +#include "ui/gfx/geometry/rect_f.h" +#include "ui/gfx/geometry/rrect_f.h" + +namespace gfx { + +class Transform; + +// This class defines a mask filter to be applied to the given rect. +class GEOMETRY_SKIA_EXPORT MaskFilterInfo { + public: + MaskFilterInfo() = default; + explicit MaskFilterInfo(const RRectF& rrect) + : rounded_corner_bounds_(rrect) {} + MaskFilterInfo(const RectF& bounds, const RoundedCornersF& radii) + : rounded_corner_bounds_(bounds, radii) {} + MaskFilterInfo(const MaskFilterInfo& copy) = default; + ~MaskFilterInfo() = default; + + // The bounds the filter will be applied to. + RectF bounds() const { return rounded_corner_bounds_.rect(); } + + // Defines the rounded corner bounds to clip. + const RRectF& rounded_corner_bounds() const { return rounded_corner_bounds_; } + + // True if this contains a rounded corner mask. + bool HasRoundedCorners() const { + return rounded_corner_bounds_.GetType() != RRectF::Type::kEmpty && + rounded_corner_bounds_.GetType() != RRectF::Type::kRect; + } + + // True if this contains no effective mask information. + bool IsEmpty() const { return rounded_corner_bounds_.IsEmpty(); } + + // Transform the mask information. Returns false if the transform + // cannot be applied. + bool Transform(const gfx::Transform& transform); + + std::string ToString() const; + + private: + // The rounded corner bounds. This also defines the bounds that the mask + // filter will be applied to. + RRectF rounded_corner_bounds_; +}; + +inline bool operator==(const MaskFilterInfo& lhs, const MaskFilterInfo& rhs) { + return lhs.rounded_corner_bounds() == rhs.rounded_corner_bounds(); +} + +inline bool operator!=(const MaskFilterInfo& lhs, const MaskFilterInfo& rhs) { + return !(lhs == rhs); +} + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_MASK_FILTER_INFO_H_ diff --git a/geometry/matrix3_f.cc b/geometry/matrix3_f.cc new file mode 100644 index 000000000000..afacbeabe175 --- /dev/null +++ b/geometry/matrix3_f.cc @@ -0,0 +1,180 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/matrix3_f.h" + +#include + +#include +#include +#include + +#include "base/numerics/math_constants.h" +#include "base/strings/stringprintf.h" + +namespace { + +// This is only to make accessing indices self-explanatory. +enum MatrixCoordinates { + M00, + M01, + M02, + M10, + M11, + M12, + M20, + M21, + M22, + M_END +}; + +template +double Determinant3x3(T data[M_END]) { + // This routine is separated from the Matrix3F::Determinant because in + // computing inverse we do want higher precision afforded by the explicit + // use of 'double'. + return + static_cast(data[M00]) * ( + static_cast(data[M11]) * data[M22] - + static_cast(data[M12]) * data[M21]) + + static_cast(data[M01]) * ( + static_cast(data[M12]) * data[M20] - + static_cast(data[M10]) * data[M22]) + + static_cast(data[M02]) * ( + static_cast(data[M10]) * data[M21] - + static_cast(data[M11]) * data[M20]); +} + +} // namespace + +namespace gfx { + +Matrix3F::Matrix3F() { +} + +Matrix3F::~Matrix3F() { +} + +// static +Matrix3F Matrix3F::Zeros() { + Matrix3F matrix; + matrix.set(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f); + return matrix; +} + +// static +Matrix3F Matrix3F::Ones() { + Matrix3F matrix; + matrix.set(1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f); + return matrix; +} + +// static +Matrix3F Matrix3F::Identity() { + Matrix3F matrix; + matrix.set(1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f); + return matrix; +} + +// static +Matrix3F Matrix3F::FromOuterProduct(const Vector3dF& a, const Vector3dF& bt) { + Matrix3F matrix; + matrix.set(a.x() * bt.x(), a.x() * bt.y(), a.x() * bt.z(), + a.y() * bt.x(), a.y() * bt.y(), a.y() * bt.z(), + a.z() * bt.x(), a.z() * bt.y(), a.z() * bt.z()); + return matrix; +} + +bool Matrix3F::IsEqual(const Matrix3F& rhs) const { + return 0 == memcmp(data_, rhs.data_, sizeof(data_)); +} + +bool Matrix3F::IsNear(const Matrix3F& rhs, float precision) const { + DCHECK(precision >= 0); + for (int i = 0; i < M_END; ++i) { + if (std::abs(data_[i] - rhs.data_[i]) > precision) + return false; + } + return true; +} + +Matrix3F Matrix3F::Add(const Matrix3F& rhs) const { + Matrix3F result; + for (int i = 0; i < M_END; ++i) + result.data_[i] = data_[i] + rhs.data_[i]; + return result; +} + +Matrix3F Matrix3F::Subtract(const Matrix3F& rhs) const { + Matrix3F result; + for (int i = 0; i < M_END; ++i) + result.data_[i] = data_[i] - rhs.data_[i]; + return result; +} + +Matrix3F Matrix3F::Inverse() const { + Matrix3F inverse = Matrix3F::Zeros(); + double determinant = Determinant3x3(data_); + if (std::numeric_limits::epsilon() > std::abs(determinant)) + return inverse; // Singular matrix. Return Zeros(). + + inverse.set( + static_cast((data_[M11] * data_[M22] - data_[M12] * data_[M21]) / + determinant), + static_cast((data_[M02] * data_[M21] - data_[M01] * data_[M22]) / + determinant), + static_cast((data_[M01] * data_[M12] - data_[M02] * data_[M11]) / + determinant), + static_cast((data_[M12] * data_[M20] - data_[M10] * data_[M22]) / + determinant), + static_cast((data_[M00] * data_[M22] - data_[M02] * data_[M20]) / + determinant), + static_cast((data_[M02] * data_[M10] - data_[M00] * data_[M12]) / + determinant), + static_cast((data_[M10] * data_[M21] - data_[M11] * data_[M20]) / + determinant), + static_cast((data_[M01] * data_[M20] - data_[M00] * data_[M21]) / + determinant), + static_cast((data_[M00] * data_[M11] - data_[M01] * data_[M10]) / + determinant)); + return inverse; +} + +Matrix3F Matrix3F::Transpose() const { + Matrix3F transpose; + transpose.set(data_[M00], data_[M10], data_[M20], data_[M01], data_[M11], + data_[M21], data_[M02], data_[M12], data_[M22]); + return transpose; +} + +float Matrix3F::Determinant() const { + return static_cast(Determinant3x3(data_)); +} + +Matrix3F MatrixProduct(const Matrix3F& lhs, const Matrix3F& rhs) { + Matrix3F result = Matrix3F::Zeros(); + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + result.set(i, j, DotProduct(lhs.get_row(i), rhs.get_column(j))); + } + } + return result; +} + +Vector3dF MatrixProduct(const Matrix3F& lhs, const Vector3dF& rhs) { + return Vector3dF(DotProduct(lhs.get_row(0), rhs), + DotProduct(lhs.get_row(1), rhs), + DotProduct(lhs.get_row(2), rhs)); +} + +std::string Matrix3F::ToString() const { + return base::StringPrintf( + "[[%+0.4f, %+0.4f, %+0.4f]," + " [%+0.4f, %+0.4f, %+0.4f]," + " [%+0.4f, %+0.4f, %+0.4f]]", + data_[M00], data_[M01], data_[M02], data_[M10], data_[M11], data_[M12], + data_[M20], data_[M21], data_[M22]); +} + +} // namespace gfx diff --git a/geometry/matrix3_f.h b/geometry/matrix3_f.h new file mode 100644 index 000000000000..0b5cc1284668 --- /dev/null +++ b/geometry/matrix3_f.h @@ -0,0 +1,127 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_MATRIX3_F_H_ +#define UI_GFX_GEOMETRY_MATRIX3_F_H_ + +#include "base/check.h" +#include "ui/gfx/geometry/vector3d_f.h" + +namespace gfx { + +class GEOMETRY_EXPORT Matrix3F { + public: + ~Matrix3F(); + + static Matrix3F Zeros(); + static Matrix3F Ones(); + static Matrix3F Identity(); + static Matrix3F FromOuterProduct(const Vector3dF& a, const Vector3dF& bt); + + bool IsEqual(const Matrix3F& rhs) const; + + // Element-wise comparison with given precision. + bool IsNear(const Matrix3F& rhs, float precision) const; + + float get(int i, int j) const { + return data_[MatrixToArrayCoords(i, j)]; + } + + void set(int i, int j, float v) { + data_[MatrixToArrayCoords(i, j)] = v; + } + + void set(float m00, float m01, float m02, + float m10, float m11, float m12, + float m20, float m21, float m22) { + data_[0] = m00; + data_[1] = m01; + data_[2] = m02; + data_[3] = m10; + data_[4] = m11; + data_[5] = m12; + data_[6] = m20; + data_[7] = m21; + data_[8] = m22; + } + + Vector3dF get_row(int i) const { + return Vector3dF(data_[MatrixToArrayCoords(i, 0)], + data_[MatrixToArrayCoords(i, 1)], + data_[MatrixToArrayCoords(i, 2)]); + } + + Vector3dF get_column(int i) const { + return Vector3dF( + data_[MatrixToArrayCoords(0, i)], + data_[MatrixToArrayCoords(1, i)], + data_[MatrixToArrayCoords(2, i)]); + } + + void set_column(int i, const Vector3dF& c) { + data_[MatrixToArrayCoords(0, i)] = c.x(); + data_[MatrixToArrayCoords(1, i)] = c.y(); + data_[MatrixToArrayCoords(2, i)] = c.z(); + } + + // Produces a new matrix by adding the elements of |rhs| to this matrix + Matrix3F Add(const Matrix3F& rhs) const; + // Produces a new matrix by subtracting elements of |rhs| from this matrix. + Matrix3F Subtract(const Matrix3F& rhs) const; + + // Returns an inverse of this if the matrix is non-singular, zero (== Zero()) + // otherwise. + Matrix3F Inverse() const; + + // Returns a transpose of this matrix. + Matrix3F Transpose() const; + + // Value of the determinant of the matrix. + float Determinant() const; + + // Trace (sum of diagonal elements) of the matrix. + float Trace() const { + return data_[MatrixToArrayCoords(0, 0)] + + data_[MatrixToArrayCoords(1, 1)] + + data_[MatrixToArrayCoords(2, 2)]; + } + + std::string ToString() const; + + private: + Matrix3F(); // Uninitialized default. + + static int MatrixToArrayCoords(int i, int j) { + DCHECK(i >= 0 && i < 3); + DCHECK(j >= 0 && j < 3); + return i * 3 + j; + } + + float data_[9]; +}; + +inline bool operator==(const Matrix3F& lhs, const Matrix3F& rhs) { + return lhs.IsEqual(rhs); +} + +// Matrix addition. Produces a new matrix by adding the corresponding elements +// together. +inline Matrix3F operator+(const Matrix3F& lhs, const Matrix3F& rhs) { + return lhs.Add(rhs); +} + +// Matrix subtraction. Produces a new matrix by subtracting elements of rhs +// from corresponding elements of lhs. +inline Matrix3F operator-(const Matrix3F& lhs, const Matrix3F& rhs) { + return lhs.Subtract(rhs); +} + +GEOMETRY_EXPORT Matrix3F MatrixProduct(const Matrix3F& lhs, + const Matrix3F& rhs); +GEOMETRY_EXPORT Vector3dF MatrixProduct(const Matrix3F& lhs, + const Vector3dF& rhs); + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_MATRIX3_F_H_ diff --git a/geometry/matrix3_unittest.cc b/geometry/matrix3_unittest.cc new file mode 100644 index 000000000000..1f550e8f2825 --- /dev/null +++ b/geometry/matrix3_unittest.cc @@ -0,0 +1,125 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/geometry/matrix3_f.h" + +namespace gfx { +namespace { + +TEST(Matrix3fTest, Constructors) { + Matrix3F zeros = Matrix3F::Zeros(); + Matrix3F ones = Matrix3F::Ones(); + Matrix3F identity = Matrix3F::Identity(); + + Matrix3F product_ones = Matrix3F::FromOuterProduct( + Vector3dF(1.0f, 1.0f, 1.0f), Vector3dF(1.0f, 1.0f, 1.0f)); + Matrix3F product_zeros = Matrix3F::FromOuterProduct( + Vector3dF(1.0f, 1.0f, 1.0f), Vector3dF(0.0f, 0.0f, 0.0f)); + EXPECT_EQ(ones, product_ones); + EXPECT_EQ(zeros, product_zeros); + + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) + EXPECT_EQ(i == j ? 1.0f : 0.0f, identity.get(i, j)); + } +} + +TEST(Matrix3fTest, DataAccess) { + Matrix3F matrix = Matrix3F::Ones(); + Matrix3F identity = Matrix3F::Identity(); + + EXPECT_EQ(Vector3dF(0.0f, 1.0f, 0.0f), identity.get_column(1)); + EXPECT_EQ(Vector3dF(0.0f, 1.0f, 0.0f), identity.get_row(1)); + matrix.set(0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f); + EXPECT_EQ(Vector3dF(2.0f, 5.0f, 8.0f), matrix.get_column(2)); + EXPECT_EQ(Vector3dF(6.0f, 7.0f, 8.0f), matrix.get_row(2)); + matrix.set_column(0, Vector3dF(0.1f, 0.2f, 0.3f)); + matrix.set_column(0, Vector3dF(0.1f, 0.2f, 0.3f)); + EXPECT_EQ(Vector3dF(0.1f, 0.2f, 0.3f), matrix.get_column(0)); + EXPECT_EQ(Vector3dF(0.1f, 1.0f, 2.0f), matrix.get_row(0)); + + EXPECT_EQ(0.1f, matrix.get(0, 0)); + EXPECT_EQ(5.0f, matrix.get(1, 2)); +} + +TEST(Matrix3fTest, Determinant) { + EXPECT_EQ(1.0f, Matrix3F::Identity().Determinant()); + EXPECT_EQ(0.0f, Matrix3F::Zeros().Determinant()); + EXPECT_EQ(0.0f, Matrix3F::Ones().Determinant()); + + // Now for something non-trivial... + Matrix3F matrix = Matrix3F::Zeros(); + matrix.set(0, 5, 6, 8, 7, 0, 1, 9, 0); + EXPECT_EQ(390.0f, matrix.Determinant()); + matrix.set(2, 0, 3 * matrix.get(0, 0)); + matrix.set(2, 1, 3 * matrix.get(0, 1)); + matrix.set(2, 2, 3 * matrix.get(0, 2)); + EXPECT_EQ(0, matrix.Determinant()); + + matrix.set(0.57f, 0.205f, 0.942f, + 0.314f, 0.845f, 0.826f, + 0.131f, 0.025f, 0.962f); + EXPECT_NEAR(0.3149f, matrix.Determinant(), 0.0001f); +} + +TEST(Matrix3fTest, Inverse) { + Matrix3F identity = Matrix3F::Identity(); + Matrix3F inv_identity = identity.Inverse(); + EXPECT_EQ(identity, inv_identity); + + Matrix3F singular = Matrix3F::Zeros(); + singular.set(1.0f, 3.0f, 4.0f, + 2.0f, 11.0f, 5.0f, + 0.5f, 1.5f, 2.0f); + EXPECT_EQ(0, singular.Determinant()); + EXPECT_EQ(Matrix3F::Zeros(), singular.Inverse()); + + Matrix3F regular = Matrix3F::Zeros(); + regular.set(0.57f, 0.205f, 0.942f, + 0.314f, 0.845f, 0.826f, + 0.131f, 0.025f, 0.962f); + Matrix3F inv_regular = regular.Inverse(); + regular.set(2.51540616f, -0.55138018f, -1.98968043f, + -0.61552266f, 1.34920184f, -0.55573636f, + -0.32653861f, 0.04002158f, 1.32488726f); + EXPECT_TRUE(regular.IsNear(inv_regular, 0.00001f)); +} + +TEST(Matrix3fTest, Transpose) { + Matrix3F matrix = Matrix3F::Zeros(); + + matrix.set(0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f); + + Matrix3F transpose = matrix.Transpose(); + EXPECT_EQ(Vector3dF(0.0f, 1.0f, 2.0f), transpose.get_column(0)); + EXPECT_EQ(Vector3dF(3.0f, 4.0f, 5.0f), transpose.get_column(1)); + EXPECT_EQ(Vector3dF(6.0f, 7.0f, 8.0f), transpose.get_column(2)); + + EXPECT_TRUE(matrix.IsEqual(transpose.Transpose())); +} + +TEST(Matrix3fTest, Operators) { + Matrix3F matrix1 = Matrix3F::Zeros(); + matrix1.set(1, 2, 3, 4, 5, 6, 7, 8, 9); + EXPECT_EQ(matrix1 + Matrix3F::Zeros(), matrix1); + + Matrix3F matrix2 = Matrix3F::Zeros(); + matrix2.set(-1, -2, -3, -4, -5, -6, -7, -8, -9); + EXPECT_EQ(matrix1 + matrix2, Matrix3F::Zeros()); + + EXPECT_EQ(Matrix3F::Zeros() - matrix1, matrix2); + + Matrix3F result = Matrix3F::Zeros(); + result.set(2, 4, 6, 8, 10, 12, 14, 16, 18); + EXPECT_EQ(matrix1 - matrix2, result); + result.set(-2, -4, -6, -8, -10, -12, -14, -16, -18); + EXPECT_EQ(matrix2 - matrix1, result); +} + +} // namespace +} // namespace gfx diff --git a/geometry/mojom/BUILD.gn b/geometry/mojom/BUILD.gn new file mode 100644 index 000000000000..a5f18d7dad98 --- /dev/null +++ b/geometry/mojom/BUILD.gn @@ -0,0 +1,106 @@ +# Copyright 2016 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//mojo/public/tools/bindings/mojom.gni") + +# This target does NOT depend on skia. One can depend on this target to avoid +# picking up a dependency on skia. +mojom("mojom") { + generate_java = true + sources = [ "geometry.mojom" ] + + check_includes_blink = false + + shared_cpp_typemap = { + types = [ + { + mojom = "gfx.mojom.Point" + cpp = "::gfx::Point" + }, + { + mojom = "gfx.mojom.PointF" + cpp = "::gfx::PointF" + }, + { + mojom = "gfx.mojom.Point3F" + cpp = "::gfx::Point3F" + }, + { + mojom = "gfx.mojom.Size" + cpp = "::gfx::Size" + }, + { + mojom = "gfx.mojom.SizeF" + cpp = "::gfx::SizeF" + }, + { + mojom = "gfx.mojom.Rect" + cpp = "::gfx::Rect" + }, + { + mojom = "gfx.mojom.RectF" + cpp = "::gfx::RectF" + }, + { + mojom = "gfx.mojom.Insets" + cpp = "::gfx::Insets" + }, + { + mojom = "gfx.mojom.InsetsF" + cpp = "::gfx::InsetsF" + }, + { + mojom = "gfx.mojom.Quaternion" + cpp = "::gfx::Quaternion" + }, + { + mojom = "gfx.mojom.Vector2d" + cpp = "::gfx::Vector2d" + }, + { + mojom = "gfx.mojom.Vector2dF" + cpp = "::gfx::Vector2dF" + }, + { + mojom = "gfx.mojom.Vector3dF" + cpp = "::gfx::Vector3dF" + }, + ] + + traits_headers = [ "geometry_mojom_traits.h" ] + traits_public_deps = [ ":mojom_traits" ] + } + cpp_typemaps = [ shared_cpp_typemap ] + blink_cpp_typemaps = [ shared_cpp_typemap ] + webui_module_path = "chrome://resources/mojo/ui/gfx/geometry/mojom" +} + +mojom("test_interfaces") { + sources = [ "geometry_traits_test_service.mojom" ] + + public_deps = [ ":mojom" ] +} + +source_set("unit_test") { + testonly = true + + sources = [ "geometry_mojom_traits_unittest.cc" ] + + deps = [ + ":test_interfaces", + "//base", + "//base/test:test_support", + "//mojo/public/cpp/bindings", + "//testing/gtest", + "//ui/gfx/geometry", + ] +} + +source_set("mojom_traits") { + sources = [ "geometry_mojom_traits.h" ] + public_deps = [ + ":mojom_shared", + "//ui/gfx/geometry", + ] +} diff --git a/geometry/mojom/DEPS b/geometry/mojom/DEPS new file mode 100644 index 000000000000..3ad6543823bc --- /dev/null +++ b/geometry/mojom/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + "+mojo/public", + "+ui/gfx/geometry", +] diff --git a/geometry/mojom/OWNERS b/geometry/mojom/OWNERS new file mode 100644 index 000000000000..7ed4e44a4d5e --- /dev/null +++ b/geometry/mojom/OWNERS @@ -0,0 +1,8 @@ +per-file *_mojom_traits*.*=set noparent +per-file *_mojom_traits*.*=file://ipc/SECURITY_OWNERS + +per-file *.mojom=set noparent +per-file *.mojom=file://ipc/SECURITY_OWNERS + +per-file *.typemap=set noparent +per-file *.typemap=file://ipc/SECURITY_OWNERS diff --git a/geometry/mojom/geometry.mojom b/geometry/mojom/geometry.mojom new file mode 100644 index 000000000000..30be3e6062eb --- /dev/null +++ b/geometry/mojom/geometry.mojom @@ -0,0 +1,88 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module gfx.mojom; + +[Stable] +struct Point { + int32 x; + int32 y; +}; + +[Stable] +struct PointF { + float x; + float y; +}; + +struct Point3F { + float x; + float y; + float z; +}; + +[Stable] +struct Size { + int32 width; + int32 height; +}; + +struct SizeF { + float width; + float height; +}; + +[Stable] +struct Rect { + int32 x; + int32 y; + int32 width; + int32 height; +}; + +[Stable] +struct RectF { + float x; + float y; + float width; + float height; +}; + +[Stable] +struct Insets { + int32 top; + int32 left; + int32 bottom; + int32 right; +}; + +struct InsetsF { + float top; + float left; + float bottom; + float right; +}; + +struct Vector2d { + int32 x; + int32 y; +}; + +struct Vector2dF { + float x; + float y; +}; + +struct Vector3dF { + float x; + float y; + float z; +}; + +struct Quaternion { + double x; + double y; + double z; + double w; +}; diff --git a/geometry/mojom/geometry_mojom_traits.h b/geometry/mojom/geometry_mojom_traits.h new file mode 100644 index 000000000000..aa4827312002 --- /dev/null +++ b/geometry/mojom/geometry_mojom_traits.h @@ -0,0 +1,188 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_MOJOM_GEOMETRY_MOJOM_TRAITS_H_ +#define UI_GFX_GEOMETRY_MOJOM_GEOMETRY_MOJOM_TRAITS_H_ + +#include "ui/gfx/geometry/insets.h" +#include "ui/gfx/geometry/insets_f.h" +#include "ui/gfx/geometry/mojom/geometry.mojom-shared.h" +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/point3_f.h" +#include "ui/gfx/geometry/point_f.h" +#include "ui/gfx/geometry/quaternion.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/rect_f.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/geometry/size_f.h" +#include "ui/gfx/geometry/vector2d.h" +#include "ui/gfx/geometry/vector2d_f.h" +#include "ui/gfx/geometry/vector3d_f.h" + +namespace mojo { + +template <> +struct StructTraits { + static int top(const gfx::Insets& p) { return p.top(); } + static int left(const gfx::Insets& p) { return p.left(); } + static int bottom(const gfx::Insets& p) { return p.bottom(); } + static int right(const gfx::Insets& p) { return p.right(); } + static bool Read(gfx::mojom::InsetsDataView data, gfx::Insets* out) { + out->Set(data.top(), data.left(), data.bottom(), data.right()); + return true; + } +}; + +template <> +struct StructTraits { + static float top(const gfx::InsetsF& p) { return p.top(); } + static float left(const gfx::InsetsF& p) { return p.left(); } + static float bottom(const gfx::InsetsF& p) { return p.bottom(); } + static float right(const gfx::InsetsF& p) { return p.right(); } + static bool Read(gfx::mojom::InsetsFDataView data, gfx::InsetsF* out) { + out->Set(data.top(), data.left(), data.bottom(), data.right()); + return true; + } +}; + +template <> +struct StructTraits { + static int x(const gfx::Point& p) { return p.x(); } + static int y(const gfx::Point& p) { return p.y(); } + static bool Read(gfx::mojom::PointDataView data, gfx::Point* out) { + out->SetPoint(data.x(), data.y()); + return true; + } +}; + +template <> +struct StructTraits { + static float x(const gfx::PointF& p) { return p.x(); } + static float y(const gfx::PointF& p) { return p.y(); } + static bool Read(gfx::mojom::PointFDataView data, gfx::PointF* out) { + out->SetPoint(data.x(), data.y()); + return true; + } +}; + +template <> +struct StructTraits { + static float x(const gfx::Point3F& p) { return p.x(); } + static float y(const gfx::Point3F& p) { return p.y(); } + static float z(const gfx::Point3F& p) { return p.z(); } + static bool Read(gfx::mojom::Point3FDataView data, gfx::Point3F* out) { + out->SetPoint(data.x(), data.y(), data.z()); + return true; + } +}; + +template <> +struct StructTraits { + static int x(const gfx::Rect& p) { return p.x(); } + static int y(const gfx::Rect& p) { return p.y(); } + static int width(const gfx::Rect& p) { return p.width(); } + static int height(const gfx::Rect& p) { return p.height(); } + static bool Read(gfx::mojom::RectDataView data, gfx::Rect* out) { + if (data.width() < 0 || data.height() < 0) + return false; + + out->SetRect(data.x(), data.y(), data.width(), data.height()); + return true; + } +}; + +template <> +struct StructTraits { + static float x(const gfx::RectF& p) { return p.x(); } + static float y(const gfx::RectF& p) { return p.y(); } + static float width(const gfx::RectF& p) { return p.width(); } + static float height(const gfx::RectF& p) { return p.height(); } + static bool Read(gfx::mojom::RectFDataView data, gfx::RectF* out) { + if (data.width() < 0 || data.height() < 0) + return false; + + out->SetRect(data.x(), data.y(), data.width(), data.height()); + return true; + } +}; + +template <> +struct StructTraits { + static int width(const gfx::Size& p) { return p.width(); } + static int height(const gfx::Size& p) { return p.height(); } + static bool Read(gfx::mojom::SizeDataView data, gfx::Size* out) { + if (data.width() < 0 || data.height() < 0) + return false; + + out->SetSize(data.width(), data.height()); + return true; + } +}; + +template <> +struct StructTraits { + static float width(const gfx::SizeF& p) { return p.width(); } + static float height(const gfx::SizeF& p) { return p.height(); } + static bool Read(gfx::mojom::SizeFDataView data, gfx::SizeF* out) { + if (data.width() < 0 || data.height() < 0) + return false; + + out->SetSize(data.width(), data.height()); + return true; + } +}; + +template <> +struct StructTraits { + static int x(const gfx::Vector2d& v) { return v.x(); } + static int y(const gfx::Vector2d& v) { return v.y(); } + static bool Read(gfx::mojom::Vector2dDataView data, gfx::Vector2d* out) { + out->set_x(data.x()); + out->set_y(data.y()); + return true; + } +}; + +template <> +struct StructTraits { + static float x(const gfx::Vector2dF& v) { return v.x(); } + static float y(const gfx::Vector2dF& v) { return v.y(); } + static bool Read(gfx::mojom::Vector2dFDataView data, gfx::Vector2dF* out) { + out->set_x(data.x()); + out->set_y(data.y()); + return true; + } +}; + +template <> +struct StructTraits { + static float x(const gfx::Vector3dF& v) { return v.x(); } + static float y(const gfx::Vector3dF& v) { return v.y(); } + static float z(const gfx::Vector3dF& v) { return v.z(); } + static bool Read(gfx::mojom::Vector3dFDataView data, gfx::Vector3dF* out) { + out->set_x(data.x()); + out->set_y(data.y()); + out->set_z(data.z()); + return true; + } +}; + +template <> +struct StructTraits { + static double x(const gfx::Quaternion& q) { return q.x(); } + static double y(const gfx::Quaternion& q) { return q.y(); } + static double z(const gfx::Quaternion& q) { return q.z(); } + static double w(const gfx::Quaternion& q) { return q.w(); } + static bool Read(gfx::mojom::QuaternionDataView data, gfx::Quaternion* out) { + out->set_x(data.x()); + out->set_y(data.y()); + out->set_z(data.z()); + out->set_w(data.w()); + return true; + } +}; + +} // namespace mojo + +#endif // UI_GFX_GEOMETRY_MOJOM_GEOMETRY_MOJOM_TRAITS_H_ diff --git a/geometry/mojom/geometry_mojom_traits_unittest.cc b/geometry/mojom/geometry_mojom_traits_unittest.cc new file mode 100644 index 000000000000..d2c595919589 --- /dev/null +++ b/geometry/mojom/geometry_mojom_traits_unittest.cc @@ -0,0 +1,264 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/test/task_environment.h" +#include "mojo/public/cpp/bindings/receiver_set.h" +#include "mojo/public/cpp/bindings/remote.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/geometry/mojom/geometry_traits_test_service.mojom.h" +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/quaternion.h" + +namespace gfx { + +namespace { + +class GeometryStructTraitsTest : public testing::Test, + public mojom::GeometryTraitsTestService { + public: + GeometryStructTraitsTest() {} + + GeometryStructTraitsTest(const GeometryStructTraitsTest&) = delete; + GeometryStructTraitsTest& operator=(const GeometryStructTraitsTest&) = delete; + + protected: + mojo::Remote GetTraitsTestRemote() { + mojo::Remote remote; + traits_test_receivers_.Add(this, remote.BindNewPipeAndPassReceiver()); + return remote; + } + + private: + // GeometryTraitsTestService: + void EchoPoint(const Point& p, EchoPointCallback callback) override { + std::move(callback).Run(p); + } + + void EchoPointF(const PointF& p, EchoPointFCallback callback) override { + std::move(callback).Run(p); + } + + void EchoPoint3F(const Point3F& p, EchoPoint3FCallback callback) override { + std::move(callback).Run(p); + } + + void EchoSize(const Size& s, EchoSizeCallback callback) override { + std::move(callback).Run(s); + } + + void EchoSizeF(const SizeF& s, EchoSizeFCallback callback) override { + std::move(callback).Run(s); + } + + void EchoRect(const Rect& r, EchoRectCallback callback) override { + std::move(callback).Run(r); + } + + void EchoRectF(const RectF& r, EchoRectFCallback callback) override { + std::move(callback).Run(r); + } + + void EchoInsets(const Insets& i, EchoInsetsCallback callback) override { + std::move(callback).Run(i); + } + + void EchoInsetsF(const InsetsF& i, EchoInsetsFCallback callback) override { + std::move(callback).Run(i); + } + + void EchoVector2d(const Vector2d& v, EchoVector2dCallback callback) override { + std::move(callback).Run(v); + } + + void EchoVector2dF(const Vector2dF& v, + EchoVector2dFCallback callback) override { + std::move(callback).Run(v); + } + + void EchoVector3dF(const Vector3dF& v, + EchoVector3dFCallback callback) override { + std::move(callback).Run(v); + } + + void EchoQuaternion(const Quaternion& q, + EchoQuaternionCallback callback) override { + std::move(callback).Run(q); + } + + base::test::TaskEnvironment task_environment_; + mojo::ReceiverSet traits_test_receivers_; +}; + +} // namespace + +TEST_F(GeometryStructTraitsTest, Point) { + const int32_t x = 1234; + const int32_t y = -5678; + gfx::Point input(x, y); + mojo::Remote remote = GetTraitsTestRemote(); + gfx::Point output; + remote->EchoPoint(input, &output); + EXPECT_EQ(x, output.x()); + EXPECT_EQ(y, output.y()); +} + +TEST_F(GeometryStructTraitsTest, PointF) { + const float x = 1234.5f; + const float y = 6789.6f; + gfx::PointF input(x, y); + mojo::Remote remote = GetTraitsTestRemote(); + gfx::PointF output; + remote->EchoPointF(input, &output); + EXPECT_EQ(x, output.x()); + EXPECT_EQ(y, output.y()); +} + +TEST_F(GeometryStructTraitsTest, Point3F) { + const float x = 1234.5f; + const float y = 6789.6f; + const float z = 5432.1f; + gfx::Point3F input(x, y, z); + mojo::Remote remote = GetTraitsTestRemote(); + gfx::Point3F output; + remote->EchoPoint3F(input, &output); + EXPECT_EQ(x, output.x()); + EXPECT_EQ(y, output.y()); + EXPECT_EQ(z, output.z()); +} + +TEST_F(GeometryStructTraitsTest, Size) { + const int32_t width = 1234; + const int32_t height = 5678; + gfx::Size input(width, height); + mojo::Remote remote = GetTraitsTestRemote(); + gfx::Size output; + remote->EchoSize(input, &output); + EXPECT_EQ(width, output.width()); + EXPECT_EQ(height, output.height()); +} + +TEST_F(GeometryStructTraitsTest, SizeF) { + const float width = 1234.5f; + const float height = 6789.6f; + gfx::SizeF input(width, height); + mojo::Remote remote = GetTraitsTestRemote(); + gfx::SizeF output; + remote->EchoSizeF(input, &output); + EXPECT_EQ(width, output.width()); + EXPECT_EQ(height, output.height()); +} + +TEST_F(GeometryStructTraitsTest, Rect) { + const int32_t x = 1234; + const int32_t y = 5678; + const int32_t width = 4321; + const int32_t height = 8765; + gfx::Rect input(x, y, width, height); + mojo::Remote remote = GetTraitsTestRemote(); + gfx::Rect output; + remote->EchoRect(input, &output); + EXPECT_EQ(x, output.x()); + EXPECT_EQ(y, output.y()); + EXPECT_EQ(width, output.width()); + EXPECT_EQ(height, output.height()); +} + +TEST_F(GeometryStructTraitsTest, RectF) { + const float x = 1234.1f; + const float y = 5678.2f; + const float width = 4321.3f; + const float height = 8765.4f; + gfx::RectF input(x, y, width, height); + mojo::Remote remote = GetTraitsTestRemote(); + gfx::RectF output; + remote->EchoRectF(input, &output); + EXPECT_EQ(x, output.x()); + EXPECT_EQ(y, output.y()); + EXPECT_EQ(width, output.width()); + EXPECT_EQ(height, output.height()); +} + +TEST_F(GeometryStructTraitsTest, Insets) { + const int32_t top = 1234; + const int32_t left = 5678; + const int32_t bottom = 4321; + const int32_t right = 8765; + gfx::Insets input(top, left, bottom, right); + mojo::Remote remote = GetTraitsTestRemote(); + gfx::Insets output; + remote->EchoInsets(input, &output); + EXPECT_EQ(top, output.top()); + EXPECT_EQ(left, output.left()); + EXPECT_EQ(bottom, output.bottom()); + EXPECT_EQ(right, output.right()); +} + +TEST_F(GeometryStructTraitsTest, InsetsF) { + const float top = 1234.1f; + const float left = 5678.2f; + const float bottom = 4321.3f; + const float right = 8765.4f; + gfx::InsetsF input(top, left, bottom, right); + mojo::Remote remote = GetTraitsTestRemote(); + gfx::InsetsF output; + remote->EchoInsetsF(input, &output); + EXPECT_EQ(top, output.top()); + EXPECT_EQ(left, output.left()); + EXPECT_EQ(bottom, output.bottom()); + EXPECT_EQ(right, output.right()); +} + +TEST_F(GeometryStructTraitsTest, Vector2d) { + const int32_t x = 1234; + const int32_t y = -5678; + gfx::Vector2d input(x, y); + mojo::Remote remote = GetTraitsTestRemote(); + gfx::Vector2d output; + remote->EchoVector2d(input, &output); + EXPECT_EQ(x, output.x()); + EXPECT_EQ(y, output.y()); +} + +TEST_F(GeometryStructTraitsTest, Vector2dF) { + const float x = 1234.5f; + const float y = 6789.6f; + gfx::Vector2dF input(x, y); + mojo::Remote remote = GetTraitsTestRemote(); + gfx::Vector2dF output; + remote->EchoVector2dF(input, &output); + EXPECT_EQ(x, output.x()); + EXPECT_EQ(y, output.y()); +} + +TEST_F(GeometryStructTraitsTest, Vector3dF) { + const float x = 1234.5f; + const float y = 6789.6f; + const float z = 5432.1f; + gfx::Vector3dF input(x, y, z); + mojo::Remote remote = GetTraitsTestRemote(); + gfx::Vector3dF output; + remote->EchoVector3dF(input, &output); + EXPECT_EQ(x, output.x()); + EXPECT_EQ(y, output.y()); + EXPECT_EQ(z, output.z()); +} + +TEST_F(GeometryStructTraitsTest, Quaternion) { + const double x = 1234.5; + const double y = 6789.6; + const double z = 31415.9; + const double w = 27182.8; + gfx::Quaternion input(x, y, z, w); + mojo::Remote remote = GetTraitsTestRemote(); + gfx::Quaternion output; + remote->EchoQuaternion(input, &output); + EXPECT_EQ(x, output.x()); + EXPECT_EQ(y, output.y()); + EXPECT_EQ(z, output.z()); + EXPECT_EQ(w, output.w()); +} + +} // namespace gfx diff --git a/geometry/mojom/geometry_traits_test_service.mojom b/geometry/mojom/geometry_traits_test_service.mojom new file mode 100644 index 000000000000..97b69adea1dd --- /dev/null +++ b/geometry/mojom/geometry_traits_test_service.mojom @@ -0,0 +1,50 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module gfx.mojom; + +import "ui/gfx/geometry/mojom/geometry.mojom"; + +// All functions on this interface echo their arguments to test StructTraits +// serialization and deserialization. +interface GeometryTraitsTestService { + [Sync] + EchoPoint(Point p) => (Point pass); + + [Sync] + EchoPointF(PointF p) => (PointF pass); + + [Sync] + EchoPoint3F(Point3F p) => (Point3F pass); + + [Sync] + EchoSize(Size s) => (Size pass); + + [Sync] + EchoSizeF(SizeF s) => (SizeF pass); + + [Sync] + EchoRect(Rect r) => (Rect pass); + + [Sync] + EchoRectF(RectF r) => (RectF pass); + + [Sync] + EchoInsets(Insets i) => (Insets pass); + + [Sync] + EchoInsetsF(InsetsF i) => (InsetsF pass); + + [Sync] + EchoVector2d(Vector2d v) => (Vector2d pass); + + [Sync] + EchoVector2dF(Vector2dF v) => (Vector2dF pass); + + [Sync] + EchoVector3dF(Vector3dF v) => (Vector3dF pass); + + [Sync] + EchoQuaternion(Quaternion q) => (Quaternion pass); +}; diff --git a/geometry/point.cc b/geometry/point.cc new file mode 100644 index 000000000000..1d2c19687be3 --- /dev/null +++ b/geometry/point.cc @@ -0,0 +1,105 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/point.h" + +#include "base/strings/stringprintf.h" +#include "build/build_config.h" +#include "ui/gfx/geometry/point_conversions.h" +#include "ui/gfx/geometry/point_f.h" + +#if defined(OS_WIN) +#include +#elif defined(OS_IOS) +#include +#elif defined(OS_MAC) +#include +#endif + +namespace gfx { + +#if defined(OS_WIN) +Point::Point(DWORD point) { + POINTS points = MAKEPOINTS(point); + x_ = points.x; + y_ = points.y; +} + +Point::Point(const POINT& point) : x_(point.x), y_(point.y) { +} + +Point& Point::operator=(const POINT& point) { + x_ = point.x; + y_ = point.y; + return *this; +} +#elif defined(OS_APPLE) +Point::Point(const CGPoint& point) : x_(point.x), y_(point.y) { +} +#endif + +#if defined(OS_WIN) +POINT Point::ToPOINT() const { + POINT p; + p.x = x(); + p.y = y(); + return p; +} +#elif defined(OS_APPLE) +CGPoint Point::ToCGPoint() const { + return CGPointMake(x(), y()); +} +#endif + +void Point::SetToMin(const Point& other) { + x_ = x_ <= other.x_ ? x_ : other.x_; + y_ = y_ <= other.y_ ? y_ : other.y_; +} + +void Point::SetToMax(const Point& other) { + x_ = x_ >= other.x_ ? x_ : other.x_; + y_ = y_ >= other.y_ ? y_ : other.y_; +} + +std::string Point::ToString() const { + return base::StringPrintf("%d,%d", x(), y()); +} + +Point ScaleToCeiledPoint(const Point& point, float x_scale, float y_scale) { + if (x_scale == 1.f && y_scale == 1.f) + return point; + return ToCeiledPoint(ScalePoint(gfx::PointF(point), x_scale, y_scale)); +} + +Point ScaleToCeiledPoint(const Point& point, float scale) { + if (scale == 1.f) + return point; + return ToCeiledPoint(ScalePoint(gfx::PointF(point), scale, scale)); +} + +Point ScaleToFlooredPoint(const Point& point, float x_scale, float y_scale) { + if (x_scale == 1.f && y_scale == 1.f) + return point; + return ToFlooredPoint(ScalePoint(gfx::PointF(point), x_scale, y_scale)); +} + +Point ScaleToFlooredPoint(const Point& point, float scale) { + if (scale == 1.f) + return point; + return ToFlooredPoint(ScalePoint(gfx::PointF(point), scale, scale)); +} + +Point ScaleToRoundedPoint(const Point& point, float x_scale, float y_scale) { + if (x_scale == 1.f && y_scale == 1.f) + return point; + return ToRoundedPoint(ScalePoint(gfx::PointF(point), x_scale, y_scale)); +} + +Point ScaleToRoundedPoint(const Point& point, float scale) { + if (scale == 1.f) + return point; + return ToRoundedPoint(ScalePoint(gfx::PointF(point), scale, scale)); +} + +} // namespace gfx diff --git a/geometry/point.h b/geometry/point.h new file mode 100644 index 000000000000..71c5a5b949bb --- /dev/null +++ b/geometry/point.h @@ -0,0 +1,148 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_POINT_H_ +#define UI_GFX_GEOMETRY_POINT_H_ + +#include +#include +#include + +#include "base/numerics/clamped_math.h" +#include "build/build_config.h" +#include "ui/gfx/geometry/geometry_export.h" +#include "ui/gfx/geometry/vector2d.h" + +#if defined(OS_WIN) +typedef unsigned long DWORD; +typedef struct tagPOINT POINT; +#elif defined(OS_APPLE) +typedef struct CGPoint CGPoint; +#endif + +namespace gfx { + +// A point has an x and y coordinate. +class GEOMETRY_EXPORT Point { + public: + constexpr Point() : x_(0), y_(0) {} + constexpr Point(int x, int y) : x_(x), y_(y) {} +#if defined(OS_WIN) + // |point| is a DWORD value that contains a coordinate. The x-coordinate is + // the low-order short and the y-coordinate is the high-order short. This + // value is commonly acquired from GetMessagePos/GetCursorPos. + explicit Point(DWORD point); + explicit Point(const POINT& point); + Point& operator=(const POINT& point); +#elif defined(OS_APPLE) + explicit Point(const CGPoint& point); +#endif + +#if defined(OS_WIN) + POINT ToPOINT() const; +#elif defined(OS_APPLE) + CGPoint ToCGPoint() const; +#endif + + constexpr int x() const { return x_; } + constexpr int y() const { return y_; } + void set_x(int x) { x_ = x; } + void set_y(int y) { y_ = y; } + + void SetPoint(int x, int y) { + x_ = x; + y_ = y; + } + + void Offset(int delta_x, int delta_y) { + x_ = base::ClampAdd(x_, delta_x); + y_ = base::ClampAdd(y_, delta_y); + } + + void operator+=(const Vector2d& vector) { + x_ = base::ClampAdd(x_, vector.x()); + y_ = base::ClampAdd(y_, vector.y()); + } + + void operator-=(const Vector2d& vector) { + x_ = base::ClampSub(x_, vector.x()); + y_ = base::ClampSub(y_, vector.y()); + } + + void SetToMin(const Point& other); + void SetToMax(const Point& other); + + bool IsOrigin() const { return x_ == 0 && y_ == 0; } + + Vector2d OffsetFromOrigin() const { return Vector2d(x_, y_); } + + // A point is less than another point if its y-value is closer + // to the origin. If the y-values are the same, then point with + // the x-value closer to the origin is considered less than the + // other. + // This comparison is required to use Point in sets, or sorted + // vectors. + bool operator<(const Point& rhs) const { + return std::tie(y_, x_) < std::tie(rhs.y_, rhs.x_); + } + + // Returns a string representation of point. + std::string ToString() const; + + private: + int x_; + int y_; +}; + +inline bool operator==(const Point& lhs, const Point& rhs) { + return lhs.x() == rhs.x() && lhs.y() == rhs.y(); +} + +inline bool operator!=(const Point& lhs, const Point& rhs) { + return !(lhs == rhs); +} + +inline Point operator+(const Point& lhs, const Vector2d& rhs) { + Point result(lhs); + result += rhs; + return result; +} + +inline Point operator-(const Point& lhs, const Vector2d& rhs) { + Point result(lhs); + result -= rhs; + return result; +} + +inline Vector2d operator-(const Point& lhs, const Point& rhs) { + return Vector2d(base::ClampSub(lhs.x(), rhs.x()), + base::ClampSub(lhs.y(), rhs.y())); +} + +inline Point PointAtOffsetFromOrigin(const Vector2d& offset_from_origin) { + return Point(offset_from_origin.x(), offset_from_origin.y()); +} + +// This is declared here for use in gtest-based unit tests but is defined in +// the //ui/gfx:test_support target. Depend on that to use this in your unit +// test. This should not be used in production code - call ToString() instead. +void PrintTo(const Point& point, ::std::ostream* os); + +// Helper methods to scale a gfx::Point to a new gfx::Point. +GEOMETRY_EXPORT Point ScaleToCeiledPoint(const Point& point, + float x_scale, + float y_scale); +GEOMETRY_EXPORT Point ScaleToCeiledPoint(const Point& point, float x_scale); +GEOMETRY_EXPORT Point ScaleToFlooredPoint(const Point& point, + float x_scale, + float y_scale); +GEOMETRY_EXPORT Point ScaleToFlooredPoint(const Point& point, float x_scale); +GEOMETRY_EXPORT Point ScaleToRoundedPoint(const Point& point, + float x_scale, + float y_scale); +GEOMETRY_EXPORT Point ScaleToRoundedPoint(const Point& point, float x_scale); + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_POINT_H_ diff --git a/geometry/point3_f.cc b/geometry/point3_f.cc new file mode 100644 index 000000000000..465376e55e83 --- /dev/null +++ b/geometry/point3_f.cc @@ -0,0 +1,40 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/point3_f.h" + +#include "base/strings/stringprintf.h" + +namespace gfx { + +std::string Point3F::ToString() const { + return base::StringPrintf("%f,%f,%f", x_, y_, z_); +} + +Point3F operator+(const Point3F& lhs, const Vector3dF& rhs) { + float x = lhs.x() + rhs.x(); + float y = lhs.y() + rhs.y(); + float z = lhs.z() + rhs.z(); + return Point3F(x, y, z); +} + +// Subtract a vector from a point, producing a new point offset by the vector's +// inverse. +Point3F operator-(const Point3F& lhs, const Vector3dF& rhs) { + float x = lhs.x() - rhs.x(); + float y = lhs.y() - rhs.y(); + float z = lhs.z() - rhs.z(); + return Point3F(x, y, z); +} + +// Subtract one point from another, producing a vector that represents the +// distances between the two points along each axis. +Vector3dF operator-(const Point3F& lhs, const Point3F& rhs) { + float x = lhs.x() - rhs.x(); + float y = lhs.y() - rhs.y(); + float z = lhs.z() - rhs.z(); + return Vector3dF(x, y, z); +} + +} // namespace gfx diff --git a/geometry/point3_f.h b/geometry/point3_f.h new file mode 100644 index 000000000000..e295e988ffe5 --- /dev/null +++ b/geometry/point3_f.h @@ -0,0 +1,128 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_POINT3_F_H_ +#define UI_GFX_GEOMETRY_POINT3_F_H_ + +#include +#include + +#include "ui/gfx/geometry/geometry_export.h" +#include "ui/gfx/geometry/point_f.h" +#include "ui/gfx/geometry/vector3d_f.h" + +namespace gfx { + +// A point has an x, y and z coordinate. +class GEOMETRY_EXPORT Point3F { + public: + constexpr Point3F() : x_(0), y_(0), z_(0) {} + constexpr Point3F(float x, float y, float z) : x_(x), y_(y), z_(z) {} + + constexpr explicit Point3F(const PointF& point) + : x_(point.x()), y_(point.y()), z_(0) {} + + void Scale(float scale) { + Scale(scale, scale, scale); + } + + void Scale(float x_scale, float y_scale, float z_scale) { + SetPoint(x() * x_scale, y() * y_scale, z() * z_scale); + } + + constexpr float x() const { return x_; } + constexpr float y() const { return y_; } + constexpr float z() const { return z_; } + + void set_x(float x) { x_ = x; } + void set_y(float y) { y_ = y; } + void set_z(float z) { z_ = z; } + + void SetPoint(float x, float y, float z) { + x_ = x; + y_ = y; + z_ = z; + } + + // Offset the point by the given vector. + void operator+=(const Vector3dF& v) { + x_ += v.x(); + y_ += v.y(); + z_ += v.z(); + } + + // Offset the point by the given vector's inverse. + void operator-=(const Vector3dF& v) { + x_ -= v.x(); + y_ -= v.y(); + z_ -= v.z(); + } + + // Returns the squared euclidean distance between two points. + float SquaredDistanceTo(const Point3F& other) const { + float dx = x_ - other.x_; + float dy = y_ - other.y_; + float dz = z_ - other.z_; + return dx * dx + dy * dy + dz * dz; + } + + PointF AsPointF() const { return PointF(x_, y_); } + + // Returns a string representation of 3d point. + std::string ToString() const; + + private: + float x_; + float y_; + float z_; + + // copy/assign are allowed. +}; + +inline bool operator==(const Point3F& lhs, const Point3F& rhs) { + return lhs.x() == rhs.x() && lhs.y() == rhs.y() && lhs.z() == rhs.z(); +} + +inline bool operator!=(const Point3F& lhs, const Point3F& rhs) { + return !(lhs == rhs); +} + +// Add a vector to a point, producing a new point offset by the vector. +GEOMETRY_EXPORT Point3F operator+(const Point3F& lhs, const Vector3dF& rhs); + +// Subtract a vector from a point, producing a new point offset by the vector's +// inverse. +GEOMETRY_EXPORT Point3F operator-(const Point3F& lhs, const Vector3dF& rhs); + +// Subtract one point from another, producing a vector that represents the +// distances between the two points along each axis. +GEOMETRY_EXPORT Vector3dF operator-(const Point3F& lhs, const Point3F& rhs); + +inline Point3F PointAtOffsetFromOrigin(const Vector3dF& offset) { + return Point3F(offset.x(), offset.y(), offset.z()); +} + +inline Point3F ScalePoint(const Point3F& p, + float x_scale, + float y_scale, + float z_scale) { + return Point3F(p.x() * x_scale, p.y() * y_scale, p.z() * z_scale); +} + +inline Point3F ScalePoint(const Point3F& p, const Vector3dF& v) { + return Point3F(p.x() * v.x(), p.y() * v.y(), p.z() * v.z()); +} + +inline Point3F ScalePoint(const Point3F& p, float scale) { + return ScalePoint(p, scale, scale, scale); +} + +// This is declared here for use in gtest-based unit tests but is defined in +// the //ui/gfx:test_support target. Depend on that to use this in your unit +// test. This should not be used in production code - call ToString() instead. +void PrintTo(const Point3F& point, ::std::ostream* os); + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_POINT3_F_H_ diff --git a/geometry/point3_unittest.cc b/geometry/point3_unittest.cc new file mode 100644 index 000000000000..e1926ef9f2a0 --- /dev/null +++ b/geometry/point3_unittest.cc @@ -0,0 +1,71 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/cxx17_backports.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/geometry/point3_f.h" + +namespace gfx { + +TEST(Point3Test, VectorArithmetic) { + gfx::Point3F a(1.6f, 5.1f, 3.2f); + gfx::Vector3dF v1(3.1f, -3.2f, 9.3f); + gfx::Vector3dF v2(-8.1f, 1.2f, 3.3f); + + static const struct { + gfx::Point3F expected; + gfx::Point3F actual; + } tests[] = { + { gfx::Point3F(4.7f, 1.9f, 12.5f), a + v1 }, + { gfx::Point3F(-1.5f, 8.3f, -6.1f), a - v1 }, + { a, a - v1 + v1 }, + { a, a + v1 - v1 }, + { a, a + gfx::Vector3dF() }, + { gfx::Point3F(12.8f, 0.7f, 9.2f), a + v1 - v2 }, + { gfx::Point3F(-9.6f, 9.5f, -2.8f), a - v1 + v2 } + }; + + for (size_t i = 0; i < base::size(tests); ++i) + EXPECT_EQ(tests[i].expected.ToString(), + tests[i].actual.ToString()); + + a += v1; + EXPECT_EQ(Point3F(4.7f, 1.9f, 12.5f).ToString(), a.ToString()); + + a -= v2; + EXPECT_EQ(Point3F(12.8f, 0.7f, 9.2f).ToString(), a.ToString()); +} + +TEST(Point3Test, VectorFromPoints) { + gfx::Point3F a(1.6f, 5.2f, 3.2f); + gfx::Vector3dF v1(3.1f, -3.2f, 9.3f); + + gfx::Point3F b(a + v1); + EXPECT_EQ((b - a).ToString(), v1.ToString()); +} + +TEST(Point3Test, Scale) { + EXPECT_EQ(Point3F().ToString(), ScalePoint(Point3F(), 2.f).ToString()); + EXPECT_EQ(Point3F().ToString(), + ScalePoint(Point3F(), 2.f, 2.f, 2.f).ToString()); + + EXPECT_EQ(Point3F(2.f, -2.f, 4.f).ToString(), + ScalePoint(Point3F(1.f, -1.f, 2.f), 2.f).ToString()); + EXPECT_EQ(Point3F(2.f, -3.f, 8.f).ToString(), + ScalePoint(Point3F(1.f, -1.f, 2.f), 2.f, 3.f, 4.f).ToString()); + + Point3F zero; + zero.Scale(2.f); + zero.Scale(6.f, 3.f, 1.5f); + EXPECT_EQ(Point3F().ToString(), zero.ToString()); + + Point3F point(1.f, -1.f, 2.f); + point.Scale(2.f); + point.Scale(6.f, 3.f, 1.5f); + EXPECT_EQ(Point3F(12.f, -6.f, 6.f).ToString(), point.ToString()); +} + +} // namespace gfx diff --git a/geometry/point_conversions.cc b/geometry/point_conversions.cc new file mode 100644 index 000000000000..bf800da0289b --- /dev/null +++ b/geometry/point_conversions.cc @@ -0,0 +1,24 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/point_conversions.h" + +#include "base/numerics/safe_conversions.h" + +namespace gfx { + +Point ToFlooredPoint(const PointF& point) { + return Point(base::ClampFloor(point.x()), base::ClampFloor(point.y())); +} + +Point ToCeiledPoint(const PointF& point) { + return Point(base::ClampCeil(point.x()), base::ClampCeil(point.y())); +} + +Point ToRoundedPoint(const PointF& point) { + return Point(base::ClampRound(point.x()), base::ClampRound(point.y())); +} + +} // namespace gfx + diff --git a/geometry/point_conversions.h b/geometry/point_conversions.h new file mode 100644 index 000000000000..894272c6bd3c --- /dev/null +++ b/geometry/point_conversions.h @@ -0,0 +1,24 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_POINT_CONVERSIONS_H_ +#define UI_GFX_GEOMETRY_POINT_CONVERSIONS_H_ + +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/point_f.h" + +namespace gfx { + +// Returns a Point with each component from the input PointF floored. +GEOMETRY_EXPORT Point ToFlooredPoint(const PointF& point); + +// Returns a Point with each component from the input PointF ceiled. +GEOMETRY_EXPORT Point ToCeiledPoint(const PointF& point); + +// Returns a Point with each component from the input PointF rounded. +GEOMETRY_EXPORT Point ToRoundedPoint(const PointF& point); + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_POINT_CONVERSIONS_H_ diff --git a/geometry/point_f.cc b/geometry/point_f.cc new file mode 100644 index 000000000000..0a7e593516f3 --- /dev/null +++ b/geometry/point_f.cc @@ -0,0 +1,44 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/point_f.h" + +#include + +#include "base/check.h" +#include "base/strings/stringprintf.h" + +namespace gfx { + +void PointF::SetToMin(const PointF& other) { + x_ = x_ <= other.x_ ? x_ : other.x_; + y_ = y_ <= other.y_ ? y_ : other.y_; +} + +void PointF::SetToMax(const PointF& other) { + x_ = x_ >= other.x_ ? x_ : other.x_; + y_ = y_ >= other.y_ ? y_ : other.y_; +} + +bool PointF::IsWithinDistance(const PointF& rhs, + const float allowed_distance) const { + DCHECK(allowed_distance > 0); + float diff_x = x_ - rhs.x(); + float diff_y = y_ - rhs.y(); + float distance = std::sqrt(diff_x * diff_x + diff_y * diff_y); + return distance < allowed_distance; +} + +std::string PointF::ToString() const { + return base::StringPrintf("%f,%f", x(), y()); +} + +PointF ScalePoint(const PointF& p, float x_scale, float y_scale) { + PointF scaled_p(p); + scaled_p.Scale(x_scale, y_scale); + return scaled_p; +} + + +} // namespace gfx diff --git a/geometry/point_f.h b/geometry/point_f.h new file mode 100644 index 000000000000..b020325a7685 --- /dev/null +++ b/geometry/point_f.h @@ -0,0 +1,133 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_POINT_F_H_ +#define UI_GFX_GEOMETRY_POINT_F_H_ + +#include +#include +#include + +#include "ui/gfx/geometry/geometry_export.h" +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/vector2d_f.h" + +namespace gfx { + +// A floating version of gfx::Point. +class GEOMETRY_EXPORT PointF { + public: + constexpr PointF() : x_(0.f), y_(0.f) {} + constexpr PointF(float x, float y) : x_(x), y_(y) {} + + constexpr explicit PointF(const Point& p) + : PointF(static_cast(p.x()), static_cast(p.y())) {} + + constexpr float x() const { return x_; } + constexpr float y() const { return y_; } + void set_x(float x) { x_ = x; } + void set_y(float y) { y_ = y; } + + void SetPoint(float x, float y) { + x_ = x; + y_ = y; + } + + void Offset(float delta_x, float delta_y) { + x_ += delta_x; + y_ += delta_y; + } + + void operator+=(const Vector2dF& vector) { + x_ += vector.x(); + y_ += vector.y(); + } + + void operator-=(const Vector2dF& vector) { + x_ -= vector.x(); + y_ -= vector.y(); + } + + void SetToMin(const PointF& other); + void SetToMax(const PointF& other); + + bool IsOrigin() const { return x_ == 0 && y_ == 0; } + + Vector2dF OffsetFromOrigin() const { return Vector2dF(x_, y_); } + + // A point is less than another point if its y-value is closer + // to the origin. If the y-values are the same, then point with + // the x-value closer to the origin is considered less than the + // other. + // This comparison is required to use PointF in sets, or sorted + // vectors. + bool operator<(const PointF& rhs) const { + return std::tie(y_, x_) < std::tie(rhs.y_, rhs.x_); + } + + void Scale(float scale) { + Scale(scale, scale); + } + + void Scale(float x_scale, float y_scale) { + SetPoint(x() * x_scale, y() * y_scale); + } + + // Uses the Pythagorean theorem to determine the straight line distance + // between the two points, and returns true if it is less than + // |allowed_distance|. + bool IsWithinDistance(const PointF& rhs, const float allowed_distance) const; + + // Returns a string representation of point. + std::string ToString() const; + + private: + float x_; + float y_; +}; + +inline bool operator==(const PointF& lhs, const PointF& rhs) { + return lhs.x() == rhs.x() && lhs.y() == rhs.y(); +} + +inline bool operator!=(const PointF& lhs, const PointF& rhs) { + return !(lhs == rhs); +} + +inline PointF operator+(const PointF& lhs, const Vector2dF& rhs) { + PointF result(lhs); + result += rhs; + return result; +} + +inline PointF operator-(const PointF& lhs, const Vector2dF& rhs) { + PointF result(lhs); + result -= rhs; + return result; +} + +inline Vector2dF operator-(const PointF& lhs, const PointF& rhs) { + return Vector2dF(lhs.x() - rhs.x(), lhs.y() - rhs.y()); +} + +inline PointF PointAtOffsetFromOrigin(const Vector2dF& offset_from_origin) { + return PointF(offset_from_origin.x(), offset_from_origin.y()); +} + +GEOMETRY_EXPORT PointF ScalePoint(const PointF& p, + float x_scale, + float y_scale); + +inline PointF ScalePoint(const PointF& p, float scale) { + return ScalePoint(p, scale, scale); +} + +// This is declared here for use in gtest-based unit tests but is defined in +// the //ui/gfx:test_support target. Depend on that to use this in your unit +// test. This should not be used in production code - call ToString() instead. +void PrintTo(const PointF& point, ::std::ostream* os); + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_POINT_F_H_ diff --git a/geometry/point_unittest.cc b/geometry/point_unittest.cc new file mode 100644 index 000000000000..90f61aef9ade --- /dev/null +++ b/geometry/point_unittest.cc @@ -0,0 +1,255 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/cxx17_backports.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/point_conversions.h" +#include "ui/gfx/geometry/point_f.h" + +namespace gfx { + +TEST(PointTest, ToPointF) { + // Check that explicit conversion from integer to float compiles. + Point a(10, 20); + PointF b = PointF(a); + + EXPECT_EQ(static_cast(a.x()), b.x()); + EXPECT_EQ(static_cast(a.y()), b.y()); +} + +TEST(PointTest, IsOrigin) { + EXPECT_FALSE(Point(1, 0).IsOrigin()); + EXPECT_FALSE(Point(0, 1).IsOrigin()); + EXPECT_FALSE(Point(1, 2).IsOrigin()); + EXPECT_FALSE(Point(-1, 0).IsOrigin()); + EXPECT_FALSE(Point(0, -1).IsOrigin()); + EXPECT_FALSE(Point(-1, -2).IsOrigin()); + EXPECT_TRUE(Point(0, 0).IsOrigin()); + + EXPECT_FALSE(PointF(0.1f, 0).IsOrigin()); + EXPECT_FALSE(PointF(0, 0.1f).IsOrigin()); + EXPECT_FALSE(PointF(0.1f, 2).IsOrigin()); + EXPECT_FALSE(PointF(-0.1f, 0).IsOrigin()); + EXPECT_FALSE(PointF(0, -0.1f).IsOrigin()); + EXPECT_FALSE(PointF(-0.1f, -2).IsOrigin()); + EXPECT_TRUE(PointF(0, 0).IsOrigin()); +} + +TEST(PointTest, VectorArithmetic) { + Point a(1, 5); + Vector2d v1(3, -3); + Vector2d v2(-8, 1); + + static const struct { + Point expected; + Point actual; + } tests[] = { + { Point(4, 2), a + v1 }, + { Point(-2, 8), a - v1 }, + { a, a - v1 + v1 }, + { a, a + v1 - v1 }, + { a, a + Vector2d() }, + { Point(12, 1), a + v1 - v2 }, + { Point(-10, 9), a - v1 + v2 } + }; + + for (size_t i = 0; i < base::size(tests); ++i) + EXPECT_EQ(tests[i].expected.ToString(), tests[i].actual.ToString()); +} + +TEST(PointTest, OffsetFromPoint) { + Point a(1, 5); + Point b(-20, 8); + EXPECT_EQ(Vector2d(-20 - 1, 8 - 5).ToString(), (b - a).ToString()); +} + +TEST(PointTest, ToRoundedPoint) { + EXPECT_EQ(Point(0, 0), ToRoundedPoint(PointF(0, 0))); + EXPECT_EQ(Point(0, 0), ToRoundedPoint(PointF(0.0001f, 0.0001f))); + EXPECT_EQ(Point(0, 0), ToRoundedPoint(PointF(0.4999f, 0.4999f))); + EXPECT_EQ(Point(1, 1), ToRoundedPoint(PointF(0.5f, 0.5f))); + EXPECT_EQ(Point(1, 1), ToRoundedPoint(PointF(0.9999f, 0.9999f))); + + EXPECT_EQ(Point(10, 10), ToRoundedPoint(PointF(10, 10))); + EXPECT_EQ(Point(10, 10), ToRoundedPoint(PointF(10.0001f, 10.0001f))); + EXPECT_EQ(Point(10, 10), ToRoundedPoint(PointF(10.4999f, 10.4999f))); + EXPECT_EQ(Point(11, 11), ToRoundedPoint(PointF(10.5f, 10.5f))); + EXPECT_EQ(Point(11, 11), ToRoundedPoint(PointF(10.9999f, 10.9999f))); + + EXPECT_EQ(Point(-10, -10), ToRoundedPoint(PointF(-10, -10))); + EXPECT_EQ(Point(-10, -10), ToRoundedPoint(PointF(-10.0001f, -10.0001f))); + EXPECT_EQ(Point(-10, -10), ToRoundedPoint(PointF(-10.4999f, -10.4999f))); + EXPECT_EQ(Point(-11, -11), ToRoundedPoint(PointF(-10.5f, -10.5f))); + EXPECT_EQ(Point(-11, -11), ToRoundedPoint(PointF(-10.9999f, -10.9999f))); +} + +TEST(PointTest, Scale) { + EXPECT_EQ(PointF().ToString(), ScalePoint(PointF(), 2).ToString()); + EXPECT_EQ(PointF().ToString(), ScalePoint(PointF(), 2, 2).ToString()); + + EXPECT_EQ(PointF(2, -2).ToString(), ScalePoint(PointF(1, -1), 2).ToString()); + EXPECT_EQ(PointF(2, -2).ToString(), + ScalePoint(PointF(1, -1), 2, 2).ToString()); + + PointF zero; + PointF one(1, -1); + + zero.Scale(2); + zero.Scale(3, 1.5); + + one.Scale(2); + one.Scale(3, 1.5); + + EXPECT_EQ(PointF().ToString(), zero.ToString()); + EXPECT_EQ(PointF(6, -3).ToString(), one.ToString()); +} + +TEST(PointTest, ClampPoint) { + Point a; + + a = Point(3, 5); + EXPECT_EQ(Point(3, 5).ToString(), a.ToString()); + a.SetToMax(Point(2, 4)); + EXPECT_EQ(Point(3, 5).ToString(), a.ToString()); + a.SetToMax(Point(3, 5)); + EXPECT_EQ(Point(3, 5).ToString(), a.ToString()); + a.SetToMax(Point(4, 2)); + EXPECT_EQ(Point(4, 5).ToString(), a.ToString()); + a.SetToMax(Point(8, 10)); + EXPECT_EQ(Point(8, 10).ToString(), a.ToString()); + + a.SetToMin(Point(9, 11)); + EXPECT_EQ(Point(8, 10).ToString(), a.ToString()); + a.SetToMin(Point(8, 10)); + EXPECT_EQ(Point(8, 10).ToString(), a.ToString()); + a.SetToMin(Point(11, 9)); + EXPECT_EQ(Point(8, 9).ToString(), a.ToString()); + a.SetToMin(Point(7, 11)); + EXPECT_EQ(Point(7, 9).ToString(), a.ToString()); + a.SetToMin(Point(3, 5)); + EXPECT_EQ(Point(3, 5).ToString(), a.ToString()); +} + +TEST(PointTest, ClampPointF) { + PointF a; + + a = PointF(3.5f, 5.5f); + EXPECT_EQ(PointF(3.5f, 5.5f).ToString(), a.ToString()); + a.SetToMax(PointF(2.5f, 4.5f)); + EXPECT_EQ(PointF(3.5f, 5.5f).ToString(), a.ToString()); + a.SetToMax(PointF(3.5f, 5.5f)); + EXPECT_EQ(PointF(3.5f, 5.5f).ToString(), a.ToString()); + a.SetToMax(PointF(4.5f, 2.5f)); + EXPECT_EQ(PointF(4.5f, 5.5f).ToString(), a.ToString()); + a.SetToMax(PointF(8.5f, 10.5f)); + EXPECT_EQ(PointF(8.5f, 10.5f).ToString(), a.ToString()); + + a.SetToMin(PointF(9.5f, 11.5f)); + EXPECT_EQ(PointF(8.5f, 10.5f).ToString(), a.ToString()); + a.SetToMin(PointF(8.5f, 10.5f)); + EXPECT_EQ(PointF(8.5f, 10.5f).ToString(), a.ToString()); + a.SetToMin(PointF(11.5f, 9.5f)); + EXPECT_EQ(PointF(8.5f, 9.5f).ToString(), a.ToString()); + a.SetToMin(PointF(7.5f, 11.5f)); + EXPECT_EQ(PointF(7.5f, 9.5f).ToString(), a.ToString()); + a.SetToMin(PointF(3.5f, 5.5f)); + EXPECT_EQ(PointF(3.5f, 5.5f).ToString(), a.ToString()); +} + +TEST(PointTest, Offset) { + Point test(3, 4); + test.Offset(5, -8); + EXPECT_EQ(test, Point(8, -4)); +} + +TEST(PointTest, VectorMath) { + Point test = Point(3, 4); + test += Vector2d(5, -8); + EXPECT_EQ(test, Point(8, -4)); + + Point test2 = Point(3, 4); + test2 -= Vector2d(5, -8); + EXPECT_EQ(test2, Point(-2, 12)); +} + +TEST(PointTest, IntegerOverflow) { + int int_max = std::numeric_limits::max(); + int int_min = std::numeric_limits::min(); + + Point max_point(int_max, int_max); + Point min_point(int_min, int_min); + Point test; + + test = Point(); + test.Offset(int_max, int_max); + EXPECT_EQ(test, max_point); + + test = Point(); + test.Offset(int_min, int_min); + EXPECT_EQ(test, min_point); + + test = Point(10, 20); + test.Offset(int_max, int_max); + EXPECT_EQ(test, max_point); + + test = Point(-10, -20); + test.Offset(int_min, int_min); + EXPECT_EQ(test, min_point); + + test = Point(); + test += Vector2d(int_max, int_max); + EXPECT_EQ(test, max_point); + + test = Point(); + test += Vector2d(int_min, int_min); + EXPECT_EQ(test, min_point); + + test = Point(10, 20); + test += Vector2d(int_max, int_max); + EXPECT_EQ(test, max_point); + + test = Point(-10, -20); + test += Vector2d(int_min, int_min); + EXPECT_EQ(test, min_point); + + test = Point(); + test -= Vector2d(int_max, int_max); + EXPECT_EQ(test, Point(-int_max, -int_max)); + + test = Point(); + test -= Vector2d(int_min, int_min); + EXPECT_EQ(test, max_point); + + test = Point(10, 20); + test -= Vector2d(int_min, int_min); + EXPECT_EQ(test, max_point); + + test = Point(-10, -20); + test -= Vector2d(int_max, int_max); + EXPECT_EQ(test, min_point); +} + +TEST(PointTest, IsWithinDistance) { + PointF pt(10.f, 10.f); + EXPECT_TRUE(pt.IsWithinDistance(PointF(10.f, 10.f), + /*allowed_distance=*/0.0000000000001f)); + EXPECT_FALSE(pt.IsWithinDistance(PointF(8.f, 8.f), /*allowed_distance=*/1.f)); + + pt = PointF(-10.f, -10.f); + EXPECT_FALSE( + pt.IsWithinDistance(PointF(10.f, 10.f), /*allowed_distance=*/10.f)); + EXPECT_TRUE(pt.IsWithinDistance(PointF(-9.9988f, -10.0013f), + /*epsallowed_distanceilon=*/0.0017689f)); + + pt = PointF(std::numeric_limits::max(), + std::numeric_limits::max()); + EXPECT_FALSE(pt.IsWithinDistance(PointF(std::numeric_limits::min(), + std::numeric_limits::min()), + /*allowed_distance=*/100.f)); +} + +} // namespace gfx diff --git a/geometry/quad_f.cc b/geometry/quad_f.cc new file mode 100644 index 000000000000..8ed8b91700fb --- /dev/null +++ b/geometry/quad_f.cc @@ -0,0 +1,134 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/quad_f.h" + +#include + +#include "base/strings/stringprintf.h" + +namespace gfx { + +void QuadF::operator=(const RectF& rect) { + p1_ = PointF(rect.x(), rect.y()); + p2_ = PointF(rect.right(), rect.y()); + p3_ = PointF(rect.right(), rect.bottom()); + p4_ = PointF(rect.x(), rect.bottom()); +} + +std::string QuadF::ToString() const { + return base::StringPrintf("%s;%s;%s;%s", + p1_.ToString().c_str(), + p2_.ToString().c_str(), + p3_.ToString().c_str(), + p4_.ToString().c_str()); +} + +static inline bool WithinEpsilon(float a, float b) { + return std::abs(a - b) < std::numeric_limits::epsilon(); +} + +bool QuadF::IsRectilinear() const { + return + (WithinEpsilon(p1_.x(), p2_.x()) && WithinEpsilon(p2_.y(), p3_.y()) && + WithinEpsilon(p3_.x(), p4_.x()) && WithinEpsilon(p4_.y(), p1_.y())) || + (WithinEpsilon(p1_.y(), p2_.y()) && WithinEpsilon(p2_.x(), p3_.x()) && + WithinEpsilon(p3_.y(), p4_.y()) && WithinEpsilon(p4_.x(), p1_.x())); +} + +bool QuadF::IsCounterClockwise() const { + // This math computes the signed area of the quad. Positive area + // indicates the quad is clockwise; negative area indicates the quad is + // counter-clockwise. Note carefully: this is backwards from conventional + // math because our geometric space uses screen coordiantes with y-axis + // pointing downards. + // Reference: http://mathworld.wolfram.com/PolygonArea.html. + // The equation can be written: + // Signed area = determinant1 + determinant2 + determinant3 + determinant4 + // In practise, Refactoring the computation of adding determinants so that + // reducing the number of operations. The equation is: + // Signed area = element1 + element2 - element3 - element4 + + float p24 = p2_.y() - p4_.y(); + float p31 = p3_.y() - p1_.y(); + + // Up-cast to double so this cannot overflow. + double element1 = static_cast(p1_.x()) * p24; + double element2 = static_cast(p2_.x()) * p31; + double element3 = static_cast(p3_.x()) * p24; + double element4 = static_cast(p4_.x()) * p31; + + return element1 + element2 < element3 + element4; +} + +static inline bool PointIsInTriangle(const PointF& point, + const PointF& r1, + const PointF& r2, + const PointF& r3) { + // Compute the barycentric coordinates (u, v, w) of |point| relative to the + // triangle (r1, r2, r3) by the solving the system of equations: + // 1) point = u * r1 + v * r2 + w * r3 + // 2) u + v + w = 1 + // This algorithm comes from Christer Ericson's Real-Time Collision Detection. + + Vector2dF r31 = r1 - r3; + Vector2dF r32 = r2 - r3; + Vector2dF r3p = point - r3; + + // Promote to doubles so all the math below is done with doubles, because + // otherwise it gets incorrect results on arm64. + double r31x = r31.x(); + double r31y = r31.y(); + double r32x = r32.x(); + double r32y = r32.y(); + + double denom = r32y * r31x - r32x * r31y; + double u = (r32y * r3p.x() - r32x * r3p.y()) / denom; + double v = (r31x * r3p.y() - r31y * r3p.x()) / denom; + double w = 1.0 - u - v; + + // Use the barycentric coordinates to test if |point| is inside the + // triangle (r1, r2, r2). + return (u >= 0) && (v >= 0) && (w >= 0); +} + +bool QuadF::Contains(const PointF& point) const { + return PointIsInTriangle(point, p1_, p2_, p3_) + || PointIsInTriangle(point, p1_, p3_, p4_); +} + +void QuadF::Scale(float x_scale, float y_scale) { + p1_.Scale(x_scale, y_scale); + p2_.Scale(x_scale, y_scale); + p3_.Scale(x_scale, y_scale); + p4_.Scale(x_scale, y_scale); +} + +void QuadF::operator+=(const Vector2dF& rhs) { + p1_ += rhs; + p2_ += rhs; + p3_ += rhs; + p4_ += rhs; +} + +void QuadF::operator-=(const Vector2dF& rhs) { + p1_ -= rhs; + p2_ -= rhs; + p3_ -= rhs; + p4_ -= rhs; +} + +QuadF operator+(const QuadF& lhs, const Vector2dF& rhs) { + QuadF result = lhs; + result += rhs; + return result; +} + +QuadF operator-(const QuadF& lhs, const Vector2dF& rhs) { + QuadF result = lhs; + result -= rhs; + return result; +} + +} // namespace gfx diff --git a/geometry/quad_f.h b/geometry/quad_f.h new file mode 100644 index 000000000000..3fe8cf10d5df --- /dev/null +++ b/geometry/quad_f.h @@ -0,0 +1,130 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_QUAD_F_H_ +#define UI_GFX_GEOMETRY_QUAD_F_H_ + +#include + +#include +#include +#include +#include + +#include "base/check_op.h" +#include "ui/gfx/geometry/geometry_export.h" +#include "ui/gfx/geometry/point_f.h" +#include "ui/gfx/geometry/rect_f.h" + +namespace gfx { + +// A Quad is defined by four corners, allowing it to have edges that are not +// axis-aligned, unlike a Rect. +class GEOMETRY_EXPORT QuadF { + public: + constexpr QuadF() = default; + constexpr QuadF(const PointF& p1, + const PointF& p2, + const PointF& p3, + const PointF& p4) + : p1_(p1), p2_(p2), p3_(p3), p4_(p4) {} + + constexpr explicit QuadF(const RectF& rect) + : p1_(rect.x(), rect.y()), + p2_(rect.right(), rect.y()), + p3_(rect.right(), rect.bottom()), + p4_(rect.x(), rect.bottom()) {} + + void operator=(const RectF& rect); + + void set_p1(const PointF& p) { p1_ = p; } + void set_p2(const PointF& p) { p2_ = p; } + void set_p3(const PointF& p) { p3_ = p; } + void set_p4(const PointF& p) { p4_ = p; } + + constexpr const PointF& p1() const { return p1_; } + constexpr const PointF& p2() const { return p2_; } + constexpr const PointF& p3() const { return p3_; } + constexpr const PointF& p4() const { return p4_; } + + // Returns true if the quad is an axis-aligned rectangle. + bool IsRectilinear() const; + + // Returns true if the points of the quad are in counter-clockwise order. This + // assumes that the quad is convex, and that no three points are collinear. + bool IsCounterClockwise() const; + + // Returns true if the |point| is contained within the quad, or lies on on + // edge of the quad. This assumes that the quad is convex. + bool Contains(const gfx::PointF& point) const; + + // Returns a rectangle that bounds the four points of the quad. The points of + // the quad may lie on the right/bottom edge of the resulting rectangle, + // rather than being strictly inside it. + RectF BoundingBox() const { + float rl = std::min({p1_.x(), p2_.x(), p3_.x(), p4_.x()}); + float rr = std::max({p1_.x(), p2_.x(), p3_.x(), p4_.x()}); + float rt = std::min({p1_.y(), p2_.y(), p3_.y(), p4_.y()}); + float rb = std::max({p1_.y(), p2_.y(), p3_.y(), p4_.y()}); + return RectF(rl, rt, rr - rl, rb - rt); + } + + // Realigns the corners in the quad by rotating them n corners to the right. + void Realign(size_t times) { + DCHECK_LE(times, 4u); + for (size_t i = 0; i < times; ++i) { + PointF temp = p1_; + p1_ = p2_; + p2_ = p3_; + p3_ = p4_; + p4_ = temp; + } + } + + // Add a vector to the quad, offseting each point in the quad by the vector. + void operator+=(const Vector2dF& rhs); + // Subtract a vector from the quad, offseting each point in the quad by the + // inverse of the vector. + void operator-=(const Vector2dF& rhs); + + // Scale each point in the quad by the |scale| factor. + void Scale(float scale) { Scale(scale, scale); } + + // Scale each point in the quad by the scale factors along each axis. + void Scale(float x_scale, float y_scale); + + // Returns a string representation of quad. + std::string ToString() const; + + private: + PointF p1_; + PointF p2_; + PointF p3_; + PointF p4_; +}; + +inline bool operator==(const QuadF& lhs, const QuadF& rhs) { + return + lhs.p1() == rhs.p1() && lhs.p2() == rhs.p2() && + lhs.p3() == rhs.p3() && lhs.p4() == rhs.p4(); +} + +inline bool operator!=(const QuadF& lhs, const QuadF& rhs) { + return !(lhs == rhs); +} + +// Add a vector to a quad, offseting each point in the quad by the vector. +GEOMETRY_EXPORT QuadF operator+(const QuadF& lhs, const Vector2dF& rhs); +// Subtract a vector from a quad, offseting each point in the quad by the +// inverse of the vector. +GEOMETRY_EXPORT QuadF operator-(const QuadF& lhs, const Vector2dF& rhs); + +// This is declared here for use in gtest-based unit tests but is defined in +// the //ui/gfx:test_support target. Depend on that to use this in your unit +// test. This should not be used in production code - call ToString() instead. +void PrintTo(const QuadF& quad, ::std::ostream* os); + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_QUAD_F_H_ diff --git a/geometry/quad_unittest.cc b/geometry/quad_unittest.cc new file mode 100644 index 000000000000..87849b0c4513 --- /dev/null +++ b/geometry/quad_unittest.cc @@ -0,0 +1,361 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/cxx17_backports.h" +#include "build/build_config.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/geometry/quad_f.h" +#include "ui/gfx/geometry/rect_f.h" + +namespace gfx { + +TEST(QuadTest, Construction) { + // Verify constructors. + PointF a(1, 1); + PointF b(2, 1); + PointF c(2, 2); + PointF d(1, 2); + PointF e; + QuadF q1; + QuadF q2(e, e, e, e); + QuadF q3(a, b, c, d); + QuadF q4(BoundingRect(a, c)); + EXPECT_EQ(q1, q2); + EXPECT_EQ(q3, q4); + + // Verify getters. + EXPECT_EQ(q3.p1(), a); + EXPECT_EQ(q3.p2(), b); + EXPECT_EQ(q3.p3(), c); + EXPECT_EQ(q3.p4(), d); + + // Verify setters. + q3.set_p1(b); + q3.set_p2(c); + q3.set_p3(d); + q3.set_p4(a); + EXPECT_EQ(q3.p1(), b); + EXPECT_EQ(q3.p2(), c); + EXPECT_EQ(q3.p3(), d); + EXPECT_EQ(q3.p4(), a); + + // Verify operator=(Rect) + EXPECT_NE(q1, q4); + q1 = BoundingRect(a, c); + EXPECT_EQ(q1, q4); + + // Verify operator=(Quad) + EXPECT_NE(q1, q3); + q1 = q3; + EXPECT_EQ(q1, q3); +} + +TEST(QuadTest, AddingVectors) { + PointF a(1, 1); + PointF b(2, 1); + PointF c(2, 2); + PointF d(1, 2); + Vector2dF v(3.5f, -2.5f); + + QuadF q1(a, b, c, d); + QuadF added = q1 + v; + q1 += v; + QuadF expected1(PointF(4.5f, -1.5f), + PointF(5.5f, -1.5f), + PointF(5.5f, -0.5f), + PointF(4.5f, -0.5f)); + EXPECT_EQ(expected1, added); + EXPECT_EQ(expected1, q1); + + QuadF q2(a, b, c, d); + QuadF subtracted = q2 - v; + q2 -= v; + QuadF expected2(PointF(-2.5f, 3.5f), + PointF(-1.5f, 3.5f), + PointF(-1.5f, 4.5f), + PointF(-2.5f, 4.5f)); + EXPECT_EQ(expected2, subtracted); + EXPECT_EQ(expected2, q2); + + QuadF q3(a, b, c, d); + q3 += v; + q3 -= v; + EXPECT_EQ(QuadF(a, b, c, d), q3); + EXPECT_EQ(q3, (q3 + v - v)); +} + +TEST(QuadTest, IsRectilinear) { + PointF a(1, 1); + PointF b(2, 1); + PointF c(2, 2); + PointF d(1, 2); + Vector2dF v(3.5f, -2.5f); + + EXPECT_TRUE(QuadF().IsRectilinear()); + EXPECT_TRUE(QuadF(a, b, c, d).IsRectilinear()); + EXPECT_TRUE((QuadF(a, b, c, d) + v).IsRectilinear()); + + float epsilon = std::numeric_limits::epsilon(); + PointF a2(1 + epsilon / 2, 1 + epsilon / 2); + PointF b2(2 + epsilon / 2, 1 + epsilon / 2); + PointF c2(2 + epsilon / 2, 2 + epsilon / 2); + PointF d2(1 + epsilon / 2, 2 + epsilon / 2); + EXPECT_TRUE(QuadF(a2, b, c, d).IsRectilinear()); + EXPECT_TRUE((QuadF(a2, b, c, d) + v).IsRectilinear()); + EXPECT_TRUE(QuadF(a, b2, c, d).IsRectilinear()); + EXPECT_TRUE((QuadF(a, b2, c, d) + v).IsRectilinear()); + EXPECT_TRUE(QuadF(a, b, c2, d).IsRectilinear()); + EXPECT_TRUE((QuadF(a, b, c2, d) + v).IsRectilinear()); + EXPECT_TRUE(QuadF(a, b, c, d2).IsRectilinear()); + EXPECT_TRUE((QuadF(a, b, c, d2) + v).IsRectilinear()); + + struct { + PointF a_off, b_off, c_off, d_off; + } tests[] = { + { + PointF(1, 1.00001f), + PointF(2, 1.00001f), + PointF(2, 2.00001f), + PointF(1, 2.00001f) + }, + { + PointF(1.00001f, 1), + PointF(2.00001f, 1), + PointF(2.00001f, 2), + PointF(1.00001f, 2) + }, + { + PointF(1.00001f, 1.00001f), + PointF(2.00001f, 1.00001f), + PointF(2.00001f, 2.00001f), + PointF(1.00001f, 2.00001f) + }, + { + PointF(1, 0.99999f), + PointF(2, 0.99999f), + PointF(2, 1.99999f), + PointF(1, 1.99999f) + }, + { + PointF(0.99999f, 1), + PointF(1.99999f, 1), + PointF(1.99999f, 2), + PointF(0.99999f, 2) + }, + { + PointF(0.99999f, 0.99999f), + PointF(1.99999f, 0.99999f), + PointF(1.99999f, 1.99999f), + PointF(0.99999f, 1.99999f) + } + }; + + for (size_t i = 0; i < base::size(tests); ++i) { + PointF a_off = tests[i].a_off; + PointF b_off = tests[i].b_off; + PointF c_off = tests[i].c_off; + PointF d_off = tests[i].d_off; + + EXPECT_FALSE(QuadF(a_off, b, c, d).IsRectilinear()); + EXPECT_FALSE((QuadF(a_off, b, c, d) + v).IsRectilinear()); + EXPECT_FALSE(QuadF(a, b_off, c, d).IsRectilinear()); + EXPECT_FALSE((QuadF(a, b_off, c, d) + v).IsRectilinear()); + EXPECT_FALSE(QuadF(a, b, c_off, d).IsRectilinear()); + EXPECT_FALSE((QuadF(a, b, c_off, d) + v).IsRectilinear()); + EXPECT_FALSE(QuadF(a, b, c, d_off).IsRectilinear()); + EXPECT_FALSE((QuadF(a, b, c, d_off) + v).IsRectilinear()); + EXPECT_FALSE(QuadF(a_off, b, c_off, d).IsRectilinear()); + EXPECT_FALSE((QuadF(a_off, b, c_off, d) + v).IsRectilinear()); + EXPECT_FALSE(QuadF(a, b_off, c, d_off).IsRectilinear()); + EXPECT_FALSE((QuadF(a, b_off, c, d_off) + v).IsRectilinear()); + EXPECT_FALSE(QuadF(a, b_off, c_off, d_off).IsRectilinear()); + EXPECT_FALSE((QuadF(a, b_off, c_off, d_off) + v).IsRectilinear()); + EXPECT_FALSE(QuadF(a_off, b, c_off, d_off).IsRectilinear()); + EXPECT_FALSE((QuadF(a_off, b, c_off, d_off) + v).IsRectilinear()); + EXPECT_FALSE(QuadF(a_off, b_off, c, d_off).IsRectilinear()); + EXPECT_FALSE((QuadF(a_off, b_off, c, d_off) + v).IsRectilinear()); + EXPECT_FALSE(QuadF(a_off, b_off, c_off, d).IsRectilinear()); + EXPECT_FALSE((QuadF(a_off, b_off, c_off, d) + v).IsRectilinear()); + EXPECT_TRUE(QuadF(a_off, b_off, c_off, d_off).IsRectilinear()); + EXPECT_TRUE((QuadF(a_off, b_off, c_off, d_off) + v).IsRectilinear()); + } +} + +TEST(QuadTest, IsCounterClockwise) { + PointF a1(1, 1); + PointF b1(2, 1); + PointF c1(2, 2); + PointF d1(1, 2); + EXPECT_FALSE(QuadF(a1, b1, c1, d1).IsCounterClockwise()); + EXPECT_FALSE(QuadF(b1, c1, d1, a1).IsCounterClockwise()); + EXPECT_TRUE(QuadF(a1, d1, c1, b1).IsCounterClockwise()); + EXPECT_TRUE(QuadF(c1, b1, a1, d1).IsCounterClockwise()); + + // Slightly more complicated quads should work just as easily. + PointF a2(1.3f, 1.4f); + PointF b2(-0.7f, 4.9f); + PointF c2(1.8f, 6.2f); + PointF d2(2.1f, 1.6f); + EXPECT_TRUE(QuadF(a2, b2, c2, d2).IsCounterClockwise()); + EXPECT_TRUE(QuadF(b2, c2, d2, a2).IsCounterClockwise()); + EXPECT_FALSE(QuadF(a2, d2, c2, b2).IsCounterClockwise()); + EXPECT_FALSE(QuadF(c2, b2, a2, d2).IsCounterClockwise()); + + // Quads with 3 collinear points should work correctly, too. + PointF a3(0, 0); + PointF b3(1, 0); + PointF c3(2, 0); + PointF d3(1, 1); + EXPECT_FALSE(QuadF(a3, b3, c3, d3).IsCounterClockwise()); + EXPECT_FALSE(QuadF(b3, c3, d3, a3).IsCounterClockwise()); + EXPECT_TRUE(QuadF(a3, d3, c3, b3).IsCounterClockwise()); + // The next expectation in particular would fail for an implementation + // that incorrectly uses only a cross product of the first 3 vertices. + EXPECT_TRUE(QuadF(c3, b3, a3, d3).IsCounterClockwise()); + + // Non-convex quads should work correctly, too. + PointF a4(0, 0); + PointF b4(1, 1); + PointF c4(2, 0); + PointF d4(1, 3); + EXPECT_FALSE(QuadF(a4, b4, c4, d4).IsCounterClockwise()); + EXPECT_FALSE(QuadF(b4, c4, d4, a4).IsCounterClockwise()); + EXPECT_TRUE(QuadF(a4, d4, c4, b4).IsCounterClockwise()); + EXPECT_TRUE(QuadF(c4, b4, a4, d4).IsCounterClockwise()); + + // A quad with huge coordinates should not fail this check due to + // single-precision overflow. + PointF a5(1e30f, 1e30f); + PointF b5(1e35f, 1e30f); + PointF c5(1e35f, 1e35f); + PointF d5(1e30f, 1e35f); + EXPECT_FALSE(QuadF(a5, b5, c5, d5).IsCounterClockwise()); + EXPECT_FALSE(QuadF(b5, c5, d5, a5).IsCounterClockwise()); + EXPECT_TRUE(QuadF(a5, d5, c5, b5).IsCounterClockwise()); + EXPECT_TRUE(QuadF(c5, b5, a5, d5).IsCounterClockwise()); +} + +TEST(QuadTest, BoundingBox) { + RectF r(3.2f, 5.4f, 7.007f, 12.01f); + EXPECT_EQ(r, QuadF(r).BoundingBox()); + + PointF a(1.3f, 1.4f); + PointF b(-0.7f, 4.9f); + PointF c(1.8f, 6.2f); + PointF d(2.1f, 1.6f); + float left = -0.7f; + float top = 1.4f; + float right = 2.1f; + float bottom = 6.2f; + EXPECT_EQ(RectF(left, top, right - left, bottom - top), + QuadF(a, b, c, d).BoundingBox()); +} + +TEST(QuadTest, ContainsPoint) { + PointF a(1.3f, 1.4f); + PointF b(-0.8f, 4.4f); + PointF c(1.8f, 6.1f); + PointF d(2.1f, 1.6f); + + Vector2dF epsilon_x(2 * std::numeric_limits::epsilon(), 0); + Vector2dF epsilon_y(0, 2 * std::numeric_limits::epsilon()); + + Vector2dF ac_center = c - a; + ac_center.Scale(0.5f); + Vector2dF bd_center = d - b; + bd_center.Scale(0.5f); + + EXPECT_TRUE(QuadF(a, b, c, d).Contains(a + ac_center)); + EXPECT_TRUE(QuadF(a, b, c, d).Contains(b + bd_center)); + EXPECT_TRUE(QuadF(a, b, c, d).Contains(c - ac_center)); + EXPECT_TRUE(QuadF(a, b, c, d).Contains(d - bd_center)); + EXPECT_FALSE(QuadF(a, b, c, d).Contains(a - ac_center)); + EXPECT_FALSE(QuadF(a, b, c, d).Contains(b - bd_center)); + EXPECT_FALSE(QuadF(a, b, c, d).Contains(c + ac_center)); + EXPECT_FALSE(QuadF(a, b, c, d).Contains(d + bd_center)); + + EXPECT_TRUE(QuadF(a, b, c, d).Contains(a)); + EXPECT_FALSE(QuadF(a, b, c, d).Contains(a - epsilon_x)); + EXPECT_FALSE(QuadF(a, b, c, d).Contains(a - epsilon_y)); + EXPECT_FALSE(QuadF(a, b, c, d).Contains(a + epsilon_x)); + EXPECT_TRUE(QuadF(a, b, c, d).Contains(a + epsilon_y)); + + EXPECT_TRUE(QuadF(a, b, c, d).Contains(b)); + EXPECT_FALSE(QuadF(a, b, c, d).Contains(b - epsilon_x)); + EXPECT_FALSE(QuadF(a, b, c, d).Contains(b - epsilon_y)); + EXPECT_TRUE(QuadF(a, b, c, d).Contains(b + epsilon_x)); + EXPECT_FALSE(QuadF(a, b, c, d).Contains(b + epsilon_y)); + + EXPECT_TRUE(QuadF(a, b, c, d).Contains(c)); + EXPECT_FALSE(QuadF(a, b, c, d).Contains(c - epsilon_x)); + EXPECT_TRUE(QuadF(a, b, c, d).Contains(c - epsilon_y)); + EXPECT_FALSE(QuadF(a, b, c, d).Contains(c + epsilon_x)); + EXPECT_FALSE(QuadF(a, b, c, d).Contains(c + epsilon_y)); + + EXPECT_TRUE(QuadF(a, b, c, d).Contains(d)); + EXPECT_TRUE(QuadF(a, b, c, d).Contains(d - epsilon_x)); + EXPECT_FALSE(QuadF(a, b, c, d).Contains(d - epsilon_y)); + EXPECT_FALSE(QuadF(a, b, c, d).Contains(d + epsilon_x)); + EXPECT_FALSE(QuadF(a, b, c, d).Contains(d + epsilon_y)); + + // Test a simple square. + PointF s1(-1, -1); + PointF s2(1, -1); + PointF s3(1, 1); + PointF s4(-1, 1); + // Top edge. + EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(-1.1f, -1.0f))); + EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(-1.0f, -1.0f))); + EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(0.0f, -1.0f))); + EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(1.0f, -1.0f))); + EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(1.1f, -1.0f))); + // Bottom edge. + EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(-1.1f, 1.0f))); + EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(-1.0f, 1.0f))); + EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(0.0f, 1.0f))); + EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(1.0f, 1.0f))); + EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(1.1f, 1.0f))); + // Left edge. + EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(-1.0f, -1.1f))); + EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(-1.0f, -1.0f))); + EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(-1.0f, 0.0f))); + EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(-1.0f, 1.0f))); + EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(-1.0f, 1.1f))); + // Right edge. + EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(1.0f, -1.1f))); + EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(1.0f, -1.0f))); + EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(1.0f, 0.0f))); + EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(1.0f, 1.0f))); + EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(1.0f, 1.1f))); + // Centered inside. + EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(0, 0))); + // Centered outside. + EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(-1.1f, 0))); + EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(1.1f, 0))); + EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(0, -1.1f))); + EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(0, 1.1f))); +} + +TEST(QuadTest, Scale) { + PointF a(1.3f, 1.4f); + PointF b(-0.8f, 4.4f); + PointF c(1.8f, 6.1f); + PointF d(2.1f, 1.6f); + QuadF q1(a, b, c, d); + q1.Scale(1.5f); + + PointF a_scaled = ScalePoint(a, 1.5f); + PointF b_scaled = ScalePoint(b, 1.5f); + PointF c_scaled = ScalePoint(c, 1.5f); + PointF d_scaled = ScalePoint(d, 1.5f); + EXPECT_EQ(q1, QuadF(a_scaled, b_scaled, c_scaled, d_scaled)); + + QuadF q2; + q2.Scale(1.5f); + EXPECT_EQ(q2, q2); +} + +} // namespace gfx diff --git a/geometry/quaternion.cc b/geometry/quaternion.cc new file mode 100644 index 000000000000..86d674eef53a --- /dev/null +++ b/geometry/quaternion.cc @@ -0,0 +1,132 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/quaternion.h" + +#include +#include + +#include "base/numerics/math_constants.h" +#include "base/strings/stringprintf.h" +#include "ui/gfx/geometry/vector3d_f.h" + +namespace gfx { + +namespace { + +const double kEpsilon = 1e-5; + +} // namespace + +Quaternion::Quaternion(const Vector3dF& axis, double theta) { + // Rotation angle is the product of |angle| and the magnitude of |axis|. + double length = axis.Length(); + if (std::abs(length) < kEpsilon) + return; + + Vector3dF normalized = axis; + normalized.Scale(1.0 / length); + + theta *= 0.5; + double s = sin(theta); + x_ = normalized.x() * s; + y_ = normalized.y() * s; + z_ = normalized.z() * s; + w_ = cos(theta); +} + +Quaternion::Quaternion(const Vector3dF& from, const Vector3dF& to) { + double dot = gfx::DotProduct(from, to); + double norm = sqrt(from.LengthSquared() * to.LengthSquared()); + double real = norm + dot; + gfx::Vector3dF axis; + if (real < kEpsilon * norm) { + real = 0.0f; + axis = std::abs(from.x()) > std::abs(from.z()) + ? gfx::Vector3dF{-from.y(), from.x(), 0.0} + : gfx::Vector3dF{0.0, -from.z(), from.y()}; + } else { + axis = gfx::CrossProduct(from, to); + } + x_ = axis.x(); + y_ = axis.y(); + z_ = axis.z(); + w_ = real; + *this = this->Normalized(); +} + +Quaternion Quaternion::FromAxisAngle(double x, + double y, + double z, + double angle) { + double length = std::sqrt(x * x + y * y + z * z); + if (std::abs(length) < kEpsilon) + return Quaternion(0, 0, 0, 1); + + double scale = std::sin(0.5 * angle) / length; + return Quaternion(scale * x, scale * y, scale * z, std::cos(0.5 * angle)); +} + +// Adapted from https://www.euclideanspace.com/maths/algebra/realNormedAlgebra/ +// quaternions/slerp/index.htm +Quaternion Quaternion::Slerp(const Quaternion& to, double t) const { + Quaternion from = *this; + + double cos_half_angle = + from.x_ * to.x_ + from.y_ * to.y_ + from.z_ * to.z_ + from.w_ * to.w_; + if (cos_half_angle < 0) { + // Since the half angle is > 90 degrees, the full rotation angle would + // exceed 180 degrees. The quaternions (x, y, z, w) and (-x, -y, -z, -w) + // represent the same rotation. Flipping the orientation of either + // quaternion ensures that the half angle is less than 90 and that we are + // taking the shortest path. + from = from.flip(); + cos_half_angle = -cos_half_angle; + } + + // Ensure that acos is well behaved at the boundary. + if (cos_half_angle > 1) + cos_half_angle = 1; + + double sin_half_angle = std::sqrt(1.0 - cos_half_angle * cos_half_angle); + if (sin_half_angle < kEpsilon) { + // Quaternions share common axis and angle. + return *this; + } + + double half_angle = std::acos(cos_half_angle); + + double scaleA = std::sin((1 - t) * half_angle) / sin_half_angle; + double scaleB = std::sin(t * half_angle) / sin_half_angle; + + return (scaleA * from) + (scaleB * to); +} + +Quaternion Quaternion::Lerp(const Quaternion& q, double t) const { + return (((1.0 - t) * *this) + (t * q)).Normalized(); +} + +double Quaternion::Length() const { + return x_ * x_ + y_ * y_ + z_ * z_ + w_ * w_; +} + +Quaternion Quaternion::Normalized() const { + double length = Length(); + if (length < kEpsilon) + return *this; + return *this / sqrt(length); +} + +std::string Quaternion::ToString() const { + // q = (con(abs(v_theta)/2), v_theta/abs(v_theta) * sin(abs(v_theta)/2)) + float abs_theta = acos(w_) * 2; + float scale = 1. / sin(abs_theta * .5); + gfx::Vector3dF v(x_, y_, z_); + v.Scale(scale); + return base::StringPrintf("[%f %f %f %f], v:", x_, y_, z_, w_) + + v.ToString() + + base::StringPrintf(", θ:%fπ", abs_theta / base::kPiFloat); +} + +} // namespace gfx diff --git a/geometry/quaternion.h b/geometry/quaternion.h new file mode 100644 index 000000000000..8081881f0a37 --- /dev/null +++ b/geometry/quaternion.h @@ -0,0 +1,109 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_QUATERNION_H_ +#define UI_GFX_GEOMETRY_QUATERNION_H_ + +#include + +#include "ui/gfx/geometry/geometry_export.h" + +namespace gfx { + +class Vector3dF; + +class GEOMETRY_EXPORT Quaternion { + public: + constexpr Quaternion() = default; + constexpr Quaternion(double x, double y, double z, double w) + : x_(x), y_(y), z_(z), w_(w) {} + Quaternion(const Vector3dF& axis, double angle); + + // Constructs a quaternion representing a rotation between |from| and |to|. + Quaternion(const Vector3dF& from, const Vector3dF& to); + + static Quaternion FromAxisAngle(double x, double y, double z, double angle); + + constexpr double x() const { return x_; } + void set_x(double x) { x_ = x; } + + constexpr double y() const { return y_; } + void set_y(double y) { y_ = y; } + + constexpr double z() const { return z_; } + void set_z(double z) { z_ = z; } + + constexpr double w() const { return w_; } + void set_w(double w) { w_ = w; } + + Quaternion operator+(const Quaternion& q) const { + return {q.x_ + x_, q.y_ + y_, q.z_ + z_, q.w_ + w_}; + } + + Quaternion operator*(const Quaternion& q) const { + return {w_ * q.x_ + x_ * q.w_ + y_ * q.z_ - z_ * q.y_, + w_ * q.y_ - x_ * q.z_ + y_ * q.w_ + z_ * q.x_, + w_ * q.z_ + x_ * q.y_ - y_ * q.x_ + z_ * q.w_, + w_ * q.w_ - x_ * q.x_ - y_ * q.y_ - z_ * q.z_}; + } + + Quaternion inverse() const { return {-x_, -y_, -z_, w_}; } + + Quaternion flip() const { return {-x_, -y_, -z_, -w_}; } + + // Blends with the given quaternion, |q|, via spherical linear interpolation. + // Values of |t| in the range [0, 1] will interpolate between |this| and |q|, + // and values outside that range will extrapolate beyond in either direction. + Quaternion Slerp(const Quaternion& q, double t) const; + + // Blends with the given quaternion, |q|, via linear interpolation. This is + // rarely what you want. Use only if you know what you're doing. + // Values of |t| in the range [0, 1] will interpolate between |this| and |q|, + // and values outside that range will extrapolate beyond in either direction. + Quaternion Lerp(const Quaternion& q, double t) const; + + double Length() const; + + Quaternion Normalized() const; + + std::string ToString() const; + + private: + double x_ = 0.0; + double y_ = 0.0; + double z_ = 0.0; + double w_ = 1.0; +}; + +// |s| is an arbitrary, real constant. +inline Quaternion operator*(const Quaternion& q, double s) { + return Quaternion(q.x() * s, q.y() * s, q.z() * s, q.w() * s); +} + +// |s| is an arbitrary, real constant. +inline Quaternion operator*(double s, const Quaternion& q) { + return Quaternion(q.x() * s, q.y() * s, q.z() * s, q.w() * s); +} + +// |s| is an arbitrary, real constant. +inline Quaternion operator/(const Quaternion& q, double s) { + double inv = 1.0 / s; + return q * inv; +} + +// Returns true if the x, y, z, w values of |lhs| and |rhs| are equal. Note that +// two quaternions can represent the same orientation with different values. +// This operator will return false in that scenario. +inline bool operator==(const Quaternion& lhs, const Quaternion& rhs) { + return lhs.x() == rhs.x() && lhs.y() == rhs.y() && lhs.z() == rhs.z() && + lhs.w() == rhs.w(); +} + +inline bool operator!=(const Quaternion& lhs, const Quaternion& rhs) { + return !(lhs == rhs); +} + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_QUATERNION_H_ diff --git a/geometry/quaternion_unittest.cc b/geometry/quaternion_unittest.cc new file mode 100644 index 000000000000..3c9fd2b73639 --- /dev/null +++ b/geometry/quaternion_unittest.cc @@ -0,0 +1,237 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/cxx17_backports.h" +#include "base/numerics/math_constants.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/geometry/quaternion.h" +#include "ui/gfx/geometry/vector3d_f.h" + +namespace gfx { + +namespace { + +const double kEpsilon = 1e-7; + +#define EXPECT_QUATERNION(expected, actual) \ + do { \ + EXPECT_NEAR(expected.x(), actual.x(), kEpsilon); \ + EXPECT_NEAR(expected.y(), actual.y(), kEpsilon); \ + EXPECT_NEAR(expected.z(), actual.z(), kEpsilon); \ + EXPECT_NEAR(expected.w(), actual.w(), kEpsilon); \ + } while (false) + +void CompareQuaternions(const Quaternion& a, const Quaternion& b) { + EXPECT_FLOAT_EQ(a.x(), b.x()); + EXPECT_FLOAT_EQ(a.y(), b.y()); + EXPECT_FLOAT_EQ(a.z(), b.z()); + EXPECT_FLOAT_EQ(a.w(), b.w()); +} + +} // namespace + +TEST(QuatTest, DefaultConstruction) { + CompareQuaternions(Quaternion(0, 0, 0, 1), Quaternion()); +} + +TEST(QuatTest, AxisAngleCommon) { + double radians = 0.5; + Quaternion q(Vector3dF(1, 0, 0), radians); + CompareQuaternions( + Quaternion(std::sin(radians / 2), 0, 0, std::cos(radians / 2)), q); +} + +TEST(QuatTest, VectorToVectorRotation) { + Quaternion q(Vector3dF(1.0f, 0.0f, 0.0f), Vector3dF(0.0f, 1.0f, 0.0f)); + Quaternion r(Vector3dF(0.0f, 0.0f, 1.0f), base::kPiFloat / 2); + + EXPECT_FLOAT_EQ(r.x(), q.x()); + EXPECT_FLOAT_EQ(r.y(), q.y()); + EXPECT_FLOAT_EQ(r.z(), q.z()); + EXPECT_FLOAT_EQ(r.w(), q.w()); +} + +TEST(QuatTest, AxisAngleWithZeroLengthAxis) { + Quaternion q(Vector3dF(0, 0, 0), 0.5); + // If the axis of zero length, we should assume the default values. + CompareQuaternions(q, Quaternion()); +} + +TEST(QuatTest, Addition) { + double values[] = {0, 1, 100}; + for (size_t i = 0; i < base::size(values); ++i) { + float t = values[i]; + Quaternion a(t, 2 * t, 3 * t, 4 * t); + Quaternion b(5 * t, 4 * t, 3 * t, 2 * t); + Quaternion sum = a + b; + CompareQuaternions(Quaternion(t, t, t, t) * 6, sum); + } +} + +TEST(QuatTest, Multiplication) { + struct { + Quaternion a; + Quaternion b; + Quaternion expected; + } cases[] = { + {Quaternion(1, 0, 0, 0), Quaternion(1, 0, 0, 0), Quaternion(0, 0, 0, -1)}, + {Quaternion(0, 1, 0, 0), Quaternion(0, 1, 0, 0), Quaternion(0, 0, 0, -1)}, + {Quaternion(0, 0, 1, 0), Quaternion(0, 0, 1, 0), Quaternion(0, 0, 0, -1)}, + {Quaternion(0, 0, 0, 1), Quaternion(0, 0, 0, 1), Quaternion(0, 0, 0, 1)}, + {Quaternion(1, 2, 3, 4), Quaternion(5, 6, 7, 8), + Quaternion(24, 48, 48, -6)}, + {Quaternion(5, 6, 7, 8), Quaternion(1, 2, 3, 4), + Quaternion(32, 32, 56, -6)}, + }; + + for (size_t i = 0; i < base::size(cases); ++i) { + Quaternion product = cases[i].a * cases[i].b; + CompareQuaternions(cases[i].expected, product); + } +} + +TEST(QuatTest, Scaling) { + double values[] = {0, 10, 100}; + for (size_t i = 0; i < base::size(values); ++i) { + double s = values[i]; + Quaternion q(1, 2, 3, 4); + Quaternion expected(s, 2 * s, 3 * s, 4 * s); + CompareQuaternions(expected, q * s); + CompareQuaternions(expected, s * q); + if (s > 0) + CompareQuaternions(expected, q / (1 / s)); + } +} + +TEST(QuatTest, Normalization) { + Quaternion q(1, -1, 1, -1); + EXPECT_NEAR(q.Length(), 4, kEpsilon); + + q = q.Normalized(); + + EXPECT_NEAR(q.Length(), 1, kEpsilon); + EXPECT_NEAR(q.x(), 0.5, kEpsilon); + EXPECT_NEAR(q.y(), -0.5, kEpsilon); + EXPECT_NEAR(q.z(), 0.5, kEpsilon); + EXPECT_NEAR(q.w(), -0.5, kEpsilon); +} + +TEST(QuatTest, Lerp) { + for (size_t i = 1; i < 100; ++i) { + Quaternion a(0, 0, 0, 0); + Quaternion b(1, 2, 3, 4); + float t = static_cast(i) / 100.0f; + Quaternion interpolated = a.Lerp(b, t); + double s = 1.0 / sqrt(30.0); + CompareQuaternions(Quaternion(1, 2, 3, 4) * s, interpolated); + } + + Quaternion a(4, 3, 2, 1); + Quaternion b(1, 2, 3, 4); + CompareQuaternions(a.Normalized(), a.Lerp(b, 0)); + CompareQuaternions(b.Normalized(), a.Lerp(b, 1)); + CompareQuaternions(Quaternion(1, 1, 1, 1).Normalized(), a.Lerp(b, 0.5)); +} + +TEST(QuatTest, Slerp) { + Vector3dF axis(1, 1, 1); + double start_radians = -0.5; + double stop_radians = 0.5; + Quaternion start(axis, start_radians); + Quaternion stop(axis, stop_radians); + + for (size_t i = 0; i < 100; ++i) { + float t = static_cast(i) / 100.0f; + double radians = (1.0 - t) * start_radians + t * stop_radians; + Quaternion expected(axis, radians); + Quaternion interpolated = start.Slerp(stop, t); + EXPECT_QUATERNION(expected, interpolated); + } +} + +TEST(QuatTest, SlerpOppositeAngles) { + Vector3dF axis(1, 1, 1); + double start_radians = -base::kPiDouble / 2; + double stop_radians = base::kPiDouble / 2; + Quaternion start(axis, start_radians); + Quaternion stop(axis, stop_radians); + + // When quaternions are pointed in the fully opposite direction, this is + // ambiguous, so we rotate as per https://www.w3.org/TR/css-transforms-1/ + Quaternion expected(axis, 0); + + Quaternion interpolated = start.Slerp(stop, 0.5f); + EXPECT_QUATERNION(expected, interpolated); +} + +TEST(QuatTest, SlerpRotateXRotateY) { + Quaternion start(Vector3dF(1, 0, 0), base::kPiDouble / 2); + Quaternion stop(Vector3dF(0, 1, 0), base::kPiDouble / 2); + Quaternion interpolated = start.Slerp(stop, 0.5f); + + double expected_angle = std::acos(1.0 / 3.0); + double xy = std::sin(0.5 * expected_angle) / std::sqrt(2); + Quaternion expected(xy, xy, 0, std::cos(0.5 * expected_angle)); + EXPECT_QUATERNION(expected, interpolated); +} + +TEST(QuatTest, Slerp360) { + Quaternion start(0, 0, 0, -1); // 360 degree rotation. + Quaternion stop(Vector3dF(0, 0, 1), base::kPiDouble / 2); + Quaternion interpolated = start.Slerp(stop, 0.5f); + double expected_half_angle = base::kPiDouble / 8; + Quaternion expected(0, 0, std::sin(expected_half_angle), + std::cos(expected_half_angle)); + EXPECT_QUATERNION(expected, interpolated); +} + +TEST(QuatTest, SlerpEquivalentQuaternions) { + Quaternion start(Vector3dF(1, 0, 0), base::kPiDouble / 3); + Quaternion stop = start.flip(); + Quaternion interpolated = start.Slerp(stop, 0.5f); + EXPECT_QUATERNION(start, interpolated); +} + +TEST(QuatTest, SlerpQuaternionWithInverse) { + Quaternion start(Vector3dF(1, 0, 0), base::kPiDouble / 3); + Quaternion stop = start.inverse(); + Quaternion interpolated = start.Slerp(stop, 0.5f); + Quaternion expected(0, 0, 0, 1); + EXPECT_QUATERNION(expected, interpolated); +} + +TEST(QuatTest, SlerpObtuseAngle) { + Quaternion start(Vector3dF(1, 1, 0), base::kPiDouble / 2); + Quaternion stop(Vector3dF(0, 1, -1), 3 * base::kPiDouble / 2); + Quaternion interpolated = start.Slerp(stop, 0.5f); + double expected_half_angle = -std::atan(0.5); + double xz = std::sin(expected_half_angle) / std::sqrt(2); + Quaternion expected(xz, 0, xz, -std::cos(expected_half_angle)); + EXPECT_QUATERNION(expected, interpolated); +} + +TEST(QuatTest, Equals) { + EXPECT_TRUE(Quaternion() == Quaternion()); + EXPECT_TRUE(Quaternion() == Quaternion(0, 0, 0, 1)); + EXPECT_TRUE(Quaternion(1, 5.2, -8.5, 222.2) == + Quaternion(1, 5.2, -8.5, 222.2)); + EXPECT_FALSE(Quaternion() == Quaternion(1, 0, 0, 0)); + EXPECT_FALSE(Quaternion() == Quaternion(0, 1, 0, 0)); + EXPECT_FALSE(Quaternion() == Quaternion(0, 0, 1, 0)); + EXPECT_FALSE(Quaternion() == Quaternion(1, 0, 0, 1)); +} + +TEST(QuatTest, NotEquals) { + EXPECT_FALSE(Quaternion() != Quaternion()); + EXPECT_FALSE(Quaternion(1, 5.2, -8.5, 222.2) != + Quaternion(1, 5.2, -8.5, 222.2)); + EXPECT_TRUE(Quaternion() != Quaternion(1, 0, 0, 0)); + EXPECT_TRUE(Quaternion() != Quaternion(0, 1, 0, 0)); + EXPECT_TRUE(Quaternion() != Quaternion(0, 0, 1, 0)); + EXPECT_TRUE(Quaternion() != Quaternion(1, 0, 0, 1)); +} + +} // namespace gfx diff --git a/geometry/rect.cc b/geometry/rect.cc new file mode 100644 index 000000000000..02b7156da22a --- /dev/null +++ b/geometry/rect.cc @@ -0,0 +1,348 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/rect.h" + +#include + +#if defined(OS_WIN) +#include +#elif defined(OS_IOS) +#include +#elif defined(OS_MAC) +#include +#endif + +#include "base/check.h" +#include "base/numerics/clamped_math.h" +#include "base/strings/stringprintf.h" +#include "build/build_config.h" +#include "ui/gfx/geometry/insets.h" + +namespace gfx { + +#if defined(OS_WIN) +Rect::Rect(const RECT& r) + : origin_(r.left, r.top), + size_(std::abs(r.right - r.left), std::abs(r.bottom - r.top)) { +} +#elif defined(OS_APPLE) +Rect::Rect(const CGRect& r) + : origin_(r.origin.x, r.origin.y), size_(r.size.width, r.size.height) { +} +#endif + +#if defined(OS_WIN) +RECT Rect::ToRECT() const { + RECT r; + r.left = x(); + r.right = right(); + r.top = y(); + r.bottom = bottom(); + return r; +} +#elif defined(OS_APPLE) +CGRect Rect::ToCGRect() const { + return CGRectMake(x(), y(), width(), height()); +} +#endif + +void AdjustAlongAxis(int dst_origin, int dst_size, int* origin, int* size) { + *size = std::min(dst_size, *size); + if (*origin < dst_origin) + *origin = dst_origin; + else + *origin = std::min(dst_origin + dst_size, *origin + *size) - *size; +} + +} // namespace + +namespace gfx { + +// This is the per-axis heuristic for picking the most useful origin and +// width/height to represent the input range. +static void SaturatedClampRange(int min, int max, int* origin, int* span) { + if (max < min) { + *span = 0; + *origin = min; + return; + } + + int effective_span = base::ClampSub(max, min); + int span_loss = base::ClampSub(max, min + effective_span); + + // If the desired width is within the limits of ints, we can just + // use the simple computations to represent the range precisely. + if (span_loss == 0) { + *span = effective_span; + *origin = min; + return; + } + + // Now we have to approximate. If one of min or max is close enough + // to zero we choose to represent that one precisely. The other side is + // probably practically "infinite", so we move it. + constexpr unsigned kMaxDimension = std::numeric_limits::max() / 2; + if (base::SafeUnsignedAbs(max) < kMaxDimension) { + // Maintain origin + span == max. + *span = effective_span; + *origin = max - effective_span; + } else if (base::SafeUnsignedAbs(min) < kMaxDimension) { + // Maintain origin == min. + *span = effective_span; + *origin = min; + } else { + // Both are big, so keep the center. + *span = effective_span; + *origin = min + span_loss / 2; + } +} + +void Rect::SetByBounds(int left, int top, int right, int bottom) { + int x, y; + int width, height; + SaturatedClampRange(left, right, &x, &width); + SaturatedClampRange(top, bottom, &y, &height); + origin_.SetPoint(x, y); + size_.SetSize(width, height); +} + +void Rect::Inset(const Insets& insets) { + Inset(insets.left(), insets.top(), insets.right(), insets.bottom()); +} + +void Rect::Inset(int left, int top, int right, int bottom) { + origin_ += Vector2d(left, top); + // left+right might overflow/underflow, but width() - (left+right) might + // overflow as well. + set_width(base::ClampSub(width(), base::ClampAdd(left, right))); + set_height(base::ClampSub(height(), base::ClampAdd(top, bottom))); +} + +void Rect::Offset(const Vector2d& distance) { + origin_ += distance; + // Ensure that width and height remain valid. + set_width(width()); + set_height(height()); +} + +void Rect::operator+=(const Vector2d& offset) { + origin_ += offset; + // Ensure that width and height remain valid. + set_width(width()); + set_height(height()); +} + +void Rect::operator-=(const Vector2d& offset) { + origin_ -= offset; +} + +Insets Rect::InsetsFrom(const Rect& inner) const { + return Insets(inner.y() - y(), + inner.x() - x(), + bottom() - inner.bottom(), + right() - inner.right()); +} + +bool Rect::operator<(const Rect& other) const { + if (origin_ == other.origin_) { + if (width() == other.width()) { + return height() < other.height(); + } else { + return width() < other.width(); + } + } else { + return origin_ < other.origin_; + } +} + +bool Rect::Contains(int point_x, int point_y) const { + return (point_x >= x()) && (point_x < right()) && (point_y >= y()) && + (point_y < bottom()); +} + +bool Rect::Contains(const Rect& rect) const { + return (rect.x() >= x() && rect.right() <= right() && rect.y() >= y() && + rect.bottom() <= bottom()); +} + +bool Rect::Intersects(const Rect& rect) const { + return !(IsEmpty() || rect.IsEmpty() || rect.x() >= right() || + rect.right() <= x() || rect.y() >= bottom() || rect.bottom() <= y()); +} + +void Rect::Intersect(const Rect& rect) { + if (IsEmpty() || rect.IsEmpty()) { + SetRect(0, 0, 0, 0); // Throws away empty position. + return; + } + + int left = std::max(x(), rect.x()); + int top = std::max(y(), rect.y()); + int new_right = std::min(right(), rect.right()); + int new_bottom = std::min(bottom(), rect.bottom()); + + if (left >= new_right || top >= new_bottom) { + SetRect(0, 0, 0, 0); // Throws away empty position. + return; + } + + SetByBounds(left, top, new_right, new_bottom); +} + +void Rect::Union(const Rect& rect) { + if (IsEmpty()) { + *this = rect; + return; + } + if (rect.IsEmpty()) + return; + + SetByBounds(std::min(x(), rect.x()), std::min(y(), rect.y()), + std::max(right(), rect.right()), + std::max(bottom(), rect.bottom())); +} + +void Rect::Subtract(const Rect& rect) { + if (!Intersects(rect)) + return; + if (rect.Contains(*this)) { + SetRect(0, 0, 0, 0); + return; + } + + int rx = x(); + int ry = y(); + int rr = right(); + int rb = bottom(); + + if (rect.y() <= y() && rect.bottom() >= bottom()) { + // complete intersection in the y-direction + if (rect.x() <= x()) { + rx = rect.right(); + } else if (rect.right() >= right()) { + rr = rect.x(); + } + } else if (rect.x() <= x() && rect.right() >= right()) { + // complete intersection in the x-direction + if (rect.y() <= y()) { + ry = rect.bottom(); + } else if (rect.bottom() >= bottom()) { + rb = rect.y(); + } + } + SetByBounds(rx, ry, rr, rb); +} + +void Rect::AdjustToFit(const Rect& rect) { + int new_x = x(); + int new_y = y(); + int new_width = width(); + int new_height = height(); + AdjustAlongAxis(rect.x(), rect.width(), &new_x, &new_width); + AdjustAlongAxis(rect.y(), rect.height(), &new_y, &new_height); + SetRect(new_x, new_y, new_width, new_height); +} + +Point Rect::CenterPoint() const { + return Point(x() + width() / 2, y() + height() / 2); +} + +void Rect::ClampToCenteredSize(const Size& size) { + int new_width = std::min(width(), size.width()); + int new_height = std::min(height(), size.height()); + int new_x = x() + (width() - new_width) / 2; + int new_y = y() + (height() - new_height) / 2; + SetRect(new_x, new_y, new_width, new_height); +} + +void Rect::Transpose() { + SetRect(y(), x(), height(), width()); +} + +void Rect::SplitVertically(Rect* left_half, Rect* right_half) const { + DCHECK(left_half); + DCHECK(right_half); + + left_half->SetRect(x(), y(), width() / 2, height()); + right_half->SetRect( + left_half->right(), y(), width() - left_half->width(), height()); +} + +bool Rect::SharesEdgeWith(const Rect& rect) const { + return (y() == rect.y() && height() == rect.height() && + (x() == rect.right() || right() == rect.x())) || + (x() == rect.x() && width() == rect.width() && + (y() == rect.bottom() || bottom() == rect.y())); +} + +int Rect::ManhattanDistanceToPoint(const Point& point) const { + int x_distance = + std::max(0, std::max(x() - point.x(), point.x() - right())); + int y_distance = + std::max(0, std::max(y() - point.y(), point.y() - bottom())); + + return x_distance + y_distance; +} + +int Rect::ManhattanInternalDistance(const Rect& rect) const { + Rect c(*this); + c.Union(rect); + + int x = std::max(0, c.width() - width() - rect.width() + 1); + int y = std::max(0, c.height() - height() - rect.height() + 1); + return x + y; +} + +std::string Rect::ToString() const { + return base::StringPrintf("%s %s", + origin().ToString().c_str(), + size().ToString().c_str()); +} + +bool Rect::ApproximatelyEqual(const Rect& rect, int tolerance) const { + return std::abs(x() - rect.x()) <= tolerance && + std::abs(y() - rect.y()) <= tolerance && + std::abs(right() - rect.right()) <= tolerance && + std::abs(bottom() - rect.bottom()) <= tolerance; +} + +Rect operator+(const Rect& lhs, const Vector2d& rhs) { + Rect result(lhs); + result += rhs; + return result; +} + +Rect operator-(const Rect& lhs, const Vector2d& rhs) { + Rect result(lhs); + result -= rhs; + return result; +} + +Rect IntersectRects(const Rect& a, const Rect& b) { + Rect result = a; + result.Intersect(b); + return result; +} + +Rect UnionRects(const Rect& a, const Rect& b) { + Rect result = a; + result.Union(b); + return result; +} + +Rect SubtractRects(const Rect& a, const Rect& b) { + Rect result = a; + result.Subtract(b); + return result; +} + +Rect BoundingRect(const Point& p1, const Point& p2) { + Rect result; + result.SetByBounds(std::min(p1.x(), p2.x()), std::min(p1.y(), p2.y()), + std::max(p1.x(), p2.x()), std::max(p1.y(), p2.y())); + return result; +} + +} // namespace gfx diff --git a/geometry/rect.h b/geometry/rect.h new file mode 100644 index 000000000000..ac58f8a944e5 --- /dev/null +++ b/geometry/rect.h @@ -0,0 +1,412 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Defines a simple integer rectangle class. The containment semantics +// are array-like; that is, the coordinate (x, y) is considered to be +// contained by the rectangle, but the coordinate (x + width, y) is not. +// The class will happily let you create malformed rectangles (that is, +// rectangles with negative width and/or height), but there will be assertions +// in the operations (such as Contains()) to complain in this case. + +#ifndef UI_GFX_GEOMETRY_RECT_H_ +#define UI_GFX_GEOMETRY_RECT_H_ + +#include +#include +#include + +#include "base/check.h" +#include "base/numerics/safe_conversions.h" +#include "build/build_config.h" +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/geometry/vector2d.h" + +#if defined(OS_WIN) +typedef struct tagRECT RECT; +#elif defined(OS_APPLE) +typedef struct CGRect CGRect; +#endif + +namespace gfx { + +class Insets; + +class GEOMETRY_EXPORT Rect { + public: + constexpr Rect() = default; + constexpr Rect(int width, int height) : size_(width, height) {} + constexpr Rect(int x, int y, int width, int height) + : origin_(x, y), + size_(GetClampedValue(x, width), GetClampedValue(y, height)) {} + constexpr explicit Rect(const Size& size) : size_(size) {} + constexpr Rect(const Point& origin, const Size& size) + : origin_(origin), + size_(GetClampedValue(origin.x(), size.width()), + GetClampedValue(origin.y(), size.height())) {} + +#if defined(OS_WIN) + explicit Rect(const RECT& r); +#elif defined(OS_APPLE) + explicit Rect(const CGRect& r); +#endif + +#if defined(OS_WIN) + // Construct an equivalent Win32 RECT object. + RECT ToRECT() const; +#elif defined(OS_APPLE) + // Construct an equivalent CoreGraphics object. + CGRect ToCGRect() const; +#endif + + constexpr int x() const { return origin_.x(); } + // Sets the X position while preserving the width. + void set_x(int x) { + origin_.set_x(x); + size_.set_width(GetClampedValue(x, width())); + } + + constexpr int y() const { return origin_.y(); } + // Sets the Y position while preserving the height. + void set_y(int y) { + origin_.set_y(y); + size_.set_height(GetClampedValue(y, height())); + } + + constexpr int width() const { return size_.width(); } + void set_width(int width) { size_.set_width(GetClampedValue(x(), width)); } + + constexpr int height() const { return size_.height(); } + void set_height(int height) { + size_.set_height(GetClampedValue(y(), height)); + } + + constexpr const Point& origin() const { return origin_; } + void set_origin(const Point& origin) { + origin_ = origin; + // Ensure that width and height remain valid. + set_width(width()); + set_height(height()); + } + + constexpr const Size& size() const { return size_; } + void set_size(const Size& size) { + set_width(size.width()); + set_height(size.height()); + } + + constexpr int right() const { return x() + width(); } + constexpr int bottom() const { return y() + height(); } + + constexpr Point top_right() const { return Point(right(), y()); } + constexpr Point bottom_left() const { return Point(x(), bottom()); } + constexpr Point bottom_right() const { return Point(right(), bottom()); } + + constexpr Point left_center() const { return Point(x(), y() + height() / 2); } + constexpr Point top_center() const { return Point(x() + width() / 2, y()); } + constexpr Point right_center() const { + return Point(right(), y() + height() / 2); + } + constexpr Point bottom_center() const { + return Point(x() + width() / 2, bottom()); + } + + Vector2d OffsetFromOrigin() const { return Vector2d(x(), y()); } + + void SetRect(int x, int y, int width, int height) { + origin_.SetPoint(x, y); + // Ensure that width and height remain valid. + set_width(width); + set_height(height); + } + + // Use in place of SetRect() when you know the edges of the rectangle instead + // of the dimensions, rather than trying to determine the width/height + // yourself. This safely handles cases where the width/height would overflow. + void SetByBounds(int left, int top, int right, int bottom); + + // Shrink the rectangle by |inset| on all sides. + void Inset(int inset) { Inset(inset, inset); } + // Shrink the rectangle by a horizontal and vertical distance on all sides. + void Inset(int horizontal, int vertical) { + Inset(horizontal, vertical, horizontal, vertical); + } + + // Shrink the rectangle by the given insets. + void Inset(const Insets& insets); + + // Shrink the rectangle by the specified amount on each side. + void Inset(int left, int top, int right, int bottom); + + // Expand the rectangle by the specified amount on each side. + void Outset(int outset) { Inset(-outset); } + void Outset(int horizontal, int vertical) { Inset(-horizontal, -vertical); } + void Outset(int left, int top, int right, int bottom) { + Inset(-left, -top, -right, -bottom); + } + + // Move the rectangle by a horizontal and vertical distance. + void Offset(int horizontal, int vertical) { + Offset(Vector2d(horizontal, vertical)); + } + void Offset(const Vector2d& distance); + void operator+=(const Vector2d& offset); + void operator-=(const Vector2d& offset); + + Insets InsetsFrom(const Rect& inner) const; + + // Returns true if the area of the rectangle is zero. + bool IsEmpty() const { return size_.IsEmpty(); } + + // A rect is less than another rect if its origin is less than + // the other rect's origin. If the origins are equal, then the + // shortest rect is less than the other. If the origin and the + // height are equal, then the narrowest rect is less than. + // This comparison is required to use Rects in sets, or sorted + // vectors. + bool operator<(const Rect& other) const; + + // Returns true if the point identified by point_x and point_y falls inside + // this rectangle. The point (x, y) is inside the rectangle, but the + // point (x + width, y + height) is not. + bool Contains(int point_x, int point_y) const; + + // Returns true if the specified point is contained by this rectangle. + bool Contains(const Point& point) const { + return Contains(point.x(), point.y()); + } + + // Returns true if this rectangle contains the specified rectangle. + bool Contains(const Rect& rect) const; + + // Returns true if this rectangle intersects the specified rectangle. + // An empty rectangle doesn't intersect any rectangle. + bool Intersects(const Rect& rect) const; + + // Computes the intersection of this rectangle with the given rectangle. + void Intersect(const Rect& rect); + + // Computes the union of this rectangle with the given rectangle. The union + // is the smallest rectangle containing both rectangles. + void Union(const Rect& rect); + + // Computes the rectangle resulting from subtracting |rect| from |*this|, + // i.e. the bounding rect of |Region(*this) - Region(rect)|. + void Subtract(const Rect& rect); + + // Fits as much of the receiving rectangle into the supplied rectangle as + // possible, becoming the result. For example, if the receiver had + // a x-location of 2 and a width of 4, and the supplied rectangle had + // an x-location of 0 with a width of 5, the returned rectangle would have + // an x-location of 1 with a width of 4. + void AdjustToFit(const Rect& rect); + + // Returns the center of this rectangle. + Point CenterPoint() const; + + // Becomes a rectangle that has the same center point but with a size capped + // at given |size|. + void ClampToCenteredSize(const Size& size); + + // Transpose x and y axis. + void Transpose(); + + // Splits |this| in two halves, |left_half| and |right_half|. + void SplitVertically(Rect* left_half, Rect* right_half) const; + + // Returns true if this rectangle shares an entire edge (i.e., same width or + // same height) with the given rectangle, and the rectangles do not overlap. + bool SharesEdgeWith(const Rect& rect) const; + + // Returns the manhattan distance from the rect to the point. If the point is + // inside the rect, returns 0. + int ManhattanDistanceToPoint(const Point& point) const; + + // Returns the manhattan distance between the contents of this rect and the + // contents of the given rect. That is, if the intersection of the two rects + // is non-empty then the function returns 0. If the rects share a side, it + // returns the smallest non-zero value appropriate for int. + int ManhattanInternalDistance(const Rect& rect) const; + + std::string ToString() const; + + bool ApproximatelyEqual(const Rect& rect, int tolerance) const; + + private: + gfx::Point origin_; + gfx::Size size_; + + // Returns true iff a+b would overflow max int. + static constexpr bool AddWouldOverflow(int a, int b) { + // In this function, GCC tries to make optimizations that would only work if + // max - a wouldn't overflow but it isn't smart enough to notice that a > 0. + // So cast everything to unsigned to avoid this. As it is guaranteed that + // max - a and b are both already positive, the cast is a noop. + // + // This is intended to be: a > 0 && max - a < b + return a > 0 && b > 0 && + static_cast(std::numeric_limits::max() - a) < + static_cast(b); + } + + // Clamp the size to avoid integer overflow in bottom() and right(). + // This returns the width given an origin and a width. + // TODO(enne): this should probably use base::ClampAdd, but that + // function is not a constexpr. + static constexpr int GetClampedValue(int origin, int size) { + return AddWouldOverflow(origin, size) + ? std::numeric_limits::max() - origin + : size; + } +}; + +inline bool operator==(const Rect& lhs, const Rect& rhs) { + return lhs.origin() == rhs.origin() && lhs.size() == rhs.size(); +} + +inline bool operator!=(const Rect& lhs, const Rect& rhs) { + return !(lhs == rhs); +} + +GEOMETRY_EXPORT Rect operator+(const Rect& lhs, const Vector2d& rhs); +GEOMETRY_EXPORT Rect operator-(const Rect& lhs, const Vector2d& rhs); + +inline Rect operator+(const Vector2d& lhs, const Rect& rhs) { + return rhs + lhs; +} + +GEOMETRY_EXPORT Rect IntersectRects(const Rect& a, const Rect& b); +GEOMETRY_EXPORT Rect UnionRects(const Rect& a, const Rect& b); +GEOMETRY_EXPORT Rect SubtractRects(const Rect& a, const Rect& b); + +// Constructs a rectangle with |p1| and |p2| as opposite corners. +// +// This could also be thought of as "the smallest rect that contains both +// points", except that we consider points on the right/bottom edges of the +// rect to be outside the rect. So technically one or both points will not be +// contained within the rect, because they will appear on one of these edges. +GEOMETRY_EXPORT Rect BoundingRect(const Point& p1, const Point& p2); + +// Scales the rect and returns the enclosing rect. Use this only the inputs are +// known to not overflow. Use ScaleToEnclosingRectSafe if the inputs are +// unknown and need to use saturated math. +inline Rect ScaleToEnclosingRect(const Rect& rect, + float x_scale, + float y_scale) { + if (x_scale == 1.f && y_scale == 1.f) + return rect; + // These next functions cast instead of using e.g. base::ClampFloor() because + // we haven't checked to ensure that the clamping behavior of the helper + // functions doesn't degrade performance, and callers shouldn't be passing + // values that cause overflow anyway. + DCHECK(base::IsValueInRangeForNumericType( + std::floor(rect.x() * x_scale))); + DCHECK(base::IsValueInRangeForNumericType( + std::floor(rect.y() * y_scale))); + DCHECK(base::IsValueInRangeForNumericType( + std::ceil(rect.right() * x_scale))); + DCHECK(base::IsValueInRangeForNumericType( + std::ceil(rect.bottom() * y_scale))); + int x = static_cast(std::floor(rect.x() * x_scale)); + int y = static_cast(std::floor(rect.y() * y_scale)); + int r = rect.width() == 0 ? + x : static_cast(std::ceil(rect.right() * x_scale)); + int b = rect.height() == 0 ? + y : static_cast(std::ceil(rect.bottom() * y_scale)); + return Rect(x, y, r - x, b - y); +} + +inline Rect ScaleToEnclosingRect(const Rect& rect, float scale) { + return ScaleToEnclosingRect(rect, scale, scale); +} + +// ScaleToEnclosingRect but clamping instead of asserting if the resulting rect +// would overflow. +// TODO(pkasting): Attempt to switch ScaleTo...Rect() to this construction and +// check performance. +inline Rect ScaleToEnclosingRectSafe(const Rect& rect, + float x_scale, + float y_scale) { + if (x_scale == 1.f && y_scale == 1.f) + return rect; + int x = base::ClampFloor(rect.x() * x_scale); + int y = base::ClampFloor(rect.y() * y_scale); + int w = base::ClampCeil(rect.width() * x_scale); + int h = base::ClampCeil(rect.height() * y_scale); + return Rect(x, y, w, h); +} + +inline Rect ScaleToEnclosingRectSafe(const Rect& rect, float scale) { + return ScaleToEnclosingRectSafe(rect, scale, scale); +} + +inline Rect ScaleToEnclosedRect(const Rect& rect, + float x_scale, + float y_scale) { + if (x_scale == 1.f && y_scale == 1.f) + return rect; + DCHECK(base::IsValueInRangeForNumericType( + std::ceil(rect.x() * x_scale))); + DCHECK(base::IsValueInRangeForNumericType( + std::ceil(rect.y() * y_scale))); + DCHECK(base::IsValueInRangeForNumericType( + std::floor(rect.right() * x_scale))); + DCHECK(base::IsValueInRangeForNumericType( + std::floor(rect.bottom() * y_scale))); + int x = static_cast(std::ceil(rect.x() * x_scale)); + int y = static_cast(std::ceil(rect.y() * y_scale)); + int r = rect.width() == 0 ? + x : static_cast(std::floor(rect.right() * x_scale)); + int b = rect.height() == 0 ? + y : static_cast(std::floor(rect.bottom() * y_scale)); + return Rect(x, y, r - x, b - y); +} + +inline Rect ScaleToEnclosedRect(const Rect& rect, float scale) { + return ScaleToEnclosedRect(rect, scale, scale); +} + +// Scales |rect| by scaling its four corner points. If the corner points lie on +// non-integral coordinate after scaling, their values are rounded to the +// nearest integer. +// This is helpful during layout when relative positions of multiple gfx::Rect +// in a given coordinate space needs to be same after scaling as it was before +// scaling. ie. this gives a lossless relative positioning of rects. +inline Rect ScaleToRoundedRect(const Rect& rect, float x_scale, float y_scale) { + if (x_scale == 1.f && y_scale == 1.f) + return rect; + + DCHECK( + base::IsValueInRangeForNumericType(std::round(rect.x() * x_scale))); + DCHECK( + base::IsValueInRangeForNumericType(std::round(rect.y() * y_scale))); + DCHECK(base::IsValueInRangeForNumericType( + std::round(rect.right() * x_scale))); + DCHECK(base::IsValueInRangeForNumericType( + std::round(rect.bottom() * y_scale))); + + int x = static_cast(std::round(rect.x() * x_scale)); + int y = static_cast(std::round(rect.y() * y_scale)); + int r = rect.width() == 0 + ? x + : static_cast(std::round(rect.right() * x_scale)); + int b = rect.height() == 0 + ? y + : static_cast(std::round(rect.bottom() * y_scale)); + + return Rect(x, y, r - x, b - y); +} + +inline Rect ScaleToRoundedRect(const Rect& rect, float scale) { + return ScaleToRoundedRect(rect, scale, scale); +} + +// This is declared here for use in gtest-based unit tests but is defined in +// the //ui/gfx:test_support target. Depend on that to use this in your unit +// test. This should not be used in production code - call ToString() instead. +void PrintTo(const Rect& rect, ::std::ostream* os); + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_RECT_H_ diff --git a/geometry/rect_conversions.cc b/geometry/rect_conversions.cc new file mode 100644 index 000000000000..212318bdf1fa --- /dev/null +++ b/geometry/rect_conversions.cc @@ -0,0 +1,126 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/rect_conversions.h" + +#include +#include + +#include "base/check.h" +#include "base/numerics/safe_conversions.h" + +namespace gfx { + +namespace { + +int FloorIgnoringError(float f, float error) { + int rounded = base::ClampRound(f); + return std::abs(rounded - f) < error ? rounded : base::ClampFloor(f); +} + +int CeilIgnoringError(float f, float error) { + int rounded = base::ClampRound(f); + return std::abs(rounded - f) < error ? rounded : base::ClampCeil(f); +} + +} // anonymous namespace + +Rect ToEnclosingRect(const RectF& r) { + int left = base::ClampFloor(r.x()); + int right = r.width() ? base::ClampCeil(r.right()) : left; + int top = base::ClampFloor(r.y()); + int bottom = r.height() ? base::ClampCeil(r.bottom()) : top; + + Rect result; + result.SetByBounds(left, top, right, bottom); + return result; +} + +Rect ToEnclosingRectIgnoringError(const RectF& r, float error) { + int left = FloorIgnoringError(r.x(), error); + int right = r.width() ? CeilIgnoringError(r.right(), error) : left; + int top = FloorIgnoringError(r.y(), error); + int bottom = r.height() ? CeilIgnoringError(r.bottom(), error) : top; + + Rect result; + result.SetByBounds(left, top, right, bottom); + return result; +} + +Rect ToEnclosedRect(const RectF& rect) { + Rect result; + result.SetByBounds(base::ClampCeil(rect.x()), base::ClampCeil(rect.y()), + base::ClampFloor(rect.right()), + base::ClampFloor(rect.bottom())); + return result; +} + +Rect ToEnclosedRectIgnoringError(const RectF& r, float error) { + int left = CeilIgnoringError(r.x(), error); + int right = r.width() ? FloorIgnoringError(r.right(), error) : left; + int top = CeilIgnoringError(r.y(), error); + int bottom = r.height() ? FloorIgnoringError(r.bottom(), error) : top; + + Rect result; + result.SetByBounds(left, top, right, bottom); + return result; +} + +Rect ToNearestRect(const RectF& rect) { + float float_min_x = rect.x(); + float float_min_y = rect.y(); + float float_max_x = rect.right(); + float float_max_y = rect.bottom(); + + int min_x = base::ClampRound(float_min_x); + int min_y = base::ClampRound(float_min_y); + int max_x = base::ClampRound(float_max_x); + int max_y = base::ClampRound(float_max_y); + + // If these DCHECKs fail, you're using the wrong method, consider using + // ToEnclosingRect or ToEnclosedRect instead. + DCHECK(std::abs(min_x - float_min_x) < 0.01f); + DCHECK(std::abs(min_y - float_min_y) < 0.01f); + DCHECK(std::abs(max_x - float_max_x) < 0.01f); + DCHECK(std::abs(max_y - float_max_y) < 0.01f); + + Rect result; + result.SetByBounds(min_x, min_y, max_x, max_y); + + return result; +} + +bool IsNearestRectWithinDistance(const gfx::RectF& rect, float distance) { + float float_min_x = rect.x(); + float float_min_y = rect.y(); + float float_max_x = rect.right(); + float float_max_y = rect.bottom(); + + int min_x = base::ClampRound(float_min_x); + int min_y = base::ClampRound(float_min_y); + int max_x = base::ClampRound(float_max_x); + int max_y = base::ClampRound(float_max_y); + + return (std::abs(min_x - float_min_x) < distance) && + (std::abs(min_y - float_min_y) < distance) && + (std::abs(max_x - float_max_x) < distance) && + (std::abs(max_y - float_max_y) < distance); +} + +gfx::Rect ToRoundedRect(const gfx::RectF& rect) { + int left = base::ClampRound(rect.x()); + int top = base::ClampRound(rect.y()); + int right = base::ClampRound(rect.right()); + int bottom = base::ClampRound(rect.bottom()); + gfx::Rect result; + result.SetByBounds(left, top, right, bottom); + return result; +} + +Rect ToFlooredRectDeprecated(const RectF& rect) { + return Rect(base::ClampFloor(rect.x()), base::ClampFloor(rect.y()), + base::ClampFloor(rect.width()), base::ClampFloor(rect.height())); +} + +} // namespace gfx diff --git a/geometry/rect_conversions.h b/geometry/rect_conversions.h new file mode 100644 index 000000000000..3460dcc79281 --- /dev/null +++ b/geometry/rect_conversions.h @@ -0,0 +1,55 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_RECT_CONVERSIONS_H_ +#define UI_GFX_GEOMETRY_RECT_CONVERSIONS_H_ + +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/rect_f.h" + +namespace gfx { + +// Returns the smallest Rect that encloses the given RectF. +GEOMETRY_EXPORT Rect ToEnclosingRect(const RectF& rect); + +// Similar to ToEnclosingRect(), but for each edge, if the distance between the +// edge and the nearest integer grid is smaller than |error|, the edge is +// snapped to the integer grid. Unlike ToNearestRect() which only accepts +// integer rect with or without floating point error, this function also accepts +// non-integer rect. +GEOMETRY_EXPORT Rect ToEnclosingRectIgnoringError(const RectF& rect, + float error); + +// Returns the largest Rect that is enclosed by the given RectF. +GEOMETRY_EXPORT Rect ToEnclosedRect(const RectF& rect); + +// Similar to ToEnclosedRect(), but for each edge, if the distance between the +// edge and the nearest integer grid is smaller than |error|, the edge is +// snapped to the integer grid. Unlike ToNearestRect() which only accepts +// integer rect with or without floating point error, this function also accepts +// non-integer rect. +GEOMETRY_EXPORT Rect ToEnclosedRectIgnoringError(const RectF& rect, + float error); + +// Returns the Rect after snapping the corners of the RectF to an integer grid. +// This should only be used when the RectF you provide is expected to be an +// integer rect with floating point error. If it is an arbitrary RectF, then +// you should use a different method. +GEOMETRY_EXPORT Rect ToNearestRect(const RectF& rect); + +// Returns true if the Rect produced after snapping the corners of the RectF +// to an integer grid is withing |distance|. +GEOMETRY_EXPORT bool IsNearestRectWithinDistance(const gfx::RectF& rect, + float distance); + +// Returns the Rect after rounding the corners of the RectF to an integer grid. +GEOMETRY_EXPORT gfx::Rect ToRoundedRect(const gfx::RectF& rect); + +// Returns a Rect obtained by flooring the values of the given RectF. +// Please prefer the previous two functions in new code. +GEOMETRY_EXPORT Rect ToFlooredRectDeprecated(const RectF& rect); + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_RECT_CONVERSIONS_H_ diff --git a/geometry/rect_f.cc b/geometry/rect_f.cc new file mode 100644 index 000000000000..c8dafe2ed54d --- /dev/null +++ b/geometry/rect_f.cc @@ -0,0 +1,266 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/rect_f.h" + +#include +#include + +#include "base/check.h" +#include "base/numerics/safe_conversions.h" +#include "base/strings/stringprintf.h" +#include "build/build_config.h" +#include "ui/gfx/geometry/insets_f.h" + +#if defined(OS_IOS) +#include +#elif defined(OS_MAC) +#include +#endif + +namespace gfx { + +static void AdjustAlongAxis(float dst_origin, + float dst_size, + float* origin, + float* size) { + *size = std::min(dst_size, *size); + if (*origin < dst_origin) + *origin = dst_origin; + else + *origin = std::min(dst_origin + dst_size, *origin + *size) - *size; +} + +#if defined(OS_APPLE) +RectF::RectF(const CGRect& r) + : origin_(r.origin.x, r.origin.y), size_(r.size.width, r.size.height) { +} + +CGRect RectF::ToCGRect() const { + return CGRectMake(x(), y(), width(), height()); +} +#endif + +void RectF::Inset(const InsetsF& insets) { + Inset(insets.left(), insets.top(), insets.right(), insets.bottom()); +} + +void RectF::Inset(float left, float top, float right, float bottom) { + origin_ += Vector2dF(left, top); + set_width(std::max(width() - left - right, 0.0f)); + set_height(std::max(height() - top - bottom, 0.0f)); +} + +void RectF::Offset(float horizontal, float vertical) { + origin_ += Vector2dF(horizontal, vertical); +} + +void RectF::operator+=(const Vector2dF& offset) { + origin_ += offset; +} + +void RectF::operator-=(const Vector2dF& offset) { + origin_ -= offset; +} + +InsetsF RectF::InsetsFrom(const RectF& inner) const { + return InsetsF(inner.y() - y(), + inner.x() - x(), + bottom() - inner.bottom(), + right() - inner.right()); +} + +bool RectF::operator<(const RectF& other) const { + if (origin_ != other.origin_) + return origin_ < other.origin_; + + if (width() == other.width()) + return height() < other.height(); + return width() < other.width(); +} + +bool RectF::Contains(float point_x, float point_y) const { + return point_x >= x() && point_x < right() && point_y >= y() && + point_y < bottom(); +} + +bool RectF::Contains(const RectF& rect) const { + return rect.x() >= x() && rect.right() <= right() && rect.y() >= y() && + rect.bottom() <= bottom(); +} + +bool RectF::Intersects(const RectF& rect) const { + return !IsEmpty() && !rect.IsEmpty() && rect.x() < right() && + rect.right() > x() && rect.y() < bottom() && rect.bottom() > y(); +} + +void RectF::Intersect(const RectF& rect) { + if (IsEmpty() || rect.IsEmpty()) { + SetRect(0, 0, 0, 0); + return; + } + + float rx = std::max(x(), rect.x()); + float ry = std::max(y(), rect.y()); + float rr = std::min(right(), rect.right()); + float rb = std::min(bottom(), rect.bottom()); + + if (rx >= rr || ry >= rb) { + SetRect(0, 0, 0, 0); + return; + } + + SetRect(rx, ry, rr - rx, rb - ry); +} + +void RectF::Union(const RectF& rect) { + if (IsEmpty()) { + *this = rect; + return; + } + if (rect.IsEmpty()) + return; + + float rx = std::min(x(), rect.x()); + float ry = std::min(y(), rect.y()); + float rr = std::max(right(), rect.right()); + float rb = std::max(bottom(), rect.bottom()); + + SetRect(rx, ry, rr - rx, rb - ry); +} + +void RectF::Subtract(const RectF& rect) { + if (!Intersects(rect)) + return; + if (rect.Contains(*this)) { + SetRect(0, 0, 0, 0); + return; + } + + float rx = x(); + float ry = y(); + float rr = right(); + float rb = bottom(); + + if (rect.y() <= y() && rect.bottom() >= bottom()) { + // complete intersection in the y-direction + if (rect.x() <= x()) { + rx = rect.right(); + } else if (rect.right() >= right()) { + rr = rect.x(); + } + } else if (rect.x() <= x() && rect.right() >= right()) { + // complete intersection in the x-direction + if (rect.y() <= y()) { + ry = rect.bottom(); + } else if (rect.bottom() >= bottom()) { + rb = rect.y(); + } + } + SetRect(rx, ry, rr - rx, rb - ry); +} + +void RectF::AdjustToFit(const RectF& rect) { + float new_x = x(); + float new_y = y(); + float new_width = width(); + float new_height = height(); + AdjustAlongAxis(rect.x(), rect.width(), &new_x, &new_width); + AdjustAlongAxis(rect.y(), rect.height(), &new_y, &new_height); + SetRect(new_x, new_y, new_width, new_height); +} + +PointF RectF::CenterPoint() const { + return PointF(x() + width() / 2, y() + height() / 2); +} + +void RectF::ClampToCenteredSize(const SizeF& size) { + float new_width = std::min(width(), size.width()); + float new_height = std::min(height(), size.height()); + float new_x = x() + (width() - new_width) / 2; + float new_y = y() + (height() - new_height) / 2; + SetRect(new_x, new_y, new_width, new_height); +} + +void RectF::Transpose() { + SetRect(y(), x(), height(), width()); +} + +void RectF::SplitVertically(RectF* left_half, RectF* right_half) const { + DCHECK(left_half); + DCHECK(right_half); + + left_half->SetRect(x(), y(), width() / 2, height()); + right_half->SetRect( + left_half->right(), y(), width() - left_half->width(), height()); +} + +bool RectF::SharesEdgeWith(const RectF& rect) const { + return (y() == rect.y() && height() == rect.height() && + (x() == rect.right() || right() == rect.x())) || + (x() == rect.x() && width() == rect.width() && + (y() == rect.bottom() || bottom() == rect.y())); +} + +float RectF::ManhattanDistanceToPoint(const PointF& point) const { + float x_distance = + std::max(0, std::max(x() - point.x(), point.x() - right())); + float y_distance = + std::max(0, std::max(y() - point.y(), point.y() - bottom())); + + return x_distance + y_distance; +} + +float RectF::ManhattanInternalDistance(const RectF& rect) const { + RectF c(*this); + c.Union(rect); + + static constexpr float kEpsilon = std::numeric_limits::epsilon(); + float x = std::max(0.f, c.width() - width() - rect.width() + kEpsilon); + float y = std::max(0.f, c.height() - height() - rect.height() + kEpsilon); + return x + y; +} + +bool RectF::IsExpressibleAsRect() const { + return base::IsValueInRangeForNumericType(x()) && + base::IsValueInRangeForNumericType(y()) && + base::IsValueInRangeForNumericType(width()) && + base::IsValueInRangeForNumericType(height()) && + base::IsValueInRangeForNumericType(right()) && + base::IsValueInRangeForNumericType(bottom()); +} + +std::string RectF::ToString() const { + return base::StringPrintf("%s %s", + origin().ToString().c_str(), + size().ToString().c_str()); +} + +RectF IntersectRects(const RectF& a, const RectF& b) { + RectF result = a; + result.Intersect(b); + return result; +} + +RectF UnionRects(const RectF& a, const RectF& b) { + RectF result = a; + result.Union(b); + return result; +} + +RectF SubtractRects(const RectF& a, const RectF& b) { + RectF result = a; + result.Subtract(b); + return result; +} + +RectF BoundingRect(const PointF& p1, const PointF& p2) { + float rx = std::min(p1.x(), p2.x()); + float ry = std::min(p1.y(), p2.y()); + float rr = std::max(p1.x(), p2.x()); + float rb = std::max(p1.y(), p2.y()); + return RectF(rx, ry, rr - rx, rb - ry); +} + +} // namespace gfx diff --git a/geometry/rect_f.h b/geometry/rect_f.h new file mode 100644 index 000000000000..acda35bdbd35 --- /dev/null +++ b/geometry/rect_f.h @@ -0,0 +1,267 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_RECT_F_H_ +#define UI_GFX_GEOMETRY_RECT_F_H_ + +#include +#include + +#include "build/build_config.h" +#include "ui/gfx/geometry/point_f.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/size_f.h" +#include "ui/gfx/geometry/vector2d_f.h" + +#if defined(OS_APPLE) +typedef struct CGRect CGRect; +#endif + +namespace gfx { + +class InsetsF; + +// A floating version of gfx::Rect. +class GEOMETRY_EXPORT RectF { + public: + constexpr RectF() = default; + constexpr RectF(float width, float height) : size_(width, height) {} + constexpr RectF(float x, float y, float width, float height) + : origin_(x, y), size_(width, height) {} + constexpr explicit RectF(const SizeF& size) : size_(size) {} + constexpr RectF(const PointF& origin, const SizeF& size) + : origin_(origin), size_(size) {} + + constexpr explicit RectF(const Rect& r) + : RectF(static_cast(r.x()), + static_cast(r.y()), + static_cast(r.width()), + static_cast(r.height())) {} + +#if defined(OS_APPLE) + explicit RectF(const CGRect& r); + // Construct an equivalent CoreGraphics object. + CGRect ToCGRect() const; +#endif + + constexpr float x() const { return origin_.x(); } + void set_x(float x) { origin_.set_x(x); } + + constexpr float y() const { return origin_.y(); } + void set_y(float y) { origin_.set_y(y); } + + constexpr float width() const { return size_.width(); } + void set_width(float width) { size_.set_width(width); } + + constexpr float height() const { return size_.height(); } + void set_height(float height) { size_.set_height(height); } + + constexpr const PointF& origin() const { return origin_; } + void set_origin(const PointF& origin) { origin_ = origin; } + + constexpr const SizeF& size() const { return size_; } + void set_size(const SizeF& size) { size_ = size; } + + constexpr float right() const { return x() + width(); } + constexpr float bottom() const { return y() + height(); } + + constexpr PointF top_right() const { return PointF(right(), y()); } + constexpr PointF bottom_left() const { return PointF(x(), bottom()); } + constexpr PointF bottom_right() const { return PointF(right(), bottom()); } + + constexpr PointF left_center() const { + return PointF(x(), y() + height() / 2); + } + constexpr PointF top_center() const { return PointF(x() + width() / 2, y()); } + constexpr PointF right_center() const { + return PointF(right(), y() + height() / 2); + } + constexpr PointF bottom_center() const { + return PointF(x() + width() / 2, bottom()); + } + + Vector2dF OffsetFromOrigin() const { return Vector2dF(x(), y()); } + + void SetRect(float x, float y, float width, float height) { + origin_.SetPoint(x, y); + size_.SetSize(width, height); + } + + // Shrink the rectangle by |inset| on all sides. + void Inset(float inset) { Inset(inset, inset); } + // Shrink the rectangle by a horizontal and vertical distance on all sides. + void Inset(float horizontal, float vertical) { + Inset(horizontal, vertical, horizontal, vertical); + } + + // Shrink the rectangle by the given insets. + void Inset(const InsetsF& insets); + + // Shrink the rectangle by the specified amount on each side. + void Inset(float left, float top, float right, float bottom); + + // Expand the rectangle by the specified amount on each side. + void Outset(float outset) { Inset(-outset); } + void Outset(float horizontal, float vertical) { + Inset(-horizontal, -vertical); + } + void Outset(float left, float top, float right, float bottom) { + Inset(-left, -top, -right, -bottom); + } + + // Move the rectangle by a horizontal and vertical distance. + void Offset(float horizontal, float vertical); + void Offset(const Vector2dF& distance) { Offset(distance.x(), distance.y()); } + void operator+=(const Vector2dF& offset); + void operator-=(const Vector2dF& offset); + + InsetsF InsetsFrom(const RectF& inner) const; + + // Returns true if the area of the rectangle is zero. + bool IsEmpty() const { return size_.IsEmpty(); } + + // A rect is less than another rect if its origin is less than + // the other rect's origin. If the origins are equal, then the + // shortest rect is less than the other. If the origin and the + // height are equal, then the narrowest rect is less than. + // This comparison is required to use Rects in sets, or sorted + // vectors. + bool operator<(const RectF& other) const; + + // Returns true if the point identified by point_x and point_y falls inside + // this rectangle. The point (x, y) is inside the rectangle, but the + // point (x + width, y + height) is not. + bool Contains(float point_x, float point_y) const; + + // Returns true if the specified point is contained by this rectangle. + bool Contains(const PointF& point) const { + return Contains(point.x(), point.y()); + } + + // Returns true if this rectangle contains the specified rectangle. + bool Contains(const RectF& rect) const; + + // Returns true if this rectangle intersects the specified rectangle. + // An empty rectangle doesn't intersect any rectangle. + bool Intersects(const RectF& rect) const; + + // Computes the intersection of this rectangle with the given rectangle. + void Intersect(const RectF& rect); + + // Computes the union of this rectangle with the given rectangle. The union + // is the smallest rectangle containing both rectangles. + void Union(const RectF& rect); + + // Computes the rectangle resulting from subtracting |rect| from |*this|, + // i.e. the bounding rect of |Region(*this) - Region(rect)|. + void Subtract(const RectF& rect); + + // Fits as much of the receiving rectangle into the supplied rectangle as + // possible, becoming the result. For example, if the receiver had + // a x-location of 2 and a width of 4, and the supplied rectangle had + // an x-location of 0 with a width of 5, the returned rectangle would have + // an x-location of 1 with a width of 4. + void AdjustToFit(const RectF& rect); + + // Returns the center of this rectangle. + PointF CenterPoint() const; + + // Becomes a rectangle that has the same center point but with a size capped + // at given |size|. + void ClampToCenteredSize(const SizeF& size); + + // Transpose x and y axis. + void Transpose(); + + // Splits |this| in two halves, |left_half| and |right_half|. + void SplitVertically(RectF* left_half, RectF* right_half) const; + + // Returns true if this rectangle shares an entire edge (i.e., same width or + // same height) with the given rectangle, and the rectangles do not overlap. + bool SharesEdgeWith(const RectF& rect) const; + + // Returns the manhattan distance from the rect to the point. If the point is + // inside the rect, returns 0. + float ManhattanDistanceToPoint(const PointF& point) const; + + // Returns the manhattan distance between the contents of this rect and the + // contents of the given rect. That is, if the intersection of the two rects + // is non-empty then the function returns 0. If the rects share a side, it + // returns the smallest non-zero value appropriate for float. + float ManhattanInternalDistance(const RectF& rect) const; + + // Scales the rectangle by |scale|. + void Scale(float scale) { + Scale(scale, scale); + } + + void Scale(float x_scale, float y_scale) { + set_origin(ScalePoint(origin(), x_scale, y_scale)); + set_size(ScaleSize(size(), x_scale, y_scale)); + } + + // This method reports if the RectF can be safely converted to an integer + // Rect. When it is false, some dimension of the RectF is outside the bounds + // of what an integer can represent, and converting it to a Rect will require + // clamping. + bool IsExpressibleAsRect() const; + + std::string ToString() const; + + private: + PointF origin_; + SizeF size_; +}; + +inline bool operator==(const RectF& lhs, const RectF& rhs) { + return lhs.origin() == rhs.origin() && lhs.size() == rhs.size(); +} + +inline bool operator!=(const RectF& lhs, const RectF& rhs) { + return !(lhs == rhs); +} + +inline RectF operator+(const RectF& lhs, const Vector2dF& rhs) { + return RectF(lhs.x() + rhs.x(), lhs.y() + rhs.y(), + lhs.width(), lhs.height()); +} + +inline RectF operator-(const RectF& lhs, const Vector2dF& rhs) { + return RectF(lhs.x() - rhs.x(), lhs.y() - rhs.y(), + lhs.width(), lhs.height()); +} + +inline RectF operator+(const Vector2dF& lhs, const RectF& rhs) { + return rhs + lhs; +} + +GEOMETRY_EXPORT RectF IntersectRects(const RectF& a, const RectF& b); +GEOMETRY_EXPORT RectF UnionRects(const RectF& a, const RectF& b); +GEOMETRY_EXPORT RectF SubtractRects(const RectF& a, const RectF& b); + +inline RectF ScaleRect(const RectF& r, float x_scale, float y_scale) { + return RectF(r.x() * x_scale, r.y() * y_scale, + r.width() * x_scale, r.height() * y_scale); +} + +inline RectF ScaleRect(const RectF& r, float scale) { + return ScaleRect(r, scale, scale); +} + +// Constructs a rectangle with |p1| and |p2| as opposite corners. +// +// This could also be thought of as "the smallest rect that contains both +// points", except that we consider points on the right/bottom edges of the +// rect to be outside the rect. So technically one or both points will not be +// contained within the rect, because they will appear on one of these edges. +GEOMETRY_EXPORT RectF BoundingRect(const PointF& p1, const PointF& p2); + +// This is declared here for use in gtest-based unit tests but is defined in +// the //ui/gfx:test_support target. Depend on that to use this in your unit +// test. This should not be used in production code - call ToString() instead. +void PrintTo(const RectF& rect, ::std::ostream* os); + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_RECT_F_H_ diff --git a/geometry/rect_f_unittest.cc b/geometry/rect_f_unittest.cc new file mode 100644 index 000000000000..d3182554978d --- /dev/null +++ b/geometry/rect_f_unittest.cc @@ -0,0 +1,78 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/rect_f.h" + +#include "ui/gfx/geometry/insets_f.h" +#include "ui/gfx/geometry/rect_f.h" +#include "ui/gfx/test/gfx_util.h" + +namespace gfx { + +TEST(RectFTest, Inset) { + RectF r(10, 20, 30, 40); + r.Inset(0); + EXPECT_RECTF_EQ(RectF(10, 20, 30, 40), r); + r.Inset(1.5); + EXPECT_RECTF_EQ(RectF(11.5, 21.5, 27, 37), r); + r.Inset(-1.5); + EXPECT_RECTF_EQ(RectF(10, 20, 30, 40), r); + + r.Inset(1.5, 2.25); + EXPECT_RECTF_EQ(RectF(11.5, 22.25, 27, 35.5), r); + r.Inset(-1.5, -2.25); + EXPECT_RECTF_EQ(RectF(10, 20, 30, 40), r); + + // The parameters are left, top, right, bottom. + r.Inset(1.5, 2.25, 3.75, 4); + EXPECT_RECTF_EQ(RectF(11.5, 22.25, 24.75, 33.75), r); + r.Inset(-1.5, -2.25, -3.75, -4); + EXPECT_RECTF_EQ(RectF(10, 20, 30, 40), r); + + // InsetsF parameters are top, right, bottom, left. + r.Inset(InsetsF(1.5, 2.25, 3.75, 4)); + EXPECT_RECTF_EQ(RectF(12.25, 21.5, 23.75, 34.75), r); + r.Inset(InsetsF(-1.5, -2.25, -3.75, -4)); + EXPECT_RECTF_EQ(RectF(10, 20, 30, 40), r); +} + +TEST(RectFTest, Outset) { + RectF r(10, 20, 30, 40); + r.Outset(0); + EXPECT_RECTF_EQ(RectF(10, 20, 30, 40), r); + r.Outset(1.5); + EXPECT_RECTF_EQ(RectF(8.5, 18.5, 33, 43), r); + r.Outset(-1.5); + EXPECT_RECTF_EQ(RectF(10, 20, 30, 40), r); + + r.Outset(1.5, 2.25); + EXPECT_RECTF_EQ(RectF(8.5, 17.75, 33, 44.5), r); + r.Outset(-1.5, -2.25); + EXPECT_RECTF_EQ(RectF(10, 20, 30, 40), r); + + r.Outset(1.5, 2.25, 3.75, 4); + EXPECT_RECTF_EQ(RectF(8.5, 17.75, 35.25, 46.25), r); + r.Outset(-1.5, -2.25, -3.75, -4); + EXPECT_RECTF_EQ(RectF(10, 20, 30, 40), r); +} + +TEST(RectFTest, InsetClamped) { + RectF r(10, 20, 30, 40); + r.Inset(18); + EXPECT_RECTF_EQ(RectF(28, 38, 0, 4), r); + r.Inset(-18); + EXPECT_RECTF_EQ(RectF(10, 20, 36, 40), r); + + r.Inset(15, 30); + EXPECT_RECTF_EQ(RectF(25, 50, 6, 0), r); + r.Inset(-15, -30); + EXPECT_RECTF_EQ(RectF(10, 20, 36, 60), r); + + r.Inset(20, 30, 40, 50); + EXPECT_RECTF_EQ(RectF(30, 50, 0, 0), r); + r.Inset(-20, -30, -40, -50); + EXPECT_RECTF_EQ(RectF(10, 20, 60, 80), r); +} + +} // namespace gfx diff --git a/geometry/rect_unittest.cc b/geometry/rect_unittest.cc new file mode 100644 index 000000000000..6aceed3ffea3 --- /dev/null +++ b/geometry/rect_unittest.cc @@ -0,0 +1,1300 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +#include "base/cxx17_backports.h" +#include "build/build_config.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/geometry/insets.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/rect_conversions.h" +#include "ui/gfx/test/gfx_util.h" + +#if defined(OS_WIN) +#include +#endif + +namespace gfx { + +TEST(RectTest, Contains) { + static const struct ContainsCase { + int rect_x; + int rect_y; + int rect_width; + int rect_height; + int point_x; + int point_y; + bool contained; + } contains_cases[] = { + {0, 0, 10, 10, 0, 0, true}, + {0, 0, 10, 10, 5, 5, true}, + {0, 0, 10, 10, 9, 9, true}, + {0, 0, 10, 10, 5, 10, false}, + {0, 0, 10, 10, 10, 5, false}, + {0, 0, 10, 10, -1, -1, false}, + {0, 0, 10, 10, 50, 50, false}, + #if defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON) + {0, 0, -10, -10, 0, 0, false}, + #endif + }; + for (size_t i = 0; i < base::size(contains_cases); ++i) { + const ContainsCase& value = contains_cases[i]; + Rect rect(value.rect_x, value.rect_y, value.rect_width, value.rect_height); + EXPECT_EQ(value.contained, rect.Contains(value.point_x, value.point_y)); + } +} + +TEST(RectTest, Intersects) { + static const struct { + int x1; // rect 1 + int y1; + int w1; + int h1; + int x2; // rect 2 + int y2; + int w2; + int h2; + bool intersects; + } tests[] = { + { 0, 0, 0, 0, 0, 0, 0, 0, false }, + { 0, 0, 0, 0, -10, -10, 20, 20, false }, + { -10, 0, 0, 20, 0, -10, 20, 0, false }, + { 0, 0, 10, 10, 0, 0, 10, 10, true }, + { 0, 0, 10, 10, 10, 10, 10, 10, false }, + { 10, 10, 10, 10, 0, 0, 10, 10, false }, + { 10, 10, 10, 10, 5, 5, 10, 10, true }, + { 10, 10, 10, 10, 15, 15, 10, 10, true }, + { 10, 10, 10, 10, 20, 15, 10, 10, false }, + { 10, 10, 10, 10, 21, 15, 10, 10, false } + }; + for (size_t i = 0; i < base::size(tests); ++i) { + Rect r1(tests[i].x1, tests[i].y1, tests[i].w1, tests[i].h1); + Rect r2(tests[i].x2, tests[i].y2, tests[i].w2, tests[i].h2); + EXPECT_EQ(tests[i].intersects, r1.Intersects(r2)); + EXPECT_EQ(tests[i].intersects, r2.Intersects(r1)); + } +} + +TEST(RectTest, Intersect) { + static const struct { + int x1; // rect 1 + int y1; + int w1; + int h1; + int x2; // rect 2 + int y2; + int w2; + int h2; + int x3; // rect 3: the union of rects 1 and 2 + int y3; + int w3; + int h3; + } tests[] = { + { 0, 0, 0, 0, // zeros + 0, 0, 0, 0, + 0, 0, 0, 0 }, + { 0, 0, 4, 4, // equal + 0, 0, 4, 4, + 0, 0, 4, 4 }, + { 0, 0, 4, 4, // neighboring + 4, 4, 4, 4, + 0, 0, 0, 0 }, + { 0, 0, 4, 4, // overlapping corners + 2, 2, 4, 4, + 2, 2, 2, 2 }, + { 0, 0, 4, 4, // T junction + 3, 1, 4, 2, + 3, 1, 1, 2 }, + { 3, 0, 2, 2, // gap + 0, 0, 2, 2, + 0, 0, 0, 0 } + }; + for (size_t i = 0; i < base::size(tests); ++i) { + Rect r1(tests[i].x1, tests[i].y1, tests[i].w1, tests[i].h1); + Rect r2(tests[i].x2, tests[i].y2, tests[i].w2, tests[i].h2); + Rect r3(tests[i].x3, tests[i].y3, tests[i].w3, tests[i].h3); + Rect ir = IntersectRects(r1, r2); + EXPECT_EQ(r3.x(), ir.x()); + EXPECT_EQ(r3.y(), ir.y()); + EXPECT_EQ(r3.width(), ir.width()); + EXPECT_EQ(r3.height(), ir.height()); + } +} + +TEST(RectTest, Union) { + static const struct Test { + int x1; // rect 1 + int y1; + int w1; + int h1; + int x2; // rect 2 + int y2; + int w2; + int h2; + int x3; // rect 3: the union of rects 1 and 2 + int y3; + int w3; + int h3; + } tests[] = { + { 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0 }, + { 0, 0, 4, 4, + 0, 0, 4, 4, + 0, 0, 4, 4 }, + { 0, 0, 4, 4, + 4, 4, 4, 4, + 0, 0, 8, 8 }, + { 0, 0, 4, 4, + 0, 5, 4, 4, + 0, 0, 4, 9 }, + { 0, 0, 2, 2, + 3, 3, 2, 2, + 0, 0, 5, 5 }, + { 3, 3, 2, 2, // reverse r1 and r2 from previous test + 0, 0, 2, 2, + 0, 0, 5, 5 }, + { 0, 0, 0, 0, // union with empty rect + 2, 2, 2, 2, + 2, 2, 2, 2 } + }; + for (size_t i = 0; i < base::size(tests); ++i) { + Rect r1(tests[i].x1, tests[i].y1, tests[i].w1, tests[i].h1); + Rect r2(tests[i].x2, tests[i].y2, tests[i].w2, tests[i].h2); + Rect r3(tests[i].x3, tests[i].y3, tests[i].w3, tests[i].h3); + Rect u = UnionRects(r1, r2); + EXPECT_EQ(r3.x(), u.x()); + EXPECT_EQ(r3.y(), u.y()); + EXPECT_EQ(r3.width(), u.width()); + EXPECT_EQ(r3.height(), u.height()); + } +} + +TEST(RectTest, Equals) { + ASSERT_TRUE(Rect(0, 0, 0, 0) == Rect(0, 0, 0, 0)); + ASSERT_TRUE(Rect(1, 2, 3, 4) == Rect(1, 2, 3, 4)); + ASSERT_FALSE(Rect(0, 0, 0, 0) == Rect(0, 0, 0, 1)); + ASSERT_FALSE(Rect(0, 0, 0, 0) == Rect(0, 0, 1, 0)); + ASSERT_FALSE(Rect(0, 0, 0, 0) == Rect(0, 1, 0, 0)); + ASSERT_FALSE(Rect(0, 0, 0, 0) == Rect(1, 0, 0, 0)); +} + +TEST(RectTest, AdjustToFit) { + static const struct Test { + int x1; // source + int y1; + int w1; + int h1; + int x2; // target + int y2; + int w2; + int h2; + int x3; // rect 3: results of invoking AdjustToFit + int y3; + int w3; + int h3; + } tests[] = { + { 0, 0, 2, 2, + 0, 0, 2, 2, + 0, 0, 2, 2 }, + { 2, 2, 3, 3, + 0, 0, 4, 4, + 1, 1, 3, 3 }, + { -1, -1, 5, 5, + 0, 0, 4, 4, + 0, 0, 4, 4 }, + { 2, 2, 4, 4, + 0, 0, 3, 3, + 0, 0, 3, 3 }, + { 2, 2, 1, 1, + 0, 0, 3, 3, + 2, 2, 1, 1 } + }; + for (size_t i = 0; i < base::size(tests); ++i) { + Rect r1(tests[i].x1, tests[i].y1, tests[i].w1, tests[i].h1); + Rect r2(tests[i].x2, tests[i].y2, tests[i].w2, tests[i].h2); + Rect r3(tests[i].x3, tests[i].y3, tests[i].w3, tests[i].h3); + Rect u = r1; + u.AdjustToFit(r2); + EXPECT_EQ(r3.x(), u.x()); + EXPECT_EQ(r3.y(), u.y()); + EXPECT_EQ(r3.width(), u.width()); + EXPECT_EQ(r3.height(), u.height()); + } +} + +TEST(RectTest, Subtract) { + Rect result; + + // Matching + result = Rect(10, 10, 20, 20); + result.Subtract(Rect(10, 10, 20, 20)); + EXPECT_EQ(Rect(0, 0, 0, 0), result); + + // Contains + result = Rect(10, 10, 20, 20); + result.Subtract(Rect(5, 5, 30, 30)); + EXPECT_EQ(Rect(0, 0, 0, 0), result); + + // No intersection + result = Rect(10, 10, 20, 20); + result.Subtract(Rect(30, 30, 30, 30)); + EXPECT_EQ(Rect(10, 10, 20, 20), result); + + // Not a complete intersection in either direction + result = Rect(10, 10, 20, 20); + result.Subtract(Rect(15, 15, 20, 20)); + EXPECT_EQ(Rect(10, 10, 20, 20), result); + + // Complete intersection in the x-direction, top edge is fully covered. + result = Rect(10, 10, 20, 20); + result.Subtract(Rect(10, 15, 20, 20)); + EXPECT_EQ(Rect(10, 10, 20, 5), result); + + // Complete intersection in the x-direction, top edge is fully covered. + result = Rect(10, 10, 20, 20); + result.Subtract(Rect(5, 15, 30, 20)); + EXPECT_EQ(Rect(10, 10, 20, 5), result); + + // Complete intersection in the x-direction, bottom edge is fully covered. + result = Rect(10, 10, 20, 20); + result.Subtract(Rect(5, 5, 30, 20)); + EXPECT_EQ(Rect(10, 25, 20, 5), result); + + // Complete intersection in the x-direction, none of the edges is fully + // covered. + result = Rect(10, 10, 20, 20); + result.Subtract(Rect(5, 15, 30, 1)); + EXPECT_EQ(Rect(10, 10, 20, 20), result); + + // Complete intersection in the y-direction, left edge is fully covered. + result = Rect(10, 10, 20, 20); + result.Subtract(Rect(10, 10, 10, 30)); + EXPECT_EQ(Rect(20, 10, 10, 20), result); + + // Complete intersection in the y-direction, left edge is fully covered. + result = Rect(10, 10, 20, 20); + result.Subtract(Rect(5, 5, 20, 30)); + EXPECT_EQ(Rect(25, 10, 5, 20), result); + + // Complete intersection in the y-direction, right edge is fully covered. + result = Rect(10, 10, 20, 20); + result.Subtract(Rect(20, 5, 20, 30)); + EXPECT_EQ(Rect(10, 10, 10, 20), result); + + // Complete intersection in the y-direction, none of the edges is fully + // covered. + result = Rect(10, 10, 20, 20); + result.Subtract(Rect(15, 5, 1, 30)); + EXPECT_EQ(Rect(10, 10, 20, 20), result); +} + +TEST(RectTest, IsEmpty) { + EXPECT_TRUE(Rect(0, 0, 0, 0).IsEmpty()); + EXPECT_TRUE(Rect(0, 0, 0, 0).size().IsEmpty()); + EXPECT_TRUE(Rect(0, 0, 10, 0).IsEmpty()); + EXPECT_TRUE(Rect(0, 0, 10, 0).size().IsEmpty()); + EXPECT_TRUE(Rect(0, 0, 0, 10).IsEmpty()); + EXPECT_TRUE(Rect(0, 0, 0, 10).size().IsEmpty()); + EXPECT_FALSE(Rect(0, 0, 10, 10).IsEmpty()); + EXPECT_FALSE(Rect(0, 0, 10, 10).size().IsEmpty()); +} + +TEST(RectTest, SplitVertically) { + Rect left_half, right_half; + + // Splitting when origin is (0, 0). + Rect(0, 0, 20, 20).SplitVertically(&left_half, &right_half); + EXPECT_TRUE(left_half == Rect(0, 0, 10, 20)); + EXPECT_TRUE(right_half == Rect(10, 0, 10, 20)); + + // Splitting when origin is arbitrary. + Rect(10, 10, 20, 10).SplitVertically(&left_half, &right_half); + EXPECT_TRUE(left_half == Rect(10, 10, 10, 10)); + EXPECT_TRUE(right_half == Rect(20, 10, 10, 10)); + + // Splitting a rectangle of zero width. + Rect(10, 10, 0, 10).SplitVertically(&left_half, &right_half); + EXPECT_TRUE(left_half == Rect(10, 10, 0, 10)); + EXPECT_TRUE(right_half == Rect(10, 10, 0, 10)); + + // Splitting a rectangle of odd width. + Rect(10, 10, 5, 10).SplitVertically(&left_half, &right_half); + EXPECT_TRUE(left_half == Rect(10, 10, 2, 10)); + EXPECT_TRUE(right_half == Rect(12, 10, 3, 10)); +} + +TEST(RectTest, CenterPoint) { + Point center; + + // When origin is (0, 0). + center = Rect(0, 0, 20, 20).CenterPoint(); + EXPECT_TRUE(center == Point(10, 10)); + + // When origin is even. + center = Rect(10, 10, 20, 20).CenterPoint(); + EXPECT_TRUE(center == Point(20, 20)); + + // When origin is odd. + center = Rect(11, 11, 20, 20).CenterPoint(); + EXPECT_TRUE(center == Point(21, 21)); + + // When 0 width or height. + center = Rect(10, 10, 0, 20).CenterPoint(); + EXPECT_TRUE(center == Point(10, 20)); + center = Rect(10, 10, 20, 0).CenterPoint(); + EXPECT_TRUE(center == Point(20, 10)); + + // When an odd size. + center = Rect(10, 10, 21, 21).CenterPoint(); + EXPECT_TRUE(center == Point(20, 20)); + + // When an odd size and position. + center = Rect(11, 11, 21, 21).CenterPoint(); + EXPECT_TRUE(center == Point(21, 21)); +} + +TEST(RectTest, CenterPointF) { + PointF center; + + // When origin is (0, 0). + center = RectF(0, 0, 20, 20).CenterPoint(); + EXPECT_TRUE(center == PointF(10, 10)); + + // When origin is even. + center = RectF(10, 10, 20, 20).CenterPoint(); + EXPECT_TRUE(center == PointF(20, 20)); + + // When origin is odd. + center = RectF(11, 11, 20, 20).CenterPoint(); + EXPECT_TRUE(center == PointF(21, 21)); + + // When 0 width or height. + center = RectF(10, 10, 0, 20).CenterPoint(); + EXPECT_TRUE(center == PointF(10, 20)); + center = RectF(10, 10, 20, 0).CenterPoint(); + EXPECT_TRUE(center == PointF(20, 10)); + + // When an odd size. + center = RectF(10, 10, 21, 21).CenterPoint(); + EXPECT_TRUE(center == PointF(20.5f, 20.5f)); + + // When an odd size and position. + center = RectF(11, 11, 21, 21).CenterPoint(); + EXPECT_TRUE(center == PointF(21.5f, 21.5f)); +} + +TEST(RectTest, SharesEdgeWith) { + Rect r(2, 3, 4, 5); + + // Must be non-overlapping + EXPECT_FALSE(r.SharesEdgeWith(r)); + + Rect just_above(2, 1, 4, 2); + Rect just_below(2, 8, 4, 2); + Rect just_left(0, 3, 2, 5); + Rect just_right(6, 3, 2, 5); + + EXPECT_TRUE(r.SharesEdgeWith(just_above)); + EXPECT_TRUE(r.SharesEdgeWith(just_below)); + EXPECT_TRUE(r.SharesEdgeWith(just_left)); + EXPECT_TRUE(r.SharesEdgeWith(just_right)); + + // Wrong placement + Rect same_height_no_edge(0, 0, 1, 5); + Rect same_width_no_edge(0, 0, 4, 1); + + EXPECT_FALSE(r.SharesEdgeWith(same_height_no_edge)); + EXPECT_FALSE(r.SharesEdgeWith(same_width_no_edge)); + + Rect just_above_no_edge(2, 1, 5, 2); // too wide + Rect just_below_no_edge(2, 8, 3, 2); // too narrow + Rect just_left_no_edge(0, 3, 2, 6); // too tall + Rect just_right_no_edge(6, 3, 2, 4); // too short + + EXPECT_FALSE(r.SharesEdgeWith(just_above_no_edge)); + EXPECT_FALSE(r.SharesEdgeWith(just_below_no_edge)); + EXPECT_FALSE(r.SharesEdgeWith(just_left_no_edge)); + EXPECT_FALSE(r.SharesEdgeWith(just_right_no_edge)); +} + +// Similar to EXPECT_FLOAT_EQ, but lets NaN equal NaN +#define EXPECT_FLOAT_AND_NAN_EQ(a, b) \ + { if (a == a || b == b) { EXPECT_FLOAT_EQ(a, b); } } + +TEST(RectTest, ScaleRect) { + static const struct Test { + int x1; // source + int y1; + int w1; + int h1; + float scale; + float x2; // target + float y2; + float w2; + float h2; + } tests[] = { + { 3, 3, 3, 3, + 1.5f, + 4.5f, 4.5f, 4.5f, 4.5f }, + { 3, 3, 3, 3, + 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f }, + { 3, 3, 3, 3, + std::numeric_limits::quiet_NaN(), + std::numeric_limits::quiet_NaN(), + std::numeric_limits::quiet_NaN(), + std::numeric_limits::quiet_NaN(), + std::numeric_limits::quiet_NaN() }, + { 3, 3, 3, 3, + std::numeric_limits::max(), + std::numeric_limits::max(), + std::numeric_limits::max(), + std::numeric_limits::max(), + std::numeric_limits::max() } + }; + + for (size_t i = 0; i < base::size(tests); ++i) { + RectF r1(tests[i].x1, tests[i].y1, tests[i].w1, tests[i].h1); + RectF r2(tests[i].x2, tests[i].y2, tests[i].w2, tests[i].h2); + + RectF scaled = ScaleRect(r1, tests[i].scale); + EXPECT_FLOAT_AND_NAN_EQ(r2.x(), scaled.x()); + EXPECT_FLOAT_AND_NAN_EQ(r2.y(), scaled.y()); + EXPECT_FLOAT_AND_NAN_EQ(r2.width(), scaled.width()); + EXPECT_FLOAT_AND_NAN_EQ(r2.height(), scaled.height()); + } +} + +TEST(RectTest, ToEnclosedRect) { + static const int max_int = std::numeric_limits::max(); + static const int min_int = std::numeric_limits::min(); + static const float max_float = std::numeric_limits::max(); + static const float max_int_f = static_cast(max_int); + static const float min_int_f = static_cast(min_int); + + static const struct Test { + struct { + float x; + float y; + float width; + float height; + } in; + struct { + int x; + int y; + int width; + int height; + } expected; + } tests[] = { + {{0.0f, 0.0f, 0.0f, 0.0f}, {0, 0, 0, 0}}, + {{-1.5f, -1.5f, 3.0f, 3.0f}, {-1, -1, 2, 2}}, + {{-1.5f, -1.5f, 3.5f, 3.5f}, {-1, -1, 3, 3}}, + {{max_float, max_float, 2.0f, 2.0f}, {max_int, max_int, 0, 0}}, + {{0.0f, 0.0f, max_float, max_float}, {0, 0, max_int, max_int}}, + {{20000.5f, 20000.5f, 0.5f, 0.5f}, {20001, 20001, 0, 0}}, + {{max_int_f, max_int_f, max_int_f, max_int_f}, {max_int, max_int, 0, 0}}, + {{1.9999f, 2.0002f, 5.9998f, 6.0001f}, {2, 3, 5, 5}}, + {{1.9999f, 2.0001f, 6.0002f, 5.9998f}, {2, 3, 6, 4}}, + {{1.9998f, 2.0002f, 6.0001f, 5.9999f}, {2, 3, 5, 5}}}; + + for (size_t i = 0; i < base::size(tests); ++i) { + RectF source(tests[i].in.x, tests[i].in.y, tests[i].in.width, + tests[i].in.height); + Rect enclosed = ToEnclosedRect(source); + + EXPECT_EQ(tests[i].expected.x, enclosed.x()); + EXPECT_EQ(tests[i].expected.y, enclosed.y()); + EXPECT_EQ(tests[i].expected.width, enclosed.width()); + EXPECT_EQ(tests[i].expected.height, enclosed.height()); + } + + { + RectF source(min_int_f, min_int_f, max_int_f * 3.f, max_int_f * 3.f); + Rect enclosed = ToEnclosedRect(source); + + // That rect can't be represented, but it should be big. + EXPECT_EQ(max_int, enclosed.width()); + EXPECT_EQ(max_int, enclosed.height()); + // It should include some axis near the global origin. + EXPECT_GT(1, enclosed.x()); + EXPECT_GT(1, enclosed.y()); + // And it should not cause computation issues for itself. + EXPECT_LT(0, enclosed.right()); + EXPECT_LT(0, enclosed.bottom()); + } +} + +TEST(RectTest, ToEnclosingRect) { + static const int max_int = std::numeric_limits::max(); + static const int min_int = std::numeric_limits::min(); + static const float max_float = std::numeric_limits::max(); + static const float epsilon_float = std::numeric_limits::epsilon(); + static const float max_int_f = static_cast(max_int); + static const float min_int_f = static_cast(min_int); + static const struct Test { + struct { + float x; + float y; + float width; + float height; + } in; + struct { + int x; + int y; + int width; + int height; + } expected; + } tests[] = { + {{0.0f, 0.0f, 0.0f, 0.0f}, {0, 0, 0, 0}}, + {{5.5f, 5.5f, 0.0f, 0.0f}, {5, 5, 0, 0}}, + {{3.5f, 2.5f, epsilon_float, -0.0f}, {3, 2, 0, 0}}, + {{3.5f, 2.5f, 0.f, 0.001f}, {3, 2, 0, 1}}, + {{-1.5f, -1.5f, 3.0f, 3.0f}, {-2, -2, 4, 4}}, + {{-1.5f, -1.5f, 3.5f, 3.5f}, {-2, -2, 4, 4}}, + {{max_float, max_float, 2.0f, 2.0f}, {max_int, max_int, 0, 0}}, + {{0.0f, 0.0f, max_float, max_float}, {0, 0, max_int, max_int}}, + {{20000.5f, 20000.5f, 0.5f, 0.5f}, {20000, 20000, 1, 1}}, + {{max_int_f, max_int_f, max_int_f, max_int_f}, {max_int, max_int, 0, 0}}, + {{-0.5f, -0.5f, 22777712.f, 1.f}, {-1, -1, 22777713, 2}}, + {{1.9999f, 2.0002f, 5.9998f, 6.0001f}, {1, 2, 7, 7}}, + {{1.9999f, 2.0001f, 6.0002f, 5.9998f}, {1, 2, 8, 6}}, + {{1.9998f, 2.0002f, 6.0001f, 5.9999f}, {1, 2, 7, 7}}}; + + for (size_t i = 0; i < base::size(tests); ++i) { + RectF source(tests[i].in.x, tests[i].in.y, tests[i].in.width, + tests[i].in.height); + + Rect enclosing = ToEnclosingRect(source); + EXPECT_EQ(tests[i].expected.x, enclosing.x()); + EXPECT_EQ(tests[i].expected.y, enclosing.y()); + EXPECT_EQ(tests[i].expected.width, enclosing.width()); + EXPECT_EQ(tests[i].expected.height, enclosing.height()); + } + + { + RectF source(min_int_f, min_int_f, max_int_f * 3.f, max_int_f * 3.f); + Rect enclosing = ToEnclosingRect(source); + + // That rect can't be represented, but it should be big. + EXPECT_EQ(max_int, enclosing.width()); + EXPECT_EQ(max_int, enclosing.height()); + // It should include some axis near the global origin. + EXPECT_GT(1, enclosing.x()); + EXPECT_GT(1, enclosing.y()); + // And it should cause computation issues for itself. + EXPECT_LT(0, enclosing.right()); + EXPECT_LT(0, enclosing.bottom()); + } +} + +TEST(RectTest, ToEnclosingRectIgnoringError) { + static const int max_int = std::numeric_limits::max(); + static const float max_float = std::numeric_limits::max(); + static const float epsilon_float = std::numeric_limits::epsilon(); + static const float max_int_f = static_cast(max_int); + static const float error = 0.001f; + static const struct Test { + struct { + float x; + float y; + float width; + float height; + } in; + struct { + int x; + int y; + int width; + int height; + } expected; + } tests[] = { + {{0.0f, 0.0f, 0.0f, 0.0f}, {0, 0, 0, 0}}, + {{5.5f, 5.5f, 0.0f, 0.0f}, {5, 5, 0, 0}}, + {{3.5f, 2.5f, epsilon_float, -0.0f}, {3, 2, 0, 0}}, + {{3.5f, 2.5f, 0.f, 0.001f}, {3, 2, 0, 1}}, + {{-1.5f, -1.5f, 3.0f, 3.0f}, {-2, -2, 4, 4}}, + {{-1.5f, -1.5f, 3.5f, 3.5f}, {-2, -2, 4, 4}}, + {{max_float, max_float, 2.0f, 2.0f}, {max_int, max_int, 0, 0}}, + {{0.0f, 0.0f, max_float, max_float}, {0, 0, max_int, max_int}}, + {{20000.5f, 20000.5f, 0.5f, 0.5f}, {20000, 20000, 1, 1}}, + {{max_int_f, max_int_f, max_int_f, max_int_f}, {max_int, max_int, 0, 0}}, + {{-0.5f, -0.5f, 22777712.f, 1.f}, {-1, -1, 22777713, 2}}, + {{1.9999f, 2.0002f, 5.9998f, 6.0001f}, {2, 2, 6, 6}}, + {{1.9999f, 2.0001f, 6.0002f, 5.9998f}, {2, 2, 6, 6}}, + {{1.9998f, 2.0002f, 6.0001f, 5.9999f}, {2, 2, 6, 6}}}; + + for (size_t i = 0; i < base::size(tests); ++i) { + RectF source(tests[i].in.x, tests[i].in.y, tests[i].in.width, + tests[i].in.height); + + Rect enclosing = ToEnclosingRectIgnoringError(source, error); + EXPECT_EQ(tests[i].expected.x, enclosing.x()); + EXPECT_EQ(tests[i].expected.y, enclosing.y()); + EXPECT_EQ(tests[i].expected.width, enclosing.width()); + EXPECT_EQ(tests[i].expected.height, enclosing.height()); + } +} + +TEST(RectTest, ToNearestRect) { + Rect rect; + EXPECT_EQ(rect, ToNearestRect(RectF(rect))); + + rect = Rect(-1, -1, 3, 3); + EXPECT_EQ(rect, ToNearestRect(RectF(rect))); + + RectF rectf(-1.00001f, -0.999999f, 3.0000001f, 2.999999f); + EXPECT_EQ(rect, ToNearestRect(rectf)); +} + +TEST(RectTest, ToFlooredRect) { + static const struct Test { + float x1; // source + float y1; + float w1; + float h1; + int x2; // target + int y2; + int w2; + int h2; + } tests [] = { + { 0.0f, 0.0f, 0.0f, 0.0f, + 0, 0, 0, 0 }, + { -1.5f, -1.5f, 3.0f, 3.0f, + -2, -2, 3, 3 }, + { -1.5f, -1.5f, 3.5f, 3.5f, + -2, -2, 3, 3 }, + { 20000.5f, 20000.5f, 0.5f, 0.5f, + 20000, 20000, 0, 0 }, + }; + + for (size_t i = 0; i < base::size(tests); ++i) { + RectF r1(tests[i].x1, tests[i].y1, tests[i].w1, tests[i].h1); + Rect r2(tests[i].x2, tests[i].y2, tests[i].w2, tests[i].h2); + + Rect floored = ToFlooredRectDeprecated(r1); + EXPECT_FLOAT_EQ(r2.x(), floored.x()); + EXPECT_FLOAT_EQ(r2.y(), floored.y()); + EXPECT_FLOAT_EQ(r2.width(), floored.width()); + EXPECT_FLOAT_EQ(r2.height(), floored.height()); + } +} + +TEST(RectTest, ScaleToEnclosedRect) { + static const struct Test { + Rect input_rect; + float input_scale; + Rect expected_rect; + } tests[] = { + { + Rect(), + 5.f, + Rect(), + }, { + Rect(1, 1, 1, 1), + 5.f, + Rect(5, 5, 5, 5), + }, { + Rect(-1, -1, 0, 0), + 5.f, + Rect(-5, -5, 0, 0), + }, { + Rect(1, -1, 0, 1), + 5.f, + Rect(5, -5, 0, 5), + }, { + Rect(-1, 1, 1, 0), + 5.f, + Rect(-5, 5, 5, 0), + }, { + Rect(1, 2, 3, 4), + 1.5f, + Rect(2, 3, 4, 6), + }, { + Rect(-1, -2, 0, 0), + 1.5f, + Rect(-1, -3, 0, 0), + } + }; + + for (size_t i = 0; i < base::size(tests); ++i) { + Rect result = ScaleToEnclosedRect(tests[i].input_rect, + tests[i].input_scale); + EXPECT_EQ(tests[i].expected_rect, result); + } +} + +TEST(RectTest, ScaleToEnclosingRect) { + static const struct Test { + Rect input_rect; + float input_scale; + Rect expected_rect; + } tests[] = { + { + Rect(), + 5.f, + Rect(), + }, { + Rect(1, 1, 1, 1), + 5.f, + Rect(5, 5, 5, 5), + }, { + Rect(-1, -1, 0, 0), + 5.f, + Rect(-5, -5, 0, 0), + }, { + Rect(1, -1, 0, 1), + 5.f, + Rect(5, -5, 0, 5), + }, { + Rect(-1, 1, 1, 0), + 5.f, + Rect(-5, 5, 5, 0), + }, { + Rect(1, 2, 3, 4), + 1.5f, + Rect(1, 3, 5, 6), + }, { + Rect(-1, -2, 0, 0), + 1.5f, + Rect(-2, -3, 0, 0), + } + }; + + for (size_t i = 0; i < base::size(tests); ++i) { + Rect result = + ScaleToEnclosingRect(tests[i].input_rect, tests[i].input_scale); + EXPECT_EQ(tests[i].expected_rect, result); + Rect result_safe = + ScaleToEnclosingRectSafe(tests[i].input_rect, tests[i].input_scale); + EXPECT_EQ(tests[i].expected_rect, result_safe); + } +} + +#if defined(OS_WIN) +TEST(RectTest, ConstructAndAssign) { + const RECT rect_1 = { 0, 0, 10, 10 }; + const RECT rect_2 = { 0, 0, -10, -10 }; + Rect test1(rect_1); + Rect test2(rect_2); +} +#endif + +TEST(RectTest, ToRectF) { + // Check that explicit conversion from integer to float compiles. + Rect a(10, 20, 30, 40); + RectF b(10, 20, 30, 40); + + RectF c = RectF(a); + EXPECT_EQ(b, c); +} + +TEST(RectTest, BoundingRect) { + struct { + Point a; + Point b; + Rect expected; + } int_tests[] = { + // If point B dominates A, then A should be the origin. + { Point(4, 6), Point(4, 6), Rect(4, 6, 0, 0) }, + { Point(4, 6), Point(8, 6), Rect(4, 6, 4, 0) }, + { Point(4, 6), Point(4, 9), Rect(4, 6, 0, 3) }, + { Point(4, 6), Point(8, 9), Rect(4, 6, 4, 3) }, + // If point A dominates B, then B should be the origin. + { Point(4, 6), Point(4, 6), Rect(4, 6, 0, 0) }, + { Point(8, 6), Point(4, 6), Rect(4, 6, 4, 0) }, + { Point(4, 9), Point(4, 6), Rect(4, 6, 0, 3) }, + { Point(8, 9), Point(4, 6), Rect(4, 6, 4, 3) }, + // If neither point dominates, then the origin is a combination of the two. + { Point(4, 6), Point(6, 4), Rect(4, 4, 2, 2) }, + { Point(-4, -6), Point(-6, -4), Rect(-6, -6, 2, 2) }, + { Point(-4, 6), Point(6, -4), Rect(-4, -4, 10, 10) }, + }; + + for (size_t i = 0; i < base::size(int_tests); ++i) { + Rect actual = BoundingRect(int_tests[i].a, int_tests[i].b); + EXPECT_EQ(int_tests[i].expected, actual); + } + + struct { + PointF a; + PointF b; + RectF expected; + } float_tests[] = { + // If point B dominates A, then A should be the origin. + { PointF(4.2f, 6.8f), PointF(4.2f, 6.8f), + RectF(4.2f, 6.8f, 0, 0) }, + { PointF(4.2f, 6.8f), PointF(8.5f, 6.8f), + RectF(4.2f, 6.8f, 4.3f, 0) }, + { PointF(4.2f, 6.8f), PointF(4.2f, 9.3f), + RectF(4.2f, 6.8f, 0, 2.5f) }, + { PointF(4.2f, 6.8f), PointF(8.5f, 9.3f), + RectF(4.2f, 6.8f, 4.3f, 2.5f) }, + // If point A dominates B, then B should be the origin. + { PointF(4.2f, 6.8f), PointF(4.2f, 6.8f), + RectF(4.2f, 6.8f, 0, 0) }, + { PointF(8.5f, 6.8f), PointF(4.2f, 6.8f), + RectF(4.2f, 6.8f, 4.3f, 0) }, + { PointF(4.2f, 9.3f), PointF(4.2f, 6.8f), + RectF(4.2f, 6.8f, 0, 2.5f) }, + { PointF(8.5f, 9.3f), PointF(4.2f, 6.8f), + RectF(4.2f, 6.8f, 4.3f, 2.5f) }, + // If neither point dominates, then the origin is a combination of the two. + { PointF(4.2f, 6.8f), PointF(6.8f, 4.2f), + RectF(4.2f, 4.2f, 2.6f, 2.6f) }, + { PointF(-4.2f, -6.8f), PointF(-6.8f, -4.2f), + RectF(-6.8f, -6.8f, 2.6f, 2.6f) }, + { PointF(-4.2f, 6.8f), PointF(6.8f, -4.2f), + RectF(-4.2f, -4.2f, 11.0f, 11.0f) } + }; + + for (size_t i = 0; i < base::size(float_tests); ++i) { + RectF actual = BoundingRect(float_tests[i].a, float_tests[i].b); + EXPECT_RECTF_EQ(float_tests[i].expected, actual); + } +} + +TEST(RectTest, IsExpressibleAsRect) { + EXPECT_TRUE(RectF().IsExpressibleAsRect()); + + float min = std::numeric_limits::min(); + float max = static_cast(std::numeric_limits::max()); + float infinity = std::numeric_limits::infinity(); + + EXPECT_TRUE(RectF( + min + 200, min + 200, max - 200, max - 200).IsExpressibleAsRect()); + EXPECT_FALSE(RectF( + min - 200, min + 200, max + 200, max + 200).IsExpressibleAsRect()); + EXPECT_FALSE(RectF( + min + 200 , min - 200, max + 200, max + 200).IsExpressibleAsRect()); + EXPECT_FALSE(RectF( + min + 200, min + 200, max + 200, max - 200).IsExpressibleAsRect()); + EXPECT_FALSE(RectF( + min + 200, min + 200, max - 200, max + 200).IsExpressibleAsRect()); + + EXPECT_TRUE(RectF(0, 0, max - 200, max - 200).IsExpressibleAsRect()); + EXPECT_FALSE(RectF(200, 0, max + 200, max - 200).IsExpressibleAsRect()); + EXPECT_FALSE(RectF(0, 200, max - 200, max + 200).IsExpressibleAsRect()); + EXPECT_FALSE(RectF(0, 0, max + 200, max - 200).IsExpressibleAsRect()); + EXPECT_FALSE(RectF(0, 0, max - 200, max + 200).IsExpressibleAsRect()); + + EXPECT_FALSE(RectF(infinity, 0, 1, 1).IsExpressibleAsRect()); + EXPECT_FALSE(RectF(0, infinity, 1, 1).IsExpressibleAsRect()); + EXPECT_FALSE(RectF(0, 0, infinity, 1).IsExpressibleAsRect()); + EXPECT_FALSE(RectF(0, 0, 1, infinity).IsExpressibleAsRect()); +} + +TEST(RectTest, Offset) { + Rect i(1, 2, 3, 4); + + EXPECT_EQ(Rect(2, 1, 3, 4), (i + Vector2d(1, -1))); + EXPECT_EQ(Rect(2, 1, 3, 4), (Vector2d(1, -1) + i)); + i += Vector2d(1, -1); + EXPECT_EQ(Rect(2, 1, 3, 4), i); + EXPECT_EQ(Rect(1, 2, 3, 4), (i - Vector2d(1, -1))); + i -= Vector2d(1, -1); + EXPECT_EQ(Rect(1, 2, 3, 4), i); + + i.Offset(2, -2); + EXPECT_EQ(Rect(3, 0, 3, 4), i); + + RectF f(1.1f, 2.2f, 3.3f, 4.4f); + EXPECT_EQ(RectF(2.2f, 1.1f, 3.3f, 4.4f), (f + Vector2dF(1.1f, -1.1f))); + EXPECT_EQ(RectF(2.2f, 1.1f, 3.3f, 4.4f), (Vector2dF(1.1f, -1.1f) + f)); + f += Vector2dF(1.1f, -1.1f); + EXPECT_EQ(RectF(2.2f, 1.1f, 3.3f, 4.4f), f); + EXPECT_EQ(RectF(1.1f, 2.2f, 3.3f, 4.4f), (f - Vector2dF(1.1f, -1.1f))); + f -= Vector2dF(1.1f, -1.1f); + EXPECT_EQ(RectF(1.1f, 2.2f, 3.3f, 4.4f), f); +} + +TEST(RectTest, Corners) { + Rect i(1, 2, 3, 4); + RectF f(1.1f, 2.1f, 3.1f, 4.1f); + + EXPECT_EQ(Point(1, 2), i.origin()); + EXPECT_EQ(Point(4, 2), i.top_right()); + EXPECT_EQ(Point(1, 6), i.bottom_left()); + EXPECT_EQ(Point(4, 6), i.bottom_right()); + + EXPECT_EQ(PointF(1.1f, 2.1f), f.origin()); + EXPECT_EQ(PointF(4.2f, 2.1f), f.top_right()); + EXPECT_EQ(PointF(1.1f, 6.2f), f.bottom_left()); + EXPECT_EQ(PointF(4.2f, 6.2f), f.bottom_right()); +} + +TEST(RectTest, Centers) { + Rect i(10, 20, 30, 40); + EXPECT_EQ(Point(10, 40), i.left_center()); + EXPECT_EQ(Point(25, 20), i.top_center()); + EXPECT_EQ(Point(40, 40), i.right_center()); + EXPECT_EQ(Point(25, 60), i.bottom_center()); + + RectF f(10.1f, 20.2f, 30.3f, 40.4f); + EXPECT_EQ(PointF(10.1f, 40.4f), f.left_center()); + EXPECT_EQ(PointF(25.25f, 20.2f), f.top_center()); + EXPECT_EQ(PointF(40.4f, 40.4f), f.right_center()); + EXPECT_EQ(25.25f, f.bottom_center().x()); + EXPECT_NEAR(60.6f, f.bottom_center().y(), 0.001f); +} + +TEST(RectTest, Transpose) { + Rect i(10, 20, 30, 40); + i.Transpose(); + EXPECT_EQ(Rect(20, 10, 40, 30), i); + + RectF f(10.1f, 20.2f, 30.3f, 40.4f); + f.Transpose(); + EXPECT_EQ(RectF(20.2f, 10.1f, 40.4f, 30.3f), f); +} + +TEST(RectTest, ManhattanDistanceToPoint) { + Rect i(1, 2, 3, 4); + EXPECT_EQ(0, i.ManhattanDistanceToPoint(Point(1, 2))); + EXPECT_EQ(0, i.ManhattanDistanceToPoint(Point(4, 6))); + EXPECT_EQ(0, i.ManhattanDistanceToPoint(Point(2, 4))); + EXPECT_EQ(3, i.ManhattanDistanceToPoint(Point(0, 0))); + EXPECT_EQ(2, i.ManhattanDistanceToPoint(Point(2, 0))); + EXPECT_EQ(3, i.ManhattanDistanceToPoint(Point(5, 0))); + EXPECT_EQ(1, i.ManhattanDistanceToPoint(Point(5, 4))); + EXPECT_EQ(3, i.ManhattanDistanceToPoint(Point(5, 8))); + EXPECT_EQ(2, i.ManhattanDistanceToPoint(Point(3, 8))); + EXPECT_EQ(2, i.ManhattanDistanceToPoint(Point(0, 7))); + EXPECT_EQ(1, i.ManhattanDistanceToPoint(Point(0, 3))); + + RectF f(1.1f, 2.1f, 3.1f, 4.1f); + EXPECT_FLOAT_EQ(0.f, f.ManhattanDistanceToPoint(PointF(1.1f, 2.1f))); + EXPECT_FLOAT_EQ(0.f, f.ManhattanDistanceToPoint(PointF(4.2f, 6.f))); + EXPECT_FLOAT_EQ(0.f, f.ManhattanDistanceToPoint(PointF(2.f, 4.f))); + EXPECT_FLOAT_EQ(3.2f, f.ManhattanDistanceToPoint(PointF(0.f, 0.f))); + EXPECT_FLOAT_EQ(2.1f, f.ManhattanDistanceToPoint(PointF(2.f, 0.f))); + EXPECT_FLOAT_EQ(2.9f, f.ManhattanDistanceToPoint(PointF(5.f, 0.f))); + EXPECT_FLOAT_EQ(.8f, f.ManhattanDistanceToPoint(PointF(5.f, 4.f))); + EXPECT_FLOAT_EQ(2.6f, f.ManhattanDistanceToPoint(PointF(5.f, 8.f))); + EXPECT_FLOAT_EQ(1.8f, f.ManhattanDistanceToPoint(PointF(3.f, 8.f))); + EXPECT_FLOAT_EQ(1.9f, f.ManhattanDistanceToPoint(PointF(0.f, 7.f))); + EXPECT_FLOAT_EQ(1.1f, f.ManhattanDistanceToPoint(PointF(0.f, 3.f))); +} + +TEST(RectTest, ManhattanInternalDistance) { + Rect i(0, 0, 400, 400); + EXPECT_EQ(0, i.ManhattanInternalDistance(gfx::Rect(-1, 0, 2, 1))); + EXPECT_EQ(1, i.ManhattanInternalDistance(gfx::Rect(400, 0, 1, 400))); + EXPECT_EQ(2, i.ManhattanInternalDistance(gfx::Rect(-100, -100, 100, 100))); + EXPECT_EQ(2, i.ManhattanInternalDistance(gfx::Rect(-101, 100, 100, 100))); + EXPECT_EQ(4, i.ManhattanInternalDistance(gfx::Rect(-101, -101, 100, 100))); + EXPECT_EQ(435, i.ManhattanInternalDistance(gfx::Rect(630, 603, 100, 100))); + + RectF f(0.0f, 0.0f, 400.0f, 400.0f); + static const float kEpsilon = std::numeric_limits::epsilon(); + + EXPECT_FLOAT_EQ( + 0.0f, f.ManhattanInternalDistance(gfx::RectF(-1.0f, 0.0f, 2.0f, 1.0f))); + EXPECT_FLOAT_EQ( + kEpsilon, + f.ManhattanInternalDistance(gfx::RectF(400.0f, 0.0f, 1.0f, 400.0f))); + EXPECT_FLOAT_EQ(2.0f * kEpsilon, + f.ManhattanInternalDistance( + gfx::RectF(-100.0f, -100.0f, 100.0f, 100.0f))); + EXPECT_FLOAT_EQ( + 1.0f + kEpsilon, + f.ManhattanInternalDistance(gfx::RectF(-101.0f, 100.0f, 100.0f, 100.0f))); + EXPECT_FLOAT_EQ(2.0f + 2.0f * kEpsilon, + f.ManhattanInternalDistance( + gfx::RectF(-101.0f, -101.0f, 100.0f, 100.0f))); + EXPECT_FLOAT_EQ( + 433.0f + 2.0f * kEpsilon, + f.ManhattanInternalDistance(gfx::RectF(630.0f, 603.0f, 100.0f, 100.0f))); + + EXPECT_FLOAT_EQ( + 0.0f, f.ManhattanInternalDistance(gfx::RectF(-1.0f, 0.0f, 1.1f, 1.0f))); + EXPECT_FLOAT_EQ( + 0.1f + kEpsilon, + f.ManhattanInternalDistance(gfx::RectF(-1.5f, 0.0f, 1.4f, 1.0f))); + EXPECT_FLOAT_EQ( + kEpsilon, + f.ManhattanInternalDistance(gfx::RectF(-1.5f, 0.0f, 1.5f, 1.0f))); +} + +TEST(RectTest, IntegerOverflow) { + int limit = std::numeric_limits::max(); + int min_limit = std::numeric_limits::min(); + int expected_thickness = 10; + int large_number = limit - expected_thickness; + + Rect height_overflow(0, large_number, 100, 100); + EXPECT_EQ(large_number, height_overflow.y()); + EXPECT_EQ(expected_thickness, height_overflow.height()); + + Rect width_overflow(large_number, 0, 100, 100); + EXPECT_EQ(large_number, width_overflow.x()); + EXPECT_EQ(expected_thickness, width_overflow.width()); + + Rect size_height_overflow(Point(0, large_number), Size(100, 100)); + EXPECT_EQ(large_number, size_height_overflow.y()); + EXPECT_EQ(expected_thickness, size_height_overflow.height()); + + Rect size_width_overflow(Point(large_number, 0), Size(100, 100)); + EXPECT_EQ(large_number, size_width_overflow.x()); + EXPECT_EQ(expected_thickness, size_width_overflow.width()); + + Rect set_height_overflow(0, large_number, 100, 5); + EXPECT_EQ(5, set_height_overflow.height()); + set_height_overflow.set_height(100); + EXPECT_EQ(expected_thickness, set_height_overflow.height()); + + Rect set_y_overflow(100, 100, 100, 100); + EXPECT_EQ(100, set_y_overflow.height()); + set_y_overflow.set_y(large_number); + EXPECT_EQ(expected_thickness, set_y_overflow.height()); + + Rect set_width_overflow(large_number, 0, 5, 100); + EXPECT_EQ(5, set_width_overflow.width()); + set_width_overflow.set_width(100); + EXPECT_EQ(expected_thickness, set_width_overflow.width()); + + Rect set_x_overflow(100, 100, 100, 100); + EXPECT_EQ(100, set_x_overflow.width()); + set_x_overflow.set_x(large_number); + EXPECT_EQ(expected_thickness, set_x_overflow.width()); + + Point large_offset(large_number, large_number); + Size size(100, 100); + Size expected_size(10, 10); + + Rect set_origin_overflow(100, 100, 100, 100); + EXPECT_EQ(size, set_origin_overflow.size()); + set_origin_overflow.set_origin(large_offset); + EXPECT_EQ(large_offset, set_origin_overflow.origin()); + EXPECT_EQ(expected_size, set_origin_overflow.size()); + + Rect set_size_overflow(large_number, large_number, 5, 5); + EXPECT_EQ(Size(5, 5), set_size_overflow.size()); + set_size_overflow.set_size(size); + EXPECT_EQ(large_offset, set_size_overflow.origin()); + EXPECT_EQ(expected_size, set_size_overflow.size()); + + Rect set_rect_overflow; + set_rect_overflow.SetRect(large_number, large_number, 100, 100); + EXPECT_EQ(large_offset, set_rect_overflow.origin()); + EXPECT_EQ(expected_size, set_rect_overflow.size()); + + // Insetting an empty rect, but the total inset (left + right) could overflow. + Rect inset_overflow; + inset_overflow.Inset(large_number, large_number, 100, 100); + EXPECT_EQ(large_offset, inset_overflow.origin()); + EXPECT_EQ(gfx::Size(), inset_overflow.size()); + + // Insetting where the total inset (width - left - right) could overflow. + // Also, this insetting by the min limit in all directions cannot + // represent width() without overflow, so that will also clamp. + Rect inset_overflow2; + inset_overflow2.Inset(min_limit, min_limit, min_limit, min_limit); + EXPECT_EQ(inset_overflow2, gfx::Rect(min_limit, min_limit, limit, limit)); + + // Insetting where the width shouldn't change, but if the insets operations + // clamped in the wrong order, e.g. ((width - left) - right) vs (width - (left + // + right)) then this will not work properly. This is the proper order, + // as if left + right overflows, the width cannot be decreased by more than + // max int anyway. Additionally, if left + right underflows, it cannot be + // increased by more then max int. + Rect inset_overflow3(0, 0, limit, limit); + inset_overflow3.Inset(-100, -100, 100, 100); + EXPECT_EQ(inset_overflow3, gfx::Rect(-100, -100, limit, limit)); + + Rect inset_overflow4(-1000, -1000, limit, limit); + inset_overflow4.Inset(100, 100, -100, -100); + EXPECT_EQ(inset_overflow4, gfx::Rect(-900, -900, limit, limit)); + + Rect offset_overflow(0, 0, 100, 100); + offset_overflow.Offset(large_number, large_number); + EXPECT_EQ(large_offset, offset_overflow.origin()); + EXPECT_EQ(expected_size, offset_overflow.size()); + + Rect operator_overflow(0, 0, 100, 100); + operator_overflow += Vector2d(large_number, large_number); + EXPECT_EQ(large_offset, operator_overflow.origin()); + EXPECT_EQ(expected_size, operator_overflow.size()); + + Rect origin_maxint(limit, limit, limit, limit); + EXPECT_EQ(origin_maxint, Rect(gfx::Point(limit, limit), gfx::Size())); + + // Expect a rect at the origin and a rect whose right/bottom is maxint + // create a rect that extends from 0..maxint in both extents. + { + Rect origin_small(0, 0, 100, 100); + Rect big_clamped(50, 50, limit, limit); + EXPECT_EQ(big_clamped.right(), limit); + + Rect unioned = UnionRects(origin_small, big_clamped); + Rect rect_limit(0, 0, limit, limit); + EXPECT_EQ(unioned, rect_limit); + } + + // Expect a rect that would overflow width (but not right) to be clamped + // and to have maxint extents after unioning. + { + Rect small(-500, -400, 100, 100); + Rect big(-400, -500, limit, limit); + // Technically, this should be limit + 100 width, but will clamp to maxint. + EXPECT_EQ(UnionRects(small, big), Rect(-500, -500, limit, limit)); + } + + // Expect a rect that would overflow right *and* width to be clamped. + { + Rect clamped(500, 500, limit, limit); + Rect positive_origin(100, 100, 500, 500); + + // Ideally, this should be (100, 100, limit + 400, limit + 400). + // However, width overflows and would be clamped to limit, but right + // overflows too and so will be clamped to limit - 100. + Rect expected_rect(100, 100, limit - 100, limit - 100); + EXPECT_EQ(UnionRects(clamped, positive_origin), expected_rect); + } + + // Unioning a left=minint rect with a right=maxint rect. + // We can't represent both ends of the spectrum in the same rect. + // Make sure we keep the most useful area. + { + int part_limit = min_limit / 3; + Rect left_minint(min_limit, min_limit, 1, 1); + Rect right_maxint(limit - 1, limit - 1, limit, limit); + Rect expected_rect(part_limit, part_limit, 2 * part_limit, 2 * part_limit); + Rect result = UnionRects(left_minint, right_maxint); + + // The result should be maximally big. + EXPECT_EQ(limit, result.height()); + EXPECT_EQ(limit, result.width()); + + // The result should include the area near the origin. + EXPECT_GT(-part_limit, result.x()); + EXPECT_LT(part_limit, result.right()); + EXPECT_GT(-part_limit, result.y()); + EXPECT_LT(part_limit, result.bottom()); + + // More succinctly, but harder to read in the results. + EXPECT_TRUE(UnionRects(left_minint, right_maxint).Contains(expected_rect)); + } +} + +TEST(RectTest, ScaleToEnclosingRectSafe) { + constexpr int kMaxInt = std::numeric_limits::max(); + constexpr int kMinInt = std::numeric_limits::min(); + + Rect xy_underflow(-100000, -123456, 10, 20); + EXPECT_EQ(ScaleToEnclosingRectSafe(xy_underflow, 100000), + Rect(kMinInt, kMinInt, 1000000, 2000000)); + + // A location overflow means that width/right and bottom/top also + // overflow so need to be clamped. + Rect xy_overflow(100000, 123456, 10, 20); + EXPECT_EQ(ScaleToEnclosingRectSafe(xy_overflow, 100000), + Rect(kMaxInt, kMaxInt, 0, 0)); + + // In practice all rects are clamped to 0 width / 0 height so + // negative sizes don't matter, but try this for the sake of testing. + Rect size_underflow(-1, -2, 100000, 100000); + EXPECT_EQ(ScaleToEnclosingRectSafe(size_underflow, -100000), + Rect(100000, 200000, 0, 0)); + + Rect size_overflow(-1, -2, 123456, 234567); + EXPECT_EQ(ScaleToEnclosingRectSafe(size_overflow, 100000), + Rect(-100000, -200000, kMaxInt, kMaxInt)); + // Verify width/right gets clamped properly too if x/y positive. + Rect size_overflow2(1, 2, 123456, 234567); + EXPECT_EQ(ScaleToEnclosingRectSafe(size_overflow2, 100000), + Rect(100000, 200000, kMaxInt - 100000, kMaxInt - 200000)); + + Rect max_rect(kMaxInt, kMaxInt, kMaxInt, kMaxInt); + EXPECT_EQ(ScaleToEnclosingRectSafe(max_rect, static_cast(kMaxInt)), + Rect(kMaxInt, kMaxInt, 0, 0)); + + Rect min_rect(kMinInt, kMinInt, kMaxInt, kMaxInt); + // Min rect can't be scaled up any further in any dimension. + EXPECT_EQ(ScaleToEnclosingRectSafe(min_rect, 2, 3.5), min_rect); + EXPECT_EQ(ScaleToEnclosingRectSafe(min_rect, static_cast(kMaxInt)), + min_rect); + // Min rect scaled by min is an empty rect at (max, max) + EXPECT_EQ(ScaleToEnclosingRectSafe(min_rect, kMinInt), max_rect); +} + +TEST(RectTest, Inset) { + Rect r(10, 20, 30, 40); + r.Inset(0); + EXPECT_EQ(Rect(10, 20, 30, 40), r); + r.Inset(1); + EXPECT_EQ(Rect(11, 21, 28, 38), r); + r.Inset(-1); + EXPECT_EQ(Rect(10, 20, 30, 40), r); + + r.Inset(1, 2); + EXPECT_EQ(Rect(11, 22, 28, 36), r); + r.Inset(-1, -2); + EXPECT_EQ(Rect(10, 20, 30, 40), r); + + // The parameters are left, top, right, bottom. + r.Inset(1, 2, 3, 4); + EXPECT_EQ(Rect(11, 22, 26, 34), r); + r.Inset(-1, -2, -3, -4); + EXPECT_EQ(Rect(10, 20, 30, 40), r); + + // Insets parameters are top, right, bottom, left. + r.Inset(Insets(1, 2, 3, 4)); + EXPECT_EQ(Rect(12, 21, 24, 36), r); + r.Inset(Insets(-1, -2, -3, -4)); + EXPECT_EQ(Rect(10, 20, 30, 40), r); +} + +TEST(RectTest, Outset) { + Rect r(10, 20, 30, 40); + r.Outset(0); + EXPECT_EQ(Rect(10, 20, 30, 40), r); + r.Outset(1); + EXPECT_EQ(Rect(9, 19, 32, 42), r); + r.Outset(-1); + EXPECT_EQ(Rect(10, 20, 30, 40), r); + + r.Outset(1, 2); + EXPECT_EQ(Rect(9, 18, 32, 44), r); + r.Outset(-1, -2); + EXPECT_EQ(Rect(10, 20, 30, 40), r); + + r.Outset(1, 2, 3, 4); + EXPECT_EQ(Rect(9, 18, 34, 46), r); + r.Outset(-1, -2, -3, -4); + EXPECT_EQ(Rect(10, 20, 30, 40), r); +} + +TEST(RectTest, InsetOutsetClamped) { + Rect r(10, 20, 30, 40); + r.Inset(18); + EXPECT_EQ(Rect(28, 38, 0, 4), r); + r.Inset(-18); + EXPECT_EQ(Rect(10, 20, 36, 40), r); + + r.Inset(15, 30); + EXPECT_EQ(Rect(25, 50, 6, 0), r); + r.Inset(-15, -30); + EXPECT_EQ(Rect(10, 20, 36, 60), r); + + r.Inset(20, 30, 40, 50); + EXPECT_EQ(Rect(30, 50, 0, 0), r); + r.Inset(-20, -30, -40, -50); + EXPECT_EQ(Rect(10, 20, 60, 80), r); + + constexpr int kMaxInt = std::numeric_limits::max(); + constexpr int kMinInt = std::numeric_limits::min(); + r.Outset(kMaxInt); + EXPECT_EQ(Rect(10 - kMaxInt, 20 - kMaxInt, kMaxInt, kMaxInt), r); + r.Outset(0, kMaxInt); + EXPECT_EQ(Rect(10 - kMaxInt, kMinInt, kMaxInt, kMaxInt), r); + r.Outset(0, kMaxInt, kMaxInt, 0); + EXPECT_EQ(Rect(10 - kMaxInt, kMinInt, kMaxInt, kMaxInt), r); + r.Outset(kMaxInt, 0, kMaxInt, 0); + EXPECT_EQ(Rect(kMinInt, kMinInt, kMaxInt, kMaxInt), r); +} + +} // namespace gfx diff --git a/geometry/resize_utils.cc b/geometry/resize_utils.cc new file mode 100644 index 000000000000..73c0ccf6e3e3 --- /dev/null +++ b/geometry/resize_utils.cc @@ -0,0 +1,112 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/resize_utils.h" + +#include "base/check_op.h" +#include "base/cxx17_backports.h" +#include "base/numerics/safe_conversions.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/size.h" + +namespace gfx { +namespace { + +// This function decides whether SizeRectToAspectRatio() will adjust the height +// to match the specified width (resizing horizontally) or vice versa (resizing +// vertically). +bool IsResizingHorizontally(ResizeEdge resize_edge) { + switch (resize_edge) { + case ResizeEdge::kLeft: + case ResizeEdge::kRight: + case ResizeEdge::kTopLeft: + case ResizeEdge::kBottomLeft: + return true; + default: + return false; + } +} + +} // namespace + +void SizeRectToAspectRatio(ResizeEdge resize_edge, + float aspect_ratio, + const Size& min_window_size, + const Size& max_window_size, + Rect* rect) { + DCHECK_GT(aspect_ratio, 0.0f); + DCHECK_GE(max_window_size.width(), min_window_size.width()); + DCHECK_GE(max_window_size.height(), min_window_size.height()); + DCHECK(rect->Contains(Rect(rect->origin(), min_window_size))) + << rect->ToString() << " is smaller than the minimum size " + << min_window_size.ToString(); + DCHECK(Rect(rect->origin(), max_window_size).Contains(*rect)) + << rect->ToString() << " is larger than the maximum size " + << max_window_size.ToString(); + + Size new_size = rect->size(); + if (IsResizingHorizontally(resize_edge)) { + new_size.set_height(base::ClampRound(new_size.width() / aspect_ratio)); + if (min_window_size.height() > new_size.height() || + new_size.height() > max_window_size.height()) { + new_size.set_height(base::clamp(new_size.height(), + min_window_size.height(), + max_window_size.height())); + new_size.set_width(base::ClampRound(new_size.height() * aspect_ratio)); + } + } else { + new_size.set_width(base::ClampRound(new_size.height() * aspect_ratio)); + if (min_window_size.width() > new_size.width() || + new_size.width() > max_window_size.width()) { + new_size.set_width(base::clamp(new_size.width(), min_window_size.width(), + max_window_size.width())); + new_size.set_height(base::ClampRound(new_size.width() / aspect_ratio)); + } + } + + // The dimensions might still be outside of the allowed ranges at this point. + // This happens when the aspect ratio makes it impossible to fit |rect| + // within the size limits without letter-/pillarboxing. + new_size.SetToMin(max_window_size); + new_size.SetToMax(min_window_size); + + // |rect| bounds before sizing to aspect ratio. + int left = rect->x(); + int top = rect->y(); + int right = rect->right(); + int bottom = rect->bottom(); + + switch (resize_edge) { + case ResizeEdge::kRight: + case ResizeEdge::kBottom: + right = new_size.width() + left; + bottom = top + new_size.height(); + break; + case ResizeEdge::kTop: + right = new_size.width() + left; + top = bottom - new_size.height(); + break; + case ResizeEdge::kLeft: + case ResizeEdge::kTopLeft: + left = right - new_size.width(); + top = bottom - new_size.height(); + break; + case ResizeEdge::kTopRight: + right = left + new_size.width(); + top = bottom - new_size.height(); + break; + case ResizeEdge::kBottomLeft: + left = right - new_size.width(); + bottom = top + new_size.height(); + break; + case ResizeEdge::kBottomRight: + right = left + new_size.width(); + bottom = top + new_size.height(); + break; + } + + rect->SetByBounds(left, top, right, bottom); +} + +} // namespace gfx diff --git a/geometry/resize_utils.h b/geometry/resize_utils.h new file mode 100644 index 000000000000..318a31bdb013 --- /dev/null +++ b/geometry/resize_utils.h @@ -0,0 +1,41 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_RESIZE_UTILS_H_ +#define UI_GFX_GEOMETRY_RESIZE_UTILS_H_ + +#include "ui/gfx/geometry/geometry_export.h" + +namespace gfx { + +class Rect; +class Size; + +enum class ResizeEdge { + kBottom, + kBottomLeft, + kBottomRight, + kLeft, + kRight, + kTop, + kTopLeft, + kTopRight +}; + +// Updates |rect| to adhere to the |aspect_ratio| of the window, if it has +// been set. |resize_edge| refers to the edge of the window being sized. +// |min_window_size| and |max_window_size| are expected to adhere to the +// given aspect ratio. +// |aspect_ratio| must be valid and is found using width / height. +// TODO(apacible): |max_window_size| is expected to be non-empty. Handle +// unconstrained max sizes and sizing when windows are maximized. +void GEOMETRY_EXPORT SizeRectToAspectRatio(ResizeEdge resize_edge, + float aspect_ratio, + const Size& min_window_size, + const Size& max_window_size, + Rect* rect); + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_RESIZE_UTILS_H_ diff --git a/geometry/resize_utils_unittest.cc b/geometry/resize_utils_unittest.cc new file mode 100644 index 000000000000..bf1e90bcc2c4 --- /dev/null +++ b/geometry/resize_utils_unittest.cc @@ -0,0 +1,181 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/resize_utils.h" + +#include + +#include "base/strings/strcat.h" +#include "base/strings/string_number_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/size.h" + +namespace gfx { +namespace { + +// Aspect ratio is defined by width / height. +constexpr float kAspectRatioSquare = 1.0f; +constexpr float kAspectRatioHorizontal = 2.0f; +constexpr float kAspectRatioVertical = 0.5f; + +constexpr Size kMinSizeHorizontal(20, 10); +constexpr Size kMaxSizeHorizontal(50, 25); + +constexpr Size kMinSizeVertical(10, 20); +constexpr Size kMaxSizeVertical(25, 50); + +std::string HitTestToString(ResizeEdge resize_edge) { + switch (resize_edge) { + case ResizeEdge::kTop: + return "top"; + case ResizeEdge::kTopRight: + return "top-righ"; + case ResizeEdge::kRight: + return "right"; + case ResizeEdge::kBottomRight: + return "bottom-right"; + case ResizeEdge::kBottom: + return "bottom"; + case ResizeEdge::kBottomLeft: + return "bottom-left"; + case ResizeEdge::kLeft: + return "left"; + case ResizeEdge::kTopLeft: + return "top-left"; + } +} + +} // namespace + +struct SizingParams { + ResizeEdge resize_edge{}; + float aspect_ratio = 0.0f; + Size min_size; + Size max_size; + Rect input_rect; + Rect expected_output_rect; + + std::string ToString() const { + return base::StrCat({HitTestToString(resize_edge), + " ratio=", base::NumberToString(aspect_ratio), " [", + min_size.ToString(), "..", max_size.ToString(), "] ", + input_rect.ToString(), " -> ", + expected_output_rect.ToString()}); + } +}; + +using ResizeUtilsTest = testing::TestWithParam; + +TEST_P(ResizeUtilsTest, SizeRectToAspectRatio) { + Rect rect = GetParam().input_rect; + SizeRectToAspectRatio(GetParam().resize_edge, GetParam().aspect_ratio, + GetParam().min_size, GetParam().max_size, &rect); + EXPECT_EQ(rect, GetParam().expected_output_rect) << GetParam().ToString(); +} + +const SizingParams kSizeRectToSquareAspectRatioTestCases[] = { + // Dragging the top resizer up. + {ResizeEdge::kTop, kAspectRatioSquare, kMinSizeHorizontal, + kMaxSizeHorizontal, Rect(100, 98, 22, 24), Rect(100, 98, 24, 24)}, + + // Dragging the bottom resizer down. + {ResizeEdge::kBottom, kAspectRatioSquare, kMinSizeHorizontal, + kMaxSizeHorizontal, Rect(100, 100, 22, 24), Rect(100, 100, 24, 24)}, + + // Dragging the left resizer right. + {ResizeEdge::kLeft, kAspectRatioSquare, kMinSizeHorizontal, + kMaxSizeHorizontal, Rect(102, 100, 22, 24), Rect(102, 102, 22, 22)}, + + // Dragging the right resizer left. + {ResizeEdge::kRight, kAspectRatioSquare, kMinSizeHorizontal, + kMaxSizeHorizontal, Rect(100, 100, 22, 24), Rect(100, 100, 22, 22)}, + + // Dragging the top-left resizer right. + {ResizeEdge::kTopLeft, kAspectRatioSquare, kMinSizeHorizontal, + kMaxSizeHorizontal, Rect(102, 100, 22, 24), Rect(102, 102, 22, 22)}, + + // Dragging the top-right resizer down. + {ResizeEdge::kTopRight, kAspectRatioSquare, kMinSizeHorizontal, + kMaxSizeHorizontal, Rect(100, 102, 24, 22), Rect(100, 102, 22, 22)}, + + // Dragging the bottom-left resizer right. + {ResizeEdge::kBottomLeft, kAspectRatioSquare, kMinSizeHorizontal, + kMaxSizeHorizontal, Rect(100, 102, 22, 24), Rect(100, 102, 22, 22)}, + + // Dragging the bottom-right resizer up. + {ResizeEdge::kBottomRight, kAspectRatioSquare, kMinSizeHorizontal, + kMaxSizeHorizontal, Rect(100, 100, 24, 22), Rect(100, 100, 22, 22)}, + + // Dragging the bottom-right resizer left. + // Rect already as small as `kMinSizeHorizontal` allows. + {ResizeEdge::kBottomRight, kAspectRatioSquare, kMinSizeHorizontal, + kMaxSizeHorizontal, + Rect(100, 100, kMinSizeHorizontal.width(), kMinSizeHorizontal.width()), + Rect(100, 100, kMinSizeHorizontal.width(), kMinSizeHorizontal.width())}, + + // Dragging the top-left resizer left. + // Rect already as large as `kMaxSizeHorizontal` allows. + {ResizeEdge::kTopLeft, kAspectRatioSquare, kMinSizeHorizontal, + kMaxSizeHorizontal, + Rect(100, 100, kMaxSizeHorizontal.height(), kMaxSizeHorizontal.height()), + Rect(100, 100, kMaxSizeHorizontal.height(), kMaxSizeHorizontal.height())}, +}; + +const SizingParams kSizeRectToHorizontalAspectRatioTestCases[] = { + // Dragging the top resizer down. + {ResizeEdge::kTop, kAspectRatioHorizontal, kMinSizeHorizontal, + kMaxSizeHorizontal, Rect(100, 102, 48, 22), Rect(100, 102, 44, 22)}, + + // Dragging the left resizer left. + {ResizeEdge::kLeft, kAspectRatioHorizontal, kMinSizeHorizontal, + kMaxSizeHorizontal, Rect(96, 100, 48, 22), Rect(96, 98, 48, 24)}, + + // Rect already as small as `kMinSizeHorizontal` allows. + {ResizeEdge::kTop, kAspectRatioHorizontal, kMinSizeHorizontal, + kMaxSizeHorizontal, + Rect(100, 100, kMinSizeHorizontal.width(), kMinSizeHorizontal.height()), + Rect(100, 100, kMinSizeHorizontal.width(), kMinSizeHorizontal.height())}, + + // Rect already as large as `kMaxSizeHorizontal` allows. + {ResizeEdge::kTop, kAspectRatioHorizontal, kMinSizeHorizontal, + kMaxSizeHorizontal, + Rect(100, 100, kMaxSizeHorizontal.width(), kMaxSizeHorizontal.height()), + Rect(100, 100, kMaxSizeHorizontal.width(), kMaxSizeHorizontal.height())}, +}; + +const SizingParams kSizeRectToVerticalAspectRatioTestCases[] = { + // Dragging the bottom resizer up. + {ResizeEdge::kBottom, kAspectRatioVertical, kMinSizeVertical, + kMaxSizeVertical, Rect(100, 100, 24, 44), Rect(100, 100, 22, 44)}, + + // Dragging the right resizer right. + {ResizeEdge::kRight, kAspectRatioVertical, kMinSizeVertical, + kMaxSizeVertical, Rect(100, 100, 24, 44), Rect(100, 100, 24, 48)}, + + // Rect already as small as `kMinSizeVertical` allows. + {ResizeEdge::kTop, kAspectRatioVertical, kMinSizeVertical, kMaxSizeVertical, + Rect(100, 100, kMinSizeVertical.width(), kMinSizeVertical.height()), + Rect(100, 100, kMinSizeVertical.width(), kMinSizeVertical.height())}, + + // Rect already as large as `kMaxSizeVertical` allows. + {ResizeEdge::kTop, kAspectRatioVertical, kMinSizeVertical, kMaxSizeVertical, + Rect(100, 100, kMaxSizeVertical.width(), kMaxSizeVertical.height()), + Rect(100, 100, kMaxSizeVertical.width(), kMaxSizeVertical.height())}, +}; + +INSTANTIATE_TEST_SUITE_P( + Square, + ResizeUtilsTest, + testing::ValuesIn(kSizeRectToSquareAspectRatioTestCases)); +INSTANTIATE_TEST_SUITE_P( + Horizontal, + ResizeUtilsTest, + testing::ValuesIn(kSizeRectToHorizontalAspectRatioTestCases)); +INSTANTIATE_TEST_SUITE_P( + Vertical, + ResizeUtilsTest, + testing::ValuesIn(kSizeRectToVerticalAspectRatioTestCases)); + +} // namespace gfx diff --git a/geometry/rounded_corners_f.cc b/geometry/rounded_corners_f.cc new file mode 100644 index 000000000000..7db277a81942 --- /dev/null +++ b/geometry/rounded_corners_f.cc @@ -0,0 +1,17 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/rounded_corners_f.h" + +#include "base/strings/stringprintf.h" + +namespace gfx { + +std::string RoundedCornersF::ToString() const { + // Print members in the same order of the constructor parameters. + return base::StringPrintf("%f,%f,%f,%f", upper_left_, upper_right_, + lower_right_, lower_left_); +} + +} // namespace gfx diff --git a/geometry/rounded_corners_f.h b/geometry/rounded_corners_f.h new file mode 100644 index 000000000000..12582d3b672b --- /dev/null +++ b/geometry/rounded_corners_f.h @@ -0,0 +1,93 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_ROUNDED_CORNERS_F_H_ +#define UI_GFX_GEOMETRY_ROUNDED_CORNERS_F_H_ + +#include +#include +#include + +#include "ui/gfx/geometry/geometry_export.h" +#include "ui/gfx/geometry/vector2d_f.h" + +namespace gfx { + +// Represents the geometry of a region with rounded corners, expressed as four +// corner radii in the order: top-left, top-right, bottom-right, bottom-left. +class GEOMETRY_EXPORT RoundedCornersF { + public: + // Creates an empty RoundedCornersF with all corners having zero radius. + constexpr RoundedCornersF() : RoundedCornersF(0.0f) {} + + // Creates a RoundedCornersF with the same radius for all corners. + constexpr explicit RoundedCornersF(float all) + : RoundedCornersF(all, all, all, all) {} + + // Creates a RoundedCornersF with four different corner radii. + constexpr RoundedCornersF(float upper_left, + float upper_right, + float lower_right, + float lower_left) + : upper_left_(clamp(upper_left)), + upper_right_(clamp(upper_right)), + lower_right_(clamp(lower_right)), + lower_left_(clamp(lower_left)) {} + + constexpr float upper_left() const { return upper_left_; } + constexpr float upper_right() const { return upper_right_; } + constexpr float lower_right() const { return lower_right_; } + constexpr float lower_left() const { return lower_left_; } + + void set_upper_left(float upper_left) { upper_left_ = clamp(upper_left); } + void set_upper_right(float upper_right) { upper_right_ = clamp(upper_right); } + void set_lower_right(float lower_right) { lower_right_ = clamp(lower_right); } + void set_lower_left(float lower_left) { lower_left_ = clamp(lower_left); } + + void Set(float upper_left, + float upper_right, + float lower_right, + float lower_left) { + upper_left_ = clamp(upper_left); + upper_right_ = clamp(upper_right); + lower_right_ = clamp(lower_right); + lower_left_ = clamp(lower_left); + } + + // Returns true if all of the corners are square (zero effective radius). + bool IsEmpty() const { + return upper_left_ == 0.0f && upper_right_ == 0.0f && + lower_right_ == 0.0f && lower_left_ == 0.0f; + } + + bool operator==(const RoundedCornersF& corners) const { + return upper_left_ == corners.upper_left_ && + upper_right_ == corners.upper_right_ && + lower_right_ == corners.lower_right_ && + lower_left_ == corners.lower_left_; + } + + bool operator!=(const RoundedCornersF& corners) const { + return !(*this == corners); + } + + // Returns a string representation of the insets. + std::string ToString() const; + + private: + static constexpr float kTrivial = 8.f * std::numeric_limits::epsilon(); + + // Prevents values which are smaller than zero or negligibly small. + // Uses the same logic as gfx::Size. + static constexpr float clamp(float f) { return f > kTrivial ? f : 0.f; } + + float upper_left_ = 0.0f; + float upper_right_ = 0.0f; + float lower_right_ = 0.0f; + float lower_left_ = 0.0f; +}; + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_ROUNDED_CORNERS_F_H_ diff --git a/geometry/rounded_corners_f_unittest.cc b/geometry/rounded_corners_f_unittest.cc new file mode 100644 index 000000000000..5c9bc91b7f0b --- /dev/null +++ b/geometry/rounded_corners_f_unittest.cc @@ -0,0 +1,158 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/rounded_corners_f.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace gfx { + +TEST(RoundedCornersFTest, DefaultConstructor) { + const RoundedCornersF rc; + EXPECT_EQ(0.0f, rc.upper_left()); + EXPECT_EQ(0.0f, rc.upper_right()); + EXPECT_EQ(0.0f, rc.lower_right()); + EXPECT_EQ(0.0f, rc.lower_left()); +} + +TEST(RoundedCornersFTest, FromSingleValue) { + constexpr float kValue = 1.33f; + const RoundedCornersF rc(kValue); + EXPECT_EQ(kValue, rc.upper_left()); + EXPECT_EQ(kValue, rc.upper_right()); + EXPECT_EQ(kValue, rc.lower_right()); + EXPECT_EQ(kValue, rc.lower_left()); +} + +TEST(RoundedCornersFTest, FromFourValues) { + constexpr float kValue1 = 1.33f; + constexpr float kValue2 = 2.66f; + constexpr float kValue3 = 0.1f; + constexpr float kValue4 = 50.0f; + const RoundedCornersF rc(kValue1, kValue2, kValue3, kValue4); + EXPECT_EQ(kValue1, rc.upper_left()); + EXPECT_EQ(kValue2, rc.upper_right()); + EXPECT_EQ(kValue3, rc.lower_right()); + EXPECT_EQ(kValue4, rc.lower_left()); +} + +TEST(RoundedCornersFTest, IsEmpty) { + EXPECT_TRUE(RoundedCornersF().IsEmpty()); + EXPECT_FALSE(RoundedCornersF(1.0f).IsEmpty()); + EXPECT_FALSE(RoundedCornersF(1.0f, 0.0f, 0.0f, 0.0f).IsEmpty()); + EXPECT_FALSE(RoundedCornersF(0.0f, 1.0f, 0.0f, 0.0f).IsEmpty()); + EXPECT_FALSE(RoundedCornersF(0.0f, 0.0f, 1.0f, 0.0f).IsEmpty()); + EXPECT_FALSE(RoundedCornersF(0.0f, 0.0f, 0.0f, 1.0f).IsEmpty()); +} + +TEST(RoundedCornersFTest, Equality) { + constexpr RoundedCornersF kCorners(1.33f, 2.66f, 0.1f, 50.0f); + RoundedCornersF rc = kCorners; + // Using EXPECT_TRUE and EXPECT_FALSE to explicitly test == and != operators + // (rather than EXPECT_EQ, EXPECT_NE). + EXPECT_TRUE(rc == kCorners); + EXPECT_FALSE(rc != kCorners); + rc.set_upper_left(2.0f); + EXPECT_FALSE(rc == kCorners); + EXPECT_TRUE(rc != kCorners); + rc = kCorners; + rc.set_upper_right(2.0f); + EXPECT_FALSE(rc == kCorners); + EXPECT_TRUE(rc != kCorners); + rc = kCorners; + rc.set_lower_left(2.0f); + EXPECT_FALSE(rc == kCorners); + EXPECT_TRUE(rc != kCorners); + rc = kCorners; + rc.set_lower_right(2.0f); + EXPECT_FALSE(rc == kCorners); + EXPECT_TRUE(rc != kCorners); +} + +TEST(RoundedCornersFTest, Set) { + RoundedCornersF rc(1.0f, 2.0f, 3.0f, 4.0f); + rc.Set(4.0f, 3.0f, 2.0f, 1.0f); + EXPECT_EQ(4.0f, rc.upper_left()); + EXPECT_EQ(3.0f, rc.upper_right()); + EXPECT_EQ(2.0f, rc.lower_right()); + EXPECT_EQ(1.0f, rc.lower_left()); +} + +TEST(RoundedCornersFTest, SetProperties) { + RoundedCornersF rc(1.0f, 2.0f, 3.0f, 4.0f); + + rc.set_upper_left(50.0f); + EXPECT_EQ(50.0f, rc.upper_left()); + EXPECT_EQ(2.0f, rc.upper_right()); + EXPECT_EQ(3.0f, rc.lower_right()); + EXPECT_EQ(4.0f, rc.lower_left()); + + rc.set_upper_right(40.0f); + EXPECT_EQ(50.0f, rc.upper_left()); + EXPECT_EQ(40.0f, rc.upper_right()); + EXPECT_EQ(3.0f, rc.lower_right()); + EXPECT_EQ(4.0f, rc.lower_left()); + + rc.set_lower_right(30.0f); + EXPECT_EQ(50.0f, rc.upper_left()); + EXPECT_EQ(40.0f, rc.upper_right()); + EXPECT_EQ(30.0f, rc.lower_right()); + EXPECT_EQ(4.0f, rc.lower_left()); + + rc.set_lower_left(20.0f); + EXPECT_EQ(50.0f, rc.upper_left()); + EXPECT_EQ(40.0f, rc.upper_right()); + EXPECT_EQ(30.0f, rc.lower_right()); + EXPECT_EQ(20.0f, rc.lower_left()); +} + +namespace { + +// Verify that IsEmpty() returns true and that all values are exactly zero. +void VerifyEmptyAndZero(const RoundedCornersF& rc) { + EXPECT_TRUE(rc.IsEmpty()); + EXPECT_EQ(0.0f, rc.upper_left()); + EXPECT_EQ(0.0f, rc.upper_right()); + EXPECT_EQ(0.0f, rc.lower_right()); + EXPECT_EQ(0.0f, rc.lower_left()); +} + +} // namespace + +TEST(RoundedCornersFTest, Epsilon) { + constexpr float kEpsilon = std::numeric_limits::epsilon(); + RoundedCornersF rc(kEpsilon, kEpsilon, kEpsilon, kEpsilon); + VerifyEmptyAndZero(rc); + + rc.set_upper_left(kEpsilon); + VerifyEmptyAndZero(rc); + rc.set_upper_right(kEpsilon); + VerifyEmptyAndZero(rc); + rc.set_lower_right(kEpsilon); + VerifyEmptyAndZero(rc); + rc.set_lower_left(kEpsilon); + VerifyEmptyAndZero(rc); + + rc.Set(kEpsilon, kEpsilon, kEpsilon, kEpsilon); + VerifyEmptyAndZero(rc); +} + +TEST(RoundedCornersFTest, Negative) { + constexpr float kNegative = -0.5f; + RoundedCornersF rc(kNegative, kNegative, kNegative, kNegative); + VerifyEmptyAndZero(rc); + + rc.set_upper_left(kNegative); + VerifyEmptyAndZero(rc); + rc.set_upper_right(kNegative); + VerifyEmptyAndZero(rc); + rc.set_lower_right(kNegative); + VerifyEmptyAndZero(rc); + rc.set_lower_left(kNegative); + VerifyEmptyAndZero(rc); + + rc.Set(kNegative, kNegative, kNegative, kNegative); + VerifyEmptyAndZero(rc); +} + +} // namespace gfx diff --git a/geometry/rrect_f.cc b/geometry/rrect_f.cc new file mode 100644 index 000000000000..ab7dece394c6 --- /dev/null +++ b/geometry/rrect_f.cc @@ -0,0 +1,204 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/rrect_f.h" + +#include +#include +#include + +#include "base/values.h" +#include "third_party/skia/include/core/SkMatrix.h" + +namespace gfx { + +// Sets all x radii to x_rad, and all y radii to y_rad. If one of x_rad or +// y_rad are zero, sets ALL radii to zero. +RRectF::RRectF(float x, + float y, + float width, + float height, + float x_rad, + float y_rad) + : skrrect_(SkRRect::MakeRectXY(SkRect::MakeXYWH(x, y, width, height), + x_rad, + y_rad)) { + if (IsEmpty()) { + // Make sure that empty rects are created fully empty, not with some + // non-zero dimensions. + skrrect_ = SkRRect::MakeEmpty(); + } +} + +// Directly sets all four corners. +RRectF::RRectF(float x, + float y, + float width, + float height, + float upper_left_x, + float upper_left_y, + float upper_right_x, + float upper_right_y, + float lower_right_x, + float lower_right_y, + float lower_left_x, + float lower_left_y) { + SkVector radii[4] = { + {upper_left_x, upper_left_y}, + {upper_right_x, upper_right_y}, + {lower_right_x, lower_right_y}, + {lower_left_x, lower_left_y}, + }; + skrrect_.setRectRadii(SkRect::MakeXYWH(x, y, width, height), radii); + if (IsEmpty()) { + // Make sure that empty rects are created fully empty, not with some + // non-zero dimensions. + skrrect_ = SkRRect::MakeEmpty(); + } +} + +gfx::Vector2dF RRectF::GetSimpleRadii() const { + DCHECK(GetType() <= Type::kOval); + SkPoint result = skrrect_.getSimpleRadii(); + return gfx::Vector2dF(result.x(), result.y()); +} + +float RRectF::GetSimpleRadius() const { + DCHECK(GetType() <= Type::kOval); + SkPoint result = skrrect_.getSimpleRadii(); + DCHECK_EQ(result.x(), result.y()); + return result.x(); +} + +RRectF::Type RRectF::GetType() const { + SkPoint rad; + switch (skrrect_.getType()) { + case SkRRect::kEmpty_Type: + return Type::kEmpty; + case SkRRect::kRect_Type: + return Type::kRect; + case SkRRect::kSimple_Type: + rad = skrrect_.getSimpleRadii(); + if (rad.x() == rad.y()) { + return Type::kSingle; + } + return Type::kSimple; + case SkRRect::kOval_Type: + rad = skrrect_.getSimpleRadii(); + if (rad.x() == rad.y()) { + return Type::kSingle; + } + return Type::kOval; + case SkRRect::kNinePatch_Type: + case SkRRect::kComplex_Type: + default: + return Type::kComplex; + } +} + +gfx::Vector2dF RRectF::GetCornerRadii(Corner corner) const { + SkPoint result = skrrect_.radii(SkRRect::Corner(corner)); + return gfx::Vector2dF(result.x(), result.y()); +} + +void RRectF::GetAllRadii(SkVector radii[4]) const { + // Unfortunately, the only way to get all radii is one at a time. + radii[SkRRect::kUpperLeft_Corner] = + skrrect_.radii(SkRRect::kUpperLeft_Corner); + radii[SkRRect::kUpperRight_Corner] = + skrrect_.radii(SkRRect::kUpperRight_Corner); + radii[SkRRect::kLowerRight_Corner] = + skrrect_.radii(SkRRect::kLowerRight_Corner); + radii[SkRRect::kLowerLeft_Corner] = + skrrect_.radii(SkRRect::kLowerLeft_Corner); +} + +void RRectF::SetCornerRadii(Corner corner, float x_rad, float y_rad) { + // Unfortunately, the only way to set this is to create a new SkRRect. + SkVector radii[4]; + GetAllRadii(radii); + radii[SkRRect::Corner(corner)] = SkPoint::Make(x_rad, y_rad); + skrrect_.setRectRadii(skrrect_.rect(), radii); +} + +void RRectF::Scale(float x_scale, float y_scale) { + if (IsEmpty()) { + // SkRRect doesn't support scaling of empty rects. + return; + } + if (!x_scale || !y_scale) { + // SkRRect doesn't support scaling TO an empty rect. + skrrect_ = SkRRect::MakeEmpty(); + return; + } + SkMatrix scale = SkMatrix::Scale(x_scale, y_scale); + SkRRect result; + bool success = skrrect_.transform(scale, &result); + DCHECK(success); + skrrect_ = result; +} + +void RRectF::Offset(float horizontal, float vertical) { + skrrect_.offset(horizontal, vertical); +} + +const RRectF& RRectF::operator+=(const gfx::Vector2dF& offset) { + Offset(offset.x(), offset.y()); + return *this; +} + +const RRectF& RRectF::operator-=(const gfx::Vector2dF& offset) { + Offset(-offset.x(), -offset.y()); + return *this; +} + +std::string RRectF::ToString() const { + std::stringstream ss; + ss << std::fixed << std::setprecision(3); + ss << rect().origin().x() << "," << rect().origin().y() << " " + << rect().size().width() << "x" << rect().size().height(); + Type type = this->GetType(); + if (type <= Type::kRect) { + ss << ", rectangular"; + } else if (type <= Type::kSingle) { + ss << ", radius " << GetSimpleRadius(); + } else if (type <= Type::kSimple) { + gfx::Vector2dF radii = GetSimpleRadii(); + ss << ", x_rad " << radii.x() << ", y_rad " << radii.y(); + } else { + ss << ","; + const Corner corners[] = {Corner::kUpperLeft, Corner::kUpperRight, + Corner::kLowerRight, Corner::kLowerLeft}; + for (const auto& c : corners) { + auto this_corner = GetCornerRadii(c); + ss << " [" << this_corner.x() << " " << this_corner.y() << "]"; + } + } + return ss.str(); +} + +namespace { +inline bool AboveTol(float val1, float val2, float tolerance) { + return (std::abs(val1 - val2) > tolerance); +} +} // namespace + +bool RRectF::ApproximatelyEqual(const RRectF& rect, float tolerance) const { + if (AboveTol(skrrect_.rect().x(), rect.skrrect_.rect().x(), tolerance) || + AboveTol(skrrect_.rect().y(), rect.skrrect_.rect().y(), tolerance) || + AboveTol(skrrect_.width(), rect.skrrect_.width(), tolerance) || + AboveTol(skrrect_.height(), rect.skrrect_.height(), tolerance)) + return false; + for (int i = 0; i < 4; i++) { + SkVector r1 = skrrect_.radii(SkRRect::Corner(i)); + SkVector r2 = rect.skrrect_.radii(SkRRect::Corner(i)); + if (std::abs(r1.x() - r2.x()) > tolerance || + std::abs(r1.y() - r2.y()) > tolerance) { + return false; + } + } + return true; +} + +} // namespace gfx diff --git a/geometry/rrect_f.h b/geometry/rrect_f.h new file mode 100644 index 000000000000..7b3f46e9cc86 --- /dev/null +++ b/geometry/rrect_f.h @@ -0,0 +1,201 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_RRECT_F_H_ +#define UI_GFX_GEOMETRY_RRECT_F_H_ + +#include +#include + +#include "third_party/skia/include/core/SkRRect.h" +#include "ui/gfx/geometry/rect_f.h" +#include "ui/gfx/geometry/rounded_corners_f.h" +#include "ui/gfx/geometry/skia_conversions.h" + +namespace gfx { + +class GEOMETRY_SKIA_EXPORT RRectF { + public: + RRectF() = default; + ~RRectF() = default; + RRectF(const RRectF& rect) = default; + RRectF& operator=(const RRectF& rect) = default; + explicit RRectF(const SkRRect& rect) : skrrect_(rect) {} + explicit RRectF(const gfx::RectF& rect) : RRectF(rect, 0.f) {} + RRectF(const gfx::RectF& rect, float radius) : RRectF(rect, radius, radius) {} + RRectF(const gfx::RectF& rect, float x_rad, float y_rad) + : RRectF(rect.x(), rect.y(), rect.width(), rect.height(), x_rad, y_rad) {} + // Sets all x and y radii to radius. + RRectF(float x, float y, float width, float height, float radius) + : RRectF(x, y, width, height, radius, radius) {} + // Sets all x radii to x_rad, and all y radii to y_rad. If one of x_rad or + // y_rad are zero, sets ALL radii to zero. + RRectF(float x, float y, float width, float height, float x_rad, float y_rad); + // Directly sets all four corners. + RRectF(float x, + float y, + float width, + float height, + float upper_left_x, + float upper_left_y, + float upper_right_x, + float upper_right_y, + float lower_right_x, + float lower_right_y, + float lower_left_x, + float lower_left_y); + RRectF(const gfx::RectF& rect, + float upper_left_x, + float upper_left_y, + float upper_right_x, + float upper_right_y, + float lower_right_x, + float lower_right_y, + float lower_left_x, + float lower_left_y) + : RRectF(rect.x(), + rect.y(), + rect.width(), + rect.height(), + upper_left_x, + upper_left_y, + upper_right_x, + upper_right_y, + lower_right_x, + lower_right_y, + lower_left_x, + lower_left_y) {} + RRectF(const gfx::RectF& rect, const gfx::RoundedCornersF& corners) + : RRectF(rect.x(), + rect.y(), + rect.width(), + rect.height(), + corners.upper_left(), + corners.upper_left(), + corners.upper_right(), + corners.upper_right(), + corners.lower_right(), + corners.lower_right(), + corners.lower_left(), + corners.lower_left()) {} + + // The rectangular portion of the RRectF, without the corner radii. + gfx::RectF rect() const { return gfx::SkRectToRectF(skrrect_.rect()); } + + // Returns the radii of the all corners. DCHECKs that all corners + // have the same radii (the type is <= kOval). + gfx::Vector2dF GetSimpleRadii() const; + // Returns the radius of all corners. DCHECKs that all corners have the same + // radii, and that x_rad == y_rad (the type is <= kSingle). + float GetSimpleRadius() const; + + // Make the RRectF empty. + void Clear() { skrrect_.setEmpty(); } + + bool Equals(const RRectF& other) const { return skrrect_ == other.skrrect_; } + + // These are all mutually exclusive, and ordered in increasing complexity. The + // order is assumed in several functions. + enum class Type { + kEmpty, // Zero width or height. + kRect, // Non-zero width and height, and zeroed radii - a pure rectangle. + kSingle, // Non-zero width and height, and a single, non-zero value for all + // X and Y radii. + kSimple, // Non-zero width and height, X radii all equal and non-zero, Y + // radii all equal and non-zero, and x_rad != y_rad. + kOval, // Non-zero width and height, X radii all equal to width/2, and Y + // radii all equal to height/2, and x_rad != y_rad. + kComplex, // Non-zero width and height, and arbitrary (non-equal) radii. + }; + Type GetType() const; + + bool IsEmpty() const { return GetType() == Type::kEmpty; } + + // Enumeration of the corners of a rectangle in clockwise order. Values match + // SkRRect::Corner. + enum class Corner { + kUpperLeft = SkRRect::kUpperLeft_Corner, + kUpperRight = SkRRect::kUpperRight_Corner, + kLowerRight = SkRRect::kLowerRight_Corner, + kLowerLeft = SkRRect::kLowerLeft_Corner, + }; + // GetCornerRadii may be called for any type of RRect (kRect, kOval, etc.), + // and it will return "correct" values. If GetType() is kOval or less, all + // corner values will be identical to each other. SetCornerRadii can similarly + // be called on any type of RRect, but GetType() may change as a result of the + // call. + gfx::Vector2dF GetCornerRadii(Corner corner) const; + void SetCornerRadii(Corner corner, float x_rad, float y_rad); + void SetCornerRadii(Corner corner, const gfx::Vector2dF& radii) { + SetCornerRadii(corner, radii.x(), radii.y()); + } + + // Returns true if |rect| is inside the bounds and corner radii of this + // RRectF, and if both this RRectF and rect are not empty. + bool Contains(const RectF& rect) const { + return skrrect_.contains(gfx::RectFToSkRect(rect)); + } + + // Scales the rectangle by |scale|. + void Scale(float scale) { Scale(scale, scale); } + // Scales the rectangle by |x_scale| and |y_scale|. + void Scale(float x_scale, float y_scale); + + // Move the rectangle by a horizontal and vertical distance. + void Offset(float horizontal, float vertical); + void Offset(const Vector2dF& distance) { Offset(distance.x(), distance.y()); } + const RRectF& operator+=(const gfx::Vector2dF& offset); + const RRectF& operator-=(const gfx::Vector2dF& offset); + + std::string ToString() const; + bool ApproximatelyEqual(const RRectF& rect, float tolerance) const; + + // Insets bounds by dx and dy, and adjusts radii by dx and dy. dx and dy may + // be positive, negative, or zero. If either corner radius is zero, the corner + // has no curvature and is unchanged. Otherwise, if adjusted radius becomes + // negative, the radius is pinned to zero. + void Inset(float val) { skrrect_.inset(val, val); } + void Inset(float dx, float dy) { skrrect_.inset(dx, dy); } + // Outsets bounds by dx and dy, and adjusts radii by dx and dy. dx and dy may + // be positive, negative, or zero. If either corner radius is zero, the corner + // has no curvature and is unchanged. Otherwise, if adjusted radius becomes + // negative, the radius is pinned to zero. + void Outset(float val) { skrrect_.outset(val, val); } + void Outset(float dx, float dy) { skrrect_.outset(dx, dy); } + + explicit operator SkRRect() const { return skrrect_; } + + private: + void GetAllRadii(SkVector radii[4]) const; + + SkRRect skrrect_; +}; + +inline std::ostream& operator<<(std::ostream& os, const RRectF& rect) { + return os << rect.ToString(); +} + +inline bool operator==(const RRectF& a, const RRectF& b) { + return a.Equals(b); +} + +inline bool operator!=(const RRectF& a, const RRectF& b) { + return !(a == b); +} + +inline RRectF operator+(const RRectF& a, const gfx::Vector2dF& b) { + RRectF result = a; + result += b; + return result; +} + +inline RRectF operator-(const RRectF& a, const Vector2dF& b) { + RRectF result = a; + result -= b; + return result; +} + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_RRECT_F_H_ diff --git a/geometry/rrect_f_builder.cc b/geometry/rrect_f_builder.cc new file mode 100644 index 000000000000..0fc1a7e8f0cc --- /dev/null +++ b/geometry/rrect_f_builder.cc @@ -0,0 +1,18 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/rrect_f_builder.h" + +namespace gfx { + +RRectFBuilder::RRectFBuilder() = default; +RRectFBuilder::RRectFBuilder(RRectFBuilder&& other) = default; + +RRectF RRectFBuilder::Build() { + return RRectF(x_, y_, width_, height_, upper_left_x_, upper_left_y_, + upper_right_x_, upper_right_y_, lower_right_x_, lower_right_y_, + lower_left_x_, lower_left_y_); +} + +} // namespace gfx diff --git a/geometry/rrect_f_builder.h b/geometry/rrect_f_builder.h new file mode 100644 index 000000000000..a7c302336de8 --- /dev/null +++ b/geometry/rrect_f_builder.h @@ -0,0 +1,130 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_RRECT_F_BUILDER_H_ +#define UI_GFX_GEOMETRY_RRECT_F_BUILDER_H_ + +#include "ui/gfx/geometry/rrect_f.h" + +namespace gfx { + +// RRectFBuilder is implemented to make the parameter setting easier for RRectF. +// +// For example: To build an RRectF at point(40, 50) with size(60,70), +// with corner radii {(1, 2),(3, 4),(5, 6),(7, 8)}, use: +// +// RRectF a = RRectFBuilder() +// .set_origin(40, 50) +// .set_size(60, 70) +// .set_upper_left(1, 2) +// .set_upper_right(3, 4) +// .set_lower_right(5, 6) +// .set_lower_left(7, 8) +// .Build(); +class GEOMETRY_SKIA_EXPORT RRectFBuilder { + public: + RRectFBuilder(); + RRectFBuilder(RRectFBuilder&& other); + ~RRectFBuilder() = default; + + RRectFBuilder&& set_origin(float x, float y) { + x_ = x; + y_ = y; + return std::move(*this); + } + RRectFBuilder&& set_origin(const PointF& origin) { + x_ = origin.x(); + y_ = origin.y(); + return std::move(*this); + } + + RRectFBuilder&& set_size(float width, float height) { + width_ = width; + height_ = height; + return std::move(*this); + } + RRectFBuilder&& set_size(const SizeF& size) { + width_ = size.width(); + height_ = size.height(); + return std::move(*this); + } + + RRectFBuilder&& set_rect(const gfx::RectF& rect) { + x_ = rect.x(); + y_ = rect.y(); + width_ = rect.width(); + height_ = rect.height(); + return std::move(*this); + } + template + void set_rect(const T&) = delete; // To avoid implicit conversion. + + RRectFBuilder&& set_radius(float radius) { + set_upper_left(radius, radius); + set_upper_right(radius, radius); + set_lower_right(radius, radius); + set_lower_left(radius, radius); + return std::move(*this); + } + RRectFBuilder&& set_radius(float x_rad, float y_rad) { + set_upper_left(x_rad, y_rad); + set_upper_right(x_rad, y_rad); + set_lower_right(x_rad, y_rad); + set_lower_left(x_rad, y_rad); + return std::move(*this); + } + + RRectFBuilder&& set_upper_left(float upper_left_x, float upper_left_y) { + upper_left_x_ = upper_left_x; + upper_left_y_ = upper_left_y; + return std::move(*this); + } + RRectFBuilder&& set_upper_right(float upper_right_x, float upper_right_y) { + upper_right_x_ = upper_right_x; + upper_right_y_ = upper_right_y; + return std::move(*this); + } + RRectFBuilder&& set_lower_right(float lower_right_x, float lower_right_y) { + lower_right_x_ = lower_right_x; + lower_right_y_ = lower_right_y; + return std::move(*this); + } + RRectFBuilder&& set_lower_left(float lower_left_x, float lower_left_y) { + lower_left_x_ = lower_left_x; + lower_left_y_ = lower_left_y; + return std::move(*this); + } + + RRectFBuilder&& set_corners(const gfx::RoundedCornersF& corners) { + upper_left_x_ = corners.upper_left(); + upper_left_y_ = corners.upper_left(); + upper_right_x_ = corners.upper_right(); + upper_right_y_ = corners.upper_right(); + lower_right_x_ = corners.lower_right(); + lower_right_y_ = corners.lower_right(); + lower_left_x_ = corners.lower_left(); + lower_left_y_ = corners.lower_left(); + return std::move(*this); + } + + RRectF Build(); + + private: + float x_ = 0.f; + float y_ = 0.f; + float width_ = 0.f; + float height_ = 0.f; + float upper_left_x_ = 0.f; + float upper_left_y_ = 0.f; + float upper_right_x_ = 0.f; + float upper_right_y_ = 0.f; + float lower_right_x_ = 0.f; + float lower_right_y_ = 0.f; + float lower_left_x_ = 0.f; + float lower_left_y_ = 0.f; +}; + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_RRECT_F_BUILDER_H_ diff --git a/geometry/rrect_f_unittest.cc b/geometry/rrect_f_unittest.cc new file mode 100644 index 000000000000..45eeba116976 --- /dev/null +++ b/geometry/rrect_f_unittest.cc @@ -0,0 +1,405 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/rrect_f.h" + +#include "base/cxx17_backports.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/geometry/rrect_f_builder.h" + +namespace gfx { + +TEST(RRectFTest, IsEmpty) { + EXPECT_TRUE(RRectF().IsEmpty()); + EXPECT_TRUE(RRectF(0, 0, 0, 0, 0).IsEmpty()); + EXPECT_TRUE(RRectF(0, 0, 10, 0, 0).IsEmpty()); + EXPECT_TRUE(RRectF(0, 0, 0, 10, 0).IsEmpty()); + EXPECT_TRUE(RRectF(0, 0, 0, 10, 10).IsEmpty()); + EXPECT_FALSE(RRectF(0, 0, 10, 10, 0).IsEmpty()); +} + +TEST(RRectFTest, Equals) { + EXPECT_EQ(RRectF(0, 0, 0, 0, 0, 0), RRectF(0, 0, 0, 0, 0, 0)); + EXPECT_EQ(RRectF(1, 2, 3, 4, 5, 6), RRectF(1, 2, 3, 4, 5, 6)); + EXPECT_EQ(RRectF(1, 2, 3, 4, 5, 5), RRectF(1, 2, 3, 4, 5)); + EXPECT_EQ(RRectF(0, 0, 2, 3, 0, 0), RRectF(0, 0, 2, 3, 0, 1)); + EXPECT_EQ(RRectF(0, 0, 2, 3, 0, 0), RRectF(0, 0, 2, 3, 1, 0)); + EXPECT_EQ(RRectF(1, 2, 3, 0, 5, 6), RRectF(0, 0, 0, 0, 0, 0)); + EXPECT_EQ(RRectF(0, 0, 0, 0, 5, 6), RRectF(0, 0, 0, 0, 0, 0)); + + EXPECT_NE(RRectF(10, 20, 30, 40, 7, 8), RRectF(1, 20, 30, 40, 7, 8)); + EXPECT_NE(RRectF(10, 20, 30, 40, 7, 8), RRectF(10, 2, 30, 40, 7, 8)); + EXPECT_NE(RRectF(10, 20, 30, 40, 7, 8), RRectF(10, 20, 3, 40, 7, 8)); + EXPECT_NE(RRectF(10, 20, 30, 40, 7, 8), RRectF(10, 20, 30, 4, 7, 8)); + EXPECT_NE(RRectF(10, 20, 30, 40, 7, 8), RRectF(10, 20, 30, 40, 5, 8)); + EXPECT_NE(RRectF(10, 20, 30, 40, 7, 8), RRectF(10, 20, 30, 40, 7, 6)); +} + +TEST(RRectFTest, PlusMinusOffset) { + const RRectF a(40, 50, 60, 70, 5); + gfx::Vector2d offset(23, 34); + RRectF correct(63, 84, 60, 70, 5); + RRectF b = a + offset; + ASSERT_EQ(b, correct); + b = a; + b.Offset(offset); + ASSERT_EQ(b, correct); + + correct = RRectF(17, 16, 60, 70, 5); + b = a - offset; + ASSERT_EQ(b, correct); + b = a; + b.Offset(-offset); + ASSERT_EQ(b, correct); +} + +TEST(RRectFTest, RRectTypes) { + RRectF a(40, 50, 0, 70, 0); + EXPECT_EQ(a.GetType(), RRectF::Type::kEmpty); + EXPECT_TRUE(a.IsEmpty()); + a = RRectF(40, 50, 60, 70, 0); + EXPECT_EQ(a.GetType(), RRectF::Type::kRect); + a = RRectF(40, 50, 60, 70, 5); + EXPECT_EQ(a.GetType(), RRectF::Type::kSingle); + a = RRectF(40, 50, 60, 70, 5, 5); + EXPECT_EQ(a.GetType(), RRectF::Type::kSingle); + a = RRectF(40, 50, 60, 60, 30, 30); + EXPECT_EQ(a.GetType(), RRectF::Type::kSingle); + a = RRectF(40, 50, 60, 70, 6, 3); + EXPECT_EQ(a.GetType(), RRectF::Type::kSimple); + a = RRectF(40, 50, 60, 70, 30, 3); + EXPECT_EQ(a.GetType(), RRectF::Type::kSimple); + a = RRectF(40, 50, 60, 70, 30, 35); + EXPECT_EQ(a.GetType(), RRectF::Type::kOval); + a.SetCornerRadii(RRectF::Corner::kLowerRight, gfx::Vector2dF(7, 8)); + EXPECT_EQ(a.GetType(), RRectF::Type::kComplex); + + // When one radius is larger than half its dimension, both radii are scaled + // down proportionately. + a = RRectF(40, 50, 60, 70, 30, 70); + EXPECT_EQ(a.GetType(), RRectF::Type::kSimple); + EXPECT_EQ(a, RRectF(40, 50, 60, 70, 15, 35)); + // If they stay equal to half the radius, it stays oval. + a = RRectF(40, 50, 60, 70, 120, 140); + EXPECT_EQ(a.GetType(), RRectF::Type::kOval); +} + +void CheckRadii(RRectF val, + float ulx, + float uly, + float urx, + float ury, + float lrx, + float lry, + float llx, + float lly) { + EXPECT_EQ(val.GetCornerRadii(RRectF::Corner::kUpperLeft), + gfx::Vector2dF(ulx, uly)); + EXPECT_EQ(val.GetCornerRadii(RRectF::Corner::kUpperRight), + gfx::Vector2dF(urx, ury)); + EXPECT_EQ(val.GetCornerRadii(RRectF::Corner::kLowerRight), + gfx::Vector2dF(lrx, lry)); + EXPECT_EQ(val.GetCornerRadii(RRectF::Corner::kLowerLeft), + gfx::Vector2dF(llx, lly)); +} + +TEST(RRectFTest, RRectRadii) { + RRectF a(40, 50, 60, 70, 0); + CheckRadii(a, 0, 0, 0, 0, 0, 0, 0, 0); + + a.SetCornerRadii(RRectF::Corner::kUpperLeft, 1, 2); + CheckRadii(a, 1, 2, 0, 0, 0, 0, 0, 0); + + a.SetCornerRadii(RRectF::Corner::kUpperRight, 3, 4); + CheckRadii(a, 1, 2, 3, 4, 0, 0, 0, 0); + + a.SetCornerRadii(RRectF::Corner::kLowerRight, 5, 6); + CheckRadii(a, 1, 2, 3, 4, 5, 6, 0, 0); + + a.SetCornerRadii(RRectF::Corner::kLowerLeft, 7, 8); + CheckRadii(a, 1, 2, 3, 4, 5, 6, 7, 8); + + RRectF b(40, 50, 60, 70, 1, 2, 3, 4, 5, 6, 7, 8); + EXPECT_EQ(a, b); +} + +TEST(RRectFTest, FromRectF) { + // Check that explicit conversion from float rect works. + RectF a(40, 50, 60, 70); + RRectF b(40, 50, 60, 70, 0); + RRectF c = RRectF(a); + EXPECT_EQ(b, c); +} + +TEST(RRectFTest, FromSkRRect) { + // Check that explicit conversion from SkRRect works. + SkRRect a = SkRRect::MakeRectXY(SkRect::MakeXYWH(40, 50, 60, 70), 15, 25); + RRectF b(40, 50, 60, 70, 15, 25); + RRectF c = RRectF(a); + EXPECT_EQ(b, c); + + // Try with single radius constructor. + a = SkRRect::MakeRectXY(SkRect::MakeXYWH(40, 50, 60, 70), 15, 15); + b = RRectF(40, 50, 60, 70, 15); + c = RRectF(a); + EXPECT_EQ(b, c); +} + +TEST(RRectFTest, FromRoundedCornersF) { + constexpr RectF kRect(50.0f, 40.0f); + constexpr RoundedCornersF kCorners(1.5f, 2.5f, 3.5f, 4.5f); + const RRectF rrect_f(kRect, kCorners); + + const auto upper_left = rrect_f.GetCornerRadii(RRectF::Corner::kUpperLeft); + EXPECT_EQ(kCorners.upper_left(), upper_left.x()); + EXPECT_EQ(kCorners.upper_left(), upper_left.y()); + const auto upper_right = rrect_f.GetCornerRadii(RRectF::Corner::kUpperRight); + EXPECT_EQ(kCorners.upper_right(), upper_right.x()); + EXPECT_EQ(kCorners.upper_right(), upper_right.y()); + const auto lower_right = rrect_f.GetCornerRadii(RRectF::Corner::kLowerRight); + EXPECT_EQ(kCorners.lower_right(), lower_right.x()); + EXPECT_EQ(kCorners.lower_right(), lower_right.y()); + const auto lower_left = rrect_f.GetCornerRadii(RRectF::Corner::kLowerLeft); + EXPECT_EQ(kCorners.lower_left(), lower_left.x()); + EXPECT_EQ(kCorners.lower_left(), lower_left.y()); +} + +TEST(RRectFTest, ToString) { + RRectF a(40, 50, 60, 70, 0); + EXPECT_EQ(a.ToString(), "40.000,50.000 60.000x70.000, rectangular"); + a = RRectF(40, 50, 60, 70, 15); + EXPECT_EQ(a.ToString(), "40.000,50.000 60.000x70.000, radius 15.000"); + a = RRectF(40, 50, 60, 70, 15, 25); + EXPECT_EQ(a.ToString(), + "40.000,50.000 60.000x70.000, x_rad 15.000, y_rad 25.000"); + a.SetCornerRadii(RRectF::Corner::kLowerRight, gfx::Vector2dF(7, 8)); + EXPECT_EQ(a.ToString(), + "40.000,50.000 60.000x70.000, [15.000 25.000] " + "[15.000 25.000] [7.000 8.000] [15.000 25.000]"); +} + +TEST(RRectFTest, Sizes) { + RRectF a(40, 50, 60, 70, 5, 6); + EXPECT_EQ(a.rect().x(), 40); + EXPECT_EQ(a.rect().y(), 50); + EXPECT_EQ(a.rect().width(), 60); + EXPECT_EQ(a.rect().height(), 70); + EXPECT_EQ(a.GetSimpleRadii().x(), 5); + EXPECT_EQ(a.GetSimpleRadii().y(), 6); + a = RRectF(40, 50, 60, 70, 5, 5); + EXPECT_EQ(a.GetSimpleRadius(), 5); + a.Clear(); + EXPECT_TRUE(a.IsEmpty()); + // Make sure ovals can still get simple radii + a = RRectF(40, 50, 60, 70, 30, 35); + EXPECT_EQ(a.GetType(), RRectF::Type::kOval); + EXPECT_EQ(a.GetSimpleRadii().x(), 30); + EXPECT_EQ(a.GetSimpleRadii().y(), 35); +} + +TEST(RRectFTest, Contains) { + RRectF a(40, 50, 60, 70, 5, 6); + RectF b(50, 60, 5, 6); + EXPECT_TRUE(a.Contains(b)); + b = RectF(40, 50, 5, 6); // Right on the border + EXPECT_FALSE(a.Contains(b)); + b = RectF(95, 114, 5, 6); // Right on the border + EXPECT_FALSE(a.Contains(b)); + b = RectF(40, 50, 60, 70); + EXPECT_FALSE(a.Contains(b)); +} + +TEST(RRectFTest, Scale) { + // Note that SKRRect (the backing for RRectF) does not support scaling by NaN, + // or scaling out of numerical bounds. So this test doesn't exercise those. + static const struct Test { + float x1; // source + float y1; + float w1; + float h1; + float x_rad1; + float y_rad1; + + float x_scale; + float y_scale; + float x2; // target + float y2; + float w2; + float h2; + float x_rad2; + float y_rad2; + } tests[] = { + {3.0f, 4.0f, 5.0f, 6.0f, 0.0f, 0.0f, 1.5f, 1.5f, 4.5f, 6.0f, 7.5f, 9.0f, + 0.0f, 0.0f}, + {3.0f, 4.0f, 5.0f, 6.0f, 1.0f, 1.0f, 1.5f, 1.5f, 4.5f, 6.0f, 7.5f, 9.0f, + 1.5f, 1.5f}, + {3.0f, 4.0f, 5.0f, 6.0f, 0.0f, 0.0f, 1.5f, 3.0f, 4.5f, 12.0f, 7.5f, 18.0f, + 0.0f, 0.0f}, + {3.0f, 4.0f, 5.0f, 6.0f, 1.0f, 1.0f, 1.5f, 3.0f, 4.5f, 12.0f, 7.5f, 18.0f, + 1.5f, 3.0f}, + {3.0f, 4.0f, 0.0f, 6.0f, 1.0f, 1.0f, 1.5f, 1.5f, 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f}, + {3.0f, 4.0f, 5.0f, 6.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f}, + {3.0f, 4.0f, 5.0f, 6.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f}, + {3.0f, 4.0f, 5.0f, 6.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f}, + }; + + for (auto& test : tests) { + RRectF r1(test.x1, test.y1, test.w1, test.h1, test.x_rad1, test.y_rad1); + RRectF r2(test.x2, test.y2, test.w2, test.h2, test.x_rad2, test.y_rad2); + + r1.Scale(test.x_scale, test.y_scale); + ASSERT_TRUE(r1.GetType() <= RRectF::Type::kSimple); + EXPECT_EQ(r1.rect().x(), r2.rect().x()); + EXPECT_EQ(r1.rect().y(), r2.rect().y()); + EXPECT_EQ(r1.rect().width(), r2.rect().width()); + EXPECT_EQ(r1.rect().height(), r2.rect().height()); + EXPECT_EQ(r1.GetSimpleRadii(), r2.GetSimpleRadii()); + } +} + +TEST(RRectFTest, InsetOutset) { + RRectF a(40, 50, 60, 70, 5); + RRectF b = a; + b.Inset(3); + ASSERT_EQ(b, RRectF(43, 53, 54, 64, 2)); + b = a; + b.Outset(3); + ASSERT_EQ(b, RRectF(37, 47, 66, 76, 8)); +} + +// The following tests(started with "Build*") are for RRectFBuilder. All +// different tests are to make sure that existing RRectF definitions can be +// implemented with RRectFBuilder. +TEST(RRectFTest, BuildFromRectF) { + RectF a = RectF(); + RRectF b(a); + RRectF c = RRectFBuilder().set_rect(a).Build(); + EXPECT_EQ(b, c); + + a = RectF(60, 70); + b = RRectF(a); + c = RRectFBuilder().set_rect(a).Build(); + EXPECT_EQ(b, c); + + a = RectF(40, 50, 60, 70); + b = RRectF(a); + c = RRectFBuilder().set_rect(a).Build(); + EXPECT_EQ(b, c); +} + +TEST(RRectFTest, BuildFromRadius) { + RRectF a(40, 50, 60, 70, 15); + RRectF b = RRectFBuilder() + .set_origin(40, 50) + .set_size(60, 70) + .set_radius(15) + .Build(); + EXPECT_EQ(a, b); + + a = RRectF(40, 50, 60, 70, 15, 25); + b = RRectFBuilder() + .set_origin(40, 50) + .set_size(60, 70) + .set_radius(15, 25) + .Build(); + EXPECT_EQ(a, b); + + const PointF p(40, 50); + const SizeF s(60, 70); + b = RRectFBuilder().set_origin(p).set_size(s).set_radius(15, 25).Build(); + EXPECT_EQ(a, b); +} + +TEST(RRectFTest, BuildFromRectFWithRadius) { + RectF a(40, 50, 60, 70); + RRectF b(a, 15); + RRectF c = RRectFBuilder().set_rect(a).set_radius(15).Build(); + EXPECT_EQ(b, c); + + b = RRectF(a, 15, 25); + c = RRectFBuilder().set_rect(a).set_radius(15, 25).Build(); + EXPECT_EQ(b, c); +} + +TEST(RRectFTest, BuildFromCorners) { + RRectF a(40, 50, 60, 70, 1, 2, 3, 4, 5, 6, 7, 8); + RRectF b = RRectFBuilder() + .set_origin(40, 50) + .set_size(60, 70) + .set_upper_left(1, 2) + .set_upper_right(3, 4) + .set_lower_right(5, 6) + .set_lower_left(7, 8) + .Build(); + EXPECT_EQ(a, b); +} + +TEST(RRectFTest, BuildFromRectFWithCorners) { + RectF a(40, 50, 60, 70); + RRectF b(a, 1, 2, 3, 4, 5, 6, 7, 8); + RRectF c = RRectFBuilder() + .set_rect(a) + .set_upper_left(1, 2) + .set_upper_right(3, 4) + .set_lower_right(5, 6) + .set_lower_left(7, 8) + .Build(); + EXPECT_EQ(b, c); +} + +TEST(RRectFTest, BuildFromRoundedCornersF) { + RectF a(40, 50, 60, 70); + RoundedCornersF corners(1.5f, 2.5f, 3.5f, 4.5f); + RRectF b(a, corners); + RRectF c = RRectFBuilder().set_rect(a).set_corners(corners).Build(); + EXPECT_EQ(b, c); +} + +// In the following tests(*CornersHigherThanSize), we test whether the corner +// radii gets truncated in case of being greater than the width/height. +TEST(RRectFTest, BuildFromCornersHigherThanSize) { + RRectF a(0, 0, 20, 10, 12, 2, 8, 4, 14, 6, 6, 8); + RRectF b = RRectFBuilder() + .set_origin(0, 0) + .set_size(20, 10) + .set_upper_left(48, 8) + .set_upper_right(32, 16) + .set_lower_right(56, 24) + .set_lower_left(24, 32) + .Build(); + EXPECT_EQ(a, b); +} + +TEST(RRectFTest, BuildFromRectFWithCornersHigherThanSize) { + RectF a(0, 0, 20, 10); + RRectF b(a, 12, 2, 8, 4, 14, 6, 6, 8); + RRectF c = RRectFBuilder() + .set_rect(a) + .set_upper_left(48, 8) + .set_upper_right(32, 16) + .set_lower_right(56, 24) + .set_lower_left(24, 32) + .Build(); + EXPECT_EQ(b, c); +} + +// In this test, we set the radius first but then change the value of the +// corners. +TEST(RRectFTest, BuildFromRadiusAndCorners) { + RRectF a(40, 50, 60, 70, 1, 2, 3, 4, 15, 25, 15, 25); + RRectF b = RRectFBuilder() + .set_origin(40, 50) + .set_size(60, 70) + .set_radius(15, 25) + .set_upper_left(1, 2) + .set_upper_right(3, 4) + .Build(); + EXPECT_EQ(a, b); +} + +} // namespace gfx diff --git a/geometry/size.cc b/geometry/size.cc new file mode 100644 index 000000000000..a7e330cd3537 --- /dev/null +++ b/geometry/size.cc @@ -0,0 +1,122 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/size.h" + +#if defined(OS_WIN) +#include +#elif defined(OS_IOS) +#include +#elif defined(OS_MAC) +#include +#endif + +#include "base/numerics/clamped_math.h" +#include "base/numerics/safe_math.h" +#include "base/strings/stringprintf.h" +#include "build/build_config.h" +#include "ui/gfx/geometry/size_conversions.h" + +namespace gfx { + +#if defined(OS_APPLE) +Size::Size(const CGSize& s) + : width_(s.width < 0 ? 0 : s.width), + height_(s.height < 0 ? 0 : s.height) { +} + +Size& Size::operator=(const CGSize& s) { + set_width(s.width); + set_height(s.height); + return *this; +} +#endif + +void Size::operator+=(const Size& size) { + Enlarge(size.width(), size.height()); +} + +void Size::operator-=(const Size& size) { + Enlarge(-size.width(), -size.height()); +} + +#if defined(OS_WIN) +SIZE Size::ToSIZE() const { + SIZE s; + s.cx = width(); + s.cy = height(); + return s; +} +#elif defined(OS_APPLE) +CGSize Size::ToCGSize() const { + return CGSizeMake(width(), height()); +} +#endif + +int Size::GetArea() const { + return GetCheckedArea().ValueOrDie(); +} + +base::CheckedNumeric Size::GetCheckedArea() const { + base::CheckedNumeric checked_area = width(); + checked_area *= height(); + return checked_area; +} + +void Size::Enlarge(int grow_width, int grow_height) { + SetSize(base::ClampAdd(width(), grow_width), + base::ClampAdd(height(), grow_height)); +} + +void Size::SetToMin(const Size& other) { + width_ = width() <= other.width() ? width() : other.width(); + height_ = height() <= other.height() ? height() : other.height(); +} + +void Size::SetToMax(const Size& other) { + width_ = width() >= other.width() ? width() : other.width(); + height_ = height() >= other.height() ? height() : other.height(); +} + +std::string Size::ToString() const { + return base::StringPrintf("%dx%d", width(), height()); +} + +Size ScaleToCeiledSize(const Size& size, float x_scale, float y_scale) { + if (x_scale == 1.f && y_scale == 1.f) + return size; + return ToCeiledSize(ScaleSize(gfx::SizeF(size), x_scale, y_scale)); +} + +Size ScaleToCeiledSize(const Size& size, float scale) { + if (scale == 1.f) + return size; + return ToCeiledSize(ScaleSize(gfx::SizeF(size), scale, scale)); +} + +Size ScaleToFlooredSize(const Size& size, float x_scale, float y_scale) { + if (x_scale == 1.f && y_scale == 1.f) + return size; + return ToFlooredSize(ScaleSize(gfx::SizeF(size), x_scale, y_scale)); +} + +Size ScaleToFlooredSize(const Size& size, float scale) { + if (scale == 1.f) + return size; + return ToFlooredSize(ScaleSize(gfx::SizeF(size), scale, scale)); +} + +Size ScaleToRoundedSize(const Size& size, float x_scale, float y_scale) { + if (x_scale == 1.f && y_scale == 1.f) + return size; + return ToRoundedSize(ScaleSize(gfx::SizeF(size), x_scale, y_scale)); +} + +Size ScaleToRoundedSize(const Size& size, float scale) { + if (scale == 1.f) + return size; + return ToRoundedSize(ScaleSize(gfx::SizeF(size), scale, scale)); +} + +} // namespace gfx diff --git a/geometry/size.h b/geometry/size.h new file mode 100644 index 000000000000..3472dacf24b1 --- /dev/null +++ b/geometry/size.h @@ -0,0 +1,118 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_SIZE_H_ +#define UI_GFX_GEOMETRY_SIZE_H_ + +#include +#include +#include + +#include "base/compiler_specific.h" +#include "base/numerics/safe_math.h" +#include "build/build_config.h" +#include "ui/gfx/geometry/geometry_export.h" + +#if defined(OS_WIN) +typedef struct tagSIZE SIZE; +#elif defined(OS_APPLE) +typedef struct CGSize CGSize; +#endif + +namespace gfx { + +// A size has width and height values. +class GEOMETRY_EXPORT Size { + public: + constexpr Size() : width_(0), height_(0) {} + constexpr Size(int width, int height) + : width_(std::max(0, width)), height_(std::max(0, height)) {} +#if defined(OS_APPLE) + explicit Size(const CGSize& s); +#endif + +#if defined(OS_APPLE) + Size& operator=(const CGSize& s); +#endif + + void operator+=(const Size& size); + + void operator-=(const Size& size); + +#if defined(OS_WIN) + SIZE ToSIZE() const; +#elif defined(OS_APPLE) + CGSize ToCGSize() const; +#endif + + constexpr int width() const { return width_; } + constexpr int height() const { return height_; } + + void set_width(int width) { width_ = std::max(0, width); } + void set_height(int height) { height_ = std::max(0, height); } + + // This call will CHECK if the area of this size would overflow int. + int GetArea() const; + // Returns a checked numeric representation of the area. + base::CheckedNumeric GetCheckedArea() const; + + void SetSize(int width, int height) { + set_width(width); + set_height(height); + } + + void Enlarge(int grow_width, int grow_height); + + void SetToMin(const Size& other); + void SetToMax(const Size& other); + + bool IsEmpty() const { return !width() || !height(); } + + std::string ToString() const; + + private: + int width_; + int height_; +}; + +inline bool operator==(const Size& lhs, const Size& rhs) { + return lhs.width() == rhs.width() && lhs.height() == rhs.height(); +} + +inline bool operator!=(const Size& lhs, const Size& rhs) { + return !(lhs == rhs); +} + +inline Size operator+(Size lhs, const Size& rhs) { + lhs += rhs; + return lhs; +} + +inline Size operator-(Size lhs, const Size& rhs) { + lhs -= rhs; + return lhs; +} + +// This is declared here for use in gtest-based unit tests but is defined in +// the //ui/gfx:test_support target. Depend on that to use this in your unit +// test. This should not be used in production code - call ToString() instead. +void PrintTo(const Size& size, ::std::ostream* os); + +// Helper methods to scale a gfx::Size to a new gfx::Size. +GEOMETRY_EXPORT Size ScaleToCeiledSize(const Size& size, + float x_scale, + float y_scale); +GEOMETRY_EXPORT Size ScaleToCeiledSize(const Size& size, float scale); +GEOMETRY_EXPORT Size ScaleToFlooredSize(const Size& size, + float x_scale, + float y_scale); +GEOMETRY_EXPORT Size ScaleToFlooredSize(const Size& size, float scale); +GEOMETRY_EXPORT Size ScaleToRoundedSize(const Size& size, + float x_scale, + float y_scale); +GEOMETRY_EXPORT Size ScaleToRoundedSize(const Size& size, float scale); + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_SIZE_H_ diff --git a/geometry/size_conversions.cc b/geometry/size_conversions.cc new file mode 100644 index 000000000000..18dab3340a6c --- /dev/null +++ b/geometry/size_conversions.cc @@ -0,0 +1,24 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/size_conversions.h" + +#include "base/numerics/safe_conversions.h" + +namespace gfx { + +Size ToFlooredSize(const SizeF& size) { + return Size(base::ClampFloor(size.width()), base::ClampFloor(size.height())); +} + +Size ToCeiledSize(const SizeF& size) { + return Size(base::ClampCeil(size.width()), base::ClampCeil(size.height())); +} + +Size ToRoundedSize(const SizeF& size) { + return Size(base::ClampRound(size.width()), base::ClampRound(size.height())); +} + +} // namespace gfx + diff --git a/geometry/size_conversions.h b/geometry/size_conversions.h new file mode 100644 index 000000000000..6bc74c0a6128 --- /dev/null +++ b/geometry/size_conversions.h @@ -0,0 +1,24 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_SIZE_CONVERSIONS_H_ +#define UI_GFX_GEOMETRY_SIZE_CONVERSIONS_H_ + +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/geometry/size_f.h" + +namespace gfx { + +// Returns a Size with each component from the input SizeF floored. +GEOMETRY_EXPORT Size ToFlooredSize(const SizeF& size); + +// Returns a Size with each component from the input SizeF ceiled. +GEOMETRY_EXPORT Size ToCeiledSize(const SizeF& size); + +// Returns a Size with each component from the input SizeF rounded. +GEOMETRY_EXPORT Size ToRoundedSize(const SizeF& size); + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_SIZE_CONVERSIONS_H_ diff --git a/geometry/size_f.cc b/geometry/size_f.cc new file mode 100644 index 000000000000..6d08e18c62ea --- /dev/null +++ b/geometry/size_f.cc @@ -0,0 +1,39 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/size_f.h" + +#include "base/strings/stringprintf.h" + +namespace gfx { + +float SizeF::GetArea() const { + return width() * height(); +} + +void SizeF::Enlarge(float grow_width, float grow_height) { + SetSize(width() + grow_width, height() + grow_height); +} + +void SizeF::SetToMin(const SizeF& other) { + width_ = width() <= other.width() ? width() : other.width(); + height_ = height() <= other.height() ? height() : other.height(); +} + +void SizeF::SetToMax(const SizeF& other) { + width_ = width() >= other.width() ? width() : other.width(); + height_ = height() >= other.height() ? height() : other.height(); +} + +std::string SizeF::ToString() const { + return base::StringPrintf("%fx%f", width(), height()); +} + +SizeF ScaleSize(const SizeF& s, float x_scale, float y_scale) { + SizeF scaled_s(s); + scaled_s.Scale(x_scale, y_scale); + return scaled_s; +} + +} // namespace gfx diff --git a/geometry/size_f.h b/geometry/size_f.h new file mode 100644 index 000000000000..ceda9edfc448 --- /dev/null +++ b/geometry/size_f.h @@ -0,0 +1,97 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_SIZE_F_H_ +#define UI_GFX_GEOMETRY_SIZE_F_H_ + +#include +#include + +#include "base/compiler_specific.h" +#include "base/gtest_prod_util.h" +#include "ui/gfx/geometry/geometry_export.h" +#include "ui/gfx/geometry/size.h" + +namespace gfx { + +FORWARD_DECLARE_TEST(SizeTest, TrivialDimensionTests); +FORWARD_DECLARE_TEST(SizeTest, ClampsToZero); +FORWARD_DECLARE_TEST(SizeTest, ConsistentClamping); + +// A floating version of gfx::Size. +class GEOMETRY_EXPORT SizeF { + public: + constexpr SizeF() : width_(0.f), height_(0.f) {} + constexpr SizeF(float width, float height) + : width_(clamp(width)), height_(clamp(height)) {} + + constexpr explicit SizeF(const Size& size) + : SizeF(static_cast(size.width()), + static_cast(size.height())) {} + + constexpr float width() const { return width_; } + constexpr float height() const { return height_; } + + void set_width(float width) { width_ = clamp(width); } + void set_height(float height) { height_ = clamp(height); } + + float GetArea() const; + + void SetSize(float width, float height) { + set_width(width); + set_height(height); + } + + void Enlarge(float grow_width, float grow_height); + + void SetToMin(const SizeF& other); + void SetToMax(const SizeF& other); + + bool IsEmpty() const { return !width() || !height(); } + + void Scale(float scale) { + Scale(scale, scale); + } + + void Scale(float x_scale, float y_scale) { + SetSize(width() * x_scale, height() * y_scale); + } + + std::string ToString() const; + + private: + FRIEND_TEST_ALL_PREFIXES(SizeTest, TrivialDimensionTests); + FRIEND_TEST_ALL_PREFIXES(SizeTest, ClampsToZero); + FRIEND_TEST_ALL_PREFIXES(SizeTest, ConsistentClamping); + + static constexpr float kTrivial = 8.f * std::numeric_limits::epsilon(); + + static constexpr float clamp(float f) { return f > kTrivial ? f : 0.f; } + + float width_; + float height_; +}; + +inline bool operator==(const SizeF& lhs, const SizeF& rhs) { + return lhs.width() == rhs.width() && lhs.height() == rhs.height(); +} + +inline bool operator!=(const SizeF& lhs, const SizeF& rhs) { + return !(lhs == rhs); +} + +GEOMETRY_EXPORT SizeF ScaleSize(const SizeF& p, float x_scale, float y_scale); + +inline SizeF ScaleSize(const SizeF& p, float scale) { + return ScaleSize(p, scale, scale); +} + +// This is declared here for use in gtest-based unit tests but is defined in +// the //ui/gfx:test_support target. Depend on that to use this in your unit +// test. This should not be used in production code - call ToString() instead. +void PrintTo(const SizeF& size, ::std::ostream* os); + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_SIZE_F_H_ diff --git a/geometry/size_unittest.cc b/geometry/size_unittest.cc new file mode 100644 index 000000000000..b3ec2ec07b0d --- /dev/null +++ b/geometry/size_unittest.cc @@ -0,0 +1,300 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/geometry/size_conversions.h" +#include "ui/gfx/geometry/size_f.h" + +namespace gfx { + +namespace { + +int TestSizeF(const SizeF& s) { + return s.width(); +} + +} // namespace + +TEST(SizeTest, ToSizeF) { + // Check that explicit conversion from integer to float compiles. + Size a(10, 20); + float width = TestSizeF(gfx::SizeF(a)); + EXPECT_EQ(width, a.width()); + + SizeF b(10, 20); + + EXPECT_EQ(b, gfx::SizeF(a)); +} + +TEST(SizeTest, ToFlooredSize) { + EXPECT_EQ(Size(0, 0), ToFlooredSize(SizeF(0, 0))); + EXPECT_EQ(Size(0, 0), ToFlooredSize(SizeF(0.0001f, 0.0001f))); + EXPECT_EQ(Size(0, 0), ToFlooredSize(SizeF(0.4999f, 0.4999f))); + EXPECT_EQ(Size(0, 0), ToFlooredSize(SizeF(0.5f, 0.5f))); + EXPECT_EQ(Size(0, 0), ToFlooredSize(SizeF(0.9999f, 0.9999f))); + + EXPECT_EQ(Size(10, 10), ToFlooredSize(SizeF(10, 10))); + EXPECT_EQ(Size(10, 10), ToFlooredSize(SizeF(10.0001f, 10.0001f))); + EXPECT_EQ(Size(10, 10), ToFlooredSize(SizeF(10.4999f, 10.4999f))); + EXPECT_EQ(Size(10, 10), ToFlooredSize(SizeF(10.5f, 10.5f))); + EXPECT_EQ(Size(10, 10), ToFlooredSize(SizeF(10.9999f, 10.9999f))); +} + +TEST(SizeTest, ToCeiledSize) { + EXPECT_EQ(Size(0, 0), ToCeiledSize(SizeF(0, 0))); + EXPECT_EQ(Size(1, 1), ToCeiledSize(SizeF(0.0001f, 0.0001f))); + EXPECT_EQ(Size(1, 1), ToCeiledSize(SizeF(0.4999f, 0.4999f))); + EXPECT_EQ(Size(1, 1), ToCeiledSize(SizeF(0.5f, 0.5f))); + EXPECT_EQ(Size(1, 1), ToCeiledSize(SizeF(0.9999f, 0.9999f))); + + EXPECT_EQ(Size(10, 10), ToCeiledSize(SizeF(10, 10))); + EXPECT_EQ(Size(11, 11), ToCeiledSize(SizeF(10.0001f, 10.0001f))); + EXPECT_EQ(Size(11, 11), ToCeiledSize(SizeF(10.4999f, 10.4999f))); + EXPECT_EQ(Size(11, 11), ToCeiledSize(SizeF(10.5f, 10.5f))); + EXPECT_EQ(Size(11, 11), ToCeiledSize(SizeF(10.9999f, 10.9999f))); +} + +TEST(SizeTest, ToRoundedSize) { + EXPECT_EQ(Size(0, 0), ToRoundedSize(SizeF(0, 0))); + EXPECT_EQ(Size(0, 0), ToRoundedSize(SizeF(0.0001f, 0.0001f))); + EXPECT_EQ(Size(0, 0), ToRoundedSize(SizeF(0.4999f, 0.4999f))); + EXPECT_EQ(Size(1, 1), ToRoundedSize(SizeF(0.5f, 0.5f))); + EXPECT_EQ(Size(1, 1), ToRoundedSize(SizeF(0.9999f, 0.9999f))); + + EXPECT_EQ(Size(10, 10), ToRoundedSize(SizeF(10, 10))); + EXPECT_EQ(Size(10, 10), ToRoundedSize(SizeF(10.0001f, 10.0001f))); + EXPECT_EQ(Size(10, 10), ToRoundedSize(SizeF(10.4999f, 10.4999f))); + EXPECT_EQ(Size(11, 11), ToRoundedSize(SizeF(10.5f, 10.5f))); + EXPECT_EQ(Size(11, 11), ToRoundedSize(SizeF(10.9999f, 10.9999f))); +} + +TEST(SizeTest, ClampSize) { + Size a; + + a = Size(3, 5); + EXPECT_EQ(Size(3, 5).ToString(), a.ToString()); + a.SetToMax(Size(2, 4)); + EXPECT_EQ(Size(3, 5).ToString(), a.ToString()); + a.SetToMax(Size(3, 5)); + EXPECT_EQ(Size(3, 5).ToString(), a.ToString()); + a.SetToMax(Size(4, 2)); + EXPECT_EQ(Size(4, 5).ToString(), a.ToString()); + a.SetToMax(Size(8, 10)); + EXPECT_EQ(Size(8, 10).ToString(), a.ToString()); + + a.SetToMin(Size(9, 11)); + EXPECT_EQ(Size(8, 10).ToString(), a.ToString()); + a.SetToMin(Size(8, 10)); + EXPECT_EQ(Size(8, 10).ToString(), a.ToString()); + a.SetToMin(Size(11, 9)); + EXPECT_EQ(Size(8, 9).ToString(), a.ToString()); + a.SetToMin(Size(7, 11)); + EXPECT_EQ(Size(7, 9).ToString(), a.ToString()); + a.SetToMin(Size(3, 5)); + EXPECT_EQ(Size(3, 5).ToString(), a.ToString()); +} + +TEST(SizeTest, ClampSizeF) { + SizeF a; + + a = SizeF(3.5f, 5.5f); + EXPECT_EQ(SizeF(3.5f, 5.5f).ToString(), a.ToString()); + a.SetToMax(SizeF(2.5f, 4.5f)); + EXPECT_EQ(SizeF(3.5f, 5.5f).ToString(), a.ToString()); + a.SetToMax(SizeF(3.5f, 5.5f)); + EXPECT_EQ(SizeF(3.5f, 5.5f).ToString(), a.ToString()); + a.SetToMax(SizeF(4.5f, 2.5f)); + EXPECT_EQ(SizeF(4.5f, 5.5f).ToString(), a.ToString()); + a.SetToMax(SizeF(8.5f, 10.5f)); + EXPECT_EQ(SizeF(8.5f, 10.5f).ToString(), a.ToString()); + + a.SetToMin(SizeF(9.5f, 11.5f)); + EXPECT_EQ(SizeF(8.5f, 10.5f).ToString(), a.ToString()); + a.SetToMin(SizeF(8.5f, 10.5f)); + EXPECT_EQ(SizeF(8.5f, 10.5f).ToString(), a.ToString()); + a.SetToMin(SizeF(11.5f, 9.5f)); + EXPECT_EQ(SizeF(8.5f, 9.5f).ToString(), a.ToString()); + a.SetToMin(SizeF(7.5f, 11.5f)); + EXPECT_EQ(SizeF(7.5f, 9.5f).ToString(), a.ToString()); + a.SetToMin(SizeF(3.5f, 5.5f)); + EXPECT_EQ(SizeF(3.5f, 5.5f).ToString(), a.ToString()); +} + +TEST(SizeTest, Enlarge) { + Size test(3, 4); + test.Enlarge(5, -8); + EXPECT_EQ(test, Size(8, -4)); +} + +TEST(SizeTest, IntegerOverflow) { + int int_max = std::numeric_limits::max(); + int int_min = std::numeric_limits::min(); + + Size max_size(int_max, int_max); + Size min_size(int_min, int_min); + Size test; + + test = Size(); + test.Enlarge(int_max, int_max); + EXPECT_EQ(test, max_size); + + test = Size(); + test.Enlarge(int_min, int_min); + EXPECT_EQ(test, min_size); + + test = Size(10, 20); + test.Enlarge(int_max, int_max); + EXPECT_EQ(test, max_size); + + test = Size(-10, -20); + test.Enlarge(int_min, int_min); + EXPECT_EQ(test, min_size); +} + +// This checks that we set IsEmpty appropriately. +TEST(SizeTest, TrivialDimensionTests) { + const float clearly_trivial = SizeF::kTrivial / 2.f; + const float massize_dimension = 4e13f; + + // First, using the constructor. + EXPECT_TRUE(SizeF(clearly_trivial, 1.f).IsEmpty()); + EXPECT_TRUE(SizeF(.01f, clearly_trivial).IsEmpty()); + EXPECT_TRUE(SizeF(0.f, 0.f).IsEmpty()); + EXPECT_FALSE(SizeF(.01f, .01f).IsEmpty()); + + // Then use the setter. + SizeF test(2.f, 1.f); + EXPECT_FALSE(test.IsEmpty()); + + test.SetSize(clearly_trivial, 1.f); + EXPECT_TRUE(test.IsEmpty()); + + test.SetSize(.01f, clearly_trivial); + EXPECT_TRUE(test.IsEmpty()); + + test.SetSize(0.f, 0.f); + EXPECT_TRUE(test.IsEmpty()); + + test.SetSize(.01f, .01f); + EXPECT_FALSE(test.IsEmpty()); + + // Now just one dimension at a time. + test.set_width(clearly_trivial); + EXPECT_TRUE(test.IsEmpty()); + + test.set_width(massize_dimension); + test.set_height(clearly_trivial); + EXPECT_TRUE(test.IsEmpty()); + + test.set_width(clearly_trivial); + test.set_height(massize_dimension); + EXPECT_TRUE(test.IsEmpty()); + + test.set_width(2.f); + EXPECT_FALSE(test.IsEmpty()); +} + +// These are the ramifications of the decision to keep the recorded size +// at zero for trivial sizes. +TEST(SizeTest, ClampsToZero) { + const float clearly_trivial = SizeF::kTrivial / 2.f; + const float nearly_trivial = SizeF::kTrivial * 1.5f; + + SizeF test(clearly_trivial, 1.f); + + EXPECT_FLOAT_EQ(0.f, test.width()); + EXPECT_FLOAT_EQ(1.f, test.height()); + + test.SetSize(.01f, clearly_trivial); + + EXPECT_FLOAT_EQ(.01f, test.width()); + EXPECT_FLOAT_EQ(0.f, test.height()); + + test.SetSize(nearly_trivial, nearly_trivial); + + EXPECT_FLOAT_EQ(nearly_trivial, test.width()); + EXPECT_FLOAT_EQ(nearly_trivial, test.height()); + + test.Scale(0.5f); + + EXPECT_FLOAT_EQ(0.f, test.width()); + EXPECT_FLOAT_EQ(0.f, test.height()); + + test.SetSize(0.f, 0.f); + test.Enlarge(clearly_trivial, clearly_trivial); + test.Enlarge(clearly_trivial, clearly_trivial); + test.Enlarge(clearly_trivial, clearly_trivial); + + EXPECT_EQ(SizeF(0.f, 0.f), test); +} + +// These make sure the constructor and setter have the same effect on the +// boundary case. This claims to know the boundary, but not which way it goes. +TEST(SizeTest, ConsistentClamping) { + SizeF resized; + + resized.SetSize(SizeF::kTrivial, 0.f); + EXPECT_EQ(SizeF(SizeF::kTrivial, 0.f), resized); + + resized.SetSize(0.f, SizeF::kTrivial); + EXPECT_EQ(SizeF(0.f, SizeF::kTrivial), resized); +} + +// Let's make sure we don't unexpectedly grow the struct by adding constants. +// Also, if some platform packs floats inefficiently, it would be worth noting. +TEST(SizeTest, StaysSmall) { + EXPECT_EQ(2 * sizeof(float), sizeof(SizeF)); +} + +TEST(SizeTest, OperatorAddSub) { + Size lhs(100, 20); + Size rhs(50, 10); + + lhs += rhs; + EXPECT_EQ(Size(150, 30), lhs); + + lhs = Size(100, 20); + EXPECT_EQ(Size(150, 30), lhs + rhs); + + lhs = Size(100, 20); + lhs -= rhs; + EXPECT_EQ(Size(50, 10), lhs); + + lhs = Size(100, 20); + EXPECT_EQ(Size(50, 10), lhs - rhs); +} + +TEST(SizeTest, OperatorAddOverflow) { + int int_max = std::numeric_limits::max(); + + Size lhs(int_max, int_max); + Size rhs(int_max, int_max); + EXPECT_EQ(Size(int_max, int_max), lhs + rhs); +} + +TEST(SizeTest, OperatorSubClampAtZero) { + Size lhs(10, 10); + Size rhs(100, 100); + EXPECT_EQ(Size(0, 0), lhs - rhs); + + lhs = Size(10, 10); + rhs = Size(100, 100); + lhs -= rhs; + EXPECT_EQ(Size(0, 0), lhs); +} + +TEST(SizeTest, OperatorCompare) { + Size lhs(100, 20); + Size rhs(50, 10); + + EXPECT_TRUE(lhs != rhs); + EXPECT_FALSE(lhs == rhs); + + rhs = Size(100, 20); + EXPECT_TRUE(lhs == rhs); + EXPECT_FALSE(lhs != rhs); +} + +} // namespace gfx diff --git a/geometry/skia_conversions.cc b/geometry/skia_conversions.cc new file mode 100644 index 000000000000..72ac7b398639 --- /dev/null +++ b/geometry/skia_conversions.cc @@ -0,0 +1,96 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/skia_conversions.h" + +#include +#include + +#include "base/numerics/safe_conversions.h" +#include "base/numerics/safe_math.h" +#include "ui/gfx/geometry/quad_f.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/rect_f.h" +#include "ui/gfx/geometry/transform.h" + +namespace gfx { + +SkPoint PointToSkPoint(const Point& point) { + return SkPoint::Make(SkIntToScalar(point.x()), SkIntToScalar(point.y())); +} + +SkIPoint PointToSkIPoint(const Point& point) { + return SkIPoint::Make(point.x(), point.y()); +} + +SkPoint PointFToSkPoint(const PointF& point) { + return SkPoint::Make(SkFloatToScalar(point.x()), SkFloatToScalar(point.y())); +} + +SkRect RectToSkRect(const Rect& rect) { + return SkRect::MakeXYWH(SkIntToScalar(rect.x()), SkIntToScalar(rect.y()), + SkIntToScalar(rect.width()), + SkIntToScalar(rect.height())); +} + +SkIRect RectToSkIRect(const Rect& rect) { + return SkIRect::MakeXYWH(rect.x(), rect.y(), rect.width(), rect.height()); +} + +Rect SkIRectToRect(const SkIRect& rect) { + Rect result; + result.SetByBounds(rect.left(), rect.top(), rect.right(), rect.bottom()); + return result; +} + +SkRect RectFToSkRect(const RectF& rect) { + return SkRect::MakeXYWH(SkFloatToScalar(rect.x()), SkFloatToScalar(rect.y()), + SkFloatToScalar(rect.width()), + SkFloatToScalar(rect.height())); +} + +RectF SkRectToRectF(const SkRect& rect) { + return RectF(SkScalarToFloat(rect.x()), SkScalarToFloat(rect.y()), + SkScalarToFloat(rect.width()), SkScalarToFloat(rect.height())); +} + +SkSize SizeFToSkSize(const SizeF& size) { + return SkSize::Make(SkFloatToScalar(size.width()), + SkFloatToScalar(size.height())); +} + +SkISize SizeToSkISize(const Size& size) { + return SkISize::Make(size.width(), size.height()); +} + +SizeF SkSizeToSizeF(const SkSize& size) { + return SizeF(SkScalarToFloat(size.width()), SkScalarToFloat(size.height())); +} + +Size SkISizeToSize(const SkISize& size) { + return Size(size.width(), size.height()); +} + +void TransformToFlattenedSkMatrix(const gfx::Transform& transform, + SkMatrix* flattened) { + // Convert from 4x4 to 3x3 by dropping the third row and column. + flattened->set(0, transform.matrix().get(0, 0)); + flattened->set(1, transform.matrix().get(0, 1)); + flattened->set(2, transform.matrix().get(0, 3)); + flattened->set(3, transform.matrix().get(1, 0)); + flattened->set(4, transform.matrix().get(1, 1)); + flattened->set(5, transform.matrix().get(1, 3)); + flattened->set(6, transform.matrix().get(3, 0)); + flattened->set(7, transform.matrix().get(3, 1)); + flattened->set(8, transform.matrix().get(3, 3)); +} + +void QuadFToSkPoints(const gfx::QuadF& quad, SkPoint points[4]) { + points[0] = PointFToSkPoint(quad.p1()); + points[1] = PointFToSkPoint(quad.p2()); + points[2] = PointFToSkPoint(quad.p3()); + points[3] = PointFToSkPoint(quad.p4()); +} + +} // namespace gfx diff --git a/geometry/skia_conversions.h b/geometry/skia_conversions.h new file mode 100644 index 000000000000..a4c132920455 --- /dev/null +++ b/geometry/skia_conversions.h @@ -0,0 +1,47 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_SKIA_CONVERSIONS_H_ +#define UI_GFX_GEOMETRY_SKIA_CONVERSIONS_H_ + +#include "third_party/skia/include/core/SkColor.h" +#include "third_party/skia/include/core/SkRect.h" +#include "ui/gfx/geometry/geometry_skia_export.h" +#include "ui/gfx/geometry/quad_f.h" +#include "ui/gfx/geometry/size.h" + +class SkMatrix; + +namespace gfx { + +class Point; +class PointF; +class Rect; +class RectF; +class Transform; + +// Convert between Skia and gfx types. +GEOMETRY_SKIA_EXPORT SkPoint PointToSkPoint(const Point& point); +GEOMETRY_SKIA_EXPORT SkIPoint PointToSkIPoint(const Point& point); +GEOMETRY_SKIA_EXPORT SkPoint PointFToSkPoint(const PointF& point); +GEOMETRY_SKIA_EXPORT SkRect RectToSkRect(const Rect& rect); +GEOMETRY_SKIA_EXPORT SkIRect RectToSkIRect(const Rect& rect); +GEOMETRY_SKIA_EXPORT Rect SkIRectToRect(const SkIRect& rect); +GEOMETRY_SKIA_EXPORT SkRect RectFToSkRect(const RectF& rect); +GEOMETRY_SKIA_EXPORT RectF SkRectToRectF(const SkRect& rect); +GEOMETRY_SKIA_EXPORT SkSize SizeFToSkSize(const SizeF& size); +GEOMETRY_SKIA_EXPORT SkISize SizeToSkISize(const Size& size); +GEOMETRY_SKIA_EXPORT SizeF SkSizeToSizeF(const SkSize& size); +GEOMETRY_SKIA_EXPORT Size SkISizeToSize(const SkISize& size); + +GEOMETRY_SKIA_EXPORT void QuadFToSkPoints(const gfx::QuadF& quad, + SkPoint points[4]); + +GEOMETRY_SKIA_EXPORT void TransformToFlattenedSkMatrix( + const gfx::Transform& transform, + SkMatrix* flattened); + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_SKIA_CONVERSIONS_H_ diff --git a/geometry/test/rect_test_util.cc b/geometry/test/rect_test_util.cc new file mode 100644 index 000000000000..bcc943b7fc50 --- /dev/null +++ b/geometry/test/rect_test_util.cc @@ -0,0 +1,23 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/test/rect_test_util.h" + +namespace gfx { +namespace test { + +testing::AssertionResult RectContains(const gfx::Rect& outer_rect, + const gfx::Rect& inner_rect) { + if (outer_rect.Contains(inner_rect)) { + return testing::AssertionSuccess() + << "outer_rect (" << outer_rect.ToString() + << ") does contain inner_rect (" << inner_rect.ToString() << ")"; + } + return testing::AssertionFailure() << "outer_rect (" << outer_rect.ToString() + << ") does not contain inner_rect (" + << inner_rect.ToString() << ")"; +} + +} // namespace test +} // namespace gfx diff --git a/geometry/test/rect_test_util.h b/geometry/test/rect_test_util.h new file mode 100644 index 000000000000..91d7b2a1a9df --- /dev/null +++ b/geometry/test/rect_test_util.h @@ -0,0 +1,20 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_TEST_RECT_TEST_UTIL_H_ +#define UI_GFX_GEOMETRY_TEST_RECT_TEST_UTIL_H_ + +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/geometry/rect.h" + +namespace gfx { +namespace test { + +testing::AssertionResult RectContains(const gfx::Rect& outer_rect, + const gfx::Rect& inner_rect); + +} // namespace test +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_TEST_RECT_TEST_UTIL_H_ diff --git a/geometry/test/size_test_util.h b/geometry/test/size_test_util.h new file mode 100644 index 000000000000..43a6739b7081 --- /dev/null +++ b/geometry/test/size_test_util.h @@ -0,0 +1,20 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_TEST_SIZE_TEST_UTIL_H_ +#define UI_GFX_GEOMETRY_TEST_SIZE_TEST_UTIL_H_ + +#define EXPECT_FLOAT_SIZE_EQ(expected, actual) \ + do { \ + EXPECT_FLOAT_EQ((expected).width(), (actual).width()); \ + EXPECT_FLOAT_EQ((expected).height(), (actual).height()); \ + } while (false) + +#define EXPECT_SIZE_EQ(expected, actual) \ + do { \ + EXPECT_EQ((expected).width(), (actual).width()); \ + EXPECT_EQ((expected).height(), (actual).height()); \ + } while (false) + +#endif // UI_GFX_GEOMETRY_TEST_SIZE_TEST_UTIL_H_ diff --git a/geometry/test/transform_test_util.cc b/geometry/test/transform_test_util.cc new file mode 100644 index 000000000000..e8fa4b5faaf0 --- /dev/null +++ b/geometry/test/transform_test_util.cc @@ -0,0 +1,46 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/test/transform_test_util.h" + +#include "base/check.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace gfx { + +// NOTE: even though transform data types use double precision, we only check +// for equality within single-precision error bounds because many transforms +// originate from single-precision data types such as quads/rects/etc. + +void ExpectTransformationMatrixEq(const Transform& expected, + const Transform& actual) { + for (int row = 0; row < 4; ++row) { + for (int col = 0; col < 4; ++col) { + EXPECT_FLOAT_EQ(expected.matrix().get(row, col), + actual.matrix().get(row, col)) + << "row: " << row << " col: " << col; + } + } +} + +void ExpectTransformationMatrixNear(const Transform& expected, + const Transform& actual, + float abs_error) { + for (int row = 0; row < 4; ++row) { + for (int col = 0; col < 4; ++col) { + EXPECT_NEAR(expected.matrix().get(row, col), + actual.matrix().get(row, col), abs_error) + << "row: " << row << " col: " << col; + } + } +} + +Transform InvertAndCheck(const Transform& transform) { + Transform result(Transform::kSkipInitialization); + bool inverted_successfully = transform.GetInverse(&result); + DCHECK(inverted_successfully); + return result; +} + +} // namespace gfx diff --git a/geometry/test/transform_test_util.h b/geometry/test/transform_test_util.h new file mode 100644 index 000000000000..b9dbc90df350 --- /dev/null +++ b/geometry/test/transform_test_util.h @@ -0,0 +1,30 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_TEST_TRANSFORM_TEST_UTIL_H_ +#define UI_GFX_GEOMETRY_TEST_TRANSFORM_TEST_UTIL_H_ + +#include "ui/gfx/geometry/transform.h" + +namespace gfx { + +// This is a function rather than a macro because when this is included as a +// macro in bulk, it causes a significant slow-down in compilation time. This +// problem exists with both gcc and clang, and bugs have been filed at +// http://llvm.org/bugs/show_bug.cgi?id=13651 +// and http://gcc.gnu.org/bugzilla/show_bug.cgi?id=54337 +void ExpectTransformationMatrixEq(const Transform& expected, + const Transform& actual); + +void ExpectTransformationMatrixNear(const Transform& expected, + const Transform& actual, + float abs_error); + +// Should be used in test code only, for convenience. Production code should use +// the gfx::Transform::GetInverse() API. +Transform InvertAndCheck(const Transform& transform); + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_TEST_TRANSFORM_TEST_UTIL_H_ diff --git a/geometry/transform.cc b/geometry/transform.cc new file mode 100644 index 000000000000..eee5af7a697b --- /dev/null +++ b/geometry/transform.cc @@ -0,0 +1,634 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/transform.h" + +#include "base/check_op.h" +#include "base/strings/stringprintf.h" +#include "ui/gfx/geometry/angle_conversions.h" +#include "ui/gfx/geometry/box_f.h" +#include "ui/gfx/geometry/point3_f.h" +#include "ui/gfx/geometry/point_conversions.h" +#include "ui/gfx/geometry/quaternion.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/rrect_f.h" +#include "ui/gfx/geometry/skia_conversions.h" +#include "ui/gfx/geometry/transform_util.h" +#include "ui/gfx/geometry/vector3d_f.h" + +namespace gfx { + +namespace { + +const SkScalar kEpsilon = std::numeric_limits::epsilon(); + +SkScalar TanDegrees(double degrees) { + return SkDoubleToScalar(std::tan(gfx::DegToRad(degrees))); +} + +inline bool ApproximatelyZero(SkScalar x, SkScalar tolerance) { + return std::abs(x) <= tolerance; +} + +inline bool ApproximatelyOne(SkScalar x, SkScalar tolerance) { + return std::abs(x - 1) <= tolerance; +} + +} // namespace + +Transform::Transform(SkScalar col1row1, + SkScalar col2row1, + SkScalar col3row1, + SkScalar col4row1, + SkScalar col1row2, + SkScalar col2row2, + SkScalar col3row2, + SkScalar col4row2, + SkScalar col1row3, + SkScalar col2row3, + SkScalar col3row3, + SkScalar col4row3, + SkScalar col1row4, + SkScalar col2row4, + SkScalar col3row4, + SkScalar col4row4) + : matrix_(skia::Matrix44::kUninitialized_Constructor) { + matrix_.set4x4(col1row1, col1row2, col1row3, col1row4, col2row1, col2row2, + col2row3, col2row4, col3row1, col3row2, col3row3, col3row4, + col4row1, col4row2, col4row3, col4row4); +} + +Transform::Transform(SkScalar col1row1, + SkScalar col2row1, + SkScalar col1row2, + SkScalar col2row2, + SkScalar x_translation, + SkScalar y_translation) + : matrix_(skia::Matrix44::kUninitialized_Constructor) { + matrix_.set4x4(col1row1, col1row2, 0, 0, col2row1, col2row2, 0, 0, 0, 0, 1, 0, + x_translation, y_translation, 0, 1); +} + +Transform::Transform(const Quaternion& q) + : matrix_(skia::Matrix44::kUninitialized_Constructor) { + double x = q.x(); + double y = q.y(); + double z = q.z(); + double w = q.w(); + + // Implicitly calls matrix.setIdentity() + matrix_.set3x3(SkDoubleToScalar(1.0 - 2.0 * (y * y + z * z)), + SkDoubleToScalar(2.0 * (x * y + z * w)), + SkDoubleToScalar(2.0 * (x * z - y * w)), + SkDoubleToScalar(2.0 * (x * y - z * w)), + SkDoubleToScalar(1.0 - 2.0 * (x * x + z * z)), + SkDoubleToScalar(2.0 * (y * z + x * w)), + SkDoubleToScalar(2.0 * (x * z + y * w)), + SkDoubleToScalar(2.0 * (y * z - x * w)), + SkDoubleToScalar(1.0 - 2.0 * (x * x + y * y))); +} + +void Transform::RotateAboutXAxis(double degrees) { + double radians = gfx::DegToRad(degrees); + SkScalar cosTheta = SkDoubleToScalar(std::cos(radians)); + SkScalar sinTheta = SkDoubleToScalar(std::sin(radians)); + if (matrix_.isIdentity()) { + matrix_.set3x3(1, 0, 0, 0, cosTheta, sinTheta, 0, -sinTheta, cosTheta); + } else { + skia::Matrix44 rot(skia::Matrix44::kUninitialized_Constructor); + rot.set3x3(1, 0, 0, 0, cosTheta, sinTheta, 0, -sinTheta, cosTheta); + matrix_.preConcat(rot); + } +} + +void Transform::RotateAboutYAxis(double degrees) { + double radians = gfx::DegToRad(degrees); + SkScalar cosTheta = SkDoubleToScalar(std::cos(radians)); + SkScalar sinTheta = SkDoubleToScalar(std::sin(radians)); + if (matrix_.isIdentity()) { + // Note carefully the placement of the -sinTheta for rotation about + // y-axis is different than rotation about x-axis or z-axis. + matrix_.set3x3(cosTheta, 0, -sinTheta, 0, 1, 0, sinTheta, 0, cosTheta); + } else { + skia::Matrix44 rot(skia::Matrix44::kUninitialized_Constructor); + rot.set3x3(cosTheta, 0, -sinTheta, 0, 1, 0, sinTheta, 0, cosTheta); + matrix_.preConcat(rot); + } +} + +void Transform::RotateAboutZAxis(double degrees) { + double radians = gfx::DegToRad(degrees); + SkScalar cosTheta = SkDoubleToScalar(std::cos(radians)); + SkScalar sinTheta = SkDoubleToScalar(std::sin(radians)); + if (matrix_.isIdentity()) { + matrix_.set3x3(cosTheta, sinTheta, 0, -sinTheta, cosTheta, 0, 0, 0, 1); + } else { + skia::Matrix44 rot(skia::Matrix44::kUninitialized_Constructor); + rot.set3x3(cosTheta, sinTheta, 0, -sinTheta, cosTheta, 0, 0, 0, 1); + matrix_.preConcat(rot); + } +} + +void Transform::RotateAbout(const Vector3dF& axis, double degrees) { + if (matrix_.isIdentity()) { + matrix_.setRotateDegreesAbout(axis.x(), axis.y(), axis.z(), + SkDoubleToScalar(degrees)); + } else { + skia::Matrix44 rot(skia::Matrix44::kUninitialized_Constructor); + rot.setRotateDegreesAbout(axis.x(), axis.y(), axis.z(), + SkDoubleToScalar(degrees)); + matrix_.preConcat(rot); + } +} + +void Transform::Scale(SkScalar x, SkScalar y) { + matrix_.preScale(x, y, 1); +} + +void Transform::PostScale(SkScalar x, SkScalar y) { + matrix_.postScale(x, y, 1); +} + +void Transform::Scale3d(SkScalar x, SkScalar y, SkScalar z) { + matrix_.preScale(x, y, z); +} + +void Transform::Translate(const Vector2dF& offset) { + Translate(offset.x(), offset.y()); +} + +void Transform::Translate(SkScalar x, SkScalar y) { + matrix_.preTranslate(x, y, 0); +} + +void Transform::PostTranslate(const Vector2dF& offset) { + PostTranslate(offset.x(), offset.y()); +} + +void Transform::PostTranslate(SkScalar x, SkScalar y) { + matrix_.postTranslate(x, y, 0); +} + +void Transform::Translate3d(const Vector3dF& offset) { + Translate3d(offset.x(), offset.y(), offset.z()); +} + +void Transform::Translate3d(SkScalar x, SkScalar y, SkScalar z) { + matrix_.preTranslate(x, y, z); +} + +void Transform::Skew(double angle_x, double angle_y) { + if (matrix_.isIdentity()) { + matrix_.set(0, 1, TanDegrees(angle_x)); + matrix_.set(1, 0, TanDegrees(angle_y)); + } else { + skia::Matrix44 skew(skia::Matrix44::kIdentity_Constructor); + skew.set(0, 1, TanDegrees(angle_x)); + skew.set(1, 0, TanDegrees(angle_y)); + matrix_.preConcat(skew); + } +} + +void Transform::ApplyPerspectiveDepth(SkScalar depth) { + if (depth == 0) + return; + if (matrix_.isIdentity()) { + matrix_.set(3, 2, -SK_Scalar1 / depth); + } else { + skia::Matrix44 m(skia::Matrix44::kIdentity_Constructor); + m.set(3, 2, -SK_Scalar1 / depth); + matrix_.preConcat(m); + } +} + +void Transform::PreconcatTransform(const Transform& transform) { + matrix_.preConcat(transform.matrix_); +} + +void Transform::ConcatTransform(const Transform& transform) { + matrix_.postConcat(transform.matrix_); +} + +bool Transform::IsApproximatelyIdentityOrTranslation(SkScalar tolerance) const { + DCHECK_GE(tolerance, 0); + return ApproximatelyOne(matrix_.get(0, 0), tolerance) && + ApproximatelyZero(matrix_.get(1, 0), tolerance) && + ApproximatelyZero(matrix_.get(2, 0), tolerance) && + matrix_.get(3, 0) == 0 && + ApproximatelyZero(matrix_.get(0, 1), tolerance) && + ApproximatelyOne(matrix_.get(1, 1), tolerance) && + ApproximatelyZero(matrix_.get(2, 1), tolerance) && + matrix_.get(3, 1) == 0 && + ApproximatelyZero(matrix_.get(0, 2), tolerance) && + ApproximatelyZero(matrix_.get(1, 2), tolerance) && + ApproximatelyOne(matrix_.get(2, 2), tolerance) && + matrix_.get(3, 2) == 0 && matrix_.get(3, 3) == 1; +} + +bool Transform::IsApproximatelyIdentityOrIntegerTranslation( + SkScalar tolerance) const { + if (!IsApproximatelyIdentityOrTranslation(tolerance)) + return false; + + for (float t : {matrix_.get(0, 3), matrix_.get(1, 3), matrix_.get(2, 3)}) { + if (!base::IsValueInRangeForNumericType(t) || + std::abs(std::round(t) - t) > tolerance) + return false; + } + return true; +} + +bool Transform::IsIdentityOrIntegerTranslation() const { + if (!IsIdentityOrTranslation()) + return false; + + for (float t : {matrix_.get(0, 3), matrix_.get(1, 3), matrix_.get(2, 3)}) { + if (!base::IsValueInRangeForNumericType(t) || static_cast(t) != t) + return false; + } + return true; +} + +bool Transform::IsBackFaceVisible() const { + // Compute whether a layer with a forward-facing normal of (0, 0, 1, 0) + // would have its back face visible after applying the transform. + if (matrix_.isIdentity()) + return false; + + // This is done by transforming the normal and seeing if the resulting z + // value is positive or negative. However, note that transforming a normal + // actually requires using the inverse-transpose of the original transform. + // + // We can avoid inverting and transposing the matrix since we know we want + // to transform only the specific normal vector (0, 0, 1, 0). In this case, + // we only need the 3rd row, 3rd column of the inverse-transpose. We can + // calculate only the 3rd row 3rd column element of the inverse, skipping + // everything else. + // + // For more information, refer to: + // http://en.wikipedia.org/wiki/Invertible_matrix#Analytic_solution + // + + double determinant = matrix_.determinant(); + + // If matrix was not invertible, then just assume back face is not visible. + if (determinant == 0) + return false; + + // Compute the cofactor of the 3rd row, 3rd column. + double cofactor_part_1 = + matrix_.get(0, 0) * matrix_.get(1, 1) * matrix_.get(3, 3); + + double cofactor_part_2 = + matrix_.get(0, 1) * matrix_.get(1, 3) * matrix_.get(3, 0); + + double cofactor_part_3 = + matrix_.get(0, 3) * matrix_.get(1, 0) * matrix_.get(3, 1); + + double cofactor_part_4 = + matrix_.get(0, 0) * matrix_.get(1, 3) * matrix_.get(3, 1); + + double cofactor_part_5 = + matrix_.get(0, 1) * matrix_.get(1, 0) * matrix_.get(3, 3); + + double cofactor_part_6 = + matrix_.get(0, 3) * matrix_.get(1, 1) * matrix_.get(3, 0); + + double cofactor33 = cofactor_part_1 + cofactor_part_2 + cofactor_part_3 - + cofactor_part_4 - cofactor_part_5 - cofactor_part_6; + + // Technically the transformed z component is cofactor33 / determinant. But + // we can avoid the costly division because we only care about the resulting + // +/- sign; we can check this equivalently by multiplication. + return cofactor33 * determinant < -kEpsilon; +} + +bool Transform::GetInverse(Transform* transform) const { + if (!matrix_.invert(&transform->matrix_)) { + // Initialize the return value to identity if this matrix turned + // out to be un-invertible. + transform->MakeIdentity(); + return false; + } + + return true; +} + +bool Transform::Preserves2dAxisAlignment() const { + // Check whether an axis aligned 2-dimensional rect would remain axis-aligned + // after being transformed by this matrix (and implicitly projected by + // dropping any non-zero z-values). + // + // The 4th column can be ignored because translations don't affect axis + // alignment. The 3rd column can be ignored because we are assuming 2d + // inputs, where z-values will be zero. The 3rd row can also be ignored + // because we are assuming 2d outputs, and any resulting z-value is dropped + // anyway. For the inner 2x2 portion, the only effects that keep a rect axis + // aligned are (1) swapping axes and (2) scaling axes. This can be checked by + // verifying only 1 element of every column and row is non-zero. Degenerate + // cases that project the x or y dimension to zero are considered to preserve + // axis alignment. + // + // If the matrix does have perspective component that is affected by x or y + // values: The current implementation conservatively assumes that axis + // alignment is not preserved. + + bool has_x_or_y_perspective = + matrix_.get(3, 0) != 0 || matrix_.get(3, 1) != 0; + + int num_non_zero_in_row_0 = 0; + int num_non_zero_in_row_1 = 0; + int num_non_zero_in_col_0 = 0; + int num_non_zero_in_col_1 = 0; + + if (std::abs(matrix_.get(0, 0)) > kEpsilon) { + num_non_zero_in_row_0++; + num_non_zero_in_col_0++; + } + + if (std::abs(matrix_.get(0, 1)) > kEpsilon) { + num_non_zero_in_row_0++; + num_non_zero_in_col_1++; + } + + if (std::abs(matrix_.get(1, 0)) > kEpsilon) { + num_non_zero_in_row_1++; + num_non_zero_in_col_0++; + } + + if (std::abs(matrix_.get(1, 1)) > kEpsilon) { + num_non_zero_in_row_1++; + num_non_zero_in_col_1++; + } + + return num_non_zero_in_row_0 <= 1 && num_non_zero_in_row_1 <= 1 && + num_non_zero_in_col_0 <= 1 && num_non_zero_in_col_1 <= 1 && + !has_x_or_y_perspective; +} + +bool Transform::NonDegeneratePreserves2dAxisAlignment() const { + // See comments above for Preserves2dAxisAlignment. + + // This function differs from it by requiring: + // (1) that there are exactly two nonzero values on a diagonal in + // the upper left 2x2 submatrix, and + // (2) that the w perspective value is positive. + + bool has_x_or_y_perspective = + matrix_.get(3, 0) != 0 || matrix_.get(3, 1) != 0; + bool positive_w_perspective = matrix_.get(3, 3) > kEpsilon; + + bool have_0_0 = std::abs(matrix_.get(0, 0)) > kEpsilon; + bool have_0_1 = std::abs(matrix_.get(0, 1)) > kEpsilon; + bool have_1_0 = std::abs(matrix_.get(1, 0)) > kEpsilon; + bool have_1_1 = std::abs(matrix_.get(1, 1)) > kEpsilon; + + return have_0_0 == have_1_1 && have_0_1 == have_1_0 && have_0_0 != have_0_1 && + !has_x_or_y_perspective && positive_w_perspective; +} + +void Transform::Transpose() { + matrix_.transpose(); +} + +void Transform::FlattenTo2d() { + float tmp[16]; + matrix_.asColMajorf(tmp); + tmp[2] = 0.0; + tmp[6] = 0.0; + tmp[8] = 0.0; + tmp[9] = 0.0; + tmp[10] = 1.0; + tmp[11] = 0.0; + tmp[14] = 0.0; + matrix_.setColMajorf(tmp); +} + +bool Transform::IsFlat() const { + return matrix_.get(2, 0) == 0.0 && matrix_.get(2, 1) == 0.0 && + matrix_.get(0, 2) == 0.0 && matrix_.get(1, 2) == 0.0 && + matrix_.get(2, 2) == 1.0 && matrix_.get(3, 2) == 0.0 && + matrix_.get(2, 3) == 0.0; +} + +Vector2dF Transform::To2dTranslation() const { + return gfx::Vector2dF(SkScalarToFloat(matrix_.get(0, 3)), + SkScalarToFloat(matrix_.get(1, 3))); +} + +void Transform::TransformPoint(Point* point) const { + DCHECK(point); + TransformPointInternal(matrix_, point); +} + +void Transform::TransformPoint(PointF* point) const { + DCHECK(point); + TransformPointInternal(matrix_, point); +} + +void Transform::TransformPoint(Point3F* point) const { + DCHECK(point); + TransformPointInternal(matrix_, point); +} + +void Transform::TransformVector(Vector3dF* vector) const { + DCHECK(vector); + TransformVectorInternal(matrix_, vector); +} + +bool Transform::TransformPointReverse(Point* point) const { + DCHECK(point); + + // TODO(sad): Try to avoid trying to invert the matrix. + skia::Matrix44 inverse(skia::Matrix44::kUninitialized_Constructor); + if (!matrix_.invert(&inverse)) + return false; + + TransformPointInternal(inverse, point); + return true; +} + +bool Transform::TransformPointReverse(Point3F* point) const { + DCHECK(point); + + // TODO(sad): Try to avoid trying to invert the matrix. + skia::Matrix44 inverse(skia::Matrix44::kUninitialized_Constructor); + if (!matrix_.invert(&inverse)) + return false; + + TransformPointInternal(inverse, point); + return true; +} + +void Transform::TransformRect(RectF* rect) const { + if (matrix_.isIdentity()) + return; + + SkRect src = RectFToSkRect(*rect); + SkMatrix(matrix_).mapRect(&src); + *rect = SkRectToRectF(src); +} + +bool Transform::TransformRectReverse(RectF* rect) const { + if (matrix_.isIdentity()) + return true; + + skia::Matrix44 inverse(skia::Matrix44::kUninitialized_Constructor); + if (!matrix_.invert(&inverse)) + return false; + + SkRect src = RectFToSkRect(*rect); + SkMatrix(inverse).mapRect(&src); + *rect = SkRectToRectF(src); + return true; +} + +bool Transform::TransformRRectF(RRectF* rrect) const { + SkRRect result; + if (!SkRRect(*rrect).transform(SkMatrix(matrix_), &result)) + return false; + *rrect = gfx::RRectF(result); + return true; +} + +void Transform::TransformBox(BoxF* box) const { + BoxF bounds; + bool first_point = true; + for (int corner = 0; corner < 8; ++corner) { + gfx::Point3F point = box->origin(); + point += gfx::Vector3dF(corner & 1 ? box->width() : 0.f, + corner & 2 ? box->height() : 0.f, + corner & 4 ? box->depth() : 0.f); + TransformPoint(&point); + if (first_point) { + bounds.set_origin(point); + first_point = false; + } else { + bounds.ExpandTo(point); + } + } + *box = bounds; +} + +bool Transform::TransformBoxReverse(BoxF* box) const { + gfx::Transform inverse = *this; + if (!GetInverse(&inverse)) + return false; + inverse.TransformBox(box); + return true; +} + +bool Transform::Blend(const Transform& from, double progress) { + DecomposedTransform to_decomp; + DecomposedTransform from_decomp; + if (!DecomposeTransform(&to_decomp, *this) || + !DecomposeTransform(&from_decomp, from)) + return false; + + to_decomp = BlendDecomposedTransforms(to_decomp, from_decomp, progress); + + matrix_ = ComposeTransform(to_decomp).matrix(); + return true; +} + +void Transform::RoundTranslationComponents() { + matrix_.set(0, 3, std::round(matrix_.get(0, 3))); + matrix_.set(1, 3, std::round(matrix_.get(1, 3))); +} + +void Transform::TransformPointInternal(const skia::Matrix44& xform, + Point3F* point) const { + if (xform.isIdentity()) + return; + + SkScalar p[4] = {point->x(), point->y(), point->z(), 1}; + + xform.mapScalars(p); + + if (p[3] != SK_Scalar1 && p[3] != 0.f) { + float w_inverse = SK_Scalar1 / p[3]; + point->SetPoint(p[0] * w_inverse, p[1] * w_inverse, p[2] * w_inverse); + } else { + point->SetPoint(p[0], p[1], p[2]); + } +} + +void Transform::TransformVectorInternal(const skia::Matrix44& xform, + Vector3dF* vector) const { + if (xform.isIdentity()) + return; + + SkScalar p[4] = {vector->x(), vector->y(), vector->z(), 0}; + + xform.mapScalars(p); + + vector->set_x(p[0]); + vector->set_y(p[1]); + vector->set_z(p[2]); +} + +void Transform::TransformPointInternal(const skia::Matrix44& xform, + PointF* point) const { + if (xform.isIdentity()) + return; + + SkScalar p[4] = {SkIntToScalar(point->x()), SkIntToScalar(point->y()), 0, 1}; + + xform.mapScalars(p); + + point->SetPoint(p[0], p[1]); +} + +void Transform::TransformPointInternal(const skia::Matrix44& xform, + Point* point) const { + PointF point_float(*point); + TransformPointInternal(xform, &point_float); + *point = ToRoundedPoint(point_float); +} + +bool Transform::ApproximatelyEqual(const gfx::Transform& transform) const { + static const float component_tolerance = 0.1f; + + // We may have a larger discrepancy in the scroll components due to snapping + // (floating point error might round the other way). + static const float translation_tolerance = 1.f; + + for (int row = 0; row < 4; row++) { + for (int col = 0; col < 4; col++) { + const float delta = + std::abs(matrix().get(row, col) - transform.matrix().get(row, col)); + const float tolerance = + col == 3 && row < 3 ? translation_tolerance : component_tolerance; + if (delta > tolerance) + return false; + } + } + + return true; +} + +std::string Transform::ToString() const { + return base::StringPrintf( + "[ %+0.4f %+0.4f %+0.4f %+0.4f \n" + " %+0.4f %+0.4f %+0.4f %+0.4f \n" + " %+0.4f %+0.4f %+0.4f %+0.4f \n" + " %+0.4f %+0.4f %+0.4f %+0.4f ]\n", + matrix_.get(0, 0), matrix_.get(0, 1), matrix_.get(0, 2), + matrix_.get(0, 3), matrix_.get(1, 0), matrix_.get(1, 1), + matrix_.get(1, 2), matrix_.get(1, 3), matrix_.get(2, 0), + matrix_.get(2, 1), matrix_.get(2, 2), matrix_.get(2, 3), + matrix_.get(3, 0), matrix_.get(3, 1), matrix_.get(3, 2), + matrix_.get(3, 3)); +} + +SkM44 Transform::GetMatrixAsSkM44() const { + return SkM44(matrix_.get(0, 0), matrix_.get(0, 1), matrix_.get(0, 2), + matrix_.get(0, 3), matrix_.get(1, 0), matrix_.get(1, 1), + matrix_.get(1, 2), matrix_.get(1, 3), matrix_.get(2, 0), + matrix_.get(2, 1), matrix_.get(2, 2), matrix_.get(2, 3), + matrix_.get(3, 0), matrix_.get(3, 1), matrix_.get(3, 2), + matrix_.get(3, 3)); +} + +} // namespace gfx diff --git a/geometry/transform.h b/geometry/transform.h new file mode 100644 index 000000000000..e0df4d6e5b2b --- /dev/null +++ b/geometry/transform.h @@ -0,0 +1,329 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_TRANSFORM_H_ +#define UI_GFX_GEOMETRY_TRANSFORM_H_ + +#include +#include + +#include "base/compiler_specific.h" +#include "skia/ext/skia_matrix_44.h" +#include "third_party/skia/include/core/SkM44.h" +#include "ui/gfx/geometry/geometry_skia_export.h" +#include "ui/gfx/geometry/vector2d_f.h" + +namespace gfx { + +class BoxF; +class RectF; +class RRectF; +class Point; +class PointF; +class Point3F; +class Quaternion; +class Vector3dF; + +// 4x4 transformation matrix. Transform is cheap and explicitly allows +// copy/assign. +class GEOMETRY_SKIA_EXPORT Transform { + public: + enum SkipInitialization { kSkipInitialization }; + + constexpr Transform() : matrix_(skia::Matrix44::kIdentity_Constructor) {} + + // Skips initializing this matrix to avoid overhead, when we know it will be + // initialized before use. + explicit Transform(SkipInitialization) + : matrix_(skia::Matrix44::kUninitialized_Constructor) {} + Transform(const Transform& rhs) = default; + Transform& operator=(const Transform& rhs) = default; + // Initialize with the concatenation of lhs * rhs. + Transform(const Transform& lhs, const Transform& rhs) + : matrix_(lhs.matrix_, rhs.matrix_) {} + explicit Transform(const skia::Matrix44& matrix) : matrix_(matrix) {} + explicit Transform(const SkMatrix& matrix) + : matrix_(skia::Matrix44(matrix)) {} + // Constructs a transform from explicit 16 matrix elements. Elements + // should be given in row-major order. + Transform(SkScalar col1row1, + SkScalar col2row1, + SkScalar col3row1, + SkScalar col4row1, + SkScalar col1row2, + SkScalar col2row2, + SkScalar col3row2, + SkScalar col4row2, + SkScalar col1row3, + SkScalar col2row3, + SkScalar col3row3, + SkScalar col4row3, + SkScalar col1row4, + SkScalar col2row4, + SkScalar col3row4, + SkScalar col4row4); + // Constructs a transform from explicit 2d elements. All other matrix + // elements remain the same as the corresponding elements of an identity + // matrix. + Transform(SkScalar col1row1, + SkScalar col2row1, + SkScalar col1row2, + SkScalar col2row2, + SkScalar x_translation, + SkScalar y_translation); + + // Constructs a transform corresponding to the given quaternion. + explicit Transform(const Quaternion& q); + + bool operator==(const Transform& rhs) const { return matrix_ == rhs.matrix_; } + bool operator!=(const Transform& rhs) const { return matrix_ != rhs.matrix_; } + + // Resets this transform to the identity transform. + void MakeIdentity() { matrix_.setIdentity(); } + + // Applies the current transformation on a 2d rotation and assigns the result + // to |this|. + void Rotate(double degrees) { RotateAboutZAxis(degrees); } + + // Applies the current transformation on an axis-angle rotation and assigns + // the result to |this|. + void RotateAboutXAxis(double degrees); + void RotateAboutYAxis(double degrees); + void RotateAboutZAxis(double degrees); + void RotateAbout(const Vector3dF& axis, double degrees); + + // Applies the current transformation on a scaling and assigns the result + // to |this|. + void Scale(SkScalar x, SkScalar y); + void Scale3d(SkScalar x, SkScalar y, SkScalar z); + gfx::Vector2dF Scale2d() const { + return gfx::Vector2dF(matrix_.get(0, 0), matrix_.get(1, 1)); + } + + // Applies a scale to the current transformation and assigns the result to + // |this|. + void PostScale(SkScalar x, SkScalar y); + + // Applies the current transformation on a translation and assigns the result + // to |this|. + void Translate(const Vector2dF& offset); + void Translate(SkScalar x, SkScalar y); + void Translate3d(const Vector3dF& offset); + void Translate3d(SkScalar x, SkScalar y, SkScalar z); + + // Applies a translation to the current transformation and assigns the result + // to |this|. + void PostTranslate(const Vector2dF& offset); + void PostTranslate(SkScalar x, SkScalar y); + + // Applies the current transformation on a skew and assigns the result + // to |this|. + void Skew(double angle_x, double angle_y); + + // Applies the current transformation on a perspective transform and assigns + // the result to |this|. + void ApplyPerspectiveDepth(SkScalar depth); + + // Applies a transformation on the current transformation + // (i.e. 'this = this * transform;'). + void PreconcatTransform(const Transform& transform); + + // Applies a transformation on the current transformation + // (i.e. 'this = transform * this;'). + void ConcatTransform(const Transform& transform); + + // Returns true if this is the identity matrix. + // This function modifies a mutable variable in |matrix_|. + bool IsIdentity() const { return matrix_.isIdentity(); } + + // Returns true if the matrix is either identity or pure translation. + bool IsIdentityOrTranslation() const { return matrix_.isTranslate(); } + + // Returns true if the matrix is either the identity or a 2d translation. + bool IsIdentityOr2DTranslation() const { + return matrix_.isTranslate() && matrix_.get(2, 3) == 0; + } + + // Returns true if the matrix is either identity or pure translation, + // allowing for an amount of inaccuracy as specified by the parameter. + bool IsApproximatelyIdentityOrTranslation(SkScalar tolerance) const; + bool IsApproximatelyIdentityOrIntegerTranslation(SkScalar tolerance) const; + + // Returns true if the matrix is either a positive scale and/or a translation. + bool IsPositiveScaleOrTranslation() const { + if (!IsScaleOrTranslation()) + return false; + return matrix_.get(0, 0) > 0.0 && matrix_.get(1, 1) > 0.0 && + matrix_.get(2, 2) > 0.0; + } + + // Returns true if the matrix is identity or, if the matrix consists only + // of a translation whose components can be represented as integers. Returns + // false if the translation contains a fractional component or is too large to + // fit in an integer. + bool IsIdentityOrIntegerTranslation() const; + + // Returns true if the matrix had only scaling components. + bool IsScale2d() const { return matrix_.isScale(); } + + // Returns true if the matrix is has only scaling and translation components. + bool IsScaleOrTranslation() const { return matrix_.isScaleTranslate(); } + + // Returns true if axis-aligned 2d rects will remain axis-aligned after being + // transformed by this matrix. + bool Preserves2dAxisAlignment() const; + + // Returns true if axis-aligned 2d rects will remain axis-aligned and not + // clipped by perspective (w > 0) after being transformed by this matrix, + // and distinct points in the x/y plane will remain distinct after being + // transformed by this matrix and mapped back to the x/y plane. + bool NonDegeneratePreserves2dAxisAlignment() const; + + // Returns true if the matrix has any perspective component that would + // change the w-component of a homogeneous point. + bool HasPerspective() const { return matrix_.hasPerspective(); } + + // Returns true if this transform is non-singular. + bool IsInvertible() const { return matrix_.invert(nullptr); } + + // Returns true if a layer with a forward-facing normal of (0, 0, 1) would + // have its back side facing frontwards after applying the transform. + bool IsBackFaceVisible() const; + + // Inverts the transform which is passed in. Returns true if successful, or + // sets |transform| to the identify matrix on failure. + bool GetInverse(Transform* transform) const WARN_UNUSED_RESULT; + + // Transposes this transform in place. + void Transpose(); + + // Set 3rd row and 3rd column to (0, 0, 1, 0). Note that this flattening + // operation is not quite the same as an orthographic projection and is + // technically not a linear operation. + // + // One useful interpretation of doing this operation: + // - For x and y values, the new transform behaves effectively like an + // orthographic projection was added to the matrix sequence. + // - For z values, the new transform overrides any effect that the transform + // had on z, and instead it preserves the z value for any points that are + // transformed. + // - Because of linearity of transforms, this flattened transform also + // preserves the effect that any subsequent (multiplied from the right) + // transforms would have on z values. + // + void FlattenTo2d(); + + // Returns true if the 3rd row and 3rd column are both (0, 0, 1, 0). + bool IsFlat() const; + + // Returns the x and y translation components of the matrix. + Vector2dF To2dTranslation() const; + + // Applies the transformation to the point. + void TransformPoint(Point3F* point) const; + + // Applies the transformation to the point. + void TransformPoint(PointF* point) const; + + // Applies the transformation to the point. + void TransformPoint(Point* point) const; + + // Applies the transformation to the vector. + void TransformVector(Vector3dF* vector) const; + + // Applies the reverse transformation on the point. Returns true if the + // transformation can be inverted. + bool TransformPointReverse(Point3F* point) const; + + // Applies the reverse transformation on the point. Returns true if the + // transformation can be inverted. Rounds the result to the nearest point. + bool TransformPointReverse(Point* point) const; + + // Applies transformation on the given rect. After the function completes, + // |rect| will be the smallest axis aligned bounding rect containing the + // transformed rect. + void TransformRect(RectF* rect) const; + + // Applies the reverse transformation on the given rect. After the function + // completes, |rect| will be the smallest axis aligned bounding rect + // containing the transformed rect. Returns false if the matrix cannot be + // inverted. + bool TransformRectReverse(RectF* rect) const; + + // Applies transformation on the given |rrect|. Returns false if the transform + // matrix cannot be applied to rrect. + bool TransformRRectF(RRectF* rrect) const; + + // Applies transformation on the given box. After the function completes, + // |box| will be the smallest axis aligned bounding box containing the + // transformed box. + void TransformBox(BoxF* box) const; + + // Applies the reverse transformation on the given box. After the function + // completes, |box| will be the smallest axis aligned bounding box + // containing the transformed box. Returns false if the matrix cannot be + // inverted. + bool TransformBoxReverse(BoxF* box) const; + + // Decomposes |this| and |from|, interpolates the decomposed values, and + // sets |this| to the reconstituted result. Returns false if either matrix + // can't be decomposed. Uses routines described in this spec: + // http://www.w3.org/TR/css3-3d-transforms/. + // + // Note: this call is expensive since we need to decompose the transform. If + // you're going to be calling this rapidly (e.g., in an animation) you should + // decompose once using gfx::DecomposeTransforms and reuse your + // DecomposedTransform. + bool Blend(const Transform& from, double progress); + + void RoundTranslationComponents(); + + // Returns |this| * |other|. + Transform operator*(const Transform& other) const { + return Transform(*this, other); + } + + // Sets |this| = |this| * |other| + Transform& operator*=(const Transform& other) { + PreconcatTransform(other); + return *this; + } + + // Returns the underlying matrix. + const skia::Matrix44& matrix() const { return matrix_; } + skia::Matrix44& matrix() { return matrix_; } + + // TODO(crbug.com/1167153) skia::Matrix44 is deprecated, this is to help in + // moving the code base towards SkM44, although eventually this class should + // just hold an SkM44 + SkM44 GetMatrixAsSkM44() const; + + bool ApproximatelyEqual(const gfx::Transform& transform) const; + + std::string ToString() const; + + private: + void TransformPointInternal(const skia::Matrix44& xform, Point* point) const; + + void TransformPointInternal(const skia::Matrix44& xform, PointF* point) const; + + void TransformPointInternal(const skia::Matrix44& xform, + Point3F* point) const; + + void TransformVectorInternal(const skia::Matrix44& xform, + Vector3dF* vector) const; + + skia::Matrix44 matrix_; + + // copy/assign are allowed. +}; + +// This is declared here for use in gtest-based unit tests but is defined in +// the //ui/gfx:test_support target. Depend on that to use this in your unit +// test. This should not be used in production code - call ToString() instead. +void PrintTo(const Transform& transform, ::std::ostream* os); + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_TRANSFORM_H_ diff --git a/geometry/transform_operation.cc b/geometry/transform_operation.cc new file mode 100644 index 000000000000..12303f5b62fe --- /dev/null +++ b/geometry/transform_operation.cc @@ -0,0 +1,514 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +#include "ui/gfx/geometry/transform_operation.h" + +#include "base/check_op.h" +#include "base/notreached.h" +#include "base/numerics/math_constants.h" +#include "base/numerics/ranges.h" +#include "ui/gfx/geometry/angle_conversions.h" +#include "ui/gfx/geometry/box_f.h" +#include "ui/gfx/geometry/transform_operations.h" +#include "ui/gfx/geometry/transform_util.h" +#include "ui/gfx/geometry/vector3d_f.h" + +namespace { +const SkScalar kAngleEpsilon = 1e-4f; +} + +namespace gfx { + +bool TransformOperation::IsIdentity() const { + return matrix.IsIdentity(); +} + +static bool IsOperationIdentity(const TransformOperation* operation) { + return !operation || operation->IsIdentity(); +} + +static bool ShareSameAxis(const TransformOperation* from, + const TransformOperation* to, + SkScalar* axis_x, + SkScalar* axis_y, + SkScalar* axis_z, + SkScalar* angle_from) { + if (IsOperationIdentity(from) && IsOperationIdentity(to)) + return false; + + if (IsOperationIdentity(from) && !IsOperationIdentity(to)) { + *axis_x = to->rotate.axis.x; + *axis_y = to->rotate.axis.y; + *axis_z = to->rotate.axis.z; + *angle_from = 0; + return true; + } + + if (!IsOperationIdentity(from) && IsOperationIdentity(to)) { + *axis_x = from->rotate.axis.x; + *axis_y = from->rotate.axis.y; + *axis_z = from->rotate.axis.z; + *angle_from = from->rotate.angle; + return true; + } + + SkScalar length_2 = from->rotate.axis.x * from->rotate.axis.x + + from->rotate.axis.y * from->rotate.axis.y + + from->rotate.axis.z * from->rotate.axis.z; + SkScalar other_length_2 = to->rotate.axis.x * to->rotate.axis.x + + to->rotate.axis.y * to->rotate.axis.y + + to->rotate.axis.z * to->rotate.axis.z; + + if (length_2 <= kAngleEpsilon || other_length_2 <= kAngleEpsilon) + return false; + + SkScalar dot = to->rotate.axis.x * from->rotate.axis.x + + to->rotate.axis.y * from->rotate.axis.y + + to->rotate.axis.z * from->rotate.axis.z; + SkScalar error = + SkScalarAbs(SK_Scalar1 - (dot * dot) / (length_2 * other_length_2)); + bool result = error < kAngleEpsilon; + if (result) { + *axis_x = to->rotate.axis.x; + *axis_y = to->rotate.axis.y; + *axis_z = to->rotate.axis.z; + // If the axes are pointing in opposite directions, we need to reverse + // the angle. + *angle_from = dot > 0 ? from->rotate.angle : -from->rotate.angle; + } + return result; +} + +static SkScalar BlendSkScalars(SkScalar from, SkScalar to, SkScalar progress) { + return from * (1 - progress) + to * progress; +} + +void TransformOperation::Bake() { + matrix.MakeIdentity(); + switch (type) { + case TransformOperation::TRANSFORM_OPERATION_TRANSLATE: + matrix.Translate3d(translate.x, translate.y, translate.z); + break; + case TransformOperation::TRANSFORM_OPERATION_ROTATE: + matrix.RotateAbout( + gfx::Vector3dF(rotate.axis.x, rotate.axis.y, rotate.axis.z), + rotate.angle); + break; + case TransformOperation::TRANSFORM_OPERATION_SCALE: + matrix.Scale3d(scale.x, scale.y, scale.z); + break; + case TransformOperation::TRANSFORM_OPERATION_SKEWX: + case TransformOperation::TRANSFORM_OPERATION_SKEWY: + case TransformOperation::TRANSFORM_OPERATION_SKEW: + matrix.Skew(skew.x, skew.y); + break; + case TransformOperation::TRANSFORM_OPERATION_PERSPECTIVE: + matrix.ApplyPerspectiveDepth(perspective_depth); + break; + case TransformOperation::TRANSFORM_OPERATION_MATRIX: + case TransformOperation::TRANSFORM_OPERATION_IDENTITY: + break; + } +} + +bool TransformOperation::ApproximatelyEqual(const TransformOperation& other, + SkScalar tolerance) const { + DCHECK_LE(0, tolerance); + if (type != other.type) + return false; + switch (type) { + case TransformOperation::TRANSFORM_OPERATION_TRANSLATE: + return base::IsApproximatelyEqual(translate.x, other.translate.x, + tolerance) && + base::IsApproximatelyEqual(translate.y, other.translate.y, + tolerance) && + base::IsApproximatelyEqual(translate.z, other.translate.z, + tolerance); + case TransformOperation::TRANSFORM_OPERATION_ROTATE: + return base::IsApproximatelyEqual(rotate.axis.x, other.rotate.axis.x, + tolerance) && + base::IsApproximatelyEqual(rotate.axis.y, other.rotate.axis.y, + tolerance) && + base::IsApproximatelyEqual(rotate.axis.z, other.rotate.axis.z, + tolerance) && + base::IsApproximatelyEqual(rotate.angle, other.rotate.angle, + tolerance); + case TransformOperation::TRANSFORM_OPERATION_SCALE: + return base::IsApproximatelyEqual(scale.x, other.scale.x, tolerance) && + base::IsApproximatelyEqual(scale.y, other.scale.y, tolerance) && + base::IsApproximatelyEqual(scale.z, other.scale.z, tolerance); + case TransformOperation::TRANSFORM_OPERATION_SKEWX: + case TransformOperation::TRANSFORM_OPERATION_SKEWY: + case TransformOperation::TRANSFORM_OPERATION_SKEW: + return base::IsApproximatelyEqual(skew.x, other.skew.x, tolerance) && + base::IsApproximatelyEqual(skew.y, other.skew.y, tolerance); + case TransformOperation::TRANSFORM_OPERATION_PERSPECTIVE: + return base::IsApproximatelyEqual(perspective_depth, + other.perspective_depth, tolerance); + case TransformOperation::TRANSFORM_OPERATION_MATRIX: + // TODO(vollick): we could expose a tolerance on gfx::Transform, but it's + // complex since we need a different tolerance per component. Driving this + // with a single tolerance will take some care. For now, we will check + // exact equality where the tolerance is 0.0f, otherwise we will use the + // unparameterized version of gfx::Transform::ApproximatelyEqual. + if (tolerance == 0.0f) + return matrix == other.matrix; + else + return matrix.ApproximatelyEqual(other.matrix); + case TransformOperation::TRANSFORM_OPERATION_IDENTITY: + return other.matrix.IsIdentity(); + } + NOTREACHED(); + return false; +} + +bool TransformOperation::BlendTransformOperations( + const TransformOperation* from, + const TransformOperation* to, + SkScalar progress, + TransformOperation* result) { + if (IsOperationIdentity(from) && IsOperationIdentity(to)) + return true; + + TransformOperation::Type interpolation_type = + TransformOperation::TRANSFORM_OPERATION_IDENTITY; + if (IsOperationIdentity(to)) + interpolation_type = from->type; + else + interpolation_type = to->type; + result->type = interpolation_type; + + switch (interpolation_type) { + case TransformOperation::TRANSFORM_OPERATION_TRANSLATE: { + SkScalar from_x = IsOperationIdentity(from) ? 0 : from->translate.x; + SkScalar from_y = IsOperationIdentity(from) ? 0 : from->translate.y; + SkScalar from_z = IsOperationIdentity(from) ? 0 : from->translate.z; + SkScalar to_x = IsOperationIdentity(to) ? 0 : to->translate.x; + SkScalar to_y = IsOperationIdentity(to) ? 0 : to->translate.y; + SkScalar to_z = IsOperationIdentity(to) ? 0 : to->translate.z; + result->translate.x = BlendSkScalars(from_x, to_x, progress), + result->translate.y = BlendSkScalars(from_y, to_y, progress), + result->translate.z = BlendSkScalars(from_z, to_z, progress), + result->Bake(); + break; + } + case TransformOperation::TRANSFORM_OPERATION_ROTATE: { + SkScalar axis_x = 0; + SkScalar axis_y = 0; + SkScalar axis_z = 1; + SkScalar from_angle = 0; + SkScalar to_angle = IsOperationIdentity(to) ? 0 : to->rotate.angle; + if (ShareSameAxis(from, to, &axis_x, &axis_y, &axis_z, &from_angle)) { + result->rotate.axis.x = axis_x; + result->rotate.axis.y = axis_y; + result->rotate.axis.z = axis_z; + result->rotate.angle = BlendSkScalars(from_angle, to_angle, progress); + result->Bake(); + } else { + if (!IsOperationIdentity(to)) + result->matrix = to->matrix; + gfx::Transform from_matrix; + if (!IsOperationIdentity(from)) + from_matrix = from->matrix; + if (!result->matrix.Blend(from_matrix, progress)) + return false; + } + break; + } + case TransformOperation::TRANSFORM_OPERATION_SCALE: { + SkScalar from_x = IsOperationIdentity(from) ? 1 : from->scale.x; + SkScalar from_y = IsOperationIdentity(from) ? 1 : from->scale.y; + SkScalar from_z = IsOperationIdentity(from) ? 1 : from->scale.z; + SkScalar to_x = IsOperationIdentity(to) ? 1 : to->scale.x; + SkScalar to_y = IsOperationIdentity(to) ? 1 : to->scale.y; + SkScalar to_z = IsOperationIdentity(to) ? 1 : to->scale.z; + result->scale.x = BlendSkScalars(from_x, to_x, progress); + result->scale.y = BlendSkScalars(from_y, to_y, progress); + result->scale.z = BlendSkScalars(from_z, to_z, progress); + result->Bake(); + break; + } + case TransformOperation::TRANSFORM_OPERATION_SKEWX: + case TransformOperation::TRANSFORM_OPERATION_SKEWY: + case TransformOperation::TRANSFORM_OPERATION_SKEW: { + SkScalar from_x = IsOperationIdentity(from) ? 0 : from->skew.x; + SkScalar from_y = IsOperationIdentity(from) ? 0 : from->skew.y; + SkScalar to_x = IsOperationIdentity(to) ? 0 : to->skew.x; + SkScalar to_y = IsOperationIdentity(to) ? 0 : to->skew.y; + result->skew.x = BlendSkScalars(from_x, to_x, progress); + result->skew.y = BlendSkScalars(from_y, to_y, progress); + result->Bake(); + break; + } + case TransformOperation::TRANSFORM_OPERATION_PERSPECTIVE: { + SkScalar from_perspective_depth = + IsOperationIdentity(from) ? std::numeric_limits::max() + : from->perspective_depth; + SkScalar to_perspective_depth = IsOperationIdentity(to) + ? std::numeric_limits::max() + : to->perspective_depth; + if (from_perspective_depth == 0.f || to_perspective_depth == 0.f) + return false; + + SkScalar blended_perspective_depth = BlendSkScalars( + 1.f / from_perspective_depth, 1.f / to_perspective_depth, progress); + + if (blended_perspective_depth == 0.f) + return false; + + result->perspective_depth = 1.f / blended_perspective_depth; + result->Bake(); + break; + } + case TransformOperation::TRANSFORM_OPERATION_MATRIX: { + if (!IsOperationIdentity(to)) + result->matrix = to->matrix; + gfx::Transform from_matrix; + if (!IsOperationIdentity(from)) + from_matrix = from->matrix; + if (!result->matrix.Blend(from_matrix, progress)) + return false; + break; + } + case TransformOperation::TRANSFORM_OPERATION_IDENTITY: + // Do nothing. + break; + } + + return true; +} + +// If p = (px, py) is a point in the plane being rotated about (0, 0, nz), this +// function computes the angles we would have to rotate from p to get to +// (length(p), 0), (-length(p), 0), (0, length(p)), (0, -length(p)). If nz is +// negative, these angles will need to be reversed. +static void FindCandidatesInPlane(float px, + float py, + float nz, + double* candidates, + int* num_candidates) { + double phi = atan2(px, py); + *num_candidates = 4; + candidates[0] = phi; + for (int i = 1; i < *num_candidates; ++i) + candidates[i] = candidates[i - 1] + base::kPiDouble / 2; + if (nz < 0.f) { + for (int i = 0; i < *num_candidates; ++i) + candidates[i] *= -1.f; + } +} + +static void BoundingBoxForArc(const gfx::Point3F& point, + const TransformOperation* from, + const TransformOperation* to, + SkScalar min_progress, + SkScalar max_progress, + gfx::BoxF* box) { + const TransformOperation* exemplar = from ? from : to; + gfx::Vector3dF axis(exemplar->rotate.axis.x, exemplar->rotate.axis.y, + exemplar->rotate.axis.z); + + const bool x_is_zero = axis.x() == 0.f; + const bool y_is_zero = axis.y() == 0.f; + const bool z_is_zero = axis.z() == 0.f; + + // We will have at most 6 angles to test (excluding from->angle and + // to->angle). + static const int kMaxNumCandidates = 6; + double candidates[kMaxNumCandidates]; + int num_candidates = kMaxNumCandidates; + + if (x_is_zero && y_is_zero && z_is_zero) + return; + + SkScalar from_angle = from ? from->rotate.angle : 0.f; + SkScalar to_angle = to ? to->rotate.angle : 0.f; + + // If the axes of rotation are pointing in opposite directions, we need to + // flip one of the angles. Note, if both |from| and |to| exist, then axis will + // correspond to |from|. + if (from && to) { + gfx::Vector3dF other_axis(to->rotate.axis.x, to->rotate.axis.y, + to->rotate.axis.z); + if (gfx::DotProduct(axis, other_axis) < 0.f) + to_angle *= -1.f; + } + + float min_degrees = + SkScalarToFloat(BlendSkScalars(from_angle, to_angle, min_progress)); + float max_degrees = + SkScalarToFloat(BlendSkScalars(from_angle, to_angle, max_progress)); + if (max_degrees < min_degrees) + std::swap(min_degrees, max_degrees); + + gfx::Transform from_transform; + from_transform.RotateAbout(axis, min_degrees); + gfx::Transform to_transform; + to_transform.RotateAbout(axis, max_degrees); + + *box = gfx::BoxF(); + + gfx::Point3F point_rotated_from = point; + from_transform.TransformPoint(&point_rotated_from); + gfx::Point3F point_rotated_to = point; + to_transform.TransformPoint(&point_rotated_to); + + box->set_origin(point_rotated_from); + box->ExpandTo(point_rotated_to); + + if (x_is_zero && y_is_zero) { + FindCandidatesInPlane(point.x(), point.y(), axis.z(), candidates, + &num_candidates); + } else if (x_is_zero && z_is_zero) { + FindCandidatesInPlane(point.z(), point.x(), axis.y(), candidates, + &num_candidates); + } else if (y_is_zero && z_is_zero) { + FindCandidatesInPlane(point.y(), point.z(), axis.x(), candidates, + &num_candidates); + } else { + gfx::Vector3dF normal = axis; + normal.Scale(1.f / normal.Length()); + + // First, find center of rotation. + gfx::Point3F origin; + gfx::Vector3dF to_point = point - origin; + gfx::Point3F center = + origin + gfx::ScaleVector3d(normal, gfx::DotProduct(to_point, normal)); + + // Now we need to find two vectors in the plane of rotation. One pointing + // towards point and another, perpendicular vector in the plane. + gfx::Vector3dF v1 = point - center; + float v1_length = v1.Length(); + if (v1_length == 0.f) + return; + + v1.Scale(1.f / v1_length); + gfx::Vector3dF v2 = gfx::CrossProduct(normal, v1); + // v1 is the basis vector in the direction of the point. + // i.e. with a rotation of 0, v1 is our +x vector. + // v2 is a perpenticular basis vector of our plane (+y). + + // Take the parametric equation of a circle. + // x = r*cos(t); y = r*sin(t); + // We can treat that as a circle on the plane v1xv2. + // From that we get the parametric equations for a circle on the + // plane in 3d space of: + // x(t) = r*cos(t)*v1.x + r*sin(t)*v2.x + cx + // y(t) = r*cos(t)*v1.y + r*sin(t)*v2.y + cy + // z(t) = r*cos(t)*v1.z + r*sin(t)*v2.z + cz + // Taking the derivative of (x, y, z) and solving for 0 gives us our + // maximum/minimum x, y, z values. + // x'(t) = r*cos(t)*v2.x - r*sin(t)*v1.x = 0 + // tan(t) = v2.x/v1.x + // t = atan2(v2.x, v1.x) + n*pi; + candidates[0] = atan2(v2.x(), v1.x()); + candidates[1] = candidates[0] + base::kPiDouble; + candidates[2] = atan2(v2.y(), v1.y()); + candidates[3] = candidates[2] + base::kPiDouble; + candidates[4] = atan2(v2.z(), v1.z()); + candidates[5] = candidates[4] + base::kPiDouble; + } + + double min_radians = gfx::DegToRad(min_degrees); + double max_radians = gfx::DegToRad(max_degrees); + + for (int i = 0; i < num_candidates; ++i) { + double radians = candidates[i]; + while (radians < min_radians) + radians += 2.0 * base::kPiDouble; + while (radians > max_radians) + radians -= 2.0 * base::kPiDouble; + if (radians < min_radians) + continue; + + gfx::Transform rotation; + rotation.RotateAbout(axis, gfx::RadToDeg(radians)); + gfx::Point3F rotated = point; + rotation.TransformPoint(&rotated); + + box->ExpandTo(rotated); + } +} + +bool TransformOperation::BlendedBoundsForBox(const gfx::BoxF& box, + const TransformOperation* from, + const TransformOperation* to, + SkScalar min_progress, + SkScalar max_progress, + gfx::BoxF* bounds) { + bool is_identity_from = IsOperationIdentity(from); + bool is_identity_to = IsOperationIdentity(to); + if (is_identity_from && is_identity_to) { + *bounds = box; + return true; + } + + TransformOperation::Type interpolation_type = + TransformOperation::TRANSFORM_OPERATION_IDENTITY; + if (is_identity_to) + interpolation_type = from->type; + else + interpolation_type = to->type; + + switch (interpolation_type) { + case TransformOperation::TRANSFORM_OPERATION_IDENTITY: + *bounds = box; + return true; + case TransformOperation::TRANSFORM_OPERATION_TRANSLATE: + case TransformOperation::TRANSFORM_OPERATION_SKEWX: + case TransformOperation::TRANSFORM_OPERATION_SKEWY: + case TransformOperation::TRANSFORM_OPERATION_SKEW: + case TransformOperation::TRANSFORM_OPERATION_PERSPECTIVE: + case TransformOperation::TRANSFORM_OPERATION_SCALE: { + TransformOperation from_operation; + TransformOperation to_operation; + if (!BlendTransformOperations(from, to, min_progress, &from_operation) || + !BlendTransformOperations(from, to, max_progress, &to_operation)) + return false; + + *bounds = box; + from_operation.matrix.TransformBox(bounds); + + gfx::BoxF to_box = box; + to_operation.matrix.TransformBox(&to_box); + bounds->ExpandTo(to_box); + + return true; + } + case TransformOperation::TRANSFORM_OPERATION_ROTATE: { + SkScalar axis_x = 0; + SkScalar axis_y = 0; + SkScalar axis_z = 1; + SkScalar from_angle = 0; + if (!ShareSameAxis(from, to, &axis_x, &axis_y, &axis_z, &from_angle)) + return false; + + bool first_point = true; + for (int i = 0; i < 8; ++i) { + gfx::Point3F corner = box.origin(); + corner += gfx::Vector3dF(i & 1 ? box.width() : 0.f, + i & 2 ? box.height() : 0.f, + i & 4 ? box.depth() : 0.f); + gfx::BoxF box_for_arc; + BoundingBoxForArc(corner, from, to, min_progress, max_progress, + &box_for_arc); + if (first_point) + *bounds = box_for_arc; + else + bounds->Union(box_for_arc); + first_point = false; + } + return true; + } + case TransformOperation::TRANSFORM_OPERATION_MATRIX: + return false; + } + NOTREACHED(); + return false; +} + +} // namespace gfx diff --git a/geometry/transform_operation.h b/geometry/transform_operation.h new file mode 100644 index 000000000000..8026bdff91a7 --- /dev/null +++ b/geometry/transform_operation.h @@ -0,0 +1,77 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_TRANSFORM_OPERATION_H_ +#define UI_GFX_GEOMETRY_TRANSFORM_OPERATION_H_ + +#include "ui/gfx/geometry/geometry_skia_export.h" +#include "ui/gfx/geometry/transform.h" + +namespace gfx { +class BoxF; + +struct GEOMETRY_SKIA_EXPORT TransformOperation { + enum Type { + TRANSFORM_OPERATION_TRANSLATE, + TRANSFORM_OPERATION_ROTATE, + TRANSFORM_OPERATION_SCALE, + TRANSFORM_OPERATION_SKEWX, + TRANSFORM_OPERATION_SKEWY, + TRANSFORM_OPERATION_SKEW, + TRANSFORM_OPERATION_PERSPECTIVE, + TRANSFORM_OPERATION_MATRIX, + TRANSFORM_OPERATION_IDENTITY + }; + + Type type = TRANSFORM_OPERATION_IDENTITY; + gfx::Transform matrix; + + union { + SkScalar perspective_depth; + + struct { + SkScalar x, y; + } skew; + + struct { + SkScalar x, y, z; + } scale; + + struct { + SkScalar x, y, z; + } translate; + + struct { + struct { + SkScalar x, y, z; + } axis; + + SkScalar angle; + } rotate; + }; + + bool IsIdentity() const; + + // Sets |matrix| based on type and the union values. + void Bake(); + + bool ApproximatelyEqual(const TransformOperation& other, + SkScalar tolerance) const; + + static bool BlendTransformOperations(const TransformOperation* from, + const TransformOperation* to, + SkScalar progress, + TransformOperation* result); + + static bool BlendedBoundsForBox(const gfx::BoxF& box, + const TransformOperation* from, + const TransformOperation* to, + SkScalar min_progress, + SkScalar max_progress, + gfx::BoxF* bounds); +}; + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_TRANSFORM_OPERATION_H_ diff --git a/geometry/transform_operations.cc b/geometry/transform_operations.cc new file mode 100644 index 000000000000..5d93258e07ae --- /dev/null +++ b/geometry/transform_operations.cc @@ -0,0 +1,384 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/transform_operations.h" + +#include + +#include +#include + +#include "ui/gfx/geometry/angle_conversions.h" +#include "ui/gfx/geometry/box_f.h" +#include "ui/gfx/geometry/transform_util.h" +#include "ui/gfx/geometry/vector3d_f.h" + +namespace gfx { + +TransformOperations::TransformOperations() = default; + +TransformOperations::TransformOperations(const TransformOperations& other) { + operations_ = other.operations_; +} + +TransformOperations::~TransformOperations() = default; + +TransformOperations& TransformOperations::operator=( + const TransformOperations& other) { + operations_ = other.operations_; + return *this; +} + +Transform TransformOperations::Apply() const { + return ApplyRemaining(0); +} + +Transform TransformOperations::ApplyRemaining(size_t start) const { + Transform to_return; + for (size_t i = start; i < operations_.size(); i++) { + to_return.PreconcatTransform(operations_[i].matrix); + } + return to_return; +} + +// TODO(crbug.com/914397): Consolidate blink and cc implementations of transform +// interpolation. +TransformOperations TransformOperations::Blend(const TransformOperations& from, + SkScalar progress) const { + TransformOperations to_return; + if (!BlendInternal(from, progress, &to_return)) { + // If the matrices cannot be blended, fallback to discrete animation logic. + // See https://drafts.csswg.org/css-transforms/#matrix-interpolation + to_return = progress < 0.5 ? from : *this; + } + return to_return; +} + +bool TransformOperations::BlendedBoundsForBox(const BoxF& box, + const TransformOperations& from, + SkScalar min_progress, + SkScalar max_progress, + BoxF* bounds) const { + *bounds = box; + + bool from_identity = from.IsIdentity(); + bool to_identity = IsIdentity(); + if (from_identity && to_identity) + return true; + + if (!MatchesTypes(from)) + return false; + + size_t num_operations = std::max(from_identity ? 0 : from.operations_.size(), + to_identity ? 0 : operations_.size()); + + // Because we are squashing all of the matrices together when applying + // them to the animation, we must apply them in reverse order when + // not squashing them. + for (size_t i = 0; i < num_operations; ++i) { + size_t operation_index = num_operations - 1 - i; + BoxF bounds_for_operation; + const TransformOperation* from_op = + from_identity ? nullptr : &from.operations_[operation_index]; + const TransformOperation* to_op = + to_identity ? nullptr : &operations_[operation_index]; + if (!TransformOperation::BlendedBoundsForBox(*bounds, from_op, to_op, + min_progress, max_progress, + &bounds_for_operation)) { + return false; + } + *bounds = bounds_for_operation; + } + + return true; +} + +bool TransformOperations::PreservesAxisAlignment() const { + for (auto& operation : operations_) { + switch (operation.type) { + case TransformOperation::TRANSFORM_OPERATION_IDENTITY: + case TransformOperation::TRANSFORM_OPERATION_TRANSLATE: + case TransformOperation::TRANSFORM_OPERATION_SCALE: + continue; + case TransformOperation::TRANSFORM_OPERATION_MATRIX: + if (!operation.matrix.IsIdentity() && + !operation.matrix.IsScaleOrTranslation()) + return false; + continue; + case TransformOperation::TRANSFORM_OPERATION_ROTATE: + case TransformOperation::TRANSFORM_OPERATION_SKEWX: + case TransformOperation::TRANSFORM_OPERATION_SKEWY: + case TransformOperation::TRANSFORM_OPERATION_SKEW: + case TransformOperation::TRANSFORM_OPERATION_PERSPECTIVE: + return false; + } + } + return true; +} + +bool TransformOperations::IsTranslation() const { + for (auto& operation : operations_) { + switch (operation.type) { + case TransformOperation::TRANSFORM_OPERATION_IDENTITY: + case TransformOperation::TRANSFORM_OPERATION_TRANSLATE: + continue; + case TransformOperation::TRANSFORM_OPERATION_MATRIX: + if (!operation.matrix.IsIdentityOrTranslation()) + return false; + continue; + case TransformOperation::TRANSFORM_OPERATION_ROTATE: + case TransformOperation::TRANSFORM_OPERATION_SCALE: + case TransformOperation::TRANSFORM_OPERATION_SKEWX: + case TransformOperation::TRANSFORM_OPERATION_SKEWY: + case TransformOperation::TRANSFORM_OPERATION_SKEW: + case TransformOperation::TRANSFORM_OPERATION_PERSPECTIVE: + return false; + } + } + return true; +} + +static SkScalar TanDegrees(double degrees) { + return SkDoubleToScalar(std::tan(DegToRad(degrees))); +} + +bool TransformOperations::ScaleComponent(SkScalar* scale) const { + SkScalar operations_scale = 1.f; + for (auto& operation : operations_) { + switch (operation.type) { + case TransformOperation::TRANSFORM_OPERATION_IDENTITY: + case TransformOperation::TRANSFORM_OPERATION_TRANSLATE: + case TransformOperation::TRANSFORM_OPERATION_ROTATE: + continue; + case TransformOperation::TRANSFORM_OPERATION_MATRIX: { + if (operation.matrix.HasPerspective()) + return false; + Vector2dF scale_components = + ComputeTransform2dScaleComponents(operation.matrix, 1.f); + operations_scale *= + std::max(scale_components.x(), scale_components.y()); + break; + } + case TransformOperation::TRANSFORM_OPERATION_SKEWX: + case TransformOperation::TRANSFORM_OPERATION_SKEWY: + case TransformOperation::TRANSFORM_OPERATION_SKEW: { + SkScalar x_component = TanDegrees(operation.skew.x); + SkScalar y_component = TanDegrees(operation.skew.y); + SkScalar x_scale = std::sqrt(x_component * x_component + 1); + SkScalar y_scale = std::sqrt(y_component * y_component + 1); + operations_scale *= std::max(x_scale, y_scale); + break; + } + case TransformOperation::TRANSFORM_OPERATION_PERSPECTIVE: + return false; + case TransformOperation::TRANSFORM_OPERATION_SCALE: + operations_scale *= std::max( + std::abs(operation.scale.x), + std::max(std::abs(operation.scale.y), std::abs(operation.scale.z))); + } + } + *scale = operations_scale; + return true; +} + +bool TransformOperations::MatchesTypes(const TransformOperations& other) const { + if (operations_.size() == 0 || other.operations_.size() == 0) + return true; + + if (operations_.size() != other.operations_.size()) + return false; + + for (size_t i = 0; i < operations_.size(); ++i) { + if (operations_[i].type != other.operations_[i].type) + return false; + } + + return true; +} + +size_t TransformOperations::MatchingPrefixLength( + const TransformOperations& other) const { + size_t num_operations = + std::min(operations_.size(), other.operations_.size()); + for (size_t i = 0; i < num_operations; ++i) { + if (operations_[i].type != other.operations_[i].type) { + // Remaining operations in each operations list require matrix/matrix3d + // interpolation. + return i; + } + } + // If the operations match to the length of the shorter list, then pad its + // length with the matching identity operations. + // https://drafts.csswg.org/css-transforms/#transform-function-lists + return std::max(operations_.size(), other.operations_.size()); +} + +bool TransformOperations::CanBlendWith(const TransformOperations& other) const { + TransformOperations dummy; + return BlendInternal(other, 0.5, &dummy); +} + +void TransformOperations::AppendTranslate(SkScalar x, SkScalar y, SkScalar z) { + TransformOperation to_add; + to_add.matrix.Translate3d(x, y, z); + to_add.type = TransformOperation::TRANSFORM_OPERATION_TRANSLATE; + to_add.translate.x = x; + to_add.translate.y = y; + to_add.translate.z = z; + operations_.push_back(to_add); + decomposed_transforms_.clear(); +} + +void TransformOperations::AppendRotate(SkScalar x, + SkScalar y, + SkScalar z, + SkScalar degrees) { + TransformOperation to_add; + to_add.type = TransformOperation::TRANSFORM_OPERATION_ROTATE; + to_add.rotate.axis.x = x; + to_add.rotate.axis.y = y; + to_add.rotate.axis.z = z; + to_add.rotate.angle = degrees; + to_add.Bake(); + operations_.push_back(to_add); + decomposed_transforms_.clear(); +} + +void TransformOperations::AppendScale(SkScalar x, SkScalar y, SkScalar z) { + TransformOperation to_add; + to_add.type = TransformOperation::TRANSFORM_OPERATION_SCALE; + to_add.scale.x = x; + to_add.scale.y = y; + to_add.scale.z = z; + to_add.Bake(); + operations_.push_back(to_add); + decomposed_transforms_.clear(); +} + +void TransformOperations::AppendSkewX(SkScalar x) { + TransformOperation to_add; + to_add.type = TransformOperation::TRANSFORM_OPERATION_SKEWX; + to_add.skew.x = x; + to_add.skew.y = 0; + to_add.Bake(); + operations_.push_back(to_add); + decomposed_transforms_.clear(); +} + +void TransformOperations::AppendSkewY(SkScalar y) { + TransformOperation to_add; + to_add.type = TransformOperation::TRANSFORM_OPERATION_SKEWY; + to_add.skew.x = 0; + to_add.skew.y = y; + to_add.Bake(); + operations_.push_back(to_add); + decomposed_transforms_.clear(); +} + +void TransformOperations::AppendSkew(SkScalar x, SkScalar y) { + TransformOperation to_add; + to_add.type = TransformOperation::TRANSFORM_OPERATION_SKEW; + to_add.skew.x = x; + to_add.skew.y = y; + to_add.Bake(); + operations_.push_back(to_add); + decomposed_transforms_.clear(); +} + +void TransformOperations::AppendPerspective(SkScalar depth) { + TransformOperation to_add; + to_add.type = TransformOperation::TRANSFORM_OPERATION_PERSPECTIVE; + to_add.perspective_depth = depth; + to_add.Bake(); + operations_.push_back(to_add); + decomposed_transforms_.clear(); +} + +void TransformOperations::AppendMatrix(const Transform& matrix) { + TransformOperation to_add; + to_add.matrix = matrix; + to_add.type = TransformOperation::TRANSFORM_OPERATION_MATRIX; + operations_.push_back(to_add); + decomposed_transforms_.clear(); +} + +void TransformOperations::AppendIdentity() { + operations_.emplace_back(); +} + +void TransformOperations::Append(const TransformOperation& operation) { + operations_.push_back(operation); + decomposed_transforms_.clear(); +} + +bool TransformOperations::IsIdentity() const { + for (auto& operation : operations_) { + if (!operation.IsIdentity()) + return false; + } + return true; +} + +bool TransformOperations::ApproximatelyEqual(const TransformOperations& other, + SkScalar tolerance) const { + if (size() != other.size()) + return false; + for (size_t i = 0; i < operations_.size(); ++i) { + if (!operations_[i].ApproximatelyEqual(other.operations_[i], tolerance)) + return false; + } + return true; +} + +bool TransformOperations::BlendInternal(const TransformOperations& from, + SkScalar progress, + TransformOperations* result) const { + bool from_identity = from.IsIdentity(); + bool to_identity = IsIdentity(); + if (from_identity && to_identity) + return true; + + size_t matching_prefix_length = MatchingPrefixLength(from); + size_t from_size = from_identity ? 0 : from.operations_.size(); + size_t to_size = to_identity ? 0 : operations_.size(); + size_t num_operations = std::max(from_size, to_size); + + for (size_t i = 0; i < matching_prefix_length; ++i) { + TransformOperation blended; + if (!TransformOperation::BlendTransformOperations( + i >= from_size ? nullptr : &from.operations_[i], + i >= to_size ? nullptr : &operations_[i], progress, &blended)) { + return false; + } + result->Append(blended); + } + + if (matching_prefix_length < num_operations) { + if (!ComputeDecomposedTransform(matching_prefix_length) || + !from.ComputeDecomposedTransform(matching_prefix_length)) { + return false; + } + DecomposedTransform matrix_transform = BlendDecomposedTransforms( + *decomposed_transforms_[matching_prefix_length].get(), + *from.decomposed_transforms_[matching_prefix_length].get(), progress); + result->AppendMatrix(ComposeTransform(matrix_transform)); + } + return true; +} + +bool TransformOperations::ComputeDecomposedTransform( + size_t start_offset) const { + auto it = decomposed_transforms_.find(start_offset); + if (it == decomposed_transforms_.end()) { + std::unique_ptr decomposed_transform = + std::make_unique(); + Transform transform = ApplyRemaining(start_offset); + if (!DecomposeTransform(decomposed_transform.get(), transform)) + return false; + decomposed_transforms_[start_offset] = std::move(decomposed_transform); + } + return true; +} + +} // namespace gfx diff --git a/geometry/transform_operations.h b/geometry/transform_operations.h new file mode 100644 index 000000000000..f1c9a891166e --- /dev/null +++ b/geometry/transform_operations.h @@ -0,0 +1,140 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_TRANSFORM_OPERATIONS_H_ +#define UI_GFX_GEOMETRY_TRANSFORM_OPERATIONS_H_ + +#include +#include +#include + +#include "base/check_op.h" +#include "base/gtest_prod_util.h" +#include "ui/gfx/geometry/geometry_skia_export.h" +#include "ui/gfx/geometry/transform.h" +#include "ui/gfx/geometry/transform_operation.h" + +namespace gfx { + +class BoxF; +struct DecomposedTransform; + +// Transform operations are a decomposed transformation matrix. It can be +// applied to obtain a Transform at any time, and can be blended +// intelligently with other transform operations, so long as they represent the +// same decomposition. For example, if we have a transform that is made up of +// a rotation followed by skew, it can be blended intelligently with another +// transform made up of a rotation followed by a skew. Blending is possible if +// we have two dissimilar sets of transform operations, but the effect may not +// be what was intended. For more information, see the comments for the blend +// function below. +class GEOMETRY_SKIA_EXPORT TransformOperations { + public: + TransformOperations(); + TransformOperations(const TransformOperations& other); + ~TransformOperations(); + + TransformOperations& operator=(const TransformOperations& other); + + // Returns a transformation matrix representing these transform operations. + Transform Apply() const; + + // Returns a transformation matrix representing the set of transform + // operations from index |start| to the end of the list. + Transform ApplyRemaining(size_t start) const; + + // Given another set of transform operations and a progress in the range + // [0, 1], returns a transformation matrix representing the intermediate + // value. If this->MatchesTypes(from), then each of the operations are + // blended separately and then combined. Otherwise, the two sets of + // transforms are baked to matrices (using apply), and the matrices are + // then decomposed and interpolated. For more information, see + // http://www.w3.org/TR/2011/WD-css3-2d-transforms-20111215/#matrix-decomposition. + // + // If either of the matrices are non-decomposable for the blend, Blend applies + // discrete interpolation between them based on the progress value. + TransformOperations Blend(const TransformOperations& from, + SkScalar progress) const; + + // Sets |bounds| be the bounding box for the region within which |box| will + // exist when it is transformed by the result of calling Blend on |from| and + // with progress in the range [min_progress, max_progress]. If this region + // cannot be computed, returns false. + bool BlendedBoundsForBox(const BoxF& box, + const TransformOperations& from, + SkScalar min_progress, + SkScalar max_progress, + BoxF* bounds) const; + + // Returns true if these operations are only translations. + bool IsTranslation() const; + + // Returns false if the operations affect 2d axis alignment. + bool PreservesAxisAlignment() const; + + // Returns true if this operation and its descendants have the same types + // as other and its descendants. + bool MatchesTypes(const TransformOperations& other) const; + + // Returns the number of matching transform operations at the start of the + // transform lists. If one list is shorter but pairwise compatible, it will be + // extended with matching identity operators per spec + // (https://drafts.csswg.org/css-transforms/#interpolation-of-transforms). + size_t MatchingPrefixLength(const TransformOperations& other) const; + + // Returns true if these operations can be blended. It will only return + // false if we must resort to matrix interpolation, and matrix interpolation + // fails (this can happen if either matrix cannot be decomposed). + bool CanBlendWith(const TransformOperations& other) const; + + // If none of these operations have a perspective component, sets |scale| to + // be the product of the scale component of every operation. Otherwise, + // returns false. + bool ScaleComponent(SkScalar* scale) const; + + void AppendTranslate(SkScalar x, SkScalar y, SkScalar z); + void AppendRotate(SkScalar x, SkScalar y, SkScalar z, SkScalar degrees); + void AppendScale(SkScalar x, SkScalar y, SkScalar z); + void AppendSkewX(SkScalar x); + void AppendSkewY(SkScalar y); + void AppendSkew(SkScalar x, SkScalar y); + void AppendPerspective(SkScalar depth); + void AppendMatrix(const Transform& matrix); + void AppendIdentity(); + void Append(const TransformOperation& operation); + bool IsIdentity() const; + + size_t size() const { return operations_.size(); } + + const TransformOperation& at(size_t index) const { + DCHECK_LT(index, size()); + return operations_[index]; + } + TransformOperation& at(size_t index) { + DCHECK_LT(index, size()); + return operations_[index]; + } + + bool ApproximatelyEqual(const TransformOperations& other, + SkScalar tolerance) const; + + private: + FRIEND_TEST_ALL_PREFIXES(TransformOperationsTest, TestDecompositionCache); + + bool BlendInternal(const TransformOperations& from, + SkScalar progress, + TransformOperations* result) const; + + std::vector operations_; + + bool ComputeDecomposedTransform(size_t start_offset) const; + + // For efficiency, we cache the decomposed transforms. + mutable std::unordered_map> + decomposed_transforms_; +}; + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_TRANSFORM_OPERATIONS_H_ diff --git a/geometry/transform_operations_unittest.cc b/geometry/transform_operations_unittest.cc new file mode 100644 index 000000000000..75f9144a6485 --- /dev/null +++ b/geometry/transform_operations_unittest.cc @@ -0,0 +1,1813 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/transform_operations.h" + +#include + +#include +#include +#include + +#include "base/cxx17_backports.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/animation/tween.h" +#include "ui/gfx/geometry/box_f.h" +#include "ui/gfx/geometry/rect_conversions.h" +#include "ui/gfx/geometry/test/transform_test_util.h" +#include "ui/gfx/geometry/vector3d_f.h" + +namespace gfx { +namespace { + +void ExpectTransformOperationEqual(const TransformOperation& lhs, + const TransformOperation& rhs) { + EXPECT_EQ(lhs.type, rhs.type); + ExpectTransformationMatrixEq(lhs.matrix, rhs.matrix); + switch (lhs.type) { + case TransformOperation::TRANSFORM_OPERATION_TRANSLATE: + EXPECT_FLOAT_EQ(lhs.translate.x, rhs.translate.x); + EXPECT_FLOAT_EQ(lhs.translate.y, rhs.translate.y); + EXPECT_FLOAT_EQ(lhs.translate.z, rhs.translate.z); + break; + case TransformOperation::TRANSFORM_OPERATION_ROTATE: + EXPECT_FLOAT_EQ(lhs.rotate.axis.x, rhs.rotate.axis.x); + EXPECT_FLOAT_EQ(lhs.rotate.axis.y, rhs.rotate.axis.y); + EXPECT_FLOAT_EQ(lhs.rotate.axis.z, rhs.rotate.axis.z); + EXPECT_FLOAT_EQ(lhs.rotate.angle, rhs.rotate.angle); + break; + case TransformOperation::TRANSFORM_OPERATION_SCALE: + EXPECT_FLOAT_EQ(lhs.scale.x, rhs.scale.x); + EXPECT_FLOAT_EQ(lhs.scale.y, rhs.scale.y); + EXPECT_FLOAT_EQ(lhs.scale.z, rhs.scale.z); + break; + case TransformOperation::TRANSFORM_OPERATION_SKEWX: + case TransformOperation::TRANSFORM_OPERATION_SKEWY: + case TransformOperation::TRANSFORM_OPERATION_SKEW: + EXPECT_FLOAT_EQ(lhs.skew.x, rhs.skew.x); + EXPECT_FLOAT_EQ(lhs.skew.y, rhs.skew.y); + break; + case TransformOperation::TRANSFORM_OPERATION_PERSPECTIVE: + EXPECT_FLOAT_EQ(lhs.perspective_depth, rhs.perspective_depth); + break; + case TransformOperation::TRANSFORM_OPERATION_MATRIX: + case TransformOperation::TRANSFORM_OPERATION_IDENTITY: + break; + } +} + +TEST(TransformOperationTest, TransformTypesAreUnique) { + std::vector> transforms; + + std::unique_ptr to_add( + std::make_unique()); + to_add->AppendTranslate(1, 0, 0); + transforms.push_back(std::move(to_add)); + + to_add = std::make_unique(); + to_add->AppendRotate(0, 0, 1, 2); + transforms.push_back(std::move(to_add)); + + to_add = std::make_unique(); + to_add->AppendScale(2, 2, 2); + transforms.push_back(std::move(to_add)); + + to_add = std::make_unique(); + to_add->AppendSkew(1, 0); + transforms.push_back(std::move(to_add)); + + to_add = std::make_unique(); + to_add->AppendPerspective(800); + transforms.push_back(std::move(to_add)); + + for (size_t i = 0; i < transforms.size(); ++i) { + for (size_t j = 0; j < transforms.size(); ++j) { + bool matches_type = transforms[i]->MatchesTypes(*transforms[j]); + EXPECT_TRUE((i == j && matches_type) || !matches_type); + } + } +} + +TEST(TransformOperationTest, MatchingPrefixSameLength) { + TransformOperations translates; + translates.AppendTranslate(1, 0, 0); + translates.AppendTranslate(1, 0, 0); + translates.AppendTranslate(1, 0, 0); + + TransformOperations skews; + skews.AppendSkew(0, 2); + skews.AppendSkew(0, 2); + skews.AppendSkew(0, 2); + + TransformOperations translates2; + translates2.AppendTranslate(0, 2, 0); + translates2.AppendTranslate(0, 2, 0); + translates2.AppendTranslate(0, 2, 0); + + TransformOperations mixed; + mixed.AppendTranslate(0, 2, 0); + mixed.AppendScale(2, 1, 1); + mixed.AppendSkew(0, 2); + + TransformOperations translates3 = translates2; + + EXPECT_EQ(0UL, translates.MatchingPrefixLength(skews)); + EXPECT_EQ(3UL, translates.MatchingPrefixLength(translates2)); + EXPECT_EQ(3UL, translates.MatchingPrefixLength(translates3)); + EXPECT_EQ(1UL, translates.MatchingPrefixLength(mixed)); +} + +TEST(TransformOperationTest, MatchingPrefixDifferentLength) { + TransformOperations translates; + translates.AppendTranslate(1, 0, 0); + translates.AppendTranslate(1, 0, 0); + translates.AppendTranslate(1, 0, 0); + + TransformOperations skews; + skews.AppendSkew(2, 0); + skews.AppendSkew(2, 0); + + TransformOperations translates2; + translates2.AppendTranslate(0, 2, 0); + translates2.AppendTranslate(0, 2, 0); + + TransformOperations none; + + EXPECT_EQ(0UL, translates.MatchingPrefixLength(skews)); + // Pad the length of the shorter list provided all previous operation- + // pairs match per spec + // (https://drafts.csswg.org/css-transforms/#interpolation-of-transforms). + EXPECT_EQ(3UL, translates.MatchingPrefixLength(translates2)); + EXPECT_EQ(3UL, translates.MatchingPrefixLength(none)); +} + +std::vector> GetIdentityOperations() { + std::vector> operations; + std::unique_ptr to_add( + std::make_unique()); + operations.push_back(std::move(to_add)); + + to_add = std::make_unique(); + to_add->AppendTranslate(0, 0, 0); + operations.push_back(std::move(to_add)); + + to_add = std::make_unique(); + to_add->AppendTranslate(0, 0, 0); + to_add->AppendTranslate(0, 0, 0); + operations.push_back(std::move(to_add)); + + to_add = std::make_unique(); + to_add->AppendScale(1, 1, 1); + operations.push_back(std::move(to_add)); + + to_add = std::make_unique(); + to_add->AppendScale(1, 1, 1); + to_add->AppendScale(1, 1, 1); + operations.push_back(std::move(to_add)); + + to_add = std::make_unique(); + to_add->AppendSkew(0, 0); + operations.push_back(std::move(to_add)); + + to_add = std::make_unique(); + to_add->AppendSkew(0, 0); + to_add->AppendSkew(0, 0); + operations.push_back(std::move(to_add)); + + to_add = std::make_unique(); + to_add->AppendRotate(0, 0, 1, 0); + operations.push_back(std::move(to_add)); + + to_add = std::make_unique(); + to_add->AppendRotate(0, 0, 1, 0); + to_add->AppendRotate(0, 0, 1, 0); + operations.push_back(std::move(to_add)); + + to_add = std::make_unique(); + to_add->AppendMatrix(gfx::Transform()); + operations.push_back(std::move(to_add)); + + to_add = std::make_unique(); + to_add->AppendMatrix(gfx::Transform()); + to_add->AppendMatrix(gfx::Transform()); + operations.push_back(std::move(to_add)); + + return operations; +} + +TEST(TransformOperationTest, MatchingPrefixLengthOrder) { + TransformOperations mix_order_identity; + mix_order_identity.AppendTranslate(0, 0, 0); + mix_order_identity.AppendScale(1, 1, 1); + mix_order_identity.AppendTranslate(0, 0, 0); + + TransformOperations mix_order_one; + mix_order_one.AppendTranslate(0, 1, 0); + mix_order_one.AppendScale(2, 1, 3); + mix_order_one.AppendTranslate(1, 0, 0); + + TransformOperations mix_order_two; + mix_order_two.AppendTranslate(0, 1, 0); + mix_order_two.AppendTranslate(1, 0, 0); + mix_order_two.AppendScale(2, 1, 3); + + EXPECT_EQ(3UL, mix_order_identity.MatchingPrefixLength(mix_order_one)); + EXPECT_EQ(1UL, mix_order_identity.MatchingPrefixLength(mix_order_two)); + EXPECT_EQ(1UL, mix_order_one.MatchingPrefixLength(mix_order_two)); +} + +TEST(TransformOperationTest, NoneAlwaysMatches) { + std::vector> operations = + GetIdentityOperations(); + + TransformOperations none_operation; + for (const auto& operation : operations) { + EXPECT_EQ(operation->size(), + operation->MatchingPrefixLength(none_operation)); + } +} + +TEST(TransformOperationTest, ApplyTranslate) { + SkScalar x = 1; + SkScalar y = 2; + SkScalar z = 3; + TransformOperations operations; + operations.AppendTranslate(x, y, z); + gfx::Transform expected; + expected.Translate3d(x, y, z); + ExpectTransformationMatrixEq(expected, operations.Apply()); +} + +TEST(TransformOperationTest, ApplyRotate) { + SkScalar x = 1; + SkScalar y = 2; + SkScalar z = 3; + SkScalar degrees = 80; + TransformOperations operations; + operations.AppendRotate(x, y, z, degrees); + gfx::Transform expected; + expected.RotateAbout(gfx::Vector3dF(x, y, z), degrees); + ExpectTransformationMatrixEq(expected, operations.Apply()); +} + +TEST(TransformOperationTest, ApplyScale) { + SkScalar x = 1; + SkScalar y = 2; + SkScalar z = 3; + TransformOperations operations; + operations.AppendScale(x, y, z); + gfx::Transform expected; + expected.Scale3d(x, y, z); + ExpectTransformationMatrixEq(expected, operations.Apply()); +} + +TEST(TransformOperationTest, ApplySkew) { + SkScalar x = 1; + SkScalar y = 2; + TransformOperations operations; + operations.AppendSkew(x, y); + gfx::Transform expected; + expected.Skew(x, y); + ExpectTransformationMatrixEq(expected, operations.Apply()); +} + +TEST(TransformOperationTest, ApplyPerspective) { + SkScalar depth = 800; + TransformOperations operations; + operations.AppendPerspective(depth); + gfx::Transform expected; + expected.ApplyPerspectiveDepth(depth); + ExpectTransformationMatrixEq(expected, operations.Apply()); +} + +TEST(TransformOperationTest, ApplyMatrix) { + SkScalar dx = 1; + SkScalar dy = 2; + SkScalar dz = 3; + gfx::Transform expected_matrix; + expected_matrix.Translate3d(dx, dy, dz); + TransformOperations matrix_transform; + matrix_transform.AppendMatrix(expected_matrix); + ExpectTransformationMatrixEq(expected_matrix, matrix_transform.Apply()); +} + +TEST(TransformOperationTest, ApplyOrder) { + SkScalar sx = 2; + SkScalar sy = 4; + SkScalar sz = 8; + + SkScalar dx = 1; + SkScalar dy = 2; + SkScalar dz = 3; + + TransformOperations operations; + operations.AppendScale(sx, sy, sz); + operations.AppendTranslate(dx, dy, dz); + + gfx::Transform expected_scale_matrix; + expected_scale_matrix.Scale3d(sx, sy, sz); + + gfx::Transform expected_translate_matrix; + expected_translate_matrix.Translate3d(dx, dy, dz); + + gfx::Transform expected_combined_matrix = expected_scale_matrix; + expected_combined_matrix.PreconcatTransform(expected_translate_matrix); + + ExpectTransformationMatrixEq(expected_combined_matrix, operations.Apply()); +} + +TEST(TransformOperationTest, BlendOrder) { + SkScalar sx1 = 2; + SkScalar sy1 = 4; + SkScalar sz1 = 8; + + SkScalar dx1 = 1; + SkScalar dy1 = 2; + SkScalar dz1 = 3; + + SkScalar sx2 = 4; + SkScalar sy2 = 8; + SkScalar sz2 = 16; + + SkScalar dx2 = 10; + SkScalar dy2 = 20; + SkScalar dz2 = 30; + + SkScalar sx3 = 2; + SkScalar sy3 = 1; + SkScalar sz3 = 1; + + TransformOperations operations_from; + operations_from.AppendScale(sx1, sy1, sz1); + operations_from.AppendTranslate(dx1, dy1, dz1); + + TransformOperations operations_to; + operations_to.AppendScale(sx2, sy2, sz2); + operations_to.AppendTranslate(dx2, dy2, dz2); + + gfx::Transform scale_from; + scale_from.Scale3d(sx1, sy1, sz1); + gfx::Transform translate_from; + translate_from.Translate3d(dx1, dy1, dz1); + + gfx::Transform scale_to; + scale_to.Scale3d(sx2, sy2, sz2); + gfx::Transform translate_to; + translate_to.Translate3d(dx2, dy2, dz2); + + SkScalar progress = 0.25f; + + TransformOperations operations_expected; + operations_expected.AppendScale( + gfx::Tween::FloatValueBetween(progress, sx1, sx2), + gfx::Tween::FloatValueBetween(progress, sy1, sy2), + gfx::Tween::FloatValueBetween(progress, sz1, sz2)); + + operations_expected.AppendTranslate( + gfx::Tween::FloatValueBetween(progress, dx1, dx2), + gfx::Tween::FloatValueBetween(progress, dy1, dy2), + gfx::Tween::FloatValueBetween(progress, dz1, dz2)); + + gfx::Transform blended_scale = scale_to; + blended_scale.Blend(scale_from, progress); + + gfx::Transform blended_translate = translate_to; + blended_translate.Blend(translate_from, progress); + + gfx::Transform expected = blended_scale; + expected.PreconcatTransform(blended_translate); + + TransformOperations blended = operations_to.Blend(operations_from, progress); + + ExpectTransformationMatrixEq(expected, blended.Apply()); + ExpectTransformationMatrixEq(operations_expected.Apply(), blended.Apply()); + EXPECT_EQ(operations_expected.size(), blended.size()); + for (size_t i = 0; i < operations_expected.size(); ++i) { + TransformOperation expected_op = operations_expected.at(i); + TransformOperation blended_op = blended.at(i); + SCOPED_TRACE(i); + ExpectTransformOperationEqual(expected_op, blended_op); + } + + TransformOperations base_operations_expected = operations_expected; + + // Create a mismatch in number of operations. Pairwise interpolation is still + // used when the operations match up to the length of the shorter list. + operations_to.AppendScale(sx3, sy3, sz3); + + gfx::Transform appended_scale; + appended_scale.Scale3d(sx3, sy3, sz3); + + gfx::Transform blended_append_scale = appended_scale; + blended_append_scale.Blend(gfx::Transform(), progress); + expected.PreconcatTransform(blended_append_scale); + + operations_expected.AppendScale( + gfx::Tween::FloatValueBetween(progress, 1, sx3), + gfx::Tween::FloatValueBetween(progress, 1, sy3), + gfx::Tween::FloatValueBetween(progress, 1, sz3)); + + blended = operations_to.Blend(operations_from, progress); + + ExpectTransformationMatrixEq(expected, blended.Apply()); + ExpectTransformationMatrixEq(operations_expected.Apply(), blended.Apply()); + EXPECT_EQ(operations_expected.size(), blended.size()); + for (size_t i = 0; i < operations_expected.size(); ++i) { + TransformOperation expected_op = operations_expected.at(i); + TransformOperation blended_op = blended.at(i); + SCOPED_TRACE(i); + ExpectTransformOperationEqual(expected_op, blended_op); + } + + // Create a mismatch, forcing matrix interpolation for the last operator pair. + operations_from.AppendRotate(0, 0, 1, 90); + + blended = operations_to.Blend(operations_from, progress); + + gfx::Transform transform_from; + transform_from.RotateAboutZAxis(90); + gfx::Transform transform_to; + transform_to.Scale3d(sx3, sy3, sz3); + gfx::Transform blended_matrix = transform_to; + blended_matrix.Blend(transform_from, progress); + + expected = blended_scale; + expected.PreconcatTransform(blended_translate); + expected.PreconcatTransform(blended_matrix); + + operations_expected = base_operations_expected; + operations_expected.AppendMatrix(blended_matrix); + + ExpectTransformationMatrixEq(expected, blended.Apply()); + ExpectTransformationMatrixEq(operations_expected.Apply(), blended.Apply()); + EXPECT_EQ(operations_expected.size(), blended.size()); + for (size_t i = 0; i < operations_expected.size(); ++i) { + TransformOperation expected_op = operations_expected.at(i); + TransformOperation blended_op = blended.at(i); + SCOPED_TRACE(i); + ExpectTransformOperationEqual(expected_op, blended_op); + } +} + +static void CheckProgress(SkScalar progress, + const gfx::Transform& from_matrix, + const gfx::Transform& to_matrix, + const TransformOperations& from_transform, + const TransformOperations& to_transform) { + gfx::Transform expected_matrix = to_matrix; + expected_matrix.Blend(from_matrix, progress); + ExpectTransformationMatrixEq( + expected_matrix, to_transform.Blend(from_transform, progress).Apply()); +} + +TEST(TransformOperationTest, BlendProgress) { + SkScalar sx = 2; + SkScalar sy = 4; + SkScalar sz = 8; + TransformOperations operations_from; + operations_from.AppendScale(sx, sy, sz); + + gfx::Transform matrix_from; + matrix_from.Scale3d(sx, sy, sz); + + sx = 4; + sy = 8; + sz = 16; + TransformOperations operations_to; + operations_to.AppendScale(sx, sy, sz); + + gfx::Transform matrix_to; + matrix_to.Scale3d(sx, sy, sz); + + CheckProgress(-1, matrix_from, matrix_to, operations_from, operations_to); + CheckProgress(0, matrix_from, matrix_to, operations_from, operations_to); + CheckProgress(0.25f, matrix_from, matrix_to, operations_from, operations_to); + CheckProgress(0.5f, matrix_from, matrix_to, operations_from, operations_to); + CheckProgress(1, matrix_from, matrix_to, operations_from, operations_to); + CheckProgress(2, matrix_from, matrix_to, operations_from, operations_to); +} + +TEST(TransformOperationTest, BlendWhenTypesDoNotMatch) { + SkScalar sx1 = 2; + SkScalar sy1 = 4; + SkScalar sz1 = 8; + + SkScalar dx1 = 1; + SkScalar dy1 = 2; + SkScalar dz1 = 3; + + SkScalar sx2 = 4; + SkScalar sy2 = 8; + SkScalar sz2 = 16; + + SkScalar dx2 = 10; + SkScalar dy2 = 20; + SkScalar dz2 = 30; + + TransformOperations operations_from; + operations_from.AppendScale(sx1, sy1, sz1); + operations_from.AppendTranslate(dx1, dy1, dz1); + + TransformOperations operations_to; + operations_to.AppendTranslate(dx2, dy2, dz2); + operations_to.AppendScale(sx2, sy2, sz2); + + gfx::Transform from; + from.Scale3d(sx1, sy1, sz1); + from.Translate3d(dx1, dy1, dz1); + + gfx::Transform to; + to.Translate3d(dx2, dy2, dz2); + to.Scale3d(sx2, sy2, sz2); + + SkScalar progress = 0.25f; + + gfx::Transform expected = to; + expected.Blend(from, progress); + + ExpectTransformationMatrixEq( + expected, operations_to.Blend(operations_from, progress).Apply()); +} + +TEST(TransformOperationTest, LargeRotationsWithSameAxis) { + TransformOperations operations_from; + operations_from.AppendRotate(0, 0, 1, 0); + + TransformOperations operations_to; + operations_to.AppendRotate(0, 0, 2, 360); + + SkScalar progress = 0.5f; + + gfx::Transform expected; + expected.RotateAbout(gfx::Vector3dF(0, 0, 1), 180); + + ExpectTransformationMatrixEq( + expected, operations_to.Blend(operations_from, progress).Apply()); +} + +TEST(TransformOperationTest, LargeRotationsWithSameAxisInDifferentDirection) { + TransformOperations operations_from; + operations_from.AppendRotate(0, 0, 1, 180); + + TransformOperations operations_to; + operations_to.AppendRotate(0, 0, -1, 180); + + SkScalar progress = 0.5f; + + gfx::Transform expected; + + ExpectTransformationMatrixEq( + expected, operations_to.Blend(operations_from, progress).Apply()); +} + +TEST(TransformOperationTest, LargeRotationsWithDifferentAxes) { + TransformOperations operations_from; + operations_from.AppendRotate(0, 0, 1, 175); + + TransformOperations operations_to; + operations_to.AppendRotate(0, 1, 0, 175); + + SkScalar progress = 0.5f; + gfx::Transform matrix_from; + matrix_from.RotateAbout(gfx::Vector3dF(0, 0, 1), 175); + + gfx::Transform matrix_to; + matrix_to.RotateAbout(gfx::Vector3dF(0, 1, 0), 175); + + gfx::Transform expected = matrix_to; + expected.Blend(matrix_from, progress); + + ExpectTransformationMatrixEq( + expected, operations_to.Blend(operations_from, progress).Apply()); +} + +TEST(TransformOperationTest, RotationFromZeroDegDifferentAxes) { + TransformOperations operations_from; + operations_from.AppendRotate(0, 0, 1, 0); + + TransformOperations operations_to; + operations_to.AppendRotate(0, 1, 0, 450); + + SkScalar progress = 0.5f; + gfx::Transform expected; + expected.RotateAbout(gfx::Vector3dF(0, 1, 0), 225); + ExpectTransformationMatrixEq( + expected, operations_to.Blend(operations_from, progress).Apply()); +} + +TEST(TransformOperationTest, RotationFromZeroDegSameAxes) { + TransformOperations operations_from; + operations_from.AppendRotate(0, 0, 1, 0); + + TransformOperations operations_to; + operations_to.AppendRotate(0, 0, 1, 450); + + SkScalar progress = 0.5f; + gfx::Transform expected; + expected.RotateAbout(gfx::Vector3dF(0, 0, 1), 225); + ExpectTransformationMatrixEq( + expected, operations_to.Blend(operations_from, progress).Apply()); +} + +TEST(TransformOperationTest, RotationToZeroDegDifferentAxes) { + TransformOperations operations_from; + operations_from.AppendRotate(0, 1, 0, 450); + + TransformOperations operations_to; + operations_to.AppendRotate(0, 0, 1, 0); + + SkScalar progress = 0.5f; + gfx::Transform expected; + expected.RotateAbout(gfx::Vector3dF(0, 1, 0), 225); + ExpectTransformationMatrixEq( + expected, operations_to.Blend(operations_from, progress).Apply()); +} + +TEST(TransformOperationTest, RotationToZeroDegSameAxes) { + TransformOperations operations_from; + operations_from.AppendRotate(0, 0, 1, 450); + + TransformOperations operations_to; + operations_to.AppendRotate(0, 0, 1, 0); + + SkScalar progress = 0.5f; + gfx::Transform expected; + expected.RotateAbout(gfx::Vector3dF(0, 0, 1), 225); + ExpectTransformationMatrixEq( + expected, operations_to.Blend(operations_from, progress).Apply()); +} + +TEST(TransformOperationTest, BlendRotationFromIdentity) { + std::vector> identity_operations = + GetIdentityOperations(); + + for (const auto& identity_operation : identity_operations) { + TransformOperations operations; + operations.AppendRotate(0, 0, 1, 90); + + SkScalar progress = 0.5f; + + gfx::Transform expected; + expected.RotateAbout(gfx::Vector3dF(0, 0, 1), 45); + + ExpectTransformationMatrixEq( + expected, operations.Blend(*identity_operation, progress).Apply()); + + progress = -0.5f; + + expected.MakeIdentity(); + expected.RotateAbout(gfx::Vector3dF(0, 0, 1), -45); + + ExpectTransformationMatrixEq( + expected, operations.Blend(*identity_operation, progress).Apply()); + + progress = 1.5f; + + expected.MakeIdentity(); + expected.RotateAbout(gfx::Vector3dF(0, 0, 1), 135); + + ExpectTransformationMatrixEq( + expected, operations.Blend(*identity_operation, progress).Apply()); + } +} + +TEST(TransformOperationTest, BlendTranslationFromIdentity) { + std::vector> identity_operations = + GetIdentityOperations(); + + for (const auto& identity_operation : identity_operations) { + TransformOperations operations; + operations.AppendTranslate(2, 2, 2); + + SkScalar progress = 0.5f; + + gfx::Transform expected; + expected.Translate3d(1, 1, 1); + + ExpectTransformationMatrixEq( + expected, operations.Blend(*identity_operation, progress).Apply()); + + progress = -0.5f; + + expected.MakeIdentity(); + expected.Translate3d(-1, -1, -1); + + ExpectTransformationMatrixEq( + expected, operations.Blend(*identity_operation, progress).Apply()); + + progress = 1.5f; + + expected.MakeIdentity(); + expected.Translate3d(3, 3, 3); + + ExpectTransformationMatrixEq( + expected, operations.Blend(*identity_operation, progress).Apply()); + } +} + +TEST(TransformOperationTest, BlendScaleFromIdentity) { + std::vector> identity_operations = + GetIdentityOperations(); + + for (const auto& identity_operation : identity_operations) { + TransformOperations operations; + operations.AppendScale(3, 3, 3); + + SkScalar progress = 0.5f; + + gfx::Transform expected; + expected.Scale3d(2, 2, 2); + + ExpectTransformationMatrixEq( + expected, operations.Blend(*identity_operation, progress).Apply()); + + progress = -0.5f; + + expected.MakeIdentity(); + expected.Scale3d(0, 0, 0); + + ExpectTransformationMatrixEq( + expected, operations.Blend(*identity_operation, progress).Apply()); + + progress = 1.5f; + + expected.MakeIdentity(); + expected.Scale3d(4, 4, 4); + + ExpectTransformationMatrixEq( + expected, operations.Blend(*identity_operation, progress).Apply()); + } +} + +TEST(TransformOperationTest, BlendSkewFromEmpty) { + TransformOperations empty_operation; + + TransformOperations operations; + operations.AppendSkew(2, 2); + + SkScalar progress = 0.5f; + + gfx::Transform expected; + expected.Skew(1, 1); + + ExpectTransformationMatrixEq( + expected, operations.Blend(empty_operation, progress).Apply()); + + progress = -0.5f; + + expected.MakeIdentity(); + expected.Skew(-1, -1); + + ExpectTransformationMatrixEq( + expected, operations.Blend(empty_operation, progress).Apply()); + + progress = 1.5f; + + expected.MakeIdentity(); + expected.Skew(3, 3); + + ExpectTransformationMatrixEq( + expected, operations.Blend(empty_operation, progress).Apply()); +} + +TEST(TransformOperationTest, BlendPerspectiveFromIdentity) { + std::vector> identity_operations = + GetIdentityOperations(); + + for (const auto& identity_operation : identity_operations) { + TransformOperations operations; + operations.AppendPerspective(1000); + + SkScalar progress = 0.5f; + + gfx::Transform expected; + expected.ApplyPerspectiveDepth(2000); + + ExpectTransformationMatrixEq( + expected, operations.Blend(*identity_operation, progress).Apply()); + } +} + +TEST(TransformOperationTest, BlendRotationToIdentity) { + std::vector> identity_operations = + GetIdentityOperations(); + + for (const auto& identity_operation : identity_operations) { + TransformOperations operations; + operations.AppendRotate(0, 0, 1, 90); + + SkScalar progress = 0.5f; + + gfx::Transform expected; + expected.RotateAbout(gfx::Vector3dF(0, 0, 1), 45); + + ExpectTransformationMatrixEq( + expected, identity_operation->Blend(operations, progress).Apply()); + } +} + +TEST(TransformOperationTest, BlendTranslationToIdentity) { + std::vector> identity_operations = + GetIdentityOperations(); + + for (const auto& identity_operation : identity_operations) { + TransformOperations operations; + operations.AppendTranslate(2, 2, 2); + + SkScalar progress = 0.5f; + + gfx::Transform expected; + expected.Translate3d(1, 1, 1); + + ExpectTransformationMatrixEq( + expected, identity_operation->Blend(operations, progress).Apply()); + } +} + +TEST(TransformOperationTest, BlendScaleToIdentity) { + std::vector> identity_operations = + GetIdentityOperations(); + + for (const auto& identity_operation : identity_operations) { + TransformOperations operations; + operations.AppendScale(3, 3, 3); + + SkScalar progress = 0.5f; + + gfx::Transform expected; + expected.Scale3d(2, 2, 2); + + ExpectTransformationMatrixEq( + expected, identity_operation->Blend(operations, progress).Apply()); + } +} + +TEST(TransformOperationTest, BlendSkewToEmpty) { + TransformOperations empty_operation; + + TransformOperations operations; + operations.AppendSkew(2, 2); + + SkScalar progress = 0.5f; + + gfx::Transform expected; + expected.Skew(1, 1); + + ExpectTransformationMatrixEq( + expected, empty_operation.Blend(operations, progress).Apply()); +} + +TEST(TransformOperationTest, BlendPerspectiveToIdentity) { + std::vector> identity_operations = + GetIdentityOperations(); + + for (const auto& identity_operation : identity_operations) { + TransformOperations operations; + operations.AppendPerspective(1000); + + SkScalar progress = 0.5f; + + gfx::Transform expected; + expected.ApplyPerspectiveDepth(2000); + + ExpectTransformationMatrixEq( + expected, identity_operation->Blend(operations, progress).Apply()); + } +} + +TEST(TransformOperationTest, ExtrapolatePerspectiveBlending) { + TransformOperations operations1; + operations1.AppendPerspective(1000); + + TransformOperations operations2; + operations2.AppendPerspective(500); + + gfx::Transform expected; + expected.ApplyPerspectiveDepth(400); + + ExpectTransformationMatrixEq(expected, + operations1.Blend(operations2, -0.5).Apply()); + + expected.MakeIdentity(); + expected.ApplyPerspectiveDepth(2000); + + ExpectTransformationMatrixEq(expected, + operations1.Blend(operations2, 1.5).Apply()); +} + +TEST(TransformOperationTest, ExtrapolateMatrixBlending) { + gfx::Transform transform1; + transform1.Translate3d(1, 1, 1); + TransformOperations operations1; + operations1.AppendMatrix(transform1); + + gfx::Transform transform2; + transform2.Translate3d(3, 3, 3); + TransformOperations operations2; + operations2.AppendMatrix(transform2); + + gfx::Transform expected; + ExpectTransformationMatrixEq(expected, + operations1.Blend(operations2, 1.5).Apply()); + + expected.Translate3d(4, 4, 4); + ExpectTransformationMatrixEq(expected, + operations1.Blend(operations2, -0.5).Apply()); +} + +TEST(TransformOperationTest, NonDecomposableBlend) { + TransformOperations non_decomposible_transform; + gfx::Transform non_decomposible_matrix(0, 0, 0, 0, 0, 0); + non_decomposible_transform.AppendMatrix(non_decomposible_matrix); + + TransformOperations identity_transform; + gfx::Transform identity_matrix; + identity_transform.AppendMatrix(identity_matrix); + + // Before the half-way point, we should return the 'from' matrix. + ExpectTransformationMatrixEq( + non_decomposible_matrix, + identity_transform.Blend(non_decomposible_transform, 0.0f).Apply()); + ExpectTransformationMatrixEq( + non_decomposible_matrix, + identity_transform.Blend(non_decomposible_transform, 0.49f).Apply()); + + // After the half-way point, we should return the 'to' matrix. + ExpectTransformationMatrixEq( + identity_matrix, + identity_transform.Blend(non_decomposible_transform, 0.5f).Apply()); + ExpectTransformationMatrixEq( + identity_matrix, + identity_transform.Blend(non_decomposible_transform, 1.0f).Apply()); +} + +TEST(TransformOperationTest, BlendedBoundsWhenTypesDoNotMatch) { + TransformOperations operations_from; + operations_from.AppendScale(2.0, 4.0, 8.0); + operations_from.AppendTranslate(1.0, 2.0, 3.0); + + TransformOperations operations_to; + operations_to.AppendTranslate(10.0, 20.0, 30.0); + operations_to.AppendScale(4.0, 8.0, 16.0); + + gfx::BoxF box(1.f, 1.f, 1.f); + gfx::BoxF bounds; + + SkScalar min_progress = 0.f; + SkScalar max_progress = 1.f; + + EXPECT_FALSE(operations_to.BlendedBoundsForBox( + box, operations_from, min_progress, max_progress, &bounds)); +} + +TEST(TransformOperationTest, BlendedBoundsForIdentity) { + TransformOperations operations_from; + operations_from.AppendIdentity(); + TransformOperations operations_to; + operations_to.AppendIdentity(); + + gfx::BoxF box(1.f, 2.f, 3.f); + gfx::BoxF bounds; + + SkScalar min_progress = 0.f; + SkScalar max_progress = 1.f; + + EXPECT_TRUE(operations_to.BlendedBoundsForBox( + box, operations_from, min_progress, max_progress, &bounds)); + EXPECT_EQ(box.ToString(), bounds.ToString()); +} + +TEST(TransformOperationTest, BlendedBoundsForTranslate) { + TransformOperations operations_from; + operations_from.AppendTranslate(3.0, -4.0, 2.0); + TransformOperations operations_to; + operations_to.AppendTranslate(7.0, 4.0, -2.0); + + gfx::BoxF box(1.f, 2.f, 3.f, 4.f, 4.f, 4.f); + gfx::BoxF bounds; + + SkScalar min_progress = -0.5f; + SkScalar max_progress = 1.5f; + EXPECT_TRUE(operations_to.BlendedBoundsForBox( + box, operations_from, min_progress, max_progress, &bounds)); + EXPECT_EQ(gfx::BoxF(2.f, -6.f, -1.f, 12.f, 20.f, 12.f).ToString(), + bounds.ToString()); + + min_progress = 0.f; + max_progress = 1.f; + EXPECT_TRUE(operations_to.BlendedBoundsForBox( + box, operations_from, min_progress, max_progress, &bounds)); + EXPECT_EQ(gfx::BoxF(4.f, -2.f, 1.f, 8.f, 12.f, 8.f).ToString(), + bounds.ToString()); + + TransformOperations identity; + EXPECT_TRUE(operations_to.BlendedBoundsForBox(box, identity, min_progress, + max_progress, &bounds)); + EXPECT_EQ(gfx::BoxF(1.f, 2.f, 1.f, 11.f, 8.f, 6.f).ToString(), + bounds.ToString()); + + EXPECT_TRUE(identity.BlendedBoundsForBox(box, operations_from, min_progress, + max_progress, &bounds)); + EXPECT_EQ(gfx::BoxF(1.f, -2.f, 3.f, 7.f, 8.f, 6.f).ToString(), + bounds.ToString()); +} + +TEST(TransformOperationTest, BlendedBoundsForScale) { + TransformOperations operations_from; + operations_from.AppendScale(3.0, 0.5, 2.0); + TransformOperations operations_to; + operations_to.AppendScale(7.0, 4.0, -2.0); + + gfx::BoxF box(1.f, 2.f, 3.f, 4.f, 4.f, 4.f); + gfx::BoxF bounds; + + SkScalar min_progress = -0.5f; + SkScalar max_progress = 1.5f; + EXPECT_TRUE(operations_to.BlendedBoundsForBox( + box, operations_from, min_progress, max_progress, &bounds)); + EXPECT_EQ(gfx::BoxF(1.f, -7.5f, -28.f, 44.f, 42.f, 56.f).ToString(), + bounds.ToString()); + + min_progress = 0.f; + max_progress = 1.f; + EXPECT_TRUE(operations_to.BlendedBoundsForBox( + box, operations_from, min_progress, max_progress, &bounds)); + EXPECT_EQ(gfx::BoxF(3.f, 1.f, -14.f, 32.f, 23.f, 28.f).ToString(), + bounds.ToString()); + + TransformOperations identity; + EXPECT_TRUE(operations_to.BlendedBoundsForBox(box, identity, min_progress, + max_progress, &bounds)); + EXPECT_EQ(gfx::BoxF(1.f, 2.f, -14.f, 34.f, 22.f, 21.f).ToString(), + bounds.ToString()); + + EXPECT_TRUE(identity.BlendedBoundsForBox(box, operations_from, min_progress, + max_progress, &bounds)); + EXPECT_EQ(gfx::BoxF(1.f, 1.f, 3.f, 14.f, 5.f, 11.f).ToString(), + bounds.ToString()); +} + +TEST(TransformOperationTest, BlendedBoundsWithZeroScale) { + TransformOperations zero_scale; + zero_scale.AppendScale(0.0, 0.0, 0.0); + TransformOperations non_zero_scale; + non_zero_scale.AppendScale(2.0, -4.0, 5.0); + + gfx::BoxF box(1.f, 2.f, 3.f, 4.f, 4.f, 4.f); + gfx::BoxF bounds; + + SkScalar min_progress = 0.f; + SkScalar max_progress = 1.f; + EXPECT_TRUE(zero_scale.BlendedBoundsForBox(box, non_zero_scale, min_progress, + max_progress, &bounds)); + EXPECT_EQ(gfx::BoxF(0.f, -24.f, 0.f, 10.f, 24.f, 35.f).ToString(), + bounds.ToString()); + + EXPECT_TRUE(non_zero_scale.BlendedBoundsForBox(box, zero_scale, min_progress, + max_progress, &bounds)); + EXPECT_EQ(gfx::BoxF(0.f, -24.f, 0.f, 10.f, 24.f, 35.f).ToString(), + bounds.ToString()); + + EXPECT_TRUE(zero_scale.BlendedBoundsForBox(box, zero_scale, min_progress, + max_progress, &bounds)); + EXPECT_EQ(gfx::BoxF().ToString(), bounds.ToString()); +} + +TEST(TransformOperationTest, BlendedBoundsForRotationTrivial) { + TransformOperations operations_from; + operations_from.AppendRotate(0.f, 0.f, 1.f, 0.f); + TransformOperations operations_to; + operations_to.AppendRotate(0.f, 0.f, 1.f, 360.f); + + float sqrt_2 = sqrt(2.f); + gfx::BoxF box(-sqrt_2, -sqrt_2, 0.f, sqrt_2, sqrt_2, 0.f); + gfx::BoxF bounds; + + // Since we're rotating 360 degrees, any box with dimensions between 0 and + // 2 * sqrt(2) should give the same result. + float sizes[] = {0.f, 0.1f, sqrt_2, 2.f * sqrt_2}; + for (float size : sizes) { + box.set_size(size, size, 0.f); + SkScalar min_progress = 0.f; + SkScalar max_progress = 1.f; + EXPECT_TRUE(operations_to.BlendedBoundsForBox( + box, operations_from, min_progress, max_progress, &bounds)); + EXPECT_EQ(gfx::BoxF(-2.f, -2.f, 0.f, 4.f, 4.f, 0.f).ToString(), + bounds.ToString()); + } +} + +TEST(TransformOperationTest, BlendedBoundsForRotationAllExtrema) { + // If the normal is out of the plane, we can have up to 6 extrema (a min/max + // in each dimension) between the endpoints of the arc. This test ensures that + // we consider all 6. + TransformOperations operations_from; + operations_from.AppendRotate(1.f, 1.f, 1.f, 30.f); + TransformOperations operations_to; + operations_to.AppendRotate(1.f, 1.f, 1.f, 390.f); + + gfx::BoxF box(1.f, 0.f, 0.f, 0.f, 0.f, 0.f); + gfx::BoxF bounds; + + float min = -1.f / 3.f; + float max = 1.f; + float size = max - min; + EXPECT_TRUE(operations_to.BlendedBoundsForBox(box, operations_from, 0.f, 1.f, + &bounds)); + EXPECT_EQ(gfx::BoxF(min, min, min, size, size, size).ToString(), + bounds.ToString()); +} + +TEST(TransformOperationTest, BlendedBoundsForRotationDifferentAxes) { + // We can handle rotations about a single axis. If the axes are different, + // we revert to matrix interpolation for which inflated bounds cannot be + // computed. + TransformOperations operations_from; + operations_from.AppendRotate(1.f, 1.f, 1.f, 30.f); + TransformOperations operations_to_same; + operations_to_same.AppendRotate(1.f, 1.f, 1.f, 390.f); + TransformOperations operations_to_opposite; + operations_to_opposite.AppendRotate(-1.f, -1.f, -1.f, 390.f); + TransformOperations operations_to_different; + operations_to_different.AppendRotate(1.f, 3.f, 1.f, 390.f); + + gfx::BoxF box(1.f, 0.f, 0.f, 0.f, 0.f, 0.f); + gfx::BoxF bounds; + + EXPECT_TRUE(operations_to_same.BlendedBoundsForBox(box, operations_from, 0.f, + 1.f, &bounds)); + EXPECT_TRUE(operations_to_opposite.BlendedBoundsForBox(box, operations_from, + 0.f, 1.f, &bounds)); + EXPECT_FALSE(operations_to_different.BlendedBoundsForBox(box, operations_from, + 0.f, 1.f, &bounds)); +} + +TEST(TransformOperationTest, BlendedBoundsForRotationPointOnAxis) { + // Checks that if the point to rotate is sitting on the axis of rotation, that + // it does not get affected. + TransformOperations operations_from; + operations_from.AppendRotate(1.f, 1.f, 1.f, 30.f); + TransformOperations operations_to; + operations_to.AppendRotate(1.f, 1.f, 1.f, 390.f); + + gfx::BoxF box(1.f, 1.f, 1.f, 0.f, 0.f, 0.f); + gfx::BoxF bounds; + + EXPECT_TRUE(operations_to.BlendedBoundsForBox(box, operations_from, 0.f, 1.f, + &bounds)); + EXPECT_EQ(box.ToString(), bounds.ToString()); +} + +TEST(TransformOperationTest, BlendedBoundsForRotationProblematicAxes) { + // Zeros in the components of the axis of rotation turned out to be tricky to + // deal with in practice. This function tests some potentially problematic + // axes to ensure sane behavior. + + // Some common values used in the expected boxes. + float dim1 = 0.292893f; + float dim2 = sqrt(2.f); + float dim3 = 2.f * dim2; + + struct { + float x; + float y; + float z; + gfx::BoxF expected; + } tests[] = {{0.f, 0.f, 0.f, gfx::BoxF(1.f, 1.f, 1.f, 0.f, 0.f, 0.f)}, + {1.f, 0.f, 0.f, gfx::BoxF(1.f, -dim2, -dim2, 0.f, dim3, dim3)}, + {0.f, 1.f, 0.f, gfx::BoxF(-dim2, 1.f, -dim2, dim3, 0.f, dim3)}, + {0.f, 0.f, 1.f, gfx::BoxF(-dim2, -dim2, 1.f, dim3, dim3, 0.f)}, + {1.f, 1.f, 0.f, gfx::BoxF(dim1, dim1, -1.f, dim2, dim2, 2.f)}, + {0.f, 1.f, 1.f, gfx::BoxF(-1.f, dim1, dim1, 2.f, dim2, dim2)}, + {1.f, 0.f, 1.f, gfx::BoxF(dim1, -1.f, dim1, dim2, 2.f, dim2)}}; + + for (const auto& test : tests) { + float x = test.x; + float y = test.y; + float z = test.z; + TransformOperations operations_from; + operations_from.AppendRotate(x, y, z, 0.f); + TransformOperations operations_to; + operations_to.AppendRotate(x, y, z, 360.f); + gfx::BoxF box(1.f, 1.f, 1.f, 0.f, 0.f, 0.f); + gfx::BoxF bounds; + + EXPECT_TRUE(operations_to.BlendedBoundsForBox(box, operations_from, 0.f, + 1.f, &bounds)); + EXPECT_EQ(test.expected.ToString(), bounds.ToString()); + } +} + +static void ExpectBoxesApproximatelyEqual(const gfx::BoxF& lhs, + const gfx::BoxF& rhs, + float tolerance) { + EXPECT_NEAR(lhs.x(), rhs.x(), tolerance); + EXPECT_NEAR(lhs.y(), rhs.y(), tolerance); + EXPECT_NEAR(lhs.z(), rhs.z(), tolerance); + EXPECT_NEAR(lhs.width(), rhs.width(), tolerance); + EXPECT_NEAR(lhs.height(), rhs.height(), tolerance); + EXPECT_NEAR(lhs.depth(), rhs.depth(), tolerance); +} + +static void EmpiricallyTestBounds(const TransformOperations& from, + const TransformOperations& to, + SkScalar min_progress, + SkScalar max_progress, + bool test_containment_only) { + gfx::BoxF box(200.f, 500.f, 100.f, 100.f, 300.f, 200.f); + gfx::BoxF bounds; + EXPECT_TRUE( + to.BlendedBoundsForBox(box, from, min_progress, max_progress, &bounds)); + + bool first_time = true; + gfx::BoxF empirical_bounds; + static const size_t kNumSteps = 10; + for (size_t step = 0; step < kNumSteps; ++step) { + float t = step / (kNumSteps - 1.f); + t = gfx::Tween::FloatValueBetween(t, min_progress, max_progress); + gfx::Transform partial_transform = to.Blend(from, t).Apply(); + gfx::BoxF transformed = box; + partial_transform.TransformBox(&transformed); + + if (first_time) { + empirical_bounds = transformed; + first_time = false; + } else { + empirical_bounds.Union(transformed); + } + } + + if (test_containment_only) { + gfx::BoxF unified_bounds = bounds; + unified_bounds.Union(empirical_bounds); + // Convert to the screen space rects these boxes represent. + gfx::Rect bounds_rect = ToEnclosingRect( + gfx::RectF(bounds.x(), bounds.y(), bounds.width(), bounds.height())); + gfx::Rect unified_bounds_rect = ToEnclosingRect( + gfx::RectF(unified_bounds.x(), unified_bounds.y(), + unified_bounds.width(), unified_bounds.height())); + EXPECT_EQ(bounds_rect.ToString(), unified_bounds_rect.ToString()); + } else { + // Our empirical estimate will be a little rough since we're only doing + // 100 samples. + static const float kTolerance = 1e-2f; + ExpectBoxesApproximatelyEqual(empirical_bounds, bounds, kTolerance); + } +} + +static void EmpiricallyTestBoundsEquality(const TransformOperations& from, + const TransformOperations& to, + SkScalar min_progress, + SkScalar max_progress) { + EmpiricallyTestBounds(from, to, min_progress, max_progress, false); +} + +static void EmpiricallyTestBoundsContainment(const TransformOperations& from, + const TransformOperations& to, + SkScalar min_progress, + SkScalar max_progress) { + EmpiricallyTestBounds(from, to, min_progress, max_progress, true); +} + +TEST(TransformOperationTest, BlendedBoundsForRotationEmpiricalTests) { + // Sets up various axis angle combinations, computes the bounding box and + // empirically tests that the transformed bounds are indeed contained by the + // computed bounding box. + + struct { + float x; + float y; + float z; + } axes[] = {{1.f, 1.f, 1.f}, {-1.f, -1.f, -1.f}, {-1.f, 2.f, 3.f}, + {1.f, -2.f, 3.f}, {1.f, 2.f, -3.f}, {0.f, 0.f, 0.f}, + {1.f, 0.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 0.f, 1.f}, + {1.f, 1.f, 0.f}, {0.f, 1.f, 1.f}, {1.f, 0.f, 1.f}, + {-1.f, 0.f, 0.f}, {0.f, -1.f, 0.f}, {0.f, 0.f, -1.f}, + {-1.f, -1.f, 0.f}, {0.f, -1.f, -1.f}, {-1.f, 0.f, -1.f}}; + + struct { + float theta_from; + float theta_to; + } angles[] = {{5.f, 10.f}, {10.f, 5.f}, {0.f, 360.f}, {20.f, 180.f}, + {-20.f, -180.f}, {180.f, -220.f}, {220.f, 320.f}}; + + // We can go beyond the range [0, 1] (the bezier might slide out of this range + // at either end), but since the first and last knots are at (0, 0) and (1, 1) + // we will never go within it, so these tests are sufficient. + struct { + float min_progress; + float max_progress; + } progresses[] = { + {0.f, 1.f}, + {-.25f, 1.25f}, + }; + + for (const auto& axis : axes) { + for (const auto& angle : angles) { + for (const auto& progress : progresses) { + float x = axis.x; + float y = axis.y; + float z = axis.z; + TransformOperations operations_from; + operations_from.AppendRotate(x, y, z, angle.theta_from); + TransformOperations operations_to; + operations_to.AppendRotate(x, y, z, angle.theta_to); + EmpiricallyTestBoundsContainment(operations_from, operations_to, + progress.min_progress, + progress.max_progress); + } + } + } +} + +TEST(TransformOperationTest, PerspectiveMatrixAndTransformBlendingEquivalency) { + TransformOperations from_operations; + from_operations.AppendPerspective(200); + + TransformOperations to_operations; + to_operations.AppendPerspective(1000); + + gfx::Transform from_transform; + from_transform.ApplyPerspectiveDepth(200); + + gfx::Transform to_transform; + to_transform.ApplyPerspectiveDepth(1000); + + static const int steps = 20; + for (int i = 0; i < steps; ++i) { + double progress = static_cast(i) / (steps - 1); + + gfx::Transform blended_matrix = to_transform; + EXPECT_TRUE(blended_matrix.Blend(from_transform, progress)); + + gfx::Transform blended_transform = + to_operations.Blend(from_operations, progress).Apply(); + + ExpectTransformationMatrixEq(blended_matrix, blended_transform); + } +} + +TEST(TransformOperationTest, BlendedBoundsForPerspective) { + struct { + float from_depth; + float to_depth; + } perspective_depths[] = { + {600.f, 400.f}, + {800.f, 1000.f}, + {800.f, std::numeric_limits::infinity()}, + }; + + struct { + float min_progress; + float max_progress; + } progresses[] = { + {0.f, 1.f}, + {-0.1f, 1.1f}, + }; + + for (const auto& perspective_depth : perspective_depths) { + for (const auto& progress : progresses) { + TransformOperations operations_from; + operations_from.AppendPerspective(perspective_depth.from_depth); + TransformOperations operations_to; + operations_to.AppendPerspective(perspective_depth.to_depth); + EmpiricallyTestBoundsEquality(operations_from, operations_to, + progress.min_progress, + progress.max_progress); + } + } +} + +TEST(TransformOperationTest, BlendedBoundsForSkew) { + struct { + float from_x; + float from_y; + float to_x; + float to_y; + } skews[] = { + {1.f, 0.5f, 0.5f, 1.f}, + {2.f, 1.f, 0.5f, 0.5f}, + }; + + struct { + float min_progress; + float max_progress; + } progresses[] = { + {0.f, 1.f}, + {-0.1f, 1.1f}, + }; + + for (const auto& skew : skews) { + for (const auto& progress : progresses) { + TransformOperations operations_from; + operations_from.AppendSkew(skew.from_x, skew.from_y); + TransformOperations operations_to; + operations_to.AppendSkew(skew.to_x, skew.to_y); + EmpiricallyTestBoundsEquality(operations_from, operations_to, + progress.min_progress, + progress.max_progress); + } + } +} + +TEST(TransformOperationTest, NonCommutativeRotations) { + TransformOperations operations_from; + operations_from.AppendRotate(1.0, 0.0, 0.0, 0.0); + operations_from.AppendRotate(0.0, 1.0, 0.0, 0.0); + TransformOperations operations_to; + operations_to.AppendRotate(1.0, 0.0, 0.0, 45.0); + operations_to.AppendRotate(0.0, 1.0, 0.0, 135.0); + + gfx::BoxF box(0, 0, 0, 1, 1, 1); + gfx::BoxF bounds; + + SkScalar min_progress = 0.0f; + SkScalar max_progress = 1.0f; + EXPECT_TRUE(operations_to.BlendedBoundsForBox( + box, operations_from, min_progress, max_progress, &bounds)); + gfx::Transform blended_transform = + operations_to.Blend(operations_from, max_progress).Apply(); + gfx::Point3F blended_point(0.9f, 0.9f, 0.0f); + blended_transform.TransformPoint(&blended_point); + gfx::BoxF expanded_bounds = bounds; + expanded_bounds.ExpandTo(blended_point); + EXPECT_EQ(bounds.ToString(), expanded_bounds.ToString()); +} + +TEST(TransformOperationTest, BlendedBoundsForSequence) { + TransformOperations operations_from; + operations_from.AppendTranslate(1.0, -5.0, 1.0); + operations_from.AppendScale(-1.0, 2.0, 3.0); + operations_from.AppendTranslate(2.0, 4.0, -1.0); + TransformOperations operations_to; + operations_to.AppendTranslate(13.0, -1.0, 5.0); + operations_to.AppendScale(-3.0, -2.0, 5.0); + operations_to.AppendTranslate(6.0, -2.0, 3.0); + + gfx::BoxF box(1.f, 2.f, 3.f, 4.f, 4.f, 4.f); + gfx::BoxF bounds; + + SkScalar min_progress = -0.5f; + SkScalar max_progress = 1.5f; + EXPECT_TRUE(operations_to.BlendedBoundsForBox( + box, operations_from, min_progress, max_progress, &bounds)); + EXPECT_EQ(gfx::BoxF(-57.f, -59.f, -1.f, 76.f, 112.f, 80.f).ToString(), + bounds.ToString()); + + min_progress = 0.f; + max_progress = 1.f; + EXPECT_TRUE(operations_to.BlendedBoundsForBox( + box, operations_from, min_progress, max_progress, &bounds)); + EXPECT_EQ(gfx::BoxF(-32.f, -25.f, 7.f, 42.f, 44.f, 48.f).ToString(), + bounds.ToString()); + + TransformOperations identity; + EXPECT_TRUE(operations_to.BlendedBoundsForBox(box, identity, min_progress, + max_progress, &bounds)); + EXPECT_EQ(gfx::BoxF(-33.f, -13.f, 3.f, 57.f, 19.f, 52.f).ToString(), + bounds.ToString()); + + EXPECT_TRUE(identity.BlendedBoundsForBox(box, operations_from, min_progress, + max_progress, &bounds)); + EXPECT_EQ(gfx::BoxF(-7.f, -3.f, 2.f, 15.f, 23.f, 20.f).ToString(), + bounds.ToString()); +} + +TEST(TransformOperationTest, IsTranslationWithSingleOperation) { + TransformOperations empty_operations; + EXPECT_TRUE(empty_operations.IsTranslation()); + + TransformOperations identity; + identity.AppendIdentity(); + EXPECT_TRUE(identity.IsTranslation()); + + TransformOperations translate; + translate.AppendTranslate(1.f, 2.f, 3.f); + EXPECT_TRUE(translate.IsTranslation()); + + TransformOperations rotate; + rotate.AppendRotate(1.f, 2.f, 3.f, 4.f); + EXPECT_FALSE(rotate.IsTranslation()); + + TransformOperations scale; + scale.AppendScale(1.f, 2.f, 3.f); + EXPECT_FALSE(scale.IsTranslation()); + + TransformOperations skew; + skew.AppendSkew(1.f, 2.f); + EXPECT_FALSE(skew.IsTranslation()); + + TransformOperations perspective; + perspective.AppendPerspective(1.f); + EXPECT_FALSE(perspective.IsTranslation()); + + TransformOperations identity_matrix; + identity_matrix.AppendMatrix(gfx::Transform()); + EXPECT_TRUE(identity_matrix.IsTranslation()); + + TransformOperations translation_matrix; + gfx::Transform translation_transform; + translation_transform.Translate3d(1.f, 2.f, 3.f); + translation_matrix.AppendMatrix(translation_transform); + EXPECT_TRUE(translation_matrix.IsTranslation()); + + TransformOperations scaling_matrix; + gfx::Transform scaling_transform; + scaling_transform.Scale(2.f, 2.f); + scaling_matrix.AppendMatrix(scaling_transform); + EXPECT_FALSE(scaling_matrix.IsTranslation()); +} + +TEST(TransformOperationTest, IsTranslationWithMultipleOperations) { + TransformOperations operations1; + operations1.AppendSkew(1.f, 2.f); + operations1.AppendTranslate(1.f, 2.f, 3.f); + operations1.AppendIdentity(); + EXPECT_FALSE(operations1.IsTranslation()); + + TransformOperations operations2; + operations2.AppendIdentity(); + operations2.AppendTranslate(3.f, 2.f, 1.f); + gfx::Transform translation_transform; + translation_transform.Translate3d(1.f, 2.f, 3.f); + operations2.AppendMatrix(translation_transform); + EXPECT_TRUE(operations2.IsTranslation()); +} + +TEST(TransformOperationTest, ScaleComponent) { + SkScalar scale; + + // Scale. + TransformOperations operations1; + operations1.AppendScale(-3.f, 2.f, 5.f); + EXPECT_TRUE(operations1.ScaleComponent(&scale)); + EXPECT_EQ(5.f, scale); + + // Translate. + TransformOperations operations2; + operations2.AppendTranslate(1.f, 2.f, 3.f); + EXPECT_TRUE(operations2.ScaleComponent(&scale)); + EXPECT_EQ(1.f, scale); + + // Rotate. + TransformOperations operations3; + operations3.AppendRotate(1.f, 2.f, 3.f, 4.f); + EXPECT_TRUE(operations3.ScaleComponent(&scale)); + EXPECT_EQ(1.f, scale); + + // Matrix that's only a translation. + TransformOperations operations4; + gfx::Transform translation_transform; + translation_transform.Translate3d(1.f, 2.f, 3.f); + operations4.AppendMatrix(translation_transform); + EXPECT_TRUE(operations4.ScaleComponent(&scale)); + EXPECT_EQ(1.f, scale); + + // Matrix that includes scale. + TransformOperations operations5; + gfx::Transform matrix; + matrix.RotateAboutZAxis(30.0); + matrix.Scale(-7.f, 6.f); + matrix.Translate3d(gfx::Vector3dF(3.f, 7.f, 1.f)); + operations5.AppendMatrix(matrix); + EXPECT_TRUE(operations5.ScaleComponent(&scale)); + EXPECT_EQ(7.f, scale); + + // Matrix with perspective. + TransformOperations operations6; + matrix.ApplyPerspectiveDepth(2000.f); + operations6.AppendMatrix(matrix); + EXPECT_FALSE(operations6.ScaleComponent(&scale)); + + // Skew. + TransformOperations operations7; + operations7.AppendSkew(30.f, 60.f); + EXPECT_TRUE(operations7.ScaleComponent(&scale)); + EXPECT_EQ(2.f, scale); + + // Perspective. + TransformOperations operations8; + operations8.AppendPerspective(500.f); + EXPECT_FALSE(operations8.ScaleComponent(&scale)); + + // Translate + Scale. + TransformOperations operations9; + operations9.AppendTranslate(1.f, 2.f, 3.f); + operations9.AppendScale(2.f, 5.f, 4.f); + EXPECT_TRUE(operations9.ScaleComponent(&scale)); + EXPECT_EQ(5.f, scale); + + // Translate + Scale + Matrix with translate. + operations9.AppendMatrix(translation_transform); + EXPECT_TRUE(operations9.ScaleComponent(&scale)); + EXPECT_EQ(5.f, scale); + + // Scale + translate. + TransformOperations operations10; + operations10.AppendScale(2.f, 3.f, 2.f); + operations10.AppendTranslate(1.f, 2.f, 3.f); + EXPECT_TRUE(operations10.ScaleComponent(&scale)); + EXPECT_EQ(3.f, scale); + + // Two Scales. + TransformOperations operations11; + operations11.AppendScale(2.f, 3.f, 2.f); + operations11.AppendScale(-3.f, -2.f, -3.f); + EXPECT_TRUE(operations11.ScaleComponent(&scale)); + EXPECT_EQ(9.f, scale); + + // Scale + Matrix. + TransformOperations operations12; + operations12.AppendScale(2.f, 2.f, 2.f); + gfx::Transform scaling_transform; + scaling_transform.Scale(2.f, 2.f); + operations12.AppendMatrix(scaling_transform); + EXPECT_TRUE(operations12.ScaleComponent(&scale)); + EXPECT_EQ(4.f, scale); + + // Scale + Rotate. + TransformOperations operations13; + operations13.AppendScale(2.f, 2.f, 2.f); + operations13.AppendRotate(1.f, 2.f, 3.f, 4.f); + EXPECT_TRUE(operations13.ScaleComponent(&scale)); + EXPECT_EQ(2.f, scale); + + // Scale + Skew. + TransformOperations operations14; + operations14.AppendScale(2.f, 2.f, 2.f); + operations14.AppendSkew(60.f, 45.f); + EXPECT_TRUE(operations14.ScaleComponent(&scale)); + EXPECT_EQ(4.f, scale); + + // Scale + Perspective. + TransformOperations operations15; + operations15.AppendScale(2.f, 2.f, 2.f); + operations15.AppendPerspective(1.f); + EXPECT_FALSE(operations15.ScaleComponent(&scale)); + + // Matrix with skew. + TransformOperations operations16; + gfx::Transform skew_transform; + skew_transform.Skew(50.f, 60.f); + operations16.AppendMatrix(skew_transform); + EXPECT_TRUE(operations16.ScaleComponent(&scale)); + EXPECT_EQ(2.f, scale); +} + +TEST(TransformOperationsTest, ApproximateEquality) { + float noise = 1e-7f; + float tolerance = 1e-5f; + TransformOperations lhs; + TransformOperations rhs; + + // Empty lists of operations are trivially equal. + EXPECT_TRUE(lhs.ApproximatelyEqual(rhs, tolerance)); + + rhs.AppendIdentity(); + rhs.AppendTranslate(0, 0, 0); + rhs.AppendRotate(1, 0, 0, 0); + rhs.AppendScale(1, 1, 1); + rhs.AppendSkew(0, 0); + rhs.AppendMatrix(gfx::Transform()); + + // Even though both lists operations are effectively the identity matrix, rhs + // has a different number of operations and is therefore different. + EXPECT_FALSE(lhs.ApproximatelyEqual(rhs, tolerance)); + + rhs.AppendPerspective(800); + + // Assignment should produce equal lists of operations. + lhs = rhs; + EXPECT_TRUE(lhs.ApproximatelyEqual(rhs, tolerance)); + + // Cannot affect identity operations. + lhs.at(0).translate.x = 1; + EXPECT_TRUE(lhs.ApproximatelyEqual(rhs, tolerance)); + + lhs.at(1).translate.x += noise; + EXPECT_TRUE(lhs.ApproximatelyEqual(rhs, tolerance)); + lhs.at(1).translate.x += 1; + EXPECT_FALSE(lhs.ApproximatelyEqual(rhs, tolerance)); + + lhs = rhs; + lhs.at(2).rotate.angle += noise; + EXPECT_TRUE(lhs.ApproximatelyEqual(rhs, tolerance)); + lhs.at(2).rotate.angle = 1; + EXPECT_FALSE(lhs.ApproximatelyEqual(rhs, tolerance)); + + lhs = rhs; + lhs.at(3).scale.x += noise; + EXPECT_TRUE(lhs.ApproximatelyEqual(rhs, tolerance)); + lhs.at(3).scale.x += 1; + EXPECT_FALSE(lhs.ApproximatelyEqual(rhs, tolerance)); + + lhs = rhs; + lhs.at(4).skew.x += noise; + EXPECT_TRUE(lhs.ApproximatelyEqual(rhs, tolerance)); + lhs.at(4).skew.x = 2; + EXPECT_FALSE(lhs.ApproximatelyEqual(rhs, tolerance)); + + lhs = rhs; + lhs.at(5).matrix.Translate3d(noise, 0, 0); + EXPECT_TRUE(lhs.ApproximatelyEqual(rhs, tolerance)); + lhs.at(5).matrix.Translate3d(1, 1, 1); + EXPECT_FALSE(lhs.ApproximatelyEqual(rhs, tolerance)); + + lhs = rhs; + lhs.at(6).perspective_depth += noise; + EXPECT_TRUE(lhs.ApproximatelyEqual(rhs, tolerance)); + lhs.at(6).perspective_depth = 801; + EXPECT_FALSE(lhs.ApproximatelyEqual(rhs, tolerance)); +} + +} // namespace + +// This test is intentionally outside the anonymous namespace for visibility as +// it needs to be friend of TransformOperations. +TEST(TransformOperationsTest, TestDecompositionCache) { + TransformOperations transforms; + EXPECT_EQ(0UL, transforms.decomposed_transforms_.size()); + EXPECT_TRUE(transforms.ComputeDecomposedTransform(0)); + EXPECT_EQ(1UL, transforms.decomposed_transforms_.size()); + + // Reset cache when appending a scale transform. + transforms.AppendScale(2.f, 2.f, 2.f); + EXPECT_EQ(0UL, transforms.decomposed_transforms_.size()); + EXPECT_TRUE(transforms.ComputeDecomposedTransform(1)); + EXPECT_EQ(1UL, transforms.decomposed_transforms_.size()); + EXPECT_TRUE(transforms.ComputeDecomposedTransform(1)); + EXPECT_EQ(1UL, transforms.decomposed_transforms_.size()); + EXPECT_TRUE(transforms.ComputeDecomposedTransform(0)); + EXPECT_EQ(2UL, transforms.decomposed_transforms_.size()); + + // Reset cache when appending a rotation transform. + transforms.AppendRotate(1, 0, 0, 45); + EXPECT_EQ(0UL, transforms.decomposed_transforms_.size()); + EXPECT_TRUE(transforms.ComputeDecomposedTransform(0)); + EXPECT_EQ(1UL, transforms.decomposed_transforms_.size()); + + // Reset cache when appending a translation transform. + transforms.AppendTranslate(1, 1, 1); + EXPECT_EQ(0UL, transforms.decomposed_transforms_.size()); + EXPECT_TRUE(transforms.ComputeDecomposedTransform(0)); + EXPECT_EQ(1UL, transforms.decomposed_transforms_.size()); + + // Reset cache when appending a skew transform. + transforms.AppendSkew(1, 0); + EXPECT_EQ(0UL, transforms.decomposed_transforms_.size()); + EXPECT_TRUE(transforms.ComputeDecomposedTransform(0)); + EXPECT_EQ(1UL, transforms.decomposed_transforms_.size()); + + // Reset cache when appending a perspective transform. + transforms.AppendPerspective(800); + EXPECT_EQ(0UL, transforms.decomposed_transforms_.size()); + EXPECT_TRUE(transforms.ComputeDecomposedTransform(0)); + EXPECT_EQ(1UL, transforms.decomposed_transforms_.size()); + + // Reset cache when appending a matrix transform. + transforms.AppendMatrix(gfx::Transform()); + EXPECT_EQ(0UL, transforms.decomposed_transforms_.size()); + EXPECT_TRUE(transforms.ComputeDecomposedTransform(0)); + EXPECT_EQ(1UL, transforms.decomposed_transforms_.size()); + + // Reset cache when appending a generic transform operation. + transforms.Append(TransformOperation()); + EXPECT_EQ(0UL, transforms.decomposed_transforms_.size()); + EXPECT_TRUE(transforms.ComputeDecomposedTransform(0)); + EXPECT_EQ(1UL, transforms.decomposed_transforms_.size()); +} + +TEST(TransformOperationTest, BlendSkewMismatch) { + TransformOperations from_ops, to_ops, expected_ops; + from_ops.AppendSkewX(0); + from_ops.AppendRotate(0, 0, 1, 0); + to_ops.AppendSkewY(0); + to_ops.AppendRotate(0, 0, 1, 360); + + // Skew types do not match so use matrix interpolation + expected_ops.AppendMatrix(gfx::Transform()); + + TransformOperations blended_ops = to_ops.Blend(from_ops, 0.5); + ASSERT_EQ(blended_ops.size(), 1u); + ExpectTransformOperationEqual(blended_ops.at(0), expected_ops.at(0)); +} + +TEST(TransformOperationTest, BlendSkewMatch) { + TransformOperations from_ops, to_ops, expected_ops; + from_ops.AppendSkew(30, 0); + from_ops.AppendRotate(0, 0, 1, 0); + to_ops.AppendSkew(0, 30); + to_ops.AppendRotate(0, 0, 1, 360); + + // Skew types match so interpolate as a function. + expected_ops.AppendSkew(15, 15); + expected_ops.AppendRotate(0, 0, 1, 180); + + TransformOperations blended_ops = to_ops.Blend(from_ops, 0.5); + ASSERT_EQ(blended_ops.size(), 2u); + ExpectTransformOperationEqual(blended_ops.at(0), expected_ops.at(0)); + ExpectTransformOperationEqual(blended_ops.at(1), expected_ops.at(1)); +} + +} // namespace gfx diff --git a/geometry/transform_unittest.cc b/geometry/transform_unittest.cc new file mode 100644 index 000000000000..127c203a49db --- /dev/null +++ b/geometry/transform_unittest.cc @@ -0,0 +1,2729 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/transform.h" + +#include + +#include +#include + +#include "base/cxx17_backports.h" +#include "build/build_config.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/geometry/angle_conversions.h" +#include "ui/gfx/geometry/box_f.h" +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/point3_f.h" +#include "ui/gfx/geometry/quad_f.h" +#include "ui/gfx/geometry/rrect_f.h" +#include "ui/gfx/geometry/transform_util.h" +#include "ui/gfx/geometry/vector3d_f.h" + +namespace gfx { + +namespace { + +#define EXPECT_ROW1_EQ(a, b, c, d, transform) \ + EXPECT_FLOAT_EQ((a), (transform).matrix().get(0, 0)); \ + EXPECT_FLOAT_EQ((b), (transform).matrix().get(0, 1)); \ + EXPECT_FLOAT_EQ((c), (transform).matrix().get(0, 2)); \ + EXPECT_FLOAT_EQ((d), (transform).matrix().get(0, 3)); + +#define EXPECT_ROW2_EQ(a, b, c, d, transform) \ + EXPECT_FLOAT_EQ((a), (transform).matrix().get(1, 0)); \ + EXPECT_FLOAT_EQ((b), (transform).matrix().get(1, 1)); \ + EXPECT_FLOAT_EQ((c), (transform).matrix().get(1, 2)); \ + EXPECT_FLOAT_EQ((d), (transform).matrix().get(1, 3)); + +#define EXPECT_ROW3_EQ(a, b, c, d, transform) \ + EXPECT_FLOAT_EQ((a), (transform).matrix().get(2, 0)); \ + EXPECT_FLOAT_EQ((b), (transform).matrix().get(2, 1)); \ + EXPECT_FLOAT_EQ((c), (transform).matrix().get(2, 2)); \ + EXPECT_FLOAT_EQ((d), (transform).matrix().get(2, 3)); + +#define EXPECT_ROW4_EQ(a, b, c, d, transform) \ + EXPECT_FLOAT_EQ((a), (transform).matrix().get(3, 0)); \ + EXPECT_FLOAT_EQ((b), (transform).matrix().get(3, 1)); \ + EXPECT_FLOAT_EQ((c), (transform).matrix().get(3, 2)); \ + EXPECT_FLOAT_EQ((d), (transform).matrix().get(3, 3)); + +// Checking float values for equality close to zero is not robust using +// EXPECT_FLOAT_EQ (see gtest documentation). So, to verify rotation matrices, +// we must use a looser absolute error threshold in some places. +#define EXPECT_ROW1_NEAR(a, b, c, d, transform, errorThreshold) \ + EXPECT_NEAR((a), (transform).matrix().get(0, 0), (errorThreshold)); \ + EXPECT_NEAR((b), (transform).matrix().get(0, 1), (errorThreshold)); \ + EXPECT_NEAR((c), (transform).matrix().get(0, 2), (errorThreshold)); \ + EXPECT_NEAR((d), (transform).matrix().get(0, 3), (errorThreshold)); + +#define EXPECT_ROW2_NEAR(a, b, c, d, transform, errorThreshold) \ + EXPECT_NEAR((a), (transform).matrix().get(1, 0), (errorThreshold)); \ + EXPECT_NEAR((b), (transform).matrix().get(1, 1), (errorThreshold)); \ + EXPECT_NEAR((c), (transform).matrix().get(1, 2), (errorThreshold)); \ + EXPECT_NEAR((d), (transform).matrix().get(1, 3), (errorThreshold)); + +#define EXPECT_ROW3_NEAR(a, b, c, d, transform, errorThreshold) \ + EXPECT_NEAR((a), (transform).matrix().get(2, 0), (errorThreshold)); \ + EXPECT_NEAR((b), (transform).matrix().get(2, 1), (errorThreshold)); \ + EXPECT_NEAR((c), (transform).matrix().get(2, 2), (errorThreshold)); \ + EXPECT_NEAR((d), (transform).matrix().get(2, 3), (errorThreshold)); + +bool PointsAreNearlyEqual(const Point3F& lhs, const Point3F& rhs) { + float epsilon = 0.0001f; + return lhs.SquaredDistanceTo(rhs) < epsilon; +} + +bool MatricesAreNearlyEqual(const Transform& lhs, const Transform& rhs) { + float epsilon = 0.0001f; + for (int row = 0; row < 4; ++row) { + for (int col = 0; col < 4; ++col) { + if (std::abs(lhs.matrix().get(row, col) - rhs.matrix().get(row, col)) > + epsilon) + return false; + } + } + return true; +} + +void InitializeTestMatrix(Transform* transform) { + skia::Matrix44& matrix = transform->matrix(); + matrix.set(0, 0, 10.f); + matrix.set(1, 0, 11.f); + matrix.set(2, 0, 12.f); + matrix.set(3, 0, 13.f); + matrix.set(0, 1, 14.f); + matrix.set(1, 1, 15.f); + matrix.set(2, 1, 16.f); + matrix.set(3, 1, 17.f); + matrix.set(0, 2, 18.f); + matrix.set(1, 2, 19.f); + matrix.set(2, 2, 20.f); + matrix.set(3, 2, 21.f); + matrix.set(0, 3, 22.f); + matrix.set(1, 3, 23.f); + matrix.set(2, 3, 24.f); + matrix.set(3, 3, 25.f); + + // Sanity check + EXPECT_ROW1_EQ(10.0f, 14.0f, 18.0f, 22.0f, (*transform)); + EXPECT_ROW2_EQ(11.0f, 15.0f, 19.0f, 23.0f, (*transform)); + EXPECT_ROW3_EQ(12.0f, 16.0f, 20.0f, 24.0f, (*transform)); + EXPECT_ROW4_EQ(13.0f, 17.0f, 21.0f, 25.0f, (*transform)); +} + +void InitializeTestMatrix2(Transform* transform) { + skia::Matrix44& matrix = transform->matrix(); + matrix.set(0, 0, 30.f); + matrix.set(1, 0, 31.f); + matrix.set(2, 0, 32.f); + matrix.set(3, 0, 33.f); + matrix.set(0, 1, 34.f); + matrix.set(1, 1, 35.f); + matrix.set(2, 1, 36.f); + matrix.set(3, 1, 37.f); + matrix.set(0, 2, 38.f); + matrix.set(1, 2, 39.f); + matrix.set(2, 2, 40.f); + matrix.set(3, 2, 41.f); + matrix.set(0, 3, 42.f); + matrix.set(1, 3, 43.f); + matrix.set(2, 3, 44.f); + matrix.set(3, 3, 45.f); + + // Sanity check + EXPECT_ROW1_EQ(30.0f, 34.0f, 38.0f, 42.0f, (*transform)); + EXPECT_ROW2_EQ(31.0f, 35.0f, 39.0f, 43.0f, (*transform)); + EXPECT_ROW3_EQ(32.0f, 36.0f, 40.0f, 44.0f, (*transform)); + EXPECT_ROW4_EQ(33.0f, 37.0f, 41.0f, 45.0f, (*transform)); +} + +const SkScalar kApproxZero = std::numeric_limits::epsilon(); +const SkScalar kApproxOne = 1 - kApproxZero; + +void InitializeApproxIdentityMatrix(Transform* transform) { + skia::Matrix44& matrix = transform->matrix(); + matrix.set(0, 0, kApproxOne); + matrix.set(0, 1, kApproxZero); + matrix.set(0, 2, kApproxZero); + matrix.set(0, 3, kApproxZero); + + matrix.set(1, 0, kApproxZero); + matrix.set(1, 1, kApproxOne); + matrix.set(1, 2, kApproxZero); + matrix.set(1, 3, kApproxZero); + + matrix.set(2, 0, kApproxZero); + matrix.set(2, 1, kApproxZero); + matrix.set(2, 2, kApproxOne); + matrix.set(2, 3, kApproxZero); + + matrix.set(3, 0, kApproxZero); + matrix.set(3, 1, kApproxZero); + matrix.set(3, 2, kApproxZero); + matrix.set(3, 3, kApproxOne); +} + +#define ERROR_THRESHOLD 1e-7 +#define LOOSE_ERROR_THRESHOLD 1e-7 + +TEST(XFormTest, Equality) { + Transform lhs, rhs, interpolated; + rhs.matrix().set3x3(1, 2, 3, 4, 5, 6, 7, 8, 9); + interpolated = lhs; + for (int i = 0; i <= 100; ++i) { + for (int row = 0; row < 4; ++row) { + for (int col = 0; col < 4; ++col) { + float a = lhs.matrix().get(row, col); + float b = rhs.matrix().get(row, col); + float t = i / 100.0f; + interpolated.matrix().set(row, col, a + (b - a) * t); + } + } + if (i == 100) { + EXPECT_TRUE(rhs == interpolated); + } else { + EXPECT_TRUE(rhs != interpolated); + } + } + lhs = Transform(); + rhs = Transform(); + for (int i = 1; i < 100; ++i) { + lhs.MakeIdentity(); + rhs.MakeIdentity(); + lhs.Translate(i, i); + rhs.Translate(-i, -i); + EXPECT_TRUE(lhs != rhs); + rhs.Translate(2 * i, 2 * i); + EXPECT_TRUE(lhs == rhs); + } +} + +TEST(XFormTest, ConcatTranslate) { + static const struct TestCase { + int x1; + int y1; + float tx; + float ty; + int x2; + int y2; + } test_cases[] = { + {0, 0, 10.0f, 20.0f, 10, 20}, + {0, 0, -10.0f, -20.0f, 0, 0}, + {0, 0, -10.0f, -20.0f, -10, -20}, + {0, 0, std::numeric_limits::quiet_NaN(), + std::numeric_limits::quiet_NaN(), 10, 20}, + }; + + Transform xform; + for (const auto& value : test_cases) { + Transform translation; + translation.Translate(value.tx, value.ty); + xform = translation * xform; + Point3F p1(value.x1, value.y1, 0); + Point3F p2(value.x2, value.y2, 0); + xform.TransformPoint(&p1); + if (value.tx == value.tx && value.ty == value.ty) { + EXPECT_TRUE(PointsAreNearlyEqual(p1, p2)); + } + } +} + +TEST(XFormTest, ConcatScale) { + static const struct TestCase { + int before; + float scale; + int after; + } test_cases[] = {{1, 10.0f, 10}, + {1, .1f, 1}, + {1, 100.0f, 100}, + {1, -1.0f, -100}, + {1, std::numeric_limits::quiet_NaN(), 1}}; + + Transform xform; + for (const auto& value : test_cases) { + Transform scale; + scale.Scale(value.scale, value.scale); + xform = scale * xform; + Point3F p1(value.before, value.before, 0); + Point3F p2(value.after, value.after, 0); + xform.TransformPoint(&p1); + if (value.scale == value.scale) { + EXPECT_TRUE(PointsAreNearlyEqual(p1, p2)); + } + } +} + +TEST(XFormTest, ConcatRotate) { + static const struct TestCase { + int x1; + int y1; + float degrees; + int x2; + int y2; + } test_cases[] = {{1, 0, 90.0f, 0, 1}, + {1, 0, -90.0f, 1, 0}, + {1, 0, 90.0f, 0, 1}, + {1, 0, 360.0f, 0, 1}, + {1, 0, 0.0f, 0, 1}, + {1, 0, std::numeric_limits::quiet_NaN(), 1, 0}}; + + Transform xform; + for (const auto& value : test_cases) { + Transform rotation; + rotation.Rotate(value.degrees); + xform = rotation * xform; + Point3F p1(value.x1, value.y1, 0); + Point3F p2(value.x2, value.y2, 0); + xform.TransformPoint(&p1); + if (value.degrees == value.degrees) { + EXPECT_TRUE(PointsAreNearlyEqual(p1, p2)); + } + } +} + +TEST(XFormTest, SetTranslate) { + static const struct TestCase { + int x1; + int y1; + float tx; + float ty; + int x2; + int y2; + } test_cases[] = {{0, 0, 10.0f, 20.0f, 10, 20}, + {10, 20, 10.0f, 20.0f, 20, 40}, + {10, 20, 0.0f, 0.0f, 10, 20}, + {0, 0, std::numeric_limits::quiet_NaN(), + std::numeric_limits::quiet_NaN(), 0, 0}}; + + for (const auto& value : test_cases) { + for (int k = 0; k < 3; ++k) { + Point3F p0, p1, p2; + Transform xform; + switch (k) { + case 0: + p1.SetPoint(value.x1, 0, 0); + p2.SetPoint(value.x2, 0, 0); + xform.Translate(value.tx, 0.0); + break; + case 1: + p1.SetPoint(0, value.y1, 0); + p2.SetPoint(0, value.y2, 0); + xform.Translate(0.0, value.ty); + break; + case 2: + p1.SetPoint(value.x1, value.y1, 0); + p2.SetPoint(value.x2, value.y2, 0); + xform.Translate(value.tx, value.ty); + break; + } + p0 = p1; + xform.TransformPoint(&p1); + if (value.tx == value.tx && value.ty == value.ty) { + EXPECT_TRUE(PointsAreNearlyEqual(p1, p2)); + xform.TransformPointReverse(&p1); + EXPECT_TRUE(PointsAreNearlyEqual(p1, p0)); + } + } + } +} + +TEST(XFormTest, SetScale) { + static const struct TestCase { + int before; + float s; + int after; + } test_cases[] = { + {1, 10.0f, 10}, + {1, 1.0f, 1}, + {1, 0.0f, 0}, + {0, 10.0f, 0}, + {1, std::numeric_limits::quiet_NaN(), 0}, + }; + + for (const auto& value : test_cases) { + for (int k = 0; k < 3; ++k) { + Point3F p0, p1, p2; + Transform xform; + switch (k) { + case 0: + p1.SetPoint(value.before, 0, 0); + p2.SetPoint(value.after, 0, 0); + xform.Scale(value.s, 1.0); + break; + case 1: + p1.SetPoint(0, value.before, 0); + p2.SetPoint(0, value.after, 0); + xform.Scale(1.0, value.s); + break; + case 2: + p1.SetPoint(value.before, value.before, 0); + p2.SetPoint(value.after, value.after, 0); + xform.Scale(value.s, value.s); + break; + } + p0 = p1; + xform.TransformPoint(&p1); + if (value.s == value.s) { + EXPECT_TRUE(PointsAreNearlyEqual(p1, p2)); + if (value.s != 0.0f) { + xform.TransformPointReverse(&p1); + EXPECT_TRUE(PointsAreNearlyEqual(p1, p0)); + } + } + } + } +} + +TEST(XFormTest, SetRotate) { + static const struct SetRotateCase { + int x; + int y; + float degree; + int xprime; + int yprime; + } set_rotate_cases[] = {{100, 0, 90.0f, 0, 100}, + {0, 0, 90.0f, 0, 0}, + {0, 100, 90.0f, -100, 0}, + {0, 1, -90.0f, 1, 0}, + {100, 0, 0.0f, 100, 0}, + {0, 0, 0.0f, 0, 0}, + {0, 0, std::numeric_limits::quiet_NaN(), 0, 0}, + {100, 0, 360.0f, 100, 0}}; + + for (const auto& value : set_rotate_cases) { + Point3F p0; + Point3F p1(value.x, value.y, 0); + Point3F p2(value.xprime, value.yprime, 0); + p0 = p1; + Transform xform; + xform.Rotate(value.degree); + // just want to make sure that we don't crash in the case of NaN. + if (value.degree == value.degree) { + xform.TransformPoint(&p1); + EXPECT_TRUE(PointsAreNearlyEqual(p1, p2)); + xform.TransformPointReverse(&p1); + EXPECT_TRUE(PointsAreNearlyEqual(p1, p0)); + } + } +} + +// 2D tests +TEST(XFormTest, ConcatTranslate2D) { + static const struct TestCase { + int x1; + int y1; + float tx; + float ty; + int x2; + int y2; + } test_cases[] = { + {0, 0, 10.0f, 20.0f, 10, 20}, + {0, 0, -10.0f, -20.0f, 0, 0}, + {0, 0, -10.0f, -20.0f, -10, -20}, + }; + + Transform xform; + for (const auto& value : test_cases) { + Transform translation; + translation.Translate(value.tx, value.ty); + xform = translation * xform; + Point p1(value.x1, value.y1); + Point p2(value.x2, value.y2); + xform.TransformPoint(&p1); + if (value.tx == value.tx && value.ty == value.ty) { + EXPECT_EQ(p1.x(), p2.x()); + EXPECT_EQ(p1.y(), p2.y()); + } + } +} + +TEST(XFormTest, ConcatScale2D) { + static const struct TestCase { + int before; + float scale; + int after; + } test_cases[] = { + {1, 10.0f, 10}, + {1, .1f, 1}, + {1, 100.0f, 100}, + {1, -1.0f, -100}, + }; + + Transform xform; + for (const auto& value : test_cases) { + Transform scale; + scale.Scale(value.scale, value.scale); + xform = scale * xform; + Point p1(value.before, value.before); + Point p2(value.after, value.after); + xform.TransformPoint(&p1); + if (value.scale == value.scale) { + EXPECT_EQ(p1.x(), p2.x()); + EXPECT_EQ(p1.y(), p2.y()); + } + } +} + +TEST(XFormTest, ConcatRotate2D) { + static const struct TestCase { + int x1; + int y1; + float degrees; + int x2; + int y2; + } test_cases[] = { + {1, 0, 90.0f, 0, 1}, {1, 0, -90.0f, 1, 0}, {1, 0, 90.0f, 0, 1}, + {1, 0, 360.0f, 0, 1}, {1, 0, 0.0f, 0, 1}, + }; + + Transform xform; + for (const auto& value : test_cases) { + Transform rotation; + rotation.Rotate(value.degrees); + xform = rotation * xform; + Point p1(value.x1, value.y1); + Point p2(value.x2, value.y2); + xform.TransformPoint(&p1); + if (value.degrees == value.degrees) { + EXPECT_EQ(p1.x(), p2.x()); + EXPECT_EQ(p1.y(), p2.y()); + } + } +} + +TEST(XFormTest, SetTranslate2D) { + static const struct TestCase { + int x1; + int y1; + float tx; + float ty; + int x2; + int y2; + } test_cases[] = { + {0, 0, 10.0f, 20.0f, 10, 20}, + {10, 20, 10.0f, 20.0f, 20, 40}, + {10, 20, 0.0f, 0.0f, 10, 20}, + }; + + for (const auto& value : test_cases) { + for (int j = -1; j < 2; ++j) { + for (int k = 0; k < 3; ++k) { + float epsilon = 0.0001f; + Point p0, p1, p2; + Transform xform; + switch (k) { + case 0: + p1.SetPoint(value.x1, 0); + p2.SetPoint(value.x2, 0); + xform.Translate(value.tx + j * epsilon, 0.0); + break; + case 1: + p1.SetPoint(0, value.y1); + p2.SetPoint(0, value.y2); + xform.Translate(0.0, value.ty + j * epsilon); + break; + case 2: + p1.SetPoint(value.x1, value.y1); + p2.SetPoint(value.x2, value.y2); + xform.Translate(value.tx + j * epsilon, value.ty + j * epsilon); + break; + } + p0 = p1; + xform.TransformPoint(&p1); + if (value.tx == value.tx && value.ty == value.ty) { + EXPECT_EQ(p1.x(), p2.x()); + EXPECT_EQ(p1.y(), p2.y()); + xform.TransformPointReverse(&p1); + EXPECT_EQ(p1.x(), p0.x()); + EXPECT_EQ(p1.y(), p0.y()); + } + } + } + } +} + +TEST(XFormTest, SetScale2D) { + static const struct TestCase { + int before; + float s; + int after; + } test_cases[] = { + {1, 10.0f, 10}, + {1, 1.0f, 1}, + {1, 0.0f, 0}, + {0, 10.0f, 0}, + }; + + for (const auto& value : test_cases) { + for (int j = -1; j < 2; ++j) { + for (int k = 0; k < 3; ++k) { + float epsilon = 0.0001f; + Point p0, p1, p2; + Transform xform; + switch (k) { + case 0: + p1.SetPoint(value.before, 0); + p2.SetPoint(value.after, 0); + xform.Scale(value.s + j * epsilon, 1.0); + break; + case 1: + p1.SetPoint(0, value.before); + p2.SetPoint(0, value.after); + xform.Scale(1.0, value.s + j * epsilon); + break; + case 2: + p1.SetPoint(value.before, value.before); + p2.SetPoint(value.after, value.after); + xform.Scale(value.s + j * epsilon, value.s + j * epsilon); + break; + } + p0 = p1; + xform.TransformPoint(&p1); + if (value.s == value.s) { + EXPECT_EQ(p1.x(), p2.x()); + EXPECT_EQ(p1.y(), p2.y()); + if (value.s != 0.0f) { + xform.TransformPointReverse(&p1); + EXPECT_EQ(p1.x(), p0.x()); + EXPECT_EQ(p1.y(), p0.y()); + } + } + } + } + } +} + +TEST(XFormTest, SetRotate2D) { + static const struct SetRotateCase { + int x; + int y; + float degree; + int xprime; + int yprime; + } set_rotate_cases[] = {{100, 0, 90.0f, 0, 100}, + {0, 0, 90.0f, 0, 0}, + {0, 100, 90.0f, -100, 0}, + {0, 1, -90.0f, 1, 0}, + {100, 0, 0.0f, 100, 0}, + {0, 0, 0.0f, 0, 0}, + {0, 0, std::numeric_limits::quiet_NaN(), 0, 0}, + {100, 0, 360.0f, 100, 0}}; + + for (const auto& value : set_rotate_cases) { + for (int j = 1; j >= -1; --j) { + float epsilon = 0.1f; + Point pt(value.x, value.y); + Transform xform; + // should be invariant to small floating point errors. + xform.Rotate(value.degree + j * epsilon); + // just want to make sure that we don't crash in the case of NaN. + if (value.degree == value.degree) { + xform.TransformPoint(&pt); + EXPECT_EQ(value.xprime, pt.x()); + EXPECT_EQ(value.yprime, pt.y()); + xform.TransformPointReverse(&pt); + EXPECT_EQ(pt.x(), value.x); + EXPECT_EQ(pt.y(), value.y); + } + } + } +} + +TEST(XFormTest, TransformPointWithExtremePerspective) { + Point3F point(1.f, 1.f, 1.f); + Transform perspective; + perspective.ApplyPerspectiveDepth(1.f); + Point3F transformed = point; + perspective.TransformPoint(&transformed); + EXPECT_EQ(point.ToString(), transformed.ToString()); + + transformed = point; + perspective.MakeIdentity(); + perspective.ApplyPerspectiveDepth(1.1f); + perspective.TransformPoint(&transformed); + EXPECT_FLOAT_EQ(11.f, transformed.x()); + EXPECT_FLOAT_EQ(11.f, transformed.y()); + EXPECT_FLOAT_EQ(11.f, transformed.z()); +} + +TEST(XFormTest, BlendTranslate) { + Transform from; + for (int i = -5; i < 15; ++i) { + Transform to; + to.Translate3d(1, 1, 1); + double t = i / 9.0; + EXPECT_TRUE(to.Blend(from, t)); + EXPECT_FLOAT_EQ(t, to.matrix().get(0, 3)); + EXPECT_FLOAT_EQ(t, to.matrix().get(1, 3)); + EXPECT_FLOAT_EQ(t, to.matrix().get(2, 3)); + } +} + +TEST(XFormTest, BlendRotate) { + Vector3dF axes[] = {Vector3dF(1, 0, 0), Vector3dF(0, 1, 0), + Vector3dF(0, 0, 1), Vector3dF(1, 1, 1)}; + Transform from; + for (const auto& axis : axes) { + for (int i = -5; i < 15; ++i) { + Transform to; + to.RotateAbout(axis, 90); + double t = i / 9.0; + EXPECT_TRUE(to.Blend(from, t)); + + Transform expected; + expected.RotateAbout(axis, 90 * t); + + EXPECT_TRUE(MatricesAreNearlyEqual(expected, to)); + } + } +} + +TEST(XFormTest, CanBlend180DegreeRotation) { + Vector3dF axes[] = {Vector3dF(1, 0, 0), Vector3dF(0, 1, 0), + Vector3dF(0, 0, 1), Vector3dF(1, 1, 1)}; + Transform from; + for (const auto& axis : axes) { + for (int i = -5; i < 15; ++i) { + Transform to; + to.RotateAbout(axis, 180.0); + double t = i / 9.0; + EXPECT_TRUE(to.Blend(from, t)); + + // A 180 degree rotation is exactly opposite on the sphere, therefore + // either great circle arc to it is equivalent (and numerical precision + // will determine which is closer). Test both directions. + Transform expected1; + expected1.RotateAbout(axis, 180.0 * t); + Transform expected2; + expected2.RotateAbout(axis, -180.0 * t); + + EXPECT_TRUE(MatricesAreNearlyEqual(expected1, to) || + MatricesAreNearlyEqual(expected2, to)) + << "axis: " << axis.ToString() << ", i: " << i; + } + } +} + +TEST(XFormTest, BlendScale) { + Transform from; + for (int i = -5; i < 15; ++i) { + Transform to; + to.Scale3d(5, 4, 3); + double s1 = i / 9.0; + double s2 = 1 - s1; + EXPECT_TRUE(to.Blend(from, s1)); + EXPECT_FLOAT_EQ(5 * s1 + s2, to.matrix().get(0, 0)) << "i: " << i; + EXPECT_FLOAT_EQ(4 * s1 + s2, to.matrix().get(1, 1)) << "i: " << i; + EXPECT_FLOAT_EQ(3 * s1 + s2, to.matrix().get(2, 2)) << "i: " << i; + } +} + +TEST(XFormTest, BlendSkew) { + Transform from; + for (int i = 0; i < 2; ++i) { + Transform to; + to.Skew(10, 5); + double t = i; + Transform expected; + expected.Skew(t * 10, t * 5); + EXPECT_TRUE(to.Blend(from, t)); + EXPECT_TRUE(MatricesAreNearlyEqual(expected, to)); + } +} + +TEST(XFormTest, ExtrapolateSkew) { + Transform from; + for (int i = -1; i < 2; ++i) { + Transform to; + to.Skew(20, 0); + double t = i; + Transform expected; + expected.Skew(t * 20, t * 0); + EXPECT_TRUE(to.Blend(from, t)); + EXPECT_TRUE(MatricesAreNearlyEqual(expected, to)); + } +} + +TEST(XFormTest, BlendPerspective) { + Transform from; + from.ApplyPerspectiveDepth(200); + for (int i = -1; i < 3; ++i) { + Transform to; + to.ApplyPerspectiveDepth(800); + double t = i; + double depth = 1.0 / ((1.0 / 200) * (1.0 - t) + (1.0 / 800) * t); + Transform expected; + expected.ApplyPerspectiveDepth(depth); + EXPECT_TRUE(to.Blend(from, t)); + EXPECT_TRUE(MatricesAreNearlyEqual(expected, to)); + } +} + +TEST(XFormTest, BlendIdentity) { + Transform from; + Transform to; + EXPECT_TRUE(to.Blend(from, 0.5)); + EXPECT_EQ(to, from); +} + +TEST(XFormTest, CannotBlendSingularMatrix) { + Transform from; + Transform to; + to.matrix().set(1, 1, 0); + EXPECT_FALSE(to.Blend(from, 0.5)); +} + +TEST(XFormTest, VerifyBlendForTranslation) { + Transform from; + from.Translate3d(100.0, 200.0, 100.0); + + Transform to; + + to.Translate3d(200.0, 100.0, 300.0); + to.Blend(from, 0.0); + EXPECT_EQ(from, to); + + to = Transform(); + to.Translate3d(200.0, 100.0, 300.0); + to.Blend(from, 0.25); + EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 125.0f, to); + EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 175.0f, to); + EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 150.0f, to); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to); + + to = Transform(); + to.Translate3d(200.0, 100.0, 300.0); + to.Blend(from, 0.5); + EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 150.0f, to); + EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 150.0f, to); + EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 200.0f, to); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to); + + to = Transform(); + to.Translate3d(200.0, 100.0, 300.0); + to.Blend(from, 1.0); + EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 200.0f, to); + EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 100.0f, to); + EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 300.0f, to); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to); +} + +TEST(XFormTest, VerifyBlendForScale) { + Transform from; + from.Scale3d(100.0, 200.0, 100.0); + + Transform to; + + to.Scale3d(200.0, 100.0, 300.0); + to.Blend(from, 0.0); + EXPECT_EQ(from, to); + + to = Transform(); + to.Scale3d(200.0, 100.0, 300.0); + to.Blend(from, 0.25); + EXPECT_ROW1_EQ(125.0f, 0.0f, 0.0f, 0.0f, to); + EXPECT_ROW2_EQ(0.0f, 175.0f, 0.0f, 0.0f, to); + EXPECT_ROW3_EQ(0.0f, 0.0f, 150.0f, 0.0f, to); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to); + + to = Transform(); + to.Scale3d(200.0, 100.0, 300.0); + to.Blend(from, 0.5); + EXPECT_ROW1_EQ(150.0f, 0.0f, 0.0f, 0.0f, to); + EXPECT_ROW2_EQ(0.0f, 150.0f, 0.0f, 0.0f, to); + EXPECT_ROW3_EQ(0.0f, 0.0f, 200.0f, 0.0f, to); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to); + + to = Transform(); + to.Scale3d(200.0, 100.0, 300.0); + to.Blend(from, 1.0); + EXPECT_ROW1_EQ(200.0f, 0.0f, 0.0f, 0.0f, to); + EXPECT_ROW2_EQ(0.0f, 100.0f, 0.0f, 0.0f, to); + EXPECT_ROW3_EQ(0.0f, 0.0f, 300.0f, 0.0f, to); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to); +} + +TEST(XFormTest, VerifyBlendForSkew) { + // Along X axis only + Transform from; + from.Skew(0.0, 0.0); + + Transform to; + + to.Skew(45.0, 0.0); + to.Blend(from, 0.0); + EXPECT_EQ(from, to); + + to = Transform(); + to.Skew(45.0, 0.0); + to.Blend(from, 0.5); + EXPECT_ROW1_EQ(1.0f, 0.5f, 0.0f, 0.0f, to); + EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 0.0f, to); + EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, to); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to); + + to = Transform(); + to.Skew(45.0, 0.0); + to.Blend(from, 0.25); + EXPECT_ROW1_EQ(1.0f, 0.25f, 0.0f, 0.0f, to); + EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 0.0f, to); + EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, to); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to); + + to = Transform(); + to.Skew(45.0, 0.0); + to.Blend(from, 1.0); + EXPECT_ROW1_EQ(1.0f, 1.0f, 0.0f, 0.0f, to); + EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 0.0f, to); + EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, to); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to); + + // NOTE CAREFULLY: Decomposition of skew and rotation terms of the matrix + // is inherently underconstrained, and so it does not always compute the + // originally intended skew parameters. The current implementation uses QR + // decomposition, which decomposes the shear into a rotation + non-uniform + // scale. + // + // It is unlikely that the decomposition implementation will need to change + // very often, so to get any test coverage, the compromise is to verify the + // exact matrix that the.Blend() operation produces. + // + // This problem also potentially exists for skew along the X axis, but the + // current QR decomposition implementation just happens to decompose those + // test matrices intuitively. + // + // Unfortunately, this case suffers from uncomfortably large precision + // error. + + from = Transform(); + from.Skew(0.0, 0.0); + + to = Transform(); + + to.Skew(0.0, 45.0); + to.Blend(from, 0.0); + EXPECT_EQ(from, to); + + to = Transform(); + to.Skew(0.0, 45.0); + to.Blend(from, 0.25); + EXPECT_LT(1.0, to.matrix().get(0, 0)); + EXPECT_GT(1.5, to.matrix().get(0, 0)); + EXPECT_LT(0.0, to.matrix().get(0, 1)); + EXPECT_GT(0.5, to.matrix().get(0, 1)); + EXPECT_FLOAT_EQ(0.0, to.matrix().get(0, 2)); + EXPECT_FLOAT_EQ(0.0, to.matrix().get(0, 3)); + + EXPECT_LT(0.0, to.matrix().get(1, 0)); + EXPECT_GT(0.5, to.matrix().get(1, 0)); + EXPECT_LT(0.0, to.matrix().get(1, 1)); + EXPECT_GT(1.0, to.matrix().get(1, 1)); + EXPECT_FLOAT_EQ(0.0, to.matrix().get(1, 2)); + EXPECT_FLOAT_EQ(0.0, to.matrix().get(1, 3)); + + EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, to); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to); + + to = Transform(); + to.Skew(0.0, 45.0); + to.Blend(from, 0.5); + + EXPECT_LT(1.0, to.matrix().get(0, 0)); + EXPECT_GT(1.5, to.matrix().get(0, 0)); + EXPECT_LT(0.0, to.matrix().get(0, 1)); + EXPECT_GT(0.5, to.matrix().get(0, 1)); + EXPECT_FLOAT_EQ(0.0, to.matrix().get(0, 2)); + EXPECT_FLOAT_EQ(0.0, to.matrix().get(0, 3)); + + EXPECT_LT(0.0, to.matrix().get(1, 0)); + EXPECT_GT(1.0, to.matrix().get(1, 0)); + EXPECT_LT(0.0, to.matrix().get(1, 1)); + EXPECT_GT(1.0, to.matrix().get(1, 1)); + EXPECT_FLOAT_EQ(0.0, to.matrix().get(1, 2)); + EXPECT_FLOAT_EQ(0.0, to.matrix().get(1, 3)); + + EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, to); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to); + + to = Transform(); + to.Skew(0.0, 45.0); + to.Blend(from, 1.0); + EXPECT_ROW1_NEAR(1.0, 0.0, 0.0, 0.0, to, LOOSE_ERROR_THRESHOLD); + EXPECT_ROW2_NEAR(1.0, 1.0, 0.0, 0.0, to, LOOSE_ERROR_THRESHOLD); + EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, to); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to); +} + +TEST(XFormTest, VerifyBlendForRotationAboutX) { + // Even though.Blending uses quaternions, axis-aligned rotations should. + // Blend the same with quaternions or Euler angles. So we can test + // rotation.Blending by comparing against manually specified matrices from + // Euler angles. + + Transform from; + from.RotateAbout(Vector3dF(1.0, 0.0, 0.0), 0.0); + + Transform to; + + to.RotateAbout(Vector3dF(1.0, 0.0, 0.0), 90.0); + to.Blend(from, 0.0); + EXPECT_EQ(from, to); + + double expectedRotationAngle = gfx::DegToRad(22.5); + to = Transform(); + to.RotateAbout(Vector3dF(1.0, 0.0, 0.0), 90.0); + to.Blend(from, 0.25); + EXPECT_ROW1_NEAR(1.0, 0.0, 0.0, 0.0, to, ERROR_THRESHOLD); + EXPECT_ROW2_NEAR(0.0, std::cos(expectedRotationAngle), + -std::sin(expectedRotationAngle), 0.0, to, ERROR_THRESHOLD); + EXPECT_ROW3_NEAR(0.0, std::sin(expectedRotationAngle), + std::cos(expectedRotationAngle), 0.0, to, ERROR_THRESHOLD); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to); + + expectedRotationAngle = gfx::DegToRad(45.0); + to = Transform(); + to.RotateAbout(Vector3dF(1.0, 0.0, 0.0), 90.0); + to.Blend(from, 0.5); + EXPECT_ROW1_NEAR(1.0, 0.0, 0.0, 0.0, to, ERROR_THRESHOLD); + EXPECT_ROW2_NEAR(0.0, std::cos(expectedRotationAngle), + -std::sin(expectedRotationAngle), 0.0, to, ERROR_THRESHOLD); + EXPECT_ROW3_NEAR(0.0, std::sin(expectedRotationAngle), + std::cos(expectedRotationAngle), 0.0, to, ERROR_THRESHOLD); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to); + + to = Transform(); + to.RotateAbout(Vector3dF(1.0, 0.0, 0.0), 90.0); + to.Blend(from, 1.0); + EXPECT_ROW1_NEAR(1.0, 0.0, 0.0, 0.0, to, ERROR_THRESHOLD); + EXPECT_ROW2_NEAR(0.0, 0.0, -1.0, 0.0, to, ERROR_THRESHOLD); + EXPECT_ROW3_NEAR(0.0, 1.0, 0.0, 0.0, to, ERROR_THRESHOLD); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to); +} + +TEST(XFormTest, VerifyBlendForRotationAboutY) { + Transform from; + from.RotateAbout(Vector3dF(0.0, 1.0, 0.0), 0.0); + + Transform to; + + to.RotateAbout(Vector3dF(0.0, 1.0, 0.0), 90.0); + to.Blend(from, 0.0); + EXPECT_EQ(from, to); + + double expectedRotationAngle = gfx::DegToRad(22.5); + to = Transform(); + to.RotateAbout(Vector3dF(0.0, 1.0, 0.0), 90.0); + to.Blend(from, 0.25); + EXPECT_ROW1_NEAR(std::cos(expectedRotationAngle), 0.0, + std::sin(expectedRotationAngle), 0.0, to, ERROR_THRESHOLD); + EXPECT_ROW2_NEAR(0.0, 1.0, 0.0, 0.0, to, ERROR_THRESHOLD); + EXPECT_ROW3_NEAR(-std::sin(expectedRotationAngle), 0.0, + std::cos(expectedRotationAngle), 0.0, to, ERROR_THRESHOLD); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to); + + expectedRotationAngle = gfx::DegToRad(45.0); + to = Transform(); + to.RotateAbout(Vector3dF(0.0, 1.0, 0.0), 90.0); + to.Blend(from, 0.5); + EXPECT_ROW1_NEAR(std::cos(expectedRotationAngle), 0.0, + std::sin(expectedRotationAngle), 0.0, to, ERROR_THRESHOLD); + EXPECT_ROW2_NEAR(0.0, 1.0, 0.0, 0.0, to, ERROR_THRESHOLD); + EXPECT_ROW3_NEAR(-std::sin(expectedRotationAngle), 0.0, + std::cos(expectedRotationAngle), 0.0, to, ERROR_THRESHOLD); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to); + + to = Transform(); + to.RotateAbout(Vector3dF(0.0, 1.0, 0.0), 90.0); + to.Blend(from, 1.0); + EXPECT_ROW1_NEAR(0.0, 0.0, 1.0, 0.0, to, ERROR_THRESHOLD); + EXPECT_ROW2_NEAR(0.0, 1.0, 0.0, 0.0, to, ERROR_THRESHOLD); + EXPECT_ROW3_NEAR(-1.0, 0.0, 0.0, 0.0, to, ERROR_THRESHOLD); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to); +} + +TEST(XFormTest, VerifyBlendForRotationAboutZ) { + Transform from; + from.RotateAbout(Vector3dF(0.0, 0.0, 1.0), 0.0); + + Transform to; + + to.RotateAbout(Vector3dF(0.0, 0.0, 1.0), 90.0); + to.Blend(from, 0.0); + EXPECT_EQ(from, to); + + double expectedRotationAngle = gfx::DegToRad(22.5); + to = Transform(); + to.RotateAbout(Vector3dF(0.0, 0.0, 1.0), 90.0); + to.Blend(from, 0.25); + EXPECT_ROW1_NEAR(std::cos(expectedRotationAngle), + -std::sin(expectedRotationAngle), 0.0, 0.0, to, + ERROR_THRESHOLD); + EXPECT_ROW2_NEAR(std::sin(expectedRotationAngle), + std::cos(expectedRotationAngle), 0.0, 0.0, to, + ERROR_THRESHOLD); + EXPECT_ROW3_NEAR(0.0, 0.0, 1.0, 0.0, to, ERROR_THRESHOLD); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to); + + expectedRotationAngle = gfx::DegToRad(45.0); + to = Transform(); + to.RotateAbout(Vector3dF(0.0, 0.0, 1.0), 90.0); + to.Blend(from, 0.5); + EXPECT_ROW1_NEAR(std::cos(expectedRotationAngle), + -std::sin(expectedRotationAngle), 0.0, 0.0, to, + ERROR_THRESHOLD); + EXPECT_ROW2_NEAR(std::sin(expectedRotationAngle), + std::cos(expectedRotationAngle), 0.0, 0.0, to, + ERROR_THRESHOLD); + EXPECT_ROW3_NEAR(0.0, 0.0, 1.0, 0.0, to, ERROR_THRESHOLD); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to); + + to = Transform(); + to.RotateAbout(Vector3dF(0.0, 0.0, 1.0), 90.0); + to.Blend(from, 1.0); + EXPECT_ROW1_NEAR(0.0, -1.0, 0.0, 0.0, to, ERROR_THRESHOLD); + EXPECT_ROW2_NEAR(1.0, 0.0, 0.0, 0.0, to, ERROR_THRESHOLD); + EXPECT_ROW3_NEAR(0.0, 0.0, 1.0, 0.0, to, ERROR_THRESHOLD); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to); +} + +TEST(XFormTest, VerifyBlendForCompositeTransform) { + // Verify that the.Blending was done with a decomposition in correct order + // by blending a composite transform. Using matrix x vector notation + // (Ax = b, where x is column vector), the ordering should be: + // perspective * translation * rotation * skew * scale + // + // It is not as important (or meaningful) to check intermediate + // interpolations; order of operations will be tested well enough by the + // end cases that are easier to specify. + + Transform from; + Transform to; + + Transform expectedEndOfAnimation; + expectedEndOfAnimation.ApplyPerspectiveDepth(1.0); + expectedEndOfAnimation.Translate3d(10.0, 20.0, 30.0); + expectedEndOfAnimation.RotateAbout(Vector3dF(0.0, 0.0, 1.0), 25.0); + expectedEndOfAnimation.Skew(0.0, 45.0); + expectedEndOfAnimation.Scale3d(6.0, 7.0, 8.0); + + to = expectedEndOfAnimation; + to.Blend(from, 0.0); + EXPECT_EQ(from, to); + + to = expectedEndOfAnimation; + // We short circuit if blend is >= 1, so to check the numerics, we will + // check that we get close to what we expect when we're nearly done + // interpolating. + to.Blend(from, .99999f); + + // Recomposing the matrix results in a normalized matrix, so to verify we + // need to normalize the expectedEndOfAnimation before comparing elements. + // Normalizing means dividing everything by expectedEndOfAnimation.m44(). + Transform normalizedExpectedEndOfAnimation = expectedEndOfAnimation; + Transform normalizationMatrix; + normalizationMatrix.matrix().set( + 0.0, 0.0, + SkDoubleToScalar(1 / expectedEndOfAnimation.matrix().get(3.0, 3.0))); + normalizationMatrix.matrix().set( + 1.0, 1.0, + SkDoubleToScalar(1 / expectedEndOfAnimation.matrix().get(3.0, 3.0))); + normalizationMatrix.matrix().set( + 2.0, 2.0, + SkDoubleToScalar(1 / expectedEndOfAnimation.matrix().get(3.0, 3.0))); + normalizationMatrix.matrix().set( + 3.0, 3.0, + SkDoubleToScalar(1 / expectedEndOfAnimation.matrix().get(3.0, 3.0))); + normalizedExpectedEndOfAnimation.PreconcatTransform(normalizationMatrix); + + EXPECT_TRUE(MatricesAreNearlyEqual(normalizedExpectedEndOfAnimation, to)); +} + +TEST(XFormTest, DecomposedTransformCtor) { + DecomposedTransform decomp; + for (int i = 0; i < 3; ++i) { + EXPECT_EQ(0.0, decomp.translate[i]); + EXPECT_EQ(1.0, decomp.scale[i]); + EXPECT_EQ(0.0, decomp.skew[i]); + EXPECT_EQ(0.0, decomp.perspective[i]); + } + EXPECT_EQ(1.0, decomp.perspective[3]); + + EXPECT_EQ(0.0, decomp.quaternion.x()); + EXPECT_EQ(0.0, decomp.quaternion.y()); + EXPECT_EQ(0.0, decomp.quaternion.z()); + EXPECT_EQ(1.0, decomp.quaternion.w()); + + Transform identity; + Transform composed = ComposeTransform(decomp); + EXPECT_TRUE(MatricesAreNearlyEqual(identity, composed)); +} + +TEST(XFormTest, FactorTRS) { + for (int degrees = 0; degrees < 180; ++degrees) { + // build a transformation matrix. + gfx::Transform transform; + transform.Translate(degrees * 2, -degrees * 3); + transform.Rotate(degrees); + transform.Scale(degrees + 1, 2 * degrees + 1); + + // factor the matrix + DecomposedTransform decomp; + bool success = DecomposeTransform(&decomp, transform); + EXPECT_TRUE(success); + EXPECT_FLOAT_EQ(decomp.translate[0], degrees * 2); + EXPECT_FLOAT_EQ(decomp.translate[1], -degrees * 3); + double rotation = + gfx::RadToDeg(std::acos(double{decomp.quaternion.w()}) * 2); + while (rotation < 0.0) + rotation += 360.0; + while (rotation > 360.0) + rotation -= 360.0; + + const float epsilon = 0.00015f; + EXPECT_NEAR(rotation, degrees, epsilon); + EXPECT_NEAR(decomp.scale[0], degrees + 1, epsilon); + EXPECT_NEAR(decomp.scale[1], 2 * degrees + 1, epsilon); + } +} + +TEST(XFormTest, DecomposeTransform) { + for (float scale = 0.001f; scale < 2.0f; scale += 0.001f) { + gfx::Transform transform; + transform.Scale(scale, scale); + EXPECT_TRUE(transform.Preserves2dAxisAlignment()); + + DecomposedTransform decomp; + bool success = DecomposeTransform(&decomp, transform); + EXPECT_TRUE(success); + + gfx::Transform compose_transform = ComposeTransform(decomp); + EXPECT_TRUE(compose_transform.Preserves2dAxisAlignment()); + } +} + +TEST(XFormTest, IntegerTranslation) { + gfx::Transform transform; + EXPECT_TRUE(transform.IsIdentityOrIntegerTranslation()); + + transform.Translate3d(1, 2, 3); + EXPECT_TRUE(transform.IsIdentityOrIntegerTranslation()); + + transform.MakeIdentity(); + transform.Translate3d(-1, -2, -3); + EXPECT_TRUE(transform.IsIdentityOrIntegerTranslation()); + + transform.MakeIdentity(); + transform.Translate3d(4.5f, 0, 0); + EXPECT_FALSE(transform.IsIdentityOrIntegerTranslation()); + + transform.MakeIdentity(); + transform.Translate3d(0, -6.7f, 0); + EXPECT_FALSE(transform.IsIdentityOrIntegerTranslation()); + + transform.MakeIdentity(); + transform.Translate3d(0, 0, 8.9f); + EXPECT_FALSE(transform.IsIdentityOrIntegerTranslation()); + + float max_int = static_cast(std::numeric_limits::max()); + transform.MakeIdentity(); + transform.Translate3d(0, 0, max_int + 1000.5f); + EXPECT_FALSE(transform.IsIdentityOrIntegerTranslation()); + + float max_float = std::numeric_limits::max(); + transform.MakeIdentity(); + transform.Translate3d(0, 0, max_float - 0.5f); + EXPECT_FALSE(transform.IsIdentityOrIntegerTranslation()); +} + +TEST(XFormTest, verifyMatrixInversion) { + { + // Invert a translation + gfx::Transform translation; + translation.Translate3d(2.0, 3.0, 4.0); + EXPECT_TRUE(translation.IsInvertible()); + + gfx::Transform inverse_translation; + bool is_invertible = translation.GetInverse(&inverse_translation); + EXPECT_TRUE(is_invertible); + EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, -2.0f, inverse_translation); + EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, -3.0f, inverse_translation); + EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, -4.0f, inverse_translation); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, inverse_translation); + } + + { + // Invert a non-uniform scale + gfx::Transform scale; + scale.Scale3d(4.0, 10.0, 100.0); + EXPECT_TRUE(scale.IsInvertible()); + + gfx::Transform inverse_scale; + bool is_invertible = scale.GetInverse(&inverse_scale); + EXPECT_TRUE(is_invertible); + EXPECT_ROW1_EQ(0.25f, 0.0f, 0.0f, 0.0f, inverse_scale); + EXPECT_ROW2_EQ(0.0f, 0.1f, 0.0f, 0.0f, inverse_scale); + EXPECT_ROW3_EQ(0.0f, 0.0f, 0.01f, 0.0f, inverse_scale); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, inverse_scale); + } + + { + // Try to invert a matrix that is not invertible. + // The inverse() function should reset the output matrix to identity. + gfx::Transform uninvertible; + uninvertible.matrix().set(0, 0, 0.f); + uninvertible.matrix().set(1, 1, 0.f); + uninvertible.matrix().set(2, 2, 0.f); + uninvertible.matrix().set(3, 3, 0.f); + EXPECT_FALSE(uninvertible.IsInvertible()); + + gfx::Transform inverse_of_uninvertible; + + // Add a scale just to more easily ensure that inverse_of_uninvertible is + // reset to identity. + inverse_of_uninvertible.Scale3d(4.0, 10.0, 100.0); + + bool is_invertible = uninvertible.GetInverse(&inverse_of_uninvertible); + EXPECT_FALSE(is_invertible); + EXPECT_TRUE(inverse_of_uninvertible.IsIdentity()); + EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 0.0f, inverse_of_uninvertible); + EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 0.0f, inverse_of_uninvertible); + EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, inverse_of_uninvertible); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, inverse_of_uninvertible); + } +} + +TEST(XFormTest, verifyBackfaceVisibilityBasicCases) { + Transform transform; + + transform.MakeIdentity(); + EXPECT_FALSE(transform.IsBackFaceVisible()); + + transform.MakeIdentity(); + transform.RotateAboutYAxis(80.0); + EXPECT_FALSE(transform.IsBackFaceVisible()); + + transform.MakeIdentity(); + transform.RotateAboutYAxis(100.0); + EXPECT_TRUE(transform.IsBackFaceVisible()); + + // Edge case, 90 degree rotation should return false. + transform.MakeIdentity(); + transform.RotateAboutYAxis(90.0); + EXPECT_FALSE(transform.IsBackFaceVisible()); +} + +TEST(XFormTest, verifyBackfaceVisibilityForPerspective) { + Transform layer_space_to_projection_plane; + + // This tests if IsBackFaceVisible works properly under perspective + // transforms. Specifically, layers that may have their back face visible in + // orthographic projection, may not actually have back face visible under + // perspective projection. + + // Case 1: Layer is rotated by slightly more than 90 degrees, at the center + // of the perspective projection. In this case, the layer's back-side + // is visible to the camera. + layer_space_to_projection_plane.MakeIdentity(); + layer_space_to_projection_plane.ApplyPerspectiveDepth(1.0); + layer_space_to_projection_plane.Translate3d(0.0, 0.0, 0.0); + layer_space_to_projection_plane.RotateAboutYAxis(100.0); + EXPECT_TRUE(layer_space_to_projection_plane.IsBackFaceVisible()); + + // Case 2: Layer is rotated by slightly more than 90 degrees, but shifted off + // to the side of the camera. Because of the wide field-of-view, the + // layer's front side is still visible. + // + // |<-- front side of layer is visible to camera + // \ | / + // \ | / + // \| / + // | / + // |\ /<-- camera field of view + // | \ / + // back side of layer -->| \ / + // \./ <-- camera origin + // + layer_space_to_projection_plane.MakeIdentity(); + layer_space_to_projection_plane.ApplyPerspectiveDepth(1.0); + layer_space_to_projection_plane.Translate3d(-10.0, 0.0, 0.0); + layer_space_to_projection_plane.RotateAboutYAxis(100.0); + EXPECT_FALSE(layer_space_to_projection_plane.IsBackFaceVisible()); + + // Case 3: Additionally rotating the layer by 180 degrees should of course + // show the opposite result of case 2. + layer_space_to_projection_plane.RotateAboutYAxis(180.0); + EXPECT_TRUE(layer_space_to_projection_plane.IsBackFaceVisible()); +} + +TEST(XFormTest, verifyDefaultConstructorCreatesIdentityMatrix) { + Transform A; + EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 0.0f, A); + EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 0.0f, A); + EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); + EXPECT_TRUE(A.IsIdentity()); +} + +TEST(XFormTest, verifyCopyConstructor) { + Transform A; + InitializeTestMatrix(&A); + + // Copy constructor should produce exact same elements as matrix A. + Transform B(A); + EXPECT_ROW1_EQ(10.0f, 14.0f, 18.0f, 22.0f, B); + EXPECT_ROW2_EQ(11.0f, 15.0f, 19.0f, 23.0f, B); + EXPECT_ROW3_EQ(12.0f, 16.0f, 20.0f, 24.0f, B); + EXPECT_ROW4_EQ(13.0f, 17.0f, 21.0f, 25.0f, B); +} + +TEST(XFormTest, verifyConstructorFor16Elements) { + Transform transform(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, + 12.0, 13.0, 14.0, 15.0, 16.0); + + EXPECT_ROW1_EQ(1.0f, 2.0f, 3.0f, 4.0f, transform); + EXPECT_ROW2_EQ(5.0f, 6.0f, 7.0f, 8.0f, transform); + EXPECT_ROW3_EQ(9.0f, 10.0f, 11.0f, 12.0f, transform); + EXPECT_ROW4_EQ(13.0f, 14.0f, 15.0f, 16.0f, transform); +} + +TEST(XFormTest, verifyConstructorFor2dElements) { + Transform transform(1.0, 2.0, 3.0, 4.0, 5.0, 6.0); + + EXPECT_ROW1_EQ(1.0f, 2.0f, 0.0f, 5.0f, transform); + EXPECT_ROW2_EQ(3.0f, 4.0f, 0.0f, 6.0f, transform); + EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, transform); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, transform); +} + +TEST(XFormTest, verifyAssignmentOperator) { + Transform A; + InitializeTestMatrix(&A); + Transform B; + InitializeTestMatrix2(&B); + Transform C; + InitializeTestMatrix2(&C); + C = B = A; + + // Both B and C should now have been re-assigned to the value of A. + EXPECT_ROW1_EQ(10.0f, 14.0f, 18.0f, 22.0f, B); + EXPECT_ROW2_EQ(11.0f, 15.0f, 19.0f, 23.0f, B); + EXPECT_ROW3_EQ(12.0f, 16.0f, 20.0f, 24.0f, B); + EXPECT_ROW4_EQ(13.0f, 17.0f, 21.0f, 25.0f, B); + + EXPECT_ROW1_EQ(10.0f, 14.0f, 18.0f, 22.0f, C); + EXPECT_ROW2_EQ(11.0f, 15.0f, 19.0f, 23.0f, C); + EXPECT_ROW3_EQ(12.0f, 16.0f, 20.0f, 24.0f, C); + EXPECT_ROW4_EQ(13.0f, 17.0f, 21.0f, 25.0f, C); +} + +TEST(XFormTest, verifyEqualsBooleanOperator) { + Transform A; + InitializeTestMatrix(&A); + + Transform B; + InitializeTestMatrix(&B); + EXPECT_TRUE(A == B); + + // Modifying multiple elements should cause equals operator to return false. + Transform C; + InitializeTestMatrix2(&C); + EXPECT_FALSE(A == C); + + // Modifying any one individual element should cause equals operator to + // return false. + Transform D; + D = A; + D.matrix().set(0, 0, 0.f); + EXPECT_FALSE(A == D); + + D = A; + D.matrix().set(1, 0, 0.f); + EXPECT_FALSE(A == D); + + D = A; + D.matrix().set(2, 0, 0.f); + EXPECT_FALSE(A == D); + + D = A; + D.matrix().set(3, 0, 0.f); + EXPECT_FALSE(A == D); + + D = A; + D.matrix().set(0, 1, 0.f); + EXPECT_FALSE(A == D); + + D = A; + D.matrix().set(1, 1, 0.f); + EXPECT_FALSE(A == D); + + D = A; + D.matrix().set(2, 1, 0.f); + EXPECT_FALSE(A == D); + + D = A; + D.matrix().set(3, 1, 0.f); + EXPECT_FALSE(A == D); + + D = A; + D.matrix().set(0, 2, 0.f); + EXPECT_FALSE(A == D); + + D = A; + D.matrix().set(1, 2, 0.f); + EXPECT_FALSE(A == D); + + D = A; + D.matrix().set(2, 2, 0.f); + EXPECT_FALSE(A == D); + + D = A; + D.matrix().set(3, 2, 0.f); + EXPECT_FALSE(A == D); + + D = A; + D.matrix().set(0, 3, 0.f); + EXPECT_FALSE(A == D); + + D = A; + D.matrix().set(1, 3, 0.f); + EXPECT_FALSE(A == D); + + D = A; + D.matrix().set(2, 3, 0.f); + EXPECT_FALSE(A == D); + + D = A; + D.matrix().set(3, 3, 0.f); + EXPECT_FALSE(A == D); +} + +TEST(XFormTest, verifyMultiplyOperator) { + Transform A; + InitializeTestMatrix(&A); + + Transform B; + InitializeTestMatrix2(&B); + + Transform C = A * B; + EXPECT_ROW1_EQ(2036.0f, 2292.0f, 2548.0f, 2804.0f, C); + EXPECT_ROW2_EQ(2162.0f, 2434.0f, 2706.0f, 2978.0f, C); + EXPECT_ROW3_EQ(2288.0f, 2576.0f, 2864.0f, 3152.0f, C); + EXPECT_ROW4_EQ(2414.0f, 2718.0f, 3022.0f, 3326.0f, C); + + // Just an additional sanity check; matrix multiplication is not commutative. + EXPECT_FALSE(A * B == B * A); +} + +TEST(XFormTest, verifyMultiplyAndAssignOperator) { + Transform A; + InitializeTestMatrix(&A); + + Transform B; + InitializeTestMatrix2(&B); + + A *= B; + EXPECT_ROW1_EQ(2036.0f, 2292.0f, 2548.0f, 2804.0f, A); + EXPECT_ROW2_EQ(2162.0f, 2434.0f, 2706.0f, 2978.0f, A); + EXPECT_ROW3_EQ(2288.0f, 2576.0f, 2864.0f, 3152.0f, A); + EXPECT_ROW4_EQ(2414.0f, 2718.0f, 3022.0f, 3326.0f, A); + + // Just an additional sanity check; matrix multiplication is not commutative. + Transform C = A; + C *= B; + Transform D = B; + D *= A; + EXPECT_FALSE(C == D); +} + +TEST(XFormTest, verifyMatrixMultiplication) { + Transform A; + InitializeTestMatrix(&A); + + Transform B; + InitializeTestMatrix2(&B); + + A.PreconcatTransform(B); + EXPECT_ROW1_EQ(2036.0f, 2292.0f, 2548.0f, 2804.0f, A); + EXPECT_ROW2_EQ(2162.0f, 2434.0f, 2706.0f, 2978.0f, A); + EXPECT_ROW3_EQ(2288.0f, 2576.0f, 2864.0f, 3152.0f, A); + EXPECT_ROW4_EQ(2414.0f, 2718.0f, 3022.0f, 3326.0f, A); +} + +TEST(XFormTest, verifyMakeIdentiy) { + Transform A; + InitializeTestMatrix(&A); + A.MakeIdentity(); + EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 0.0f, A); + EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 0.0f, A); + EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); + EXPECT_TRUE(A.IsIdentity()); +} + +TEST(XFormTest, verifyTranslate) { + Transform A; + A.Translate(2.0, 3.0); + EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 2.0f, A); + EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 3.0f, A); + EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); + + // Verify that Translate() post-multiplies the existing matrix. + A.MakeIdentity(); + A.Scale(5.0, 5.0); + A.Translate(2.0, 3.0); + EXPECT_ROW1_EQ(5.0f, 0.0f, 0.0f, 10.0f, A); + EXPECT_ROW2_EQ(0.0f, 5.0f, 0.0f, 15.0f, A); + EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); +} + +TEST(XFormTest, verifyTranslate3d) { + Transform A; + A.Translate3d(2.0, 3.0, 4.0); + EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 2.0f, A); + EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 3.0f, A); + EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 4.0f, A); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); + + // Verify that Translate3d() post-multiplies the existing matrix. + A.MakeIdentity(); + A.Scale3d(6.0, 7.0, 8.0); + A.Translate3d(2.0, 3.0, 4.0); + EXPECT_ROW1_EQ(6.0f, 0.0f, 0.0f, 12.0f, A); + EXPECT_ROW2_EQ(0.0f, 7.0f, 0.0f, 21.0f, A); + EXPECT_ROW3_EQ(0.0f, 0.0f, 8.0f, 32.0f, A); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); +} + +TEST(XFormTest, verifyScale) { + Transform A; + A.Scale(6.0, 7.0); + EXPECT_ROW1_EQ(6.0f, 0.0f, 0.0f, 0.0f, A); + EXPECT_ROW2_EQ(0.0f, 7.0f, 0.0f, 0.0f, A); + EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); + + // Verify that Scale() post-multiplies the existing matrix. + A.MakeIdentity(); + A.Translate3d(2.0, 3.0, 4.0); + A.Scale(6.0, 7.0); + EXPECT_ROW1_EQ(6.0f, 0.0f, 0.0f, 2.0f, A); + EXPECT_ROW2_EQ(0.0f, 7.0f, 0.0f, 3.0f, A); + EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 4.0f, A); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); +} + +TEST(XFormTest, verifyScale3d) { + Transform A; + A.Scale3d(6.0, 7.0, 8.0); + EXPECT_ROW1_EQ(6.0f, 0.0f, 0.0f, 0.0f, A); + EXPECT_ROW2_EQ(0.0f, 7.0f, 0.0f, 0.0f, A); + EXPECT_ROW3_EQ(0.0f, 0.0f, 8.0f, 0.0f, A); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); + + // Verify that scale3d() post-multiplies the existing matrix. + A.MakeIdentity(); + A.Translate3d(2.0, 3.0, 4.0); + A.Scale3d(6.0, 7.0, 8.0); + EXPECT_ROW1_EQ(6.0f, 0.0f, 0.0f, 2.0f, A); + EXPECT_ROW2_EQ(0.0f, 7.0f, 0.0f, 3.0f, A); + EXPECT_ROW3_EQ(0.0f, 0.0f, 8.0f, 4.0f, A); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); +} + +TEST(XFormTest, verifyRotate) { + Transform A; + A.Rotate(90.0); + EXPECT_ROW1_NEAR(0.0, -1.0, 0.0, 0.0, A, ERROR_THRESHOLD); + EXPECT_ROW2_NEAR(1.0, 0.0, 0.0, 0.0, A, ERROR_THRESHOLD); + EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); + + // Verify that Rotate() post-multiplies the existing matrix. + A.MakeIdentity(); + A.Scale3d(6.0, 7.0, 8.0); + A.Rotate(90.0); + EXPECT_ROW1_NEAR(0.0, -6.0, 0.0, 0.0, A, ERROR_THRESHOLD); + EXPECT_ROW2_NEAR(7.0, 0.0, 0.0, 0.0, A, ERROR_THRESHOLD); + EXPECT_ROW3_EQ(0.0f, 0.0f, 8.0f, 0.0f, A); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); +} + +TEST(XFormTest, verifyRotateAboutXAxis) { + Transform A; + double sin45 = 0.5 * sqrt(2.0); + double cos45 = sin45; + + A.MakeIdentity(); + A.RotateAboutXAxis(90.0); + EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 0.0f, A); + EXPECT_ROW2_NEAR(0.0, 0.0, -1.0, 0.0, A, ERROR_THRESHOLD); + EXPECT_ROW3_NEAR(0.0, 1.0, 0.0, 0.0, A, ERROR_THRESHOLD); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); + + A.MakeIdentity(); + A.RotateAboutXAxis(45.0); + EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 0.0f, A); + EXPECT_ROW2_NEAR(0.0, cos45, -sin45, 0.0, A, ERROR_THRESHOLD); + EXPECT_ROW3_NEAR(0.0, sin45, cos45, 0.0, A, ERROR_THRESHOLD); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); + + // Verify that RotateAboutXAxis(angle) post-multiplies the existing matrix. + A.MakeIdentity(); + A.Scale3d(6.0, 7.0, 8.0); + A.RotateAboutXAxis(90.0); + EXPECT_ROW1_NEAR(6.0, 0.0, 0.0, 0.0, A, ERROR_THRESHOLD); + EXPECT_ROW2_NEAR(0.0, 0.0, -7.0, 0.0, A, ERROR_THRESHOLD); + EXPECT_ROW3_NEAR(0.0, 8.0, 0.0, 0.0, A, ERROR_THRESHOLD); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); +} + +TEST(XFormTest, verifyRotateAboutYAxis) { + Transform A; + double sin45 = 0.5 * sqrt(2.0); + double cos45 = sin45; + + // Note carefully, the expected pattern is inverted compared to rotating + // about x axis or z axis. + A.MakeIdentity(); + A.RotateAboutYAxis(90.0); + EXPECT_ROW1_NEAR(0.0, 0.0, 1.0, 0.0, A, ERROR_THRESHOLD); + EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 0.0f, A); + EXPECT_ROW3_NEAR(-1.0, 0.0, 0.0, 0.0, A, ERROR_THRESHOLD); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); + + A.MakeIdentity(); + A.RotateAboutYAxis(45.0); + EXPECT_ROW1_NEAR(cos45, 0.0, sin45, 0.0, A, ERROR_THRESHOLD); + EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 0.0f, A); + EXPECT_ROW3_NEAR(-sin45, 0.0, cos45, 0.0, A, ERROR_THRESHOLD); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); + + // Verify that RotateAboutYAxis(angle) post-multiplies the existing matrix. + A.MakeIdentity(); + A.Scale3d(6.0, 7.0, 8.0); + A.RotateAboutYAxis(90.0); + EXPECT_ROW1_NEAR(0.0, 0.0, 6.0, 0.0, A, ERROR_THRESHOLD); + EXPECT_ROW2_NEAR(0.0, 7.0, 0.0, 0.0, A, ERROR_THRESHOLD); + EXPECT_ROW3_NEAR(-8.0, 0.0, 0.0, 0.0, A, ERROR_THRESHOLD); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); +} + +TEST(XFormTest, verifyRotateAboutZAxis) { + Transform A; + double sin45 = 0.5 * sqrt(2.0); + double cos45 = sin45; + + A.MakeIdentity(); + A.RotateAboutZAxis(90.0); + EXPECT_ROW1_NEAR(0.0, -1.0, 0.0, 0.0, A, ERROR_THRESHOLD); + EXPECT_ROW2_NEAR(1.0, 0.0, 0.0, 0.0, A, ERROR_THRESHOLD); + EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); + + A.MakeIdentity(); + A.RotateAboutZAxis(45.0); + EXPECT_ROW1_NEAR(cos45, -sin45, 0.0, 0.0, A, ERROR_THRESHOLD); + EXPECT_ROW2_NEAR(sin45, cos45, 0.0, 0.0, A, ERROR_THRESHOLD); + EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); + + // Verify that RotateAboutZAxis(angle) post-multiplies the existing matrix. + A.MakeIdentity(); + A.Scale3d(6.0, 7.0, 8.0); + A.RotateAboutZAxis(90.0); + EXPECT_ROW1_NEAR(0.0, -6.0, 0.0, 0.0, A, ERROR_THRESHOLD); + EXPECT_ROW2_NEAR(7.0, 0.0, 0.0, 0.0, A, ERROR_THRESHOLD); + EXPECT_ROW3_EQ(0.0f, 0.0f, 8.0f, 0.0f, A); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); +} + +TEST(XFormTest, verifyRotateAboutForAlignedAxes) { + Transform A; + + // Check rotation about z-axis + A.MakeIdentity(); + A.RotateAbout(Vector3dF(0.0, 0.0, 1.0), 90.0); + EXPECT_ROW1_NEAR(0.0, -1.0, 0.0, 0.0, A, ERROR_THRESHOLD); + EXPECT_ROW2_NEAR(1.0, 0.0, 0.0, 0.0, A, ERROR_THRESHOLD); + EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); + + // Check rotation about x-axis + A.MakeIdentity(); + A.RotateAbout(Vector3dF(1.0, 0.0, 0.0), 90.0); + EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 0.0f, A); + EXPECT_ROW2_NEAR(0.0, 0.0, -1.0, 0.0, A, ERROR_THRESHOLD); + EXPECT_ROW3_NEAR(0.0, 1.0, 0.0, 0.0, A, ERROR_THRESHOLD); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); + + // Check rotation about y-axis. Note carefully, the expected pattern is + // inverted compared to rotating about x axis or z axis. + A.MakeIdentity(); + A.RotateAbout(Vector3dF(0.0, 1.0, 0.0), 90.0); + EXPECT_ROW1_NEAR(0.0, 0.0, 1.0, 0.0, A, ERROR_THRESHOLD); + EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 0.0f, A); + EXPECT_ROW3_NEAR(-1.0, 0.0, 0.0, 0.0, A, ERROR_THRESHOLD); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); + + // Verify that rotate3d(axis, angle) post-multiplies the existing matrix. + A.MakeIdentity(); + A.Scale3d(6.0, 7.0, 8.0); + A.RotateAboutZAxis(90.0); + EXPECT_ROW1_NEAR(0.0, -6.0, 0.0, 0.0, A, ERROR_THRESHOLD); + EXPECT_ROW2_NEAR(7.0, 0.0, 0.0, 0.0, A, ERROR_THRESHOLD); + EXPECT_ROW3_EQ(0.0f, 0.0f, 8.0f, 0.0f, A); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); +} + +TEST(XFormTest, verifyRotateAboutForArbitraryAxis) { + // Check rotation about an arbitrary non-axis-aligned vector. + Transform A; + A.RotateAbout(Vector3dF(1.0, 1.0, 1.0), 90.0); + EXPECT_ROW1_NEAR(0.3333333333333334258519187, -0.2440169358562924717404030, + 0.9106836025229592124219380, 0.0, A, ERROR_THRESHOLD); + EXPECT_ROW2_NEAR(0.9106836025229592124219380, 0.3333333333333334258519187, + -0.2440169358562924717404030, 0.0, A, ERROR_THRESHOLD); + EXPECT_ROW3_NEAR(-0.2440169358562924717404030, 0.9106836025229592124219380, + 0.3333333333333334258519187, 0.0, A, ERROR_THRESHOLD); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); +} + +TEST(XFormTest, verifyRotateAboutForDegenerateAxis) { + // Check rotation about a degenerate zero vector. + // It is expected to skip applying the rotation. + Transform A; + + A.RotateAbout(Vector3dF(0.0, 0.0, 0.0), 45.0); + // Verify that A remains unchanged. + EXPECT_TRUE(A.IsIdentity()); + + InitializeTestMatrix(&A); + A.RotateAbout(Vector3dF(0.0, 0.0, 0.0), 35.0); + + // Verify that A remains unchanged. + EXPECT_ROW1_EQ(10.0f, 14.0f, 18.0f, 22.0f, A); + EXPECT_ROW2_EQ(11.0f, 15.0f, 19.0f, 23.0f, A); + EXPECT_ROW3_EQ(12.0f, 16.0f, 20.0f, 24.0f, A); + EXPECT_ROW4_EQ(13.0f, 17.0f, 21.0f, 25.0f, A); +} + +TEST(XFormTest, verifySkew) { + // Test a skew along X axis only + Transform A; + A.Skew(45.0, 0.0); + EXPECT_ROW1_EQ(1.0f, 1.0f, 0.0f, 0.0f, A); + EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 0.0f, A); + EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); + + // Test a skew along Y axis only + A.MakeIdentity(); + A.Skew(0.0, 45.0); + EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 0.0f, A); + EXPECT_ROW2_EQ(1.0f, 1.0f, 0.0f, 0.0f, A); + EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); + + // Verify that skew() post-multiplies the existing matrix. Row 1, column 2, + // would incorrectly have value "7" if the matrix is pre-multiplied instead + // of post-multiplied. + A.MakeIdentity(); + A.Scale3d(6.0, 7.0, 8.0); + A.Skew(45.0, 0.0); + EXPECT_ROW1_EQ(6.0f, 6.0f, 0.0f, 0.0f, A); + EXPECT_ROW2_EQ(0.0f, 7.0f, 0.0f, 0.0f, A); + EXPECT_ROW3_EQ(0.0f, 0.0f, 8.0f, 0.0f, A); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); + + // Test a skew along X and Y axes both + A.MakeIdentity(); + A.Skew(45.0, 45.0); + EXPECT_ROW1_EQ(1.0f, 1.0f, 0.0f, 0.0f, A); + EXPECT_ROW2_EQ(1.0f, 1.0f, 0.0f, 0.0f, A); + EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A); + EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A); +} + +TEST(XFormTest, verifyPerspectiveDepth) { + Transform A; + A.ApplyPerspectiveDepth(1.0); + EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 0.0f, A); + EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 0.0f, A); + EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A); + EXPECT_ROW4_EQ(0.0f, 0.0f, -1.0f, 1.0f, A); + + // Verify that PerspectiveDepth() post-multiplies the existing matrix. + A.MakeIdentity(); + A.Translate3d(2.0, 3.0, 4.0); + A.ApplyPerspectiveDepth(1.0); + EXPECT_ROW1_EQ(1.0f, 0.0f, -2.0f, 2.0f, A); + EXPECT_ROW2_EQ(0.0f, 1.0f, -3.0f, 3.0f, A); + EXPECT_ROW3_EQ(0.0f, 0.0f, -3.0f, 4.0f, A); + EXPECT_ROW4_EQ(0.0f, 0.0f, -1.0f, 1.0f, A); +} + +TEST(XFormTest, verifyHasPerspective) { + Transform A; + A.ApplyPerspectiveDepth(1.0); + EXPECT_TRUE(A.HasPerspective()); + + A.MakeIdentity(); + A.ApplyPerspectiveDepth(0.0); + EXPECT_FALSE(A.HasPerspective()); + + A.MakeIdentity(); + A.matrix().set(3, 0, -1.f); + EXPECT_TRUE(A.HasPerspective()); + + A.MakeIdentity(); + A.matrix().set(3, 1, -1.f); + EXPECT_TRUE(A.HasPerspective()); + + A.MakeIdentity(); + A.matrix().set(3, 2, -0.3f); + EXPECT_TRUE(A.HasPerspective()); + + A.MakeIdentity(); + A.matrix().set(3, 3, 0.5f); + EXPECT_TRUE(A.HasPerspective()); + + A.MakeIdentity(); + A.matrix().set(3, 3, 0.f); + EXPECT_TRUE(A.HasPerspective()); +} + +TEST(XFormTest, verifyIsInvertible) { + Transform A; + + // Translations, rotations, scales, skews and arbitrary combinations of them + // are invertible. + A.MakeIdentity(); + EXPECT_TRUE(A.IsInvertible()); + + A.MakeIdentity(); + A.Translate3d(2.0, 3.0, 4.0); + EXPECT_TRUE(A.IsInvertible()); + + A.MakeIdentity(); + A.Scale3d(6.0, 7.0, 8.0); + EXPECT_TRUE(A.IsInvertible()); + + A.MakeIdentity(); + A.RotateAboutXAxis(10.0); + A.RotateAboutYAxis(20.0); + A.RotateAboutZAxis(30.0); + EXPECT_TRUE(A.IsInvertible()); + + A.MakeIdentity(); + A.Skew(45.0, 0.0); + EXPECT_TRUE(A.IsInvertible()); + + // A perspective matrix (projection plane at z=0) is invertible. The + // intuitive explanation is that perspective is equivalent to a skew of the + // w-axis; skews are invertible. + A.MakeIdentity(); + A.ApplyPerspectiveDepth(1.0); + EXPECT_TRUE(A.IsInvertible()); + + // A "pure" perspective matrix derived by similar triangles, with m44() set + // to zero (i.e. camera positioned at the origin), is not invertible. + A.MakeIdentity(); + A.ApplyPerspectiveDepth(1.0); + A.matrix().set(3, 3, 0.f); + EXPECT_FALSE(A.IsInvertible()); + + // Adding more to a non-invertible matrix will not make it invertible in the + // general case. + A.MakeIdentity(); + A.ApplyPerspectiveDepth(1.0); + A.matrix().set(3, 3, 0.f); + A.Scale3d(6.0, 7.0, 8.0); + A.RotateAboutXAxis(10.0); + A.RotateAboutYAxis(20.0); + A.RotateAboutZAxis(30.0); + A.Translate3d(6.0, 7.0, 8.0); +#if !defined(ARCH_CPU_ARM_FAMILY) + // TODO(enne): Make this pass on ARM, https://crbug.com/662558 + EXPECT_FALSE(A.IsInvertible()); +#endif + + // A degenerate matrix of all zeros is not invertible. + A.MakeIdentity(); + A.matrix().set(0, 0, 0.f); + A.matrix().set(1, 1, 0.f); + A.matrix().set(2, 2, 0.f); + A.matrix().set(3, 3, 0.f); + EXPECT_FALSE(A.IsInvertible()); +} + +TEST(XFormTest, verifyIsIdentity) { + Transform A; + + InitializeTestMatrix(&A); + EXPECT_FALSE(A.IsIdentity()); + + A.MakeIdentity(); + EXPECT_TRUE(A.IsIdentity()); + + // Modifying any one individual element should cause the matrix to no longer + // be identity. + A.MakeIdentity(); + A.matrix().set(0, 0, 2.f); + EXPECT_FALSE(A.IsIdentity()); + + A.MakeIdentity(); + A.matrix().set(1, 0, 2.f); + EXPECT_FALSE(A.IsIdentity()); + + A.MakeIdentity(); + A.matrix().set(2, 0, 2.f); + EXPECT_FALSE(A.IsIdentity()); + + A.MakeIdentity(); + A.matrix().set(3, 0, 2.f); + EXPECT_FALSE(A.IsIdentity()); + + A.MakeIdentity(); + A.matrix().set(0, 1, 2.f); + EXPECT_FALSE(A.IsIdentity()); + + A.MakeIdentity(); + A.matrix().set(1, 1, 2.f); + EXPECT_FALSE(A.IsIdentity()); + + A.MakeIdentity(); + A.matrix().set(2, 1, 2.f); + EXPECT_FALSE(A.IsIdentity()); + + A.MakeIdentity(); + A.matrix().set(3, 1, 2.f); + EXPECT_FALSE(A.IsIdentity()); + + A.MakeIdentity(); + A.matrix().set(0, 2, 2.f); + EXPECT_FALSE(A.IsIdentity()); + + A.MakeIdentity(); + A.matrix().set(1, 2, 2.f); + EXPECT_FALSE(A.IsIdentity()); + + A.MakeIdentity(); + A.matrix().set(2, 2, 2.f); + EXPECT_FALSE(A.IsIdentity()); + + A.MakeIdentity(); + A.matrix().set(3, 2, 2.f); + EXPECT_FALSE(A.IsIdentity()); + + A.MakeIdentity(); + A.matrix().set(0, 3, 2.f); + EXPECT_FALSE(A.IsIdentity()); + + A.MakeIdentity(); + A.matrix().set(1, 3, 2.f); + EXPECT_FALSE(A.IsIdentity()); + + A.MakeIdentity(); + A.matrix().set(2, 3, 2.f); + EXPECT_FALSE(A.IsIdentity()); + + A.MakeIdentity(); + A.matrix().set(3, 3, 2.f); + EXPECT_FALSE(A.IsIdentity()); +} + +TEST(XFormTest, verifyIsIdentityOrTranslation) { + Transform A; + + InitializeTestMatrix(&A); + EXPECT_FALSE(A.IsIdentityOrTranslation()); + + A.MakeIdentity(); + EXPECT_TRUE(A.IsIdentityOrTranslation()); + + // Modifying any non-translation components should cause + // IsIdentityOrTranslation() to return false. NOTE: (0, 3), (1, 3), and + // (2, 3) are the translation components, so modifying them should still + // return true. + A.MakeIdentity(); + A.matrix().set(0, 0, 2.f); + EXPECT_FALSE(A.IsIdentityOrTranslation()); + + A.MakeIdentity(); + A.matrix().set(1, 0, 2.f); + EXPECT_FALSE(A.IsIdentityOrTranslation()); + + A.MakeIdentity(); + A.matrix().set(2, 0, 2.f); + EXPECT_FALSE(A.IsIdentityOrTranslation()); + + A.MakeIdentity(); + A.matrix().set(3, 0, 2.f); + EXPECT_FALSE(A.IsIdentityOrTranslation()); + + A.MakeIdentity(); + A.matrix().set(0, 1, 2.f); + EXPECT_FALSE(A.IsIdentityOrTranslation()); + + A.MakeIdentity(); + A.matrix().set(1, 1, 2.f); + EXPECT_FALSE(A.IsIdentityOrTranslation()); + + A.MakeIdentity(); + A.matrix().set(2, 1, 2.f); + EXPECT_FALSE(A.IsIdentityOrTranslation()); + + A.MakeIdentity(); + A.matrix().set(3, 1, 2.f); + EXPECT_FALSE(A.IsIdentityOrTranslation()); + + A.MakeIdentity(); + A.matrix().set(0, 2, 2.f); + EXPECT_FALSE(A.IsIdentityOrTranslation()); + + A.MakeIdentity(); + A.matrix().set(1, 2, 2.f); + EXPECT_FALSE(A.IsIdentityOrTranslation()); + + A.MakeIdentity(); + A.matrix().set(2, 2, 2.f); + EXPECT_FALSE(A.IsIdentityOrTranslation()); + + A.MakeIdentity(); + A.matrix().set(3, 2, 2.f); + EXPECT_FALSE(A.IsIdentityOrTranslation()); + + // Note carefully - expecting true here. + A.MakeIdentity(); + A.matrix().set(0, 3, 2.f); + EXPECT_TRUE(A.IsIdentityOrTranslation()); + + // Note carefully - expecting true here. + A.MakeIdentity(); + A.matrix().set(1, 3, 2.f); + EXPECT_TRUE(A.IsIdentityOrTranslation()); + + // Note carefully - expecting true here. + A.MakeIdentity(); + A.matrix().set(2, 3, 2.f); + EXPECT_TRUE(A.IsIdentityOrTranslation()); + + A.MakeIdentity(); + A.matrix().set(3, 3, 2.f); + EXPECT_FALSE(A.IsIdentityOrTranslation()); +} + +TEST(XFormTest, verifyIsApproximatelyIdentityOrTranslation) { + Transform A; + skia::Matrix44& matrix = A.matrix(); + + // Exact pure translation. + A.MakeIdentity(); + + EXPECT_TRUE(A.IsApproximatelyIdentityOrTranslation(0)); + EXPECT_TRUE(A.IsApproximatelyIdentityOrTranslation(kApproxZero)); + EXPECT_TRUE(A.IsApproximatelyIdentityOrIntegerTranslation(0)); + EXPECT_TRUE(A.IsApproximatelyIdentityOrIntegerTranslation(kApproxZero)); + + // Set translate values to integer values other than 0 or 1. + matrix.set(0, 3, 3); + matrix.set(1, 3, 4); + matrix.set(2, 3, 5); + + EXPECT_TRUE(A.IsApproximatelyIdentityOrTranslation(0)); + EXPECT_TRUE(A.IsApproximatelyIdentityOrTranslation(kApproxZero)); + EXPECT_TRUE(A.IsApproximatelyIdentityOrIntegerTranslation(0)); + EXPECT_TRUE(A.IsApproximatelyIdentityOrIntegerTranslation(kApproxZero)); + + // Set translate values to values other than 0 or 1. + matrix.set(0, 3, 3.4f); + matrix.set(1, 3, 4.4f); + matrix.set(2, 3, 5.6f); + + EXPECT_TRUE(A.IsApproximatelyIdentityOrTranslation(0)); + EXPECT_TRUE(A.IsApproximatelyIdentityOrTranslation(kApproxZero)); + EXPECT_FALSE(A.IsApproximatelyIdentityOrIntegerTranslation(0)); + EXPECT_FALSE(A.IsApproximatelyIdentityOrIntegerTranslation(kApproxZero)); + + // Approximately pure translation. + InitializeApproxIdentityMatrix(&A); + + EXPECT_FALSE(A.IsApproximatelyIdentityOrTranslation(0)); + EXPECT_FALSE(A.IsApproximatelyIdentityOrTranslation(kApproxZero)); + EXPECT_FALSE(A.IsApproximatelyIdentityOrIntegerTranslation(0)); + EXPECT_FALSE(A.IsApproximatelyIdentityOrIntegerTranslation(kApproxZero)); + + // Some values must be exact. + matrix.set(3, 0, 0); + matrix.set(3, 1, 0); + matrix.set(3, 2, 0); + matrix.set(3, 3, 1); + + EXPECT_FALSE(A.IsApproximatelyIdentityOrTranslation(0)); + EXPECT_TRUE(A.IsApproximatelyIdentityOrTranslation(kApproxZero)); + EXPECT_FALSE(A.IsApproximatelyIdentityOrIntegerTranslation(0)); + EXPECT_TRUE(A.IsApproximatelyIdentityOrIntegerTranslation(kApproxZero)); + + // Set translate values to values other than 0 or 1. + matrix.set(0, 3, matrix.get(0, 3) + 3); + matrix.set(1, 3, matrix.get(1, 3) + 4); + matrix.set(2, 3, matrix.get(2, 3) + 5); + + EXPECT_FALSE(A.IsApproximatelyIdentityOrTranslation(0)); + EXPECT_TRUE(A.IsApproximatelyIdentityOrTranslation(kApproxZero)); + EXPECT_FALSE(A.IsApproximatelyIdentityOrIntegerTranslation(0)); + EXPECT_TRUE(A.IsApproximatelyIdentityOrIntegerTranslation(kApproxZero)); + + // Set translate values to values other than 0 or 1. + matrix.set(0, 3, 3.4f); + matrix.set(1, 3, 4.4f); + matrix.set(2, 3, 5.6f); + + EXPECT_FALSE(A.IsApproximatelyIdentityOrTranslation(0)); + EXPECT_TRUE(A.IsApproximatelyIdentityOrTranslation(kApproxZero)); + EXPECT_FALSE(A.IsApproximatelyIdentityOrIntegerTranslation(0)); + EXPECT_FALSE(A.IsApproximatelyIdentityOrIntegerTranslation(kApproxZero)); + + // Not approximately pure translation. + InitializeApproxIdentityMatrix(&A); + + // Some values must be exact. + matrix.set(3, 0, 0); + matrix.set(3, 1, 0); + matrix.set(3, 2, 0); + matrix.set(3, 3, 1); + + // Set some values (not translate values) to values other than 0 or 1. + matrix.set(0, 1, 3.4f); + matrix.set(3, 2, 4.4f); + matrix.set(2, 0, 5.6f); + + EXPECT_FALSE(A.IsApproximatelyIdentityOrTranslation(0)); + EXPECT_FALSE(A.IsApproximatelyIdentityOrTranslation(kApproxZero)); + EXPECT_FALSE(A.IsApproximatelyIdentityOrIntegerTranslation(0)); + EXPECT_FALSE(A.IsApproximatelyIdentityOrIntegerTranslation(kApproxZero)); +} + +TEST(XFormTest, verifyIsScaleOrTranslation) { + Transform A; + + InitializeTestMatrix(&A); + EXPECT_FALSE(A.IsScaleOrTranslation()); + + A.MakeIdentity(); + EXPECT_TRUE(A.IsScaleOrTranslation()); + + // Modifying any non-scale or non-translation components should cause + // IsScaleOrTranslation() to return false. (0, 0), (1, 1), (2, 2), (0, 3), + // (1, 3), and (2, 3) are the scale and translation components, so + // modifying them should still return true. + + // Note carefully - expecting true here. + A.MakeIdentity(); + A.matrix().set(0, 0, 2.f); + EXPECT_TRUE(A.IsScaleOrTranslation()); + + A.MakeIdentity(); + A.matrix().set(1, 0, 2.f); + EXPECT_FALSE(A.IsScaleOrTranslation()); + + A.MakeIdentity(); + A.matrix().set(2, 0, 2.f); + EXPECT_FALSE(A.IsScaleOrTranslation()); + + A.MakeIdentity(); + A.matrix().set(3, 0, 2.f); + EXPECT_FALSE(A.IsScaleOrTranslation()); + + A.MakeIdentity(); + A.matrix().set(0, 1, 2.f); + EXPECT_FALSE(A.IsScaleOrTranslation()); + + // Note carefully - expecting true here. + A.MakeIdentity(); + A.matrix().set(1, 1, 2.f); + EXPECT_TRUE(A.IsScaleOrTranslation()); + + A.MakeIdentity(); + A.matrix().set(2, 1, 2.f); + EXPECT_FALSE(A.IsScaleOrTranslation()); + + A.MakeIdentity(); + A.matrix().set(3, 1, 2.f); + EXPECT_FALSE(A.IsScaleOrTranslation()); + + A.MakeIdentity(); + A.matrix().set(0, 2, 2.f); + EXPECT_FALSE(A.IsScaleOrTranslation()); + + A.MakeIdentity(); + A.matrix().set(1, 2, 2.f); + EXPECT_FALSE(A.IsScaleOrTranslation()); + + // Note carefully - expecting true here. + A.MakeIdentity(); + A.matrix().set(2, 2, 2.f); + EXPECT_TRUE(A.IsScaleOrTranslation()); + + A.MakeIdentity(); + A.matrix().set(3, 2, 2.f); + EXPECT_FALSE(A.IsScaleOrTranslation()); + + // Note carefully - expecting true here. + A.MakeIdentity(); + A.matrix().set(0, 3, 2.f); + EXPECT_TRUE(A.IsScaleOrTranslation()); + + // Note carefully - expecting true here. + A.MakeIdentity(); + A.matrix().set(1, 3, 2.f); + EXPECT_TRUE(A.IsScaleOrTranslation()); + + // Note carefully - expecting true here. + A.MakeIdentity(); + A.matrix().set(2, 3, 2.f); + EXPECT_TRUE(A.IsScaleOrTranslation()); + + A.MakeIdentity(); + A.matrix().set(3, 3, 2.f); + EXPECT_FALSE(A.IsScaleOrTranslation()); +} + +TEST(XFormTest, verifyFlattenTo2d) { + Transform A; + InitializeTestMatrix(&A); + + A.FlattenTo2d(); + EXPECT_ROW1_EQ(10.0f, 14.0f, 0.0f, 22.0f, A); + EXPECT_ROW2_EQ(11.0f, 15.0f, 0.0f, 23.0f, A); + EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A); + EXPECT_ROW4_EQ(13.0f, 17.0f, 0.0f, 25.0f, A); +} + +TEST(XFormTest, IsFlat) { + Transform transform; + InitializeTestMatrix(&transform); + + // A transform with all entries non-zero isn't flat. + EXPECT_FALSE(transform.IsFlat()); + + transform.matrix().set(0, 2, 0.f); + transform.matrix().set(1, 2, 0.f); + transform.matrix().set(2, 2, 1.f); + transform.matrix().set(3, 2, 0.f); + + EXPECT_FALSE(transform.IsFlat()); + + transform.matrix().set(2, 0, 0.f); + transform.matrix().set(2, 1, 0.f); + transform.matrix().set(2, 3, 0.f); + + // Since the third column and row are both (0, 0, 1, 0), the transform is + // flat. + EXPECT_TRUE(transform.IsFlat()); +} + +// Another implementation of Preserves2dAxisAlignment that isn't as fast, +// good for testing the faster implementation. +static bool EmpiricallyPreserves2dAxisAlignment(const Transform& transform) { + Point3F p1(5.0f, 5.0f, 0.0f); + Point3F p2(10.0f, 5.0f, 0.0f); + Point3F p3(10.0f, 20.0f, 0.0f); + Point3F p4(5.0f, 20.0f, 0.0f); + + QuadF test_quad(PointF(p1.x(), p1.y()), PointF(p2.x(), p2.y()), + PointF(p3.x(), p3.y()), PointF(p4.x(), p4.y())); + EXPECT_TRUE(test_quad.IsRectilinear()); + + transform.TransformPoint(&p1); + transform.TransformPoint(&p2); + transform.TransformPoint(&p3); + transform.TransformPoint(&p4); + + QuadF transformedQuad(PointF(p1.x(), p1.y()), PointF(p2.x(), p2.y()), + PointF(p3.x(), p3.y()), PointF(p4.x(), p4.y())); + return transformedQuad.IsRectilinear(); +} + +TEST(XFormTest, Preserves2dAxisAlignment) { + static const struct TestCase { + SkScalar a; // row 1, column 1 + SkScalar b; // row 1, column 2 + SkScalar c; // row 2, column 1 + SkScalar d; // row 2, column 2 + bool expected; + bool degenerate; + } test_cases[] = { + // clang-format off + { 3.f, 0.f, + 0.f, 4.f, true, false }, // basic case + { 0.f, 4.f, + 3.f, 0.f, true, false }, // rotate by 90 + { 0.f, 0.f, + 0.f, 4.f, true, true }, // degenerate x + { 3.f, 0.f, + 0.f, 0.f, true, true }, // degenerate y + { 0.f, 0.f, + 3.f, 0.f, true, true }, // degenerate x + rotate by 90 + { 0.f, 4.f, + 0.f, 0.f, true, true }, // degenerate y + rotate by 90 + { 3.f, 4.f, + 0.f, 0.f, false, true }, + { 0.f, 0.f, + 3.f, 4.f, false, true }, + { 0.f, 3.f, + 0.f, 4.f, false, true }, + { 3.f, 0.f, + 4.f, 0.f, false, true }, + { 3.f, 4.f, + 5.f, 0.f, false, false }, + { 3.f, 4.f, + 0.f, 5.f, false, false }, + { 3.f, 0.f, + 4.f, 5.f, false, false }, + { 0.f, 3.f, + 4.f, 5.f, false, false }, + { 2.f, 3.f, + 4.f, 5.f, false, false }, + // clang-format on + }; + + Transform transform; + for (const auto& value : test_cases) { + transform.MakeIdentity(); + transform.matrix().set(0, 0, value.a); + transform.matrix().set(0, 1, value.b); + transform.matrix().set(1, 0, value.c); + transform.matrix().set(1, 1, value.d); + + if (value.expected) { + EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform)); + EXPECT_TRUE(transform.Preserves2dAxisAlignment()); + if (value.degenerate) { + EXPECT_FALSE(transform.NonDegeneratePreserves2dAxisAlignment()); + } else { + EXPECT_TRUE(transform.NonDegeneratePreserves2dAxisAlignment()); + } + } else { + EXPECT_FALSE(EmpiricallyPreserves2dAxisAlignment(transform)); + EXPECT_FALSE(transform.Preserves2dAxisAlignment()); + EXPECT_FALSE(transform.NonDegeneratePreserves2dAxisAlignment()); + } + } + + // Try the same test cases again, but this time make sure that other matrix + // elements (except perspective) have entries, to test that they are ignored. + for (const auto& value : test_cases) { + transform.MakeIdentity(); + transform.matrix().set(0, 0, value.a); + transform.matrix().set(0, 1, value.b); + transform.matrix().set(1, 0, value.c); + transform.matrix().set(1, 1, value.d); + + transform.matrix().set(0, 2, 1.f); + transform.matrix().set(0, 3, 2.f); + transform.matrix().set(1, 2, 3.f); + transform.matrix().set(1, 3, 4.f); + transform.matrix().set(2, 0, 5.f); + transform.matrix().set(2, 1, 6.f); + transform.matrix().set(2, 2, 7.f); + transform.matrix().set(2, 3, 8.f); + + if (value.expected) { + EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform)); + EXPECT_TRUE(transform.Preserves2dAxisAlignment()); + if (value.degenerate) { + EXPECT_FALSE(transform.NonDegeneratePreserves2dAxisAlignment()); + } else { + EXPECT_TRUE(transform.NonDegeneratePreserves2dAxisAlignment()); + } + } else { + EXPECT_FALSE(EmpiricallyPreserves2dAxisAlignment(transform)); + EXPECT_FALSE(transform.Preserves2dAxisAlignment()); + EXPECT_FALSE(transform.NonDegeneratePreserves2dAxisAlignment()); + } + } + + // Try the same test cases again, but this time add perspective which is + // always assumed to not-preserve axis alignment. + for (const auto& value : test_cases) { + transform.MakeIdentity(); + transform.matrix().set(0, 0, value.a); + transform.matrix().set(0, 1, value.b); + transform.matrix().set(1, 0, value.c); + transform.matrix().set(1, 1, value.d); + + transform.matrix().set(0, 2, 1.f); + transform.matrix().set(0, 3, 2.f); + transform.matrix().set(1, 2, 3.f); + transform.matrix().set(1, 3, 4.f); + transform.matrix().set(2, 0, 5.f); + transform.matrix().set(2, 1, 6.f); + transform.matrix().set(2, 2, 7.f); + transform.matrix().set(2, 3, 8.f); + transform.matrix().set(3, 0, 9.f); + transform.matrix().set(3, 1, 10.f); + transform.matrix().set(3, 2, 11.f); + transform.matrix().set(3, 3, 12.f); + + EXPECT_FALSE(EmpiricallyPreserves2dAxisAlignment(transform)); + EXPECT_FALSE(transform.Preserves2dAxisAlignment()); + EXPECT_FALSE(transform.NonDegeneratePreserves2dAxisAlignment()); + } + + // Try a few more practical situations to check precision + transform.MakeIdentity(); + transform.RotateAboutZAxis(90.0); + EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform)); + EXPECT_TRUE(transform.Preserves2dAxisAlignment()); + EXPECT_TRUE(transform.NonDegeneratePreserves2dAxisAlignment()); + + transform.MakeIdentity(); + transform.RotateAboutZAxis(180.0); + EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform)); + EXPECT_TRUE(transform.Preserves2dAxisAlignment()); + EXPECT_TRUE(transform.NonDegeneratePreserves2dAxisAlignment()); + + transform.MakeIdentity(); + transform.RotateAboutZAxis(270.0); + EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform)); + EXPECT_TRUE(transform.Preserves2dAxisAlignment()); + EXPECT_TRUE(transform.NonDegeneratePreserves2dAxisAlignment()); + + transform.MakeIdentity(); + transform.RotateAboutYAxis(90.0); + EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform)); + EXPECT_TRUE(transform.Preserves2dAxisAlignment()); + EXPECT_FALSE(transform.NonDegeneratePreserves2dAxisAlignment()); + + transform.MakeIdentity(); + transform.RotateAboutXAxis(90.0); + EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform)); + EXPECT_TRUE(transform.Preserves2dAxisAlignment()); + EXPECT_FALSE(transform.NonDegeneratePreserves2dAxisAlignment()); + + transform.MakeIdentity(); + transform.RotateAboutZAxis(90.0); + transform.RotateAboutYAxis(90.0); + EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform)); + EXPECT_TRUE(transform.Preserves2dAxisAlignment()); + EXPECT_FALSE(transform.NonDegeneratePreserves2dAxisAlignment()); + + transform.MakeIdentity(); + transform.RotateAboutZAxis(90.0); + transform.RotateAboutXAxis(90.0); + EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform)); + EXPECT_TRUE(transform.Preserves2dAxisAlignment()); + EXPECT_FALSE(transform.NonDegeneratePreserves2dAxisAlignment()); + + transform.MakeIdentity(); + transform.RotateAboutYAxis(90.0); + transform.RotateAboutZAxis(90.0); + EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform)); + EXPECT_TRUE(transform.Preserves2dAxisAlignment()); + EXPECT_FALSE(transform.NonDegeneratePreserves2dAxisAlignment()); + + transform.MakeIdentity(); + transform.RotateAboutZAxis(45.0); + EXPECT_FALSE(EmpiricallyPreserves2dAxisAlignment(transform)); + EXPECT_FALSE(transform.Preserves2dAxisAlignment()); + EXPECT_FALSE(transform.NonDegeneratePreserves2dAxisAlignment()); + + // 3-d case; In 2d after an orthographic projection, this case does + // preserve 2d axis alignment. But in 3d, it does not preserve axis + // alignment. + transform.MakeIdentity(); + transform.RotateAboutYAxis(45.0); + EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform)); + EXPECT_TRUE(transform.Preserves2dAxisAlignment()); + EXPECT_TRUE(transform.NonDegeneratePreserves2dAxisAlignment()); + + transform.MakeIdentity(); + transform.RotateAboutXAxis(45.0); + EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform)); + EXPECT_TRUE(transform.Preserves2dAxisAlignment()); + EXPECT_TRUE(transform.NonDegeneratePreserves2dAxisAlignment()); + + // Perspective cases. + transform.MakeIdentity(); + transform.ApplyPerspectiveDepth(10.0); + transform.RotateAboutYAxis(45.0); + EXPECT_FALSE(EmpiricallyPreserves2dAxisAlignment(transform)); + EXPECT_FALSE(transform.Preserves2dAxisAlignment()); + EXPECT_FALSE(transform.NonDegeneratePreserves2dAxisAlignment()); + + transform.MakeIdentity(); + transform.ApplyPerspectiveDepth(10.0); + transform.RotateAboutZAxis(90.0); + EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform)); + EXPECT_TRUE(transform.Preserves2dAxisAlignment()); + EXPECT_TRUE(transform.NonDegeneratePreserves2dAxisAlignment()); + + transform.MakeIdentity(); + transform.ApplyPerspectiveDepth(-10.0); + transform.RotateAboutZAxis(90.0); + EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform)); + EXPECT_TRUE(transform.Preserves2dAxisAlignment()); + EXPECT_TRUE(transform.NonDegeneratePreserves2dAxisAlignment()); + + // To be non-degenerate, the constant contribution to perspective must + // be positive. + + // clang-format off + transform = Transform(1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, -1.0); + // clang-format on + EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform)); + EXPECT_TRUE(transform.Preserves2dAxisAlignment()); + EXPECT_FALSE(transform.NonDegeneratePreserves2dAxisAlignment()); + + // clang-format off + transform = Transform(2.0, 0.0, 0.0, 0.0, + 0.0, 5.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 0.0); + // clang-format on + EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform)); + EXPECT_TRUE(transform.Preserves2dAxisAlignment()); + EXPECT_FALSE(transform.NonDegeneratePreserves2dAxisAlignment()); +} + +TEST(XFormTest, To2dTranslation) { + Vector2dF translation(3.f, 7.f); + Transform transform; + transform.Translate(translation.x(), translation.y() + 1); + EXPECT_NE(translation.ToString(), transform.To2dTranslation().ToString()); + transform.MakeIdentity(); + transform.Translate(translation.x(), translation.y()); + EXPECT_EQ(translation.ToString(), transform.To2dTranslation().ToString()); +} + +TEST(XFormTest, TransformRect) { + Transform translation; + translation.Translate(3.f, 7.f); + RectF rect(1.f, 2.f, 3.f, 4.f); + RectF expected(4.f, 9.f, 3.f, 4.f); + translation.TransformRect(&rect); + EXPECT_EQ(expected.ToString(), rect.ToString()); +} + +TEST(XFormTest, TransformRectReverse) { + Transform translation; + translation.Translate(3.f, 7.f); + RectF rect(1.f, 2.f, 3.f, 4.f); + RectF expected(-2.f, -5.f, 3.f, 4.f); + EXPECT_TRUE(translation.TransformRectReverse(&rect)); + EXPECT_EQ(expected.ToString(), rect.ToString()); + + Transform singular; + singular.Scale3d(0.f, 0.f, 0.f); + EXPECT_FALSE(singular.TransformRectReverse(&rect)); +} + +TEST(XFormTest, TransformRRectF) { + Transform translation; + translation.Translate(-3.f, -7.f); + RRectF rrect(1.f, 2.f, 20.f, 25.f, 5.f); + RRectF expected(-2.f, -5.f, 20.f, 25.f, 5.f); + EXPECT_TRUE(translation.TransformRRectF(&rrect)); + EXPECT_EQ(expected.ToString(), rrect.ToString()); + + skia::Matrix44 rot(skia::Matrix44::kUninitialized_Constructor); + rot.set3x3(0, 1, 0, -1, 0, 0, 0, 0, 1); + Transform rotation_90_Clock(rot); + + rrect = RRectF(gfx::RectF(0, 0, 20.f, 25.f), + gfx::RoundedCornersF(1.f, 2.f, 3.f, 4.f)); + expected = RRectF(gfx::RectF(-25.f, 0, 25.f, 20.f), + gfx::RoundedCornersF(4.f, 1.f, 2.f, 3.f)); + EXPECT_TRUE(rotation_90_Clock.TransformRRectF(&rrect)); + EXPECT_EQ(expected.ToString(), rrect.ToString()); + + Transform scale; + scale.Scale(2.f, 2.f); + rrect = RRectF(gfx::RectF(0, 0, 20.f, 25.f), + gfx::RoundedCornersF(1.f, 2.f, 3.f, 4.f)); + expected = RRectF(gfx::RectF(0, 0, 40.f, 50.f), + gfx::RoundedCornersF(2.f, 4.f, 6.f, 8.f)); + EXPECT_TRUE(scale.TransformRRectF(&rrect)); + EXPECT_EQ(expected.ToString(), rrect.ToString()); +} + +TEST(XFormTest, TransformBox) { + Transform translation; + translation.Translate3d(3.f, 7.f, 6.f); + BoxF box(1.f, 2.f, 3.f, 4.f, 5.f, 6.f); + BoxF expected(4.f, 9.f, 9.f, 4.f, 5.f, 6.f); + translation.TransformBox(&box); + EXPECT_EQ(expected.ToString(), box.ToString()); +} + +TEST(XFormTest, TransformBoxReverse) { + Transform translation; + translation.Translate3d(3.f, 7.f, 6.f); + BoxF box(1.f, 2.f, 3.f, 4.f, 5.f, 6.f); + BoxF expected(-2.f, -5.f, -3.f, 4.f, 5.f, 6.f); + EXPECT_TRUE(translation.TransformBoxReverse(&box)); + EXPECT_EQ(expected.ToString(), box.ToString()); + + Transform singular; + singular.Scale3d(0.f, 0.f, 0.f); + EXPECT_FALSE(singular.TransformBoxReverse(&box)); +} + +TEST(XFormTest, RoundTranslationComponents) { + Transform translation; + Transform expected; + + translation.RoundTranslationComponents(); + EXPECT_EQ(expected.ToString(), translation.ToString()); + + translation.Translate(1.0f, 1.0f); + expected.Translate(1.0f, 1.0f); + translation.RoundTranslationComponents(); + EXPECT_EQ(expected.ToString(), translation.ToString()); + + translation.Translate(0.5f, 0.4f); + expected.Translate(1.0f, 0.0f); + translation.RoundTranslationComponents(); + EXPECT_EQ(expected.ToString(), translation.ToString()); + + // Rounding should only affect 2d translation components. + translation.Translate3d(0.f, 0.f, 0.5f); + expected.Translate3d(0.f, 0.f, 0.5f); + translation.RoundTranslationComponents(); + EXPECT_EQ(expected.ToString(), translation.ToString()); +} + +TEST(XFormTest, BackFaceVisiblilityTolerance) { + Transform backface_invisible; + backface_invisible.matrix().set(0, 3, 1.f); + backface_invisible.matrix().set(3, 0, 1.f); + backface_invisible.matrix().set(2, 0, 1.f); + backface_invisible.matrix().set(3, 2, 1.f); + + // The transformation matrix has a determinant = 1 and cofactor33 = 0. So, + // IsBackFaceVisible should return false. + EXPECT_EQ(backface_invisible.matrix().determinant(), 1.f); + EXPECT_FALSE(backface_invisible.IsBackFaceVisible()); + + // Adding a noise to the transformsation matrix that is within the tolerance + // (machine epsilon) should not change the result. + float noise = std::numeric_limits::epsilon(); + backface_invisible.matrix().set(0, 3, 1.f + noise); + EXPECT_FALSE(backface_invisible.IsBackFaceVisible()); + + // A noise that is more than the tolerance should change the result. + backface_invisible.matrix().set(0, 3, 1.f + (2 * noise)); + EXPECT_TRUE(backface_invisible.IsBackFaceVisible()); +} + +} // namespace + +} // namespace gfx diff --git a/geometry/transform_util.cc b/geometry/transform_util.cc new file mode 100644 index 000000000000..224598d5b42f --- /dev/null +++ b/geometry/transform_util.cc @@ -0,0 +1,673 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/transform_util.h" + +#include +#include +#include + +#include "base/check.h" +#include "base/strings/stringprintf.h" +#include "ui/gfx/geometry/point3_f.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/rect_f.h" + +namespace gfx { + +namespace { + +SkScalar Length3(SkScalar v[3]) { + double vd[3] = {v[0], v[1], v[2]}; + return SkDoubleToScalar( + std::sqrt(vd[0] * vd[0] + vd[1] * vd[1] + vd[2] * vd[2])); +} + +template +SkScalar Dot(const SkScalar* a, const SkScalar* b) { + double total = 0.0; + for (int i = 0; i < n; ++i) + total += a[i] * b[i]; + return SkDoubleToScalar(total); +} + +template +void Combine(SkScalar* out, + const SkScalar* a, + const SkScalar* b, + double scale_a, + double scale_b) { + for (int i = 0; i < n; ++i) + out[i] = SkDoubleToScalar(a[i] * scale_a + b[i] * scale_b); +} + +void Cross3(SkScalar out[3], SkScalar a[3], SkScalar b[3]) { + SkScalar x = a[1] * b[2] - a[2] * b[1]; + SkScalar y = a[2] * b[0] - a[0] * b[2]; + SkScalar z = a[0] * b[1] - a[1] * b[0]; + out[0] = x; + out[1] = y; + out[2] = z; +} + +SkScalar Round(SkScalar n) { + return SkDoubleToScalar(std::floor(double{n} + 0.5)); +} + +// Returns false if the matrix cannot be normalized. +bool Normalize(skia::Matrix44& m) { + if (m.get(3, 3) == 0.0) + // Cannot normalize. + return false; + + SkScalar scale = SK_Scalar1 / m.get(3, 3); + for (int i = 0; i < 4; i++) + for (int j = 0; j < 4; j++) + m.set(i, j, m.get(i, j) * scale); + + return true; +} + +skia::Matrix44 BuildPerspectiveMatrix(const DecomposedTransform& decomp) { + skia::Matrix44 matrix(skia::Matrix44::kIdentity_Constructor); + + for (int i = 0; i < 4; i++) + matrix.setDouble(3, i, decomp.perspective[i]); + return matrix; +} + +skia::Matrix44 BuildTranslationMatrix(const DecomposedTransform& decomp) { + skia::Matrix44 matrix(skia::Matrix44::kUninitialized_Constructor); + // Implicitly calls matrix.setIdentity() + matrix.setTranslate(SkDoubleToScalar(decomp.translate[0]), + SkDoubleToScalar(decomp.translate[1]), + SkDoubleToScalar(decomp.translate[2])); + return matrix; +} + +skia::Matrix44 BuildSnappedTranslationMatrix(DecomposedTransform decomp) { + decomp.translate[0] = Round(decomp.translate[0]); + decomp.translate[1] = Round(decomp.translate[1]); + decomp.translate[2] = Round(decomp.translate[2]); + return BuildTranslationMatrix(decomp); +} + +skia::Matrix44 BuildRotationMatrix(const DecomposedTransform& decomp) { + return Transform(decomp.quaternion).matrix(); +} + +skia::Matrix44 BuildSnappedRotationMatrix(const DecomposedTransform& decomp) { + // Create snapped rotation. + skia::Matrix44 rotation_matrix = BuildRotationMatrix(decomp); + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { + SkScalar value = rotation_matrix.get(i, j); + // Snap values to -1, 0 or 1. + if (value < -0.5f) { + value = -1.0f; + } else if (value > 0.5f) { + value = 1.0f; + } else { + value = 0.0f; + } + rotation_matrix.set(i, j, value); + } + } + return rotation_matrix; +} + +skia::Matrix44 BuildSkewMatrix(const DecomposedTransform& decomp) { + skia::Matrix44 matrix(skia::Matrix44::kIdentity_Constructor); + + skia::Matrix44 temp(skia::Matrix44::kIdentity_Constructor); + if (decomp.skew[2]) { + temp.setDouble(1, 2, decomp.skew[2]); + matrix.preConcat(temp); + } + + if (decomp.skew[1]) { + temp.setDouble(1, 2, 0); + temp.setDouble(0, 2, decomp.skew[1]); + matrix.preConcat(temp); + } + + if (decomp.skew[0]) { + temp.setDouble(0, 2, 0); + temp.setDouble(0, 1, decomp.skew[0]); + matrix.preConcat(temp); + } + return matrix; +} + +skia::Matrix44 BuildScaleMatrix(const DecomposedTransform& decomp) { + skia::Matrix44 matrix(skia::Matrix44::kUninitialized_Constructor); + matrix.setScale(SkDoubleToScalar(decomp.scale[0]), + SkDoubleToScalar(decomp.scale[1]), + SkDoubleToScalar(decomp.scale[2])); + return matrix; +} + +skia::Matrix44 BuildSnappedScaleMatrix(DecomposedTransform decomp) { + decomp.scale[0] = Round(decomp.scale[0]); + decomp.scale[1] = Round(decomp.scale[1]); + decomp.scale[2] = Round(decomp.scale[2]); + return BuildScaleMatrix(decomp); +} + +Transform ComposeTransform(const skia::Matrix44& perspective, + const skia::Matrix44& translation, + const skia::Matrix44& rotation, + const skia::Matrix44& skew, + const skia::Matrix44& scale) { + skia::Matrix44 matrix(skia::Matrix44::kIdentity_Constructor); + + matrix.preConcat(perspective); + matrix.preConcat(translation); + matrix.preConcat(rotation); + matrix.preConcat(skew); + matrix.preConcat(scale); + + Transform to_return; + to_return.matrix() = matrix; + return to_return; +} + +bool CheckViewportPointMapsWithinOnePixel(const Point& point, + const Transform& transform) { + auto point_original = Point3F(PointF(point)); + auto point_transformed = Point3F(PointF(point)); + + // Can't use TransformRect here since it would give us the axis-aligned + // bounding rect of the 4 points in the initial rectable which is not what we + // want. + transform.TransformPoint(&point_transformed); + + if ((point_transformed - point_original).Length() > 1.f) { + // The changed distance should not be more than 1 pixel. + return false; + } + return true; +} + +bool CheckTransformsMapsIntViewportWithinOnePixel(const Rect& viewport, + const Transform& original, + const Transform& snapped) { + Transform original_inv(Transform::kSkipInitialization); + bool invertible = true; + invertible &= original.GetInverse(&original_inv); + DCHECK(invertible) << "Non-invertible transform, cannot snap."; + + Transform combined = snapped * original_inv; + + return CheckViewportPointMapsWithinOnePixel(viewport.origin(), combined) && + CheckViewportPointMapsWithinOnePixel(viewport.top_right(), combined) && + CheckViewportPointMapsWithinOnePixel(viewport.bottom_left(), + combined) && + CheckViewportPointMapsWithinOnePixel(viewport.bottom_right(), + combined); +} + +bool Is2dTransform(const Transform& transform) { + const skia::Matrix44 matrix = transform.matrix(); + if (matrix.hasPerspective()) + return false; + + return matrix.get(2, 0) == 0 && matrix.get(2, 1) == 0 && + matrix.get(0, 2) == 0 && matrix.get(1, 2) == 0 && + matrix.get(2, 2) == 1 && matrix.get(3, 2) == 0 && + matrix.get(2, 3) == 0; +} + +bool Decompose2DTransform(DecomposedTransform* decomp, + const Transform& transform) { + if (!Is2dTransform(transform)) { + return false; + } + + const skia::Matrix44 matrix = transform.matrix(); + double m11 = matrix.getDouble(0, 0); + double m21 = matrix.getDouble(0, 1); + double m12 = matrix.getDouble(1, 0); + double m22 = matrix.getDouble(1, 1); + + double determinant = m11 * m22 - m12 * m21; + // Test for matrix being singular. + if (determinant == 0) { + return false; + } + + // Translation transform. + // [m11 m21 0 m41] [1 0 0 Tx] [m11 m21 0 0] + // [m12 m22 0 m42] = [0 1 0 Ty] [m12 m22 0 0] + // [ 0 0 1 0 ] [0 0 1 0 ] [ 0 0 1 0] + // [ 0 0 0 1 ] [0 0 0 1 ] [ 0 0 0 1] + decomp->translate[0] = matrix.get(0, 3); + decomp->translate[1] = matrix.get(1, 3); + + // For the remainder of the decomposition process, we can focus on the upper + // 2x2 submatrix + // [m11 m21] = [cos(R) -sin(R)] [1 K] [Sx 0 ] + // [m12 m22] [sin(R) cos(R)] [0 1] [0 Sy] + // = [Sx*cos(R) Sy*(K*cos(R) - sin(R))] + // [Sx*sin(R) Sy*(K*sin(R) + cos(R))] + + // Determine sign of the x and y scale. + if (determinant < 0) { + // If the determinant is negative, we need to flip either the x or y scale. + // Flipping both is equivalent to rotating by 180 degrees. + if (m11 < m22) { + decomp->scale[0] *= -1; + } else { + decomp->scale[1] *= -1; + } + } + + // X Scale. + // m11^2 + m12^2 = Sx^2*(cos^2(R) + sin^2(R)) = Sx^2. + // Sx = +/-sqrt(m11^2 + m22^2) + decomp->scale[0] *= sqrt(m11 * m11 + m12 * m12); + m11 /= decomp->scale[0]; + m12 /= decomp->scale[0]; + + // Post normalization, the submatrix is now of the form: + // [m11 m21] = [cos(R) Sy*(K*cos(R) - sin(R))] + // [m12 m22] [sin(R) Sy*(K*sin(R) + cos(R))] + + // XY Shear. + // m11 * m21 + m12 * m22 = Sy*K*cos^2(R) - Sy*sin(R)*cos(R) + + // Sy*K*sin^2(R) + Sy*cos(R)*sin(R) + // = Sy*K + double scaledShear = m11 * m21 + m12 * m22; + m21 -= m11 * scaledShear; + m22 -= m12 * scaledShear; + + // Post normalization, the submatrix is now of the form: + // [m11 m21] = [cos(R) -Sy*sin(R)] + // [m12 m22] [sin(R) Sy*cos(R)] + + // Y Scale. + // Similar process to determining x-scale. + decomp->scale[1] *= sqrt(m21 * m21 + m22 * m22); + m21 /= decomp->scale[1]; + m22 /= decomp->scale[1]; + decomp->skew[0] = scaledShear / decomp->scale[1]; + + // Rotation transform. + // [1-2(yy+zz) 2(xy-zw) 2(xz+yw) ] [cos(R) -sin(R) 0] + // [2(xy+zw) 1-2(xx+zz) 2(yz-xw) ] = [sin(R) cos(R) 0] + // [2(xz-yw) 2*(yz+xw) 1-2(xx+yy)] [ 0 0 1] + // Comparing terms, we can conclude that x = y = 0. + // [1-2zz -2zw 0] [cos(R) -sin(R) 0] + // [ 2zw 1-2zz 0] = [sin(R) cos(R) 0] + // [ 0 0 1] [ 0 0 1] + // cos(R) = 1 - 2*z^2 + // From the double angle formula: cos(2a) = 1 - 2 sin(a)^2 + // cos(R) = 1 - 2*sin(R/2)^2 = 1 - 2*z^2 ==> z = sin(R/2) + // sin(R) = 2*z*w + // But sin(2a) = 2 sin(a) cos(a) + // sin(R) = 2 sin(R/2) cos(R/2) = 2*z*w ==> w = cos(R/2) + double angle = atan2(m12, m11); + decomp->quaternion.set_x(0); + decomp->quaternion.set_y(0); + decomp->quaternion.set_z(sin(0.5 * angle)); + decomp->quaternion.set_w(cos(0.5 * angle)); + + return true; +} + +} // namespace + +Transform GetScaleTransform(const Point& anchor, float scale) { + Transform transform; + transform.Translate(anchor.x() * (1 - scale), anchor.y() * (1 - scale)); + transform.Scale(scale, scale); + return transform; +} + +DecomposedTransform::DecomposedTransform() { + translate[0] = translate[1] = translate[2] = 0.0; + scale[0] = scale[1] = scale[2] = 1.0; + skew[0] = skew[1] = skew[2] = 0.0; + perspective[0] = perspective[1] = perspective[2] = 0.0; + perspective[3] = 1.0; +} + +DecomposedTransform BlendDecomposedTransforms(const DecomposedTransform& to, + const DecomposedTransform& from, + double progress) { + DecomposedTransform out; + double scalea = progress; + double scaleb = 1.0 - progress; + Combine<3>(out.translate, to.translate, from.translate, scalea, scaleb); + Combine<3>(out.scale, to.scale, from.scale, scalea, scaleb); + Combine<3>(out.skew, to.skew, from.skew, scalea, scaleb); + Combine<4>(out.perspective, to.perspective, from.perspective, scalea, scaleb); + out.quaternion = from.quaternion.Slerp(to.quaternion, progress); + return out; +} + +// Taken from http://www.w3.org/TR/css3-transforms/. +// TODO(crbug/937296): This implementation is virtually identical to the +// implementation in blink::TransformationMatrix with the main difference being +// the representation of the underlying matrix. These implementations should be +// consolidated. +bool DecomposeTransform(DecomposedTransform* decomp, + const Transform& transform) { + if (!decomp) + return false; + + if (Decompose2DTransform(decomp, transform)) + return true; + + // We'll operate on a copy of the matrix. + skia::Matrix44 matrix = transform.matrix(); + + // If we cannot normalize the matrix, then bail early as we cannot decompose. + if (!Normalize(matrix)) + return false; + + skia::Matrix44 perspectiveMatrix = matrix; + + for (int i = 0; i < 3; ++i) + perspectiveMatrix.set(3, i, 0.0); + + perspectiveMatrix.set(3, 3, 1.0); + + // If the perspective matrix is not invertible, we are also unable to + // decompose, so we'll bail early. Constant taken from skia::Matrix44::invert. + if (std::abs(perspectiveMatrix.determinant()) < 1e-8) + return false; + + if (matrix.get(3, 0) != 0.0 || matrix.get(3, 1) != 0.0 || + matrix.get(3, 2) != 0.0) { + // rhs is the right hand side of the equation. + SkScalar rhs[4] = {matrix.get(3, 0), matrix.get(3, 1), matrix.get(3, 2), + matrix.get(3, 3)}; + + // Solve the equation by inverting perspectiveMatrix and multiplying + // rhs by the inverse. + skia::Matrix44 inversePerspectiveMatrix( + skia::Matrix44::kUninitialized_Constructor); + if (!perspectiveMatrix.invert(&inversePerspectiveMatrix)) + return false; + + skia::Matrix44 transposedInversePerspectiveMatrix = + inversePerspectiveMatrix; + + transposedInversePerspectiveMatrix.transpose(); + transposedInversePerspectiveMatrix.mapScalars(rhs); + + for (int i = 0; i < 4; ++i) + decomp->perspective[i] = rhs[i]; + + } else { + // No perspective. + for (int i = 0; i < 3; ++i) + decomp->perspective[i] = 0.0; + decomp->perspective[3] = 1.0; + } + + for (int i = 0; i < 3; i++) + decomp->translate[i] = matrix.get(i, 3); + + // Copy of matrix is stored in column major order to facilitate column-level + // operations. + SkScalar column[3][3]; + for (int i = 0; i < 3; i++) + for (int j = 0; j < 3; ++j) + column[i][j] = matrix.get(j, i); + + // Compute X scale factor and normalize first column. + decomp->scale[0] = Length3(column[0]); + if (decomp->scale[0] != 0.0) { + column[0][0] /= decomp->scale[0]; + column[0][1] /= decomp->scale[0]; + column[0][2] /= decomp->scale[0]; + } + + // Compute XY shear factor and make 2nd column orthogonal to 1st. + decomp->skew[0] = Dot<3>(column[0], column[1]); + Combine<3>(column[1], column[1], column[0], 1.0, -decomp->skew[0]); + + // Now, compute Y scale and normalize 2nd column. + decomp->scale[1] = Length3(column[1]); + if (decomp->scale[1] != 0.0) { + column[1][0] /= decomp->scale[1]; + column[1][1] /= decomp->scale[1]; + column[1][2] /= decomp->scale[1]; + } + + decomp->skew[0] /= decomp->scale[1]; + + // Compute XZ and YZ shears, orthogonalize the 3rd column. + decomp->skew[1] = Dot<3>(column[0], column[2]); + Combine<3>(column[2], column[2], column[0], 1.0, -decomp->skew[1]); + decomp->skew[2] = Dot<3>(column[1], column[2]); + Combine<3>(column[2], column[2], column[1], 1.0, -decomp->skew[2]); + + // Next, get Z scale and normalize the 3rd column. + decomp->scale[2] = Length3(column[2]); + if (decomp->scale[2] != 0.0) { + column[2][0] /= decomp->scale[2]; + column[2][1] /= decomp->scale[2]; + column[2][2] /= decomp->scale[2]; + } + + decomp->skew[1] /= decomp->scale[2]; + decomp->skew[2] /= decomp->scale[2]; + + // At this point, the matrix is orthonormal. + // Check for a coordinate system flip. If the determinant + // is -1, then negate the matrix and the scaling factors. + // TODO(kevers): This is inconsistent from the 2D specification, in which + // only 1 axis is flipped when the determinant is negative. Verify if it is + // correct to flip all of the scales and matrix elements, as this introduces + // rotation for the simple case of a single axis scale inversion. + SkScalar pdum3[3]; + Cross3(pdum3, column[1], column[2]); + if (Dot<3>(column[0], pdum3) < 0) { + for (int i = 0; i < 3; i++) { + decomp->scale[i] *= -1.0; + for (int j = 0; j < 3; ++j) + column[i][j] *= -1.0; + } + } + + // See https://en.wikipedia.org/wiki/Rotation_matrix#Quaternion. + // Note: deviating from spec (http://www.w3.org/TR/css3-transforms/) + // which has a degenerate case of zero off-diagonal elements in the + // orthonormal matrix, which leads to errors in determining the sign + // of the quaternions. + double q_xx = column[0][0]; + double q_xy = column[1][0]; + double q_xz = column[2][0]; + double q_yx = column[0][1]; + double q_yy = column[1][1]; + double q_yz = column[2][1]; + double q_zx = column[0][2]; + double q_zy = column[1][2]; + double q_zz = column[2][2]; + + double r, s, t, x, y, z, w; + t = q_xx + q_yy + q_zz; + if (t > 0) { + r = std::sqrt(1.0 + t); + s = 0.5 / r; + w = 0.5 * r; + x = (q_zy - q_yz) * s; + y = (q_xz - q_zx) * s; + z = (q_yx - q_xy) * s; + } else if (q_xx > q_yy && q_xx > q_zz) { + r = std::sqrt(1.0 + q_xx - q_yy - q_zz); + s = 0.5 / r; + x = 0.5 * r; + y = (q_xy + q_yx) * s; + z = (q_xz + q_zx) * s; + w = (q_zy - q_yz) * s; + } else if (q_yy > q_zz) { + r = std::sqrt(1.0 - q_xx + q_yy - q_zz); + s = 0.5 / r; + x = (q_xy + q_yx) * s; + y = 0.5 * r; + z = (q_yz + q_zy) * s; + w = (q_xz - q_zx) * s; + } else { + r = std::sqrt(1.0 - q_xx - q_yy + q_zz); + s = 0.5 / r; + x = (q_xz + q_zx) * s; + y = (q_yz + q_zy) * s; + z = 0.5 * r; + w = (q_yx - q_xy) * s; + } + + decomp->quaternion.set_x(SkDoubleToScalar(x)); + decomp->quaternion.set_y(SkDoubleToScalar(y)); + decomp->quaternion.set_z(SkDoubleToScalar(z)); + decomp->quaternion.set_w(SkDoubleToScalar(w)); + + return true; +} + +// Taken from http://www.w3.org/TR/css3-transforms/. +Transform ComposeTransform(const DecomposedTransform& decomp) { + skia::Matrix44 perspective = BuildPerspectiveMatrix(decomp); + skia::Matrix44 translation = BuildTranslationMatrix(decomp); + skia::Matrix44 rotation = BuildRotationMatrix(decomp); + skia::Matrix44 skew = BuildSkewMatrix(decomp); + skia::Matrix44 scale = BuildScaleMatrix(decomp); + + return ComposeTransform(perspective, translation, rotation, skew, scale); +} + +bool SnapTransform(Transform* out, + const Transform& transform, + const Rect& viewport) { + DecomposedTransform decomp; + DecomposeTransform(&decomp, transform); + + skia::Matrix44 rotation_matrix = BuildSnappedRotationMatrix(decomp); + skia::Matrix44 translation = BuildSnappedTranslationMatrix(decomp); + skia::Matrix44 scale = BuildSnappedScaleMatrix(decomp); + + // Rebuild matrices for other unchanged components. + skia::Matrix44 perspective = BuildPerspectiveMatrix(decomp); + + // Completely ignore the skew. + skia::Matrix44 skew(skia::Matrix44::kIdentity_Constructor); + + // Get full transform. + Transform snapped = + ComposeTransform(perspective, translation, rotation_matrix, skew, scale); + + // Verify that viewport is not moved unnaturally. + bool snappable = CheckTransformsMapsIntViewportWithinOnePixel( + viewport, transform, snapped); + if (snappable) { + *out = snapped; + } + return snappable; +} + +Transform TransformAboutPivot(const Point& pivot, const Transform& transform) { + Transform result; + result.Translate(pivot.x(), pivot.y()); + result.PreconcatTransform(transform); + result.Translate(-pivot.x(), -pivot.y()); + return result; +} + +Transform TransformBetweenRects(const RectF& src, const RectF& dst) { + DCHECK(!src.IsEmpty()); + Transform result; + result.Translate(dst.origin() - src.origin()); + result.Scale(dst.width() / src.width(), dst.height() / src.height()); + return result; +} + +std::string DecomposedTransform::ToString() const { + return base::StringPrintf( + "translate: %+0.4f %+0.4f %+0.4f\n" + "scale: %+0.4f %+0.4f %+0.4f\n" + "skew: %+0.4f %+0.4f %+0.4f\n" + "perspective: %+0.4f %+0.4f %+0.4f %+0.4f\n" + "quaternion: %+0.4f %+0.4f %+0.4f %+0.4f\n", + translate[0], translate[1], translate[2], scale[0], scale[1], scale[2], + skew[0], skew[1], skew[2], perspective[0], perspective[1], perspective[2], + perspective[3], quaternion.x(), quaternion.y(), quaternion.z(), + quaternion.w()); +} + +Transform OrthoProjectionMatrix(float left, + float right, + float bottom, + float top) { + // Use the standard formula to map the clipping frustum to the cube from + // [-1, -1, -1] to [1, 1, 1]. + float delta_x = right - left; + float delta_y = top - bottom; + Transform proj; + if (!delta_x || !delta_y) + return proj; + proj.matrix().set(0, 0, 2.0f / delta_x); + proj.matrix().set(0, 3, -(right + left) / delta_x); + proj.matrix().set(1, 1, 2.0f / delta_y); + proj.matrix().set(1, 3, -(top + bottom) / delta_y); + + // Z component of vertices is always set to zero as we don't use the depth + // buffer while drawing. + proj.matrix().set(2, 2, 0); + + return proj; +} + +Transform WindowMatrix(int x, int y, int width, int height) { + Transform canvas; + + // Map to window position and scale up to pixel coordinates. + canvas.Translate3d(x, y, 0); + canvas.Scale3d(width, height, 0); + + // Map from ([-1, -1] to [1, 1]) -> ([0, 0] to [1, 1]) + canvas.Translate3d(0.5, 0.5, 0.5); + canvas.Scale3d(0.5, 0.5, 0.5); + + return canvas; +} + +static inline bool NearlyZero(double value) { + return std::abs(value) < std::numeric_limits::epsilon(); +} + +static inline float ScaleOnAxis(double a, double b, double c) { + if (NearlyZero(b) && NearlyZero(c)) + return std::abs(a); + if (NearlyZero(a) && NearlyZero(c)) + return std::abs(b); + if (NearlyZero(a) && NearlyZero(b)) + return std::abs(c); + + // Do the sqrt as a double to not lose precision. + return static_cast(std::sqrt(a * a + b * b + c * c)); +} + +Vector2dF ComputeTransform2dScaleComponents(const Transform& transform, + float fallback_value) { + if (transform.HasPerspective()) + return Vector2dF(fallback_value, fallback_value); + float x_scale = ScaleOnAxis(transform.matrix().getDouble(0, 0), + transform.matrix().getDouble(1, 0), + transform.matrix().getDouble(2, 0)); + float y_scale = ScaleOnAxis(transform.matrix().getDouble(0, 1), + transform.matrix().getDouble(1, 1), + transform.matrix().getDouble(2, 1)); + return Vector2dF(x_scale, y_scale); +} + +float ComputeApproximateMaxScale(const Transform& transform) { + RectF unit(0.f, 0.f, 1.f, 1.f); + transform.TransformRect(&unit); + return std::max(unit.width(), unit.height()); +} + +} // namespace gfx diff --git a/geometry/transform_util.h b/geometry/transform_util.h new file mode 100644 index 000000000000..235c8e10d02c --- /dev/null +++ b/geometry/transform_util.h @@ -0,0 +1,99 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_TRANSFORM_UTIL_H_ +#define UI_GFX_GEOMETRY_TRANSFORM_UTIL_H_ + +#include "ui/gfx/geometry/geometry_skia_export.h" +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/quaternion.h" +#include "ui/gfx/geometry/transform.h" + +namespace gfx { + +class Rect; +class RectF; + +// Returns a scale transform at |anchor| point. +GEOMETRY_SKIA_EXPORT Transform GetScaleTransform(const Point& anchor, + float scale); + +// Contains the components of a factored transform. These components may be +// blended and recomposed. +struct GEOMETRY_SKIA_EXPORT DecomposedTransform { + // The default constructor initializes the components in such a way that + // if used with Compose below, will produce the identity transform. + DecomposedTransform(); + + SkScalar translate[3]; + SkScalar scale[3]; + SkScalar skew[3]; + SkScalar perspective[4]; + Quaternion quaternion; + + std::string ToString() const; + + // Copy and assign are allowed. +}; + +// Interpolates the decomposed components |to| with |from| using the +// routines described in http://www.w3.org/TR/css3-3d-transform/. +// |progress| is in the range [0, 1]. If 0 we will return |from|, if 1, we will +// return |to|. +GEOMETRY_SKIA_EXPORT DecomposedTransform +BlendDecomposedTransforms(const DecomposedTransform& to, + const DecomposedTransform& from, + double progress); + +// Decomposes this transform into its translation, scale, skew, perspective, +// and rotation components following the routines detailed in this spec: +// http://www.w3.org/TR/css3-3d-transforms/. +GEOMETRY_SKIA_EXPORT bool DecomposeTransform(DecomposedTransform* out, + const Transform& transform); + +// Composes a transform from the given translation, scale, skew, perspective, +// and rotation components following the routines detailed in this spec: +// http://www.w3.org/TR/css3-3d-transforms/. +GEOMETRY_SKIA_EXPORT Transform +ComposeTransform(const DecomposedTransform& decomp); + +GEOMETRY_SKIA_EXPORT bool SnapTransform(Transform* out, + const Transform& transform, + const Rect& viewport); + +// Calculates a transform with a transformed origin. The resulting transform is +// created by composing P * T * P^-1 where P is a constant transform to the new +// origin. +GEOMETRY_SKIA_EXPORT Transform TransformAboutPivot(const Point& pivot, + const Transform& transform); + +// Calculates a transform which would transform |src| to |dst|. +GEOMETRY_SKIA_EXPORT Transform TransformBetweenRects(const RectF& src, + const RectF& dst); + +// Generates projection matrix and returns it as a Transform. +GEOMETRY_SKIA_EXPORT Transform OrthoProjectionMatrix(float left, + float right, + float bottom, + float top); + +// Generates window matrix and returns it as a Transform. +GEOMETRY_SKIA_EXPORT Transform WindowMatrix(int x, + int y, + int width, + int height); + +GEOMETRY_SKIA_EXPORT Vector2dF +ComputeTransform2dScaleComponents(const Transform& transform, + float fallback_value); + +// Returns an approximate max scale value of the transform even if it has +// perspective. Prefer to use ComputeTransform2dScaleComponents if there is no +// perspective, since it can produce more accurate results. +GEOMETRY_SKIA_EXPORT +float ComputeApproximateMaxScale(const Transform& transform); + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_TRANSFORM_UTIL_H_ diff --git a/geometry/transform_util_unittest.cc b/geometry/transform_util_unittest.cc new file mode 100644 index 000000000000..87530ddd8e84 --- /dev/null +++ b/geometry/transform_util_unittest.cc @@ -0,0 +1,352 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/transform_util.h" + +#include + +#include "base/numerics/math_constants.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/point3_f.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/rect_f.h" + +namespace gfx { +namespace { + +#define EXPECT_APPROX_EQ(val1, val2) EXPECT_NEAR(val1, val2, 1e-6); + +TEST(TransformUtilTest, GetScaleTransform) { + const Point kAnchor(20, 40); + const float kScale = 0.5f; + + Transform scale = GetScaleTransform(kAnchor, kScale); + + const int kOffset = 10; + for (int sign_x = -1; sign_x <= 1; ++sign_x) { + for (int sign_y = -1; sign_y <= 1; ++sign_y) { + Point test(kAnchor.x() + sign_x * kOffset, + kAnchor.y() + sign_y * kOffset); + scale.TransformPoint(&test); + + EXPECT_EQ(Point(kAnchor.x() + sign_x * kOffset * kScale, + kAnchor.y() + sign_y * kOffset * kScale), + test); + } + } +} + +TEST(TransformUtilTest, SnapRotation) { + Transform result(Transform::kSkipInitialization); + Transform transform; + transform.RotateAboutZAxis(89.99); + + Rect viewport(1920, 1200); + bool snapped = SnapTransform(&result, transform, viewport); + + EXPECT_TRUE(snapped) << "Viewport should snap for this rotation."; +} + +TEST(TransformUtilTest, SnapRotationDistantViewport) { + const int kOffset = 5000; + Transform result(Transform::kSkipInitialization); + Transform transform; + + transform.RotateAboutZAxis(89.99); + + Rect viewport(kOffset, kOffset, 1920, 1200); + bool snapped = SnapTransform(&result, transform, viewport); + + EXPECT_FALSE(snapped) << "Distant viewport shouldn't snap by more than 1px."; +} + +TEST(TransformUtilTest, NoSnapRotation) { + Transform result(Transform::kSkipInitialization); + Transform transform; + const int kOffset = 5000; + + transform.RotateAboutZAxis(89.9); + + Rect viewport(kOffset, kOffset, 1920, 1200); + bool snapped = SnapTransform(&result, transform, viewport); + + EXPECT_FALSE(snapped) << "Viewport should not snap for this rotation."; +} + +// Translations should always be snappable, the most we would move is 0.5 +// pixels towards either direction to the nearest value in each component. +TEST(TransformUtilTest, SnapTranslation) { + Transform result(Transform::kSkipInitialization); + Transform transform; + + transform.Translate3d(SkDoubleToScalar(1.01), SkDoubleToScalar(1.99), + SkDoubleToScalar(3.0)); + + Rect viewport(1920, 1200); + bool snapped = SnapTransform(&result, transform, viewport); + + EXPECT_TRUE(snapped) << "Viewport should snap for this translation."; +} + +TEST(TransformUtilTest, SnapTranslationDistantViewport) { + Transform result(Transform::kSkipInitialization); + Transform transform; + const int kOffset = 5000; + + transform.Translate3d(SkDoubleToScalar(1.01), SkDoubleToScalar(1.99), + SkDoubleToScalar(3.0)); + + Rect viewport(kOffset, kOffset, 1920, 1200); + bool snapped = SnapTransform(&result, transform, viewport); + + EXPECT_TRUE(snapped) + << "Distant viewport should still snap by less than 1px."; +} + +TEST(TransformUtilTest, SnapScale) { + Transform result(Transform::kSkipInitialization); + Transform transform; + + transform.Scale3d(SkDoubleToScalar(5.0), SkDoubleToScalar(2.00001), + SkDoubleToScalar(1.0)); + Rect viewport(1920, 1200); + bool snapped = SnapTransform(&result, transform, viewport); + + EXPECT_TRUE(snapped) << "Viewport should snap for this scaling."; +} + +TEST(TransformUtilTest, NoSnapScale) { + Transform result(Transform::kSkipInitialization); + Transform transform; + + transform.Scale3d(SkDoubleToScalar(5.0), SkDoubleToScalar(2.1), + SkDoubleToScalar(1.0)); + Rect viewport(1920, 1200); + bool snapped = SnapTransform(&result, transform, viewport); + + EXPECT_FALSE(snapped) << "Viewport shouldn't snap for this scaling."; +} + +TEST(TransformUtilTest, SnapCompositeTransform) { + Transform result(Transform::kSkipInitialization); + Transform transform; + + transform.Translate3d(SkDoubleToScalar(30.5), SkDoubleToScalar(20.0), + SkDoubleToScalar(10.1)); + transform.RotateAboutZAxis(89.99); + transform.Scale3d(SkDoubleToScalar(1.0), SkDoubleToScalar(3.00001), + SkDoubleToScalar(2.0)); + + Rect viewport(1920, 1200); + bool snapped = SnapTransform(&result, transform, viewport); + ASSERT_TRUE(snapped) << "Viewport should snap all components."; + + Point3F point; + + point = Point3F(PointF(viewport.origin())); + result.TransformPoint(&point); + EXPECT_EQ(Point3F(31.f, 20.f, 10.f), point) << "Transformed origin"; + + point = Point3F(PointF(viewport.top_right())); + result.TransformPoint(&point); + EXPECT_EQ(Point3F(31.f, 1940.f, 10.f), point) << "Transformed top-right"; + + point = Point3F(PointF(viewport.bottom_left())); + result.TransformPoint(&point); + EXPECT_EQ(Point3F(-3569.f, 20.f, 10.f), point) << "Transformed bottom-left"; + + point = Point3F(PointF(viewport.bottom_right())); + result.TransformPoint(&point); + EXPECT_EQ(Point3F(-3569.f, 1940.f, 10.f), point) + << "Transformed bottom-right"; +} + +TEST(TransformUtilTest, NoSnapSkewedCompositeTransform) { + Transform result(Transform::kSkipInitialization); + Transform transform; + + transform.RotateAboutZAxis(89.99); + transform.Scale3d(SkDoubleToScalar(1.0), SkDoubleToScalar(3.00001), + SkDoubleToScalar(2.0)); + transform.Translate3d(SkDoubleToScalar(30.5), SkDoubleToScalar(20.0), + SkDoubleToScalar(10.1)); + transform.Skew(20.0, 0.0); + Rect viewport(1920, 1200); + bool snapped = SnapTransform(&result, transform, viewport); + EXPECT_FALSE(snapped) << "Skewed viewport should not snap."; +} + +TEST(TransformUtilTest, TransformAboutPivot) { + Transform transform; + transform.Scale(3, 4); + transform = TransformAboutPivot(Point(7, 8), transform); + + Point point; + + point = Point(0, 0); + transform.TransformPoint(&point); + EXPECT_EQ(Point(-14, -24).ToString(), point.ToString()); + + point = Point(1, 1); + transform.TransformPoint(&point); + EXPECT_EQ(Point(-11, -20).ToString(), point.ToString()); +} + +TEST(TransformUtilTest, BlendOppositeQuaternions) { + DecomposedTransform first; + DecomposedTransform second; + second.quaternion.set_w(-second.quaternion.w()); + + DecomposedTransform result = BlendDecomposedTransforms(first, second, 0.25); + + EXPECT_TRUE(std::isfinite(result.quaternion.x())); + EXPECT_TRUE(std::isfinite(result.quaternion.y())); + EXPECT_TRUE(std::isfinite(result.quaternion.z())); + EXPECT_TRUE(std::isfinite(result.quaternion.w())); + + EXPECT_FALSE(std::isnan(result.quaternion.x())); + EXPECT_FALSE(std::isnan(result.quaternion.y())); + EXPECT_FALSE(std::isnan(result.quaternion.z())); + EXPECT_FALSE(std::isnan(result.quaternion.w())); +} + +double ComputeDecompRecompError(const Transform& transform) { + DecomposedTransform decomp; + DecomposeTransform(&decomp, transform); + Transform composed = ComposeTransform(decomp); + + float expected[16]; + float actual[16]; + transform.matrix().asRowMajorf(expected); + composed.matrix().asRowMajorf(actual); + double sse = 0; + for (int i = 0; i < 16; i++) { + double diff = expected[i] - actual[i]; + sse += diff * diff; + } + return sse; +} + +TEST(TransformUtilTest, RoundTripTest) { + // rotateZ(90deg) + EXPECT_APPROX_EQ(0, ComputeDecompRecompError(Transform(0, 1, -1, 0, 0, 0))); + + // rotateZ(180deg) + // Edge case where w = 0. + EXPECT_APPROX_EQ(0, ComputeDecompRecompError(Transform(-1, 0, 0, -1, 0, 0))); + + // rotateX(90deg) rotateY(90deg) rotateZ(90deg) + // [1 0 0][ 0 0 1][0 -1 0] [0 0 1][0 -1 0] [0 0 1] + // [0 0 -1][ 0 1 0][1 0 0] = [1 0 0][1 0 0] = [0 -1 0] + // [0 1 0][-1 0 0][0 0 1] [0 1 0][0 0 1] [1 0 0] + // This test case leads to Gimbal lock when using Euler angles. + EXPECT_APPROX_EQ(0, ComputeDecompRecompError(Transform( + 0, 0, 1, 0, 0, -1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1))); + + // Quaternion matrices with 0 off-diagonal elements, and negative trace. + // Stress tests handling of degenerate cases in computing quaternions. + // Validates fix for https://crbug.com/647554. + EXPECT_APPROX_EQ(0, ComputeDecompRecompError(Transform(1, 1, 1, 0, 0, 0))); + EXPECT_APPROX_EQ(0, ComputeDecompRecompError(Transform( + -1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1))); + EXPECT_APPROX_EQ(0, ComputeDecompRecompError(Transform( + 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1))); + EXPECT_APPROX_EQ(0, ComputeDecompRecompError(Transform( + 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1))); +} + +TEST(TransformUtilTest, Transform2D) { + // The spec covering interpolation of 2D matrix transforms calls for inverting + // one of the axis in the case of a negative determinant. This differs from + // the general 3D spec, which calls for flipping all of the scales when the + // determinant is negative. Flipping all scales not only introduces rotation + // in the case of a trivial scale inversion, but causes transformed objects + // to needlessly shrink and grow as they transform through scale = 0 along + // multiple axes. 2D transformation matrices should follow the 2D spec + // regarding matrix decomposition. + DecomposedTransform decompFlipX; + DecomposeTransform(&decompFlipX, Transform(-1, 0, 0, 1, 0, 0)); + EXPECT_APPROX_EQ(-1, decompFlipX.scale[0]); + EXPECT_APPROX_EQ(1, decompFlipX.scale[1]); + EXPECT_APPROX_EQ(1, decompFlipX.scale[2]); + EXPECT_APPROX_EQ(0, decompFlipX.quaternion.z()); + EXPECT_APPROX_EQ(1, decompFlipX.quaternion.w()); + + DecomposedTransform decompFlipY; + DecomposeTransform(&decompFlipY, Transform(1, 0, 0, -1, 0, 0)); + EXPECT_APPROX_EQ(1, decompFlipY.scale[0]); + EXPECT_APPROX_EQ(-1, decompFlipY.scale[1]); + EXPECT_APPROX_EQ(1, decompFlipY.scale[2]); + EXPECT_APPROX_EQ(0, decompFlipY.quaternion.z()); + EXPECT_APPROX_EQ(1, decompFlipY.quaternion.w()); + + DecomposedTransform decompR180; + DecomposeTransform(&decompR180, Transform(-1, 0, 0, -1, 0, 0)); + EXPECT_APPROX_EQ(1, decompR180.scale[0]); + EXPECT_APPROX_EQ(1, decompR180.scale[1]); + EXPECT_APPROX_EQ(1, decompR180.scale[2]); + EXPECT_APPROX_EQ(1, decompR180.quaternion.z()); + EXPECT_APPROX_EQ(0, decompR180.quaternion.w()); + + DecomposedTransform decompR90; + DecomposeTransform(&decompR180, Transform(0, -1, 1, 0, 0, 0)); + EXPECT_APPROX_EQ(1, decompR180.scale[0]); + EXPECT_APPROX_EQ(1, decompR180.scale[1]); + EXPECT_APPROX_EQ(1, decompR180.scale[2]); + EXPECT_APPROX_EQ(1 / sqrt(2), decompR180.quaternion.z()); + EXPECT_APPROX_EQ(1 / sqrt(2), decompR180.quaternion.w()); + + DecomposedTransform decompR90Translate; + DecomposeTransform(&decompR90Translate, Transform(0, -1, 1, 0, -1, 1)); + EXPECT_APPROX_EQ(1, decompR90Translate.scale[0]); + EXPECT_APPROX_EQ(1, decompR90Translate.scale[1]); + EXPECT_APPROX_EQ(1, decompR90Translate.scale[2]); + EXPECT_APPROX_EQ(-1, decompR90Translate.translate[0]); + EXPECT_APPROX_EQ(1, decompR90Translate.translate[1]); + EXPECT_APPROX_EQ(0, decompR90Translate.translate[2]); + EXPECT_APPROX_EQ(1 / sqrt(2), decompR90Translate.quaternion.z()); + EXPECT_APPROX_EQ(1 / sqrt(2), decompR90Translate.quaternion.w()); + + DecomposedTransform decompSkewRotate; + DecomposeTransform(&decompR90Translate, Transform(1, 1, 1, 0, 0, 0)); + EXPECT_APPROX_EQ(sqrt(2), decompR90Translate.scale[0]); + EXPECT_APPROX_EQ(-1 / sqrt(2), decompR90Translate.scale[1]); + EXPECT_APPROX_EQ(1, decompR90Translate.scale[2]); + EXPECT_APPROX_EQ(-1, decompR90Translate.skew[0]); + EXPECT_APPROX_EQ(sin(base::kPiDouble / 8), decompR90Translate.quaternion.z()); + EXPECT_APPROX_EQ(cos(base::kPiDouble / 8), decompR90Translate.quaternion.w()); +} + +TEST(TransformUtilTest, TransformBetweenRects) { + auto verify = [](const RectF& src_rect, const RectF& dst_rect) { + const Transform transform = TransformBetweenRects(src_rect, dst_rect); + + // Applies |transform| to calculate the target rectangle from |src_rect|. + // Notes that |transform| is in |src_rect|'s local coordinates. + RectF dst_in_src_coordinates = RectF(src_rect.size()); + transform.TransformRect(&dst_in_src_coordinates); + RectF dst_in_parent_coordinates = dst_in_src_coordinates; + dst_in_parent_coordinates.Offset(src_rect.OffsetFromOrigin()); + + // Verifies that the target rectangle is expected. + EXPECT_EQ(dst_rect, dst_in_parent_coordinates); + }; + + std::vector> test_cases{ + {RectF(0.f, 0.f, 2.f, 3.f), RectF(3.f, 5.f, 4.f, 9.f)}, + {RectF(10.f, 7.f, 2.f, 6.f), RectF(4.f, 2.f, 1.f, 12.f)}, + {RectF(0.f, 0.f, 3.f, 5.f), RectF(0.f, 0.f, 6.f, 2.5f)}}; + + for (const auto& test_case : test_cases) { + verify(test_case.first, test_case.second); + verify(test_case.second, test_case.first); + } + + // Tests the case where the destination is an empty rectangle. + verify(RectF(0.f, 0.f, 3.f, 5.f), RectF()); +} + +} // namespace +} // namespace gfx diff --git a/geometry/vector2d.cc b/geometry/vector2d.cc new file mode 100644 index 000000000000..0ce3b20baa5e --- /dev/null +++ b/geometry/vector2d.cc @@ -0,0 +1,40 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/vector2d.h" + +#include + +#include "base/numerics/clamped_math.h" +#include "base/strings/stringprintf.h" + +namespace gfx { + +bool Vector2d::IsZero() const { + return x_ == 0 && y_ == 0; +} + +void Vector2d::Add(const Vector2d& other) { + x_ = base::ClampAdd(other.x_, x_); + y_ = base::ClampAdd(other.y_, y_); +} + +void Vector2d::Subtract(const Vector2d& other) { + x_ = base::ClampSub(x_, other.x_); + y_ = base::ClampSub(y_, other.y_); +} + +int64_t Vector2d::LengthSquared() const { + return static_cast(x_) * x_ + static_cast(y_) * y_; +} + +float Vector2d::Length() const { + return static_cast(std::sqrt(static_cast(LengthSquared()))); +} + +std::string Vector2d::ToString() const { + return base::StringPrintf("[%d %d]", x_, y_); +} + +} // namespace gfx diff --git a/geometry/vector2d.h b/geometry/vector2d.h new file mode 100644 index 000000000000..11d55bf0e822 --- /dev/null +++ b/geometry/vector2d.h @@ -0,0 +1,99 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Defines a simple integer vector class. This class is used to indicate a +// distance in two dimensions between two points. Subtracting two points should +// produce a vector, and adding a vector to a point produces the point at the +// vector's distance from the original point. + +#ifndef UI_GFX_GEOMETRY_VECTOR2D_H_ +#define UI_GFX_GEOMETRY_VECTOR2D_H_ + +#include + +#include +#include + +#include "ui/gfx/geometry/geometry_export.h" +#include "ui/gfx/geometry/vector2d_f.h" + +namespace gfx { + +class GEOMETRY_EXPORT Vector2d { + public: + constexpr Vector2d() : x_(0), y_(0) {} + constexpr Vector2d(int x, int y) : x_(x), y_(y) {} + + constexpr int x() const { return x_; } + void set_x(int x) { x_ = x; } + + constexpr int y() const { return y_; } + void set_y(int y) { y_ = y; } + + // True if both components of the vector are 0. + bool IsZero() const; + + // Add the components of the |other| vector to the current vector. + void Add(const Vector2d& other); + // Subtract the components of the |other| vector from the current vector. + void Subtract(const Vector2d& other); + + constexpr bool operator==(const Vector2d& other) const { + return x_ == other.x_ && y_ == other.y_; + } + void operator+=(const Vector2d& other) { Add(other); } + void operator-=(const Vector2d& other) { Subtract(other); } + + void SetToMin(const Vector2d& other) { + x_ = x_ <= other.x_ ? x_ : other.x_; + y_ = y_ <= other.y_ ? y_ : other.y_; + } + + void SetToMax(const Vector2d& other) { + x_ = x_ >= other.x_ ? x_ : other.x_; + y_ = y_ >= other.y_ ? y_ : other.y_; + } + + // Gives the square of the diagonal length of the vector. Since this is + // cheaper to compute than Length(), it is useful when you want to compare + // relative lengths of different vectors without needing the actual lengths. + int64_t LengthSquared() const; + // Gives the diagonal length of the vector. + float Length() const; + + std::string ToString() const; + + operator Vector2dF() const { + return Vector2dF(static_cast(x()), static_cast(y())); + } + + private: + int x_; + int y_; +}; + +inline constexpr Vector2d operator-(const Vector2d& v) { + return Vector2d(-v.x(), -v.y()); +} + +inline Vector2d operator+(const Vector2d& lhs, const Vector2d& rhs) { + Vector2d result = lhs; + result.Add(rhs); + return result; +} + +inline Vector2d operator-(const Vector2d& lhs, const Vector2d& rhs) { + Vector2d result = lhs; + result.Add(-rhs); + return result; +} + +// This is declared here for use in gtest-based unit tests but is defined in +// the //ui/gfx:test_support target. Depend on that to use this in your unit +// test. This should not be used in production code - call ToString() instead. +void PrintTo(const Vector2d& vector, ::std::ostream* os); + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_VECTOR2D_H_ diff --git a/geometry/vector2d_conversions.cc b/geometry/vector2d_conversions.cc new file mode 100644 index 000000000000..c33199bf9661 --- /dev/null +++ b/geometry/vector2d_conversions.cc @@ -0,0 +1,26 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/vector2d_conversions.h" + +#include "base/numerics/safe_conversions.h" + +namespace gfx { + +Vector2d ToFlooredVector2d(const Vector2dF& vector2d) { + return Vector2d(base::ClampFloor(vector2d.x()), + base::ClampFloor(vector2d.y())); +} + +Vector2d ToCeiledVector2d(const Vector2dF& vector2d) { + return Vector2d(base::ClampCeil(vector2d.x()), base::ClampCeil(vector2d.y())); +} + +Vector2d ToRoundedVector2d(const Vector2dF& vector2d) { + return Vector2d(base::ClampRound(vector2d.x()), + base::ClampRound(vector2d.y())); +} + +} // namespace gfx + diff --git a/geometry/vector2d_conversions.h b/geometry/vector2d_conversions.h new file mode 100644 index 000000000000..055ebd05f18d --- /dev/null +++ b/geometry/vector2d_conversions.h @@ -0,0 +1,24 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_VECTOR2D_CONVERSIONS_H_ +#define UI_GFX_GEOMETRY_VECTOR2D_CONVERSIONS_H_ + +#include "ui/gfx/geometry/vector2d.h" +#include "ui/gfx/geometry/vector2d_f.h" + +namespace gfx { + +// Returns a Vector2d with each component from the input Vector2dF floored. +GEOMETRY_EXPORT Vector2d ToFlooredVector2d(const Vector2dF& vector2d); + +// Returns a Vector2d with each component from the input Vector2dF ceiled. +GEOMETRY_EXPORT Vector2d ToCeiledVector2d(const Vector2dF& vector2d); + +// Returns a Vector2d with each component from the input Vector2dF rounded. +GEOMETRY_EXPORT Vector2d ToRoundedVector2d(const Vector2dF& vector2d); + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_VECTOR2D_CONVERSIONS_H_ diff --git a/geometry/vector2d_f.cc b/geometry/vector2d_f.cc new file mode 100644 index 000000000000..ccb15ae0c4c2 --- /dev/null +++ b/geometry/vector2d_f.cc @@ -0,0 +1,60 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/vector2d_f.h" + +#include + +#include "base/strings/stringprintf.h" + +namespace gfx { + +std::string Vector2dF::ToString() const { + return base::StringPrintf("[%f %f]", x_, y_); +} + +bool Vector2dF::IsZero() const { + return x_ == 0 && y_ == 0; +} + +void Vector2dF::Add(const Vector2dF& other) { + x_ += other.x_; + y_ += other.y_; +} + +void Vector2dF::Subtract(const Vector2dF& other) { + x_ -= other.x_; + y_ -= other.y_; +} + +double Vector2dF::LengthSquared() const { + return static_cast(x_) * x_ + static_cast(y_) * y_; +} + +float Vector2dF::Length() const { + return static_cast(std::sqrt(LengthSquared())); +} + +void Vector2dF::Scale(float x_scale, float y_scale) { + x_ *= x_scale; + y_ *= y_scale; +} + +double CrossProduct(const Vector2dF& lhs, const Vector2dF& rhs) { + return static_cast(lhs.x()) * rhs.y() - + static_cast(lhs.y()) * rhs.x(); +} + +double DotProduct(const Vector2dF& lhs, const Vector2dF& rhs) { + return static_cast(lhs.x()) * rhs.x() + + static_cast(lhs.y()) * rhs.y(); +} + +Vector2dF ScaleVector2d(const Vector2dF& v, float x_scale, float y_scale) { + Vector2dF scaled_v(v); + scaled_v.Scale(x_scale, y_scale); + return scaled_v; +} + +} // namespace gfx diff --git a/geometry/vector2d_f.h b/geometry/vector2d_f.h new file mode 100644 index 000000000000..938e45aeacc3 --- /dev/null +++ b/geometry/vector2d_f.h @@ -0,0 +1,118 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Defines a simple float vector class. This class is used to indicate a +// distance in two dimensions between two points. Subtracting two points should +// produce a vector, and adding a vector to a point produces the point at the +// vector's distance from the original point. + +#ifndef UI_GFX_GEOMETRY_VECTOR2D_F_H_ +#define UI_GFX_GEOMETRY_VECTOR2D_F_H_ + +#include +#include + +#include "ui/gfx/geometry/geometry_export.h" + +namespace gfx { + +class GEOMETRY_EXPORT Vector2dF { + public: + constexpr Vector2dF() : x_(0), y_(0) {} + constexpr Vector2dF(float x, float y) : x_(x), y_(y) {} + + constexpr float x() const { return x_; } + void set_x(float x) { x_ = x; } + + constexpr float y() const { return y_; } + void set_y(float y) { y_ = y; } + + // True if both components of the vector are 0. + bool IsZero() const; + + // Add the components of the |other| vector to the current vector. + void Add(const Vector2dF& other); + // Subtract the components of the |other| vector from the current vector. + void Subtract(const Vector2dF& other); + + void operator+=(const Vector2dF& other) { Add(other); } + void operator-=(const Vector2dF& other) { Subtract(other); } + + void SetToMin(const Vector2dF& other) { + x_ = x_ <= other.x_ ? x_ : other.x_; + y_ = y_ <= other.y_ ? y_ : other.y_; + } + + void SetToMax(const Vector2dF& other) { + x_ = x_ >= other.x_ ? x_ : other.x_; + y_ = y_ >= other.y_ ? y_ : other.y_; + } + + // Gives the square of the diagonal length of the vector. + double LengthSquared() const; + // Gives the diagonal length of the vector. + float Length() const; + + // Scale the x and y components of the vector by |scale|. + void Scale(float scale) { Scale(scale, scale); } + // Scale the x and y components of the vector by |x_scale| and |y_scale| + // respectively. + void Scale(float x_scale, float y_scale); + + std::string ToString() const; + + private: + float x_; + float y_; +}; + +inline constexpr bool operator==(const Vector2dF& lhs, const Vector2dF& rhs) { + return lhs.x() == rhs.x() && lhs.y() == rhs.y(); +} + +inline constexpr bool operator!=(const Vector2dF& lhs, const Vector2dF& rhs) { + return !(lhs == rhs); +} + +inline constexpr Vector2dF operator-(const Vector2dF& v) { + return Vector2dF(-v.x(), -v.y()); +} + +inline Vector2dF operator+(const Vector2dF& lhs, const Vector2dF& rhs) { + Vector2dF result = lhs; + result.Add(rhs); + return result; +} + +inline Vector2dF operator-(const Vector2dF& lhs, const Vector2dF& rhs) { + Vector2dF result = lhs; + result.Add(-rhs); + return result; +} + +// Return the cross product of two vectors. +GEOMETRY_EXPORT double CrossProduct(const Vector2dF& lhs, const Vector2dF& rhs); + +// Return the dot product of two vectors. +GEOMETRY_EXPORT double DotProduct(const Vector2dF& lhs, const Vector2dF& rhs); + +// Return a vector that is |v| scaled by the given scale factors along each +// axis. +GEOMETRY_EXPORT Vector2dF ScaleVector2d(const Vector2dF& v, + float x_scale, + float y_scale); + +// Return a vector that is |v| scaled by the given scale factor. +inline Vector2dF ScaleVector2d(const Vector2dF& v, float scale) { + return ScaleVector2d(v, scale, scale); +} + +// This is declared here for use in gtest-based unit tests but is defined in +// the //ui/gfx:test_support target. Depend on that to use this in your unit +// test. This should not be used in production code - call ToString() instead. +void PrintTo(const Vector2dF& vector, ::std::ostream* os); + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_VECTOR2D_F_H_ diff --git a/geometry/vector2d_unittest.cc b/geometry/vector2d_unittest.cc new file mode 100644 index 000000000000..f4e754bcd886 --- /dev/null +++ b/geometry/vector2d_unittest.cc @@ -0,0 +1,293 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include + +#include "base/cxx17_backports.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/geometry/vector2d.h" +#include "ui/gfx/geometry/vector2d_f.h" + +namespace gfx { + +TEST(Vector2dTest, ConversionToFloat) { + Vector2d i(3, 4); + Vector2dF f = i; + EXPECT_EQ(i, f); +} + +TEST(Vector2dTest, IsZero) { + Vector2d int_zero(0, 0); + Vector2d int_nonzero(2, -2); + Vector2dF float_zero(0, 0); + Vector2dF float_nonzero(0.1f, -0.1f); + + EXPECT_TRUE(int_zero.IsZero()); + EXPECT_FALSE(int_nonzero.IsZero()); + EXPECT_TRUE(float_zero.IsZero()); + EXPECT_FALSE(float_nonzero.IsZero()); +} + +TEST(Vector2dTest, Add) { + Vector2d i1(3, 5); + Vector2d i2(4, -1); + + const struct { + Vector2d expected; + Vector2d actual; + } int_tests[] = { + { Vector2d(3, 5), i1 + Vector2d() }, + { Vector2d(3 + 4, 5 - 1), i1 + i2 }, + { Vector2d(3 - 4, 5 + 1), i1 - i2 } + }; + + for (size_t i = 0; i < base::size(int_tests); ++i) + EXPECT_EQ(int_tests[i].expected.ToString(), + int_tests[i].actual.ToString()); + + Vector2dF f1(3.1f, 5.1f); + Vector2dF f2(4.3f, -1.3f); + + const struct { + Vector2dF expected; + Vector2dF actual; + } float_tests[] = { + { Vector2dF(3.1F, 5.1F), f1 + Vector2d() }, + { Vector2dF(3.1F, 5.1F), f1 + Vector2dF() }, + { Vector2dF(3.1f + 4.3f, 5.1f - 1.3f), f1 + f2 }, + { Vector2dF(3.1f - 4.3f, 5.1f + 1.3f), f1 - f2 } + }; + + for (size_t i = 0; i < base::size(float_tests); ++i) + EXPECT_EQ(float_tests[i].expected.ToString(), + float_tests[i].actual.ToString()); +} + +TEST(Vector2dTest, Negative) { + const struct { + Vector2d expected; + Vector2d actual; + } int_tests[] = { + { Vector2d(0, 0), -Vector2d(0, 0) }, + { Vector2d(-3, -3), -Vector2d(3, 3) }, + { Vector2d(3, 3), -Vector2d(-3, -3) }, + { Vector2d(-3, 3), -Vector2d(3, -3) }, + { Vector2d(3, -3), -Vector2d(-3, 3) } + }; + + for (size_t i = 0; i < base::size(int_tests); ++i) + EXPECT_EQ(int_tests[i].expected.ToString(), + int_tests[i].actual.ToString()); + + const struct { + Vector2dF expected; + Vector2dF actual; + } float_tests[] = { + { Vector2dF(0, 0), -Vector2d(0, 0) }, + { Vector2dF(-0.3f, -0.3f), -Vector2dF(0.3f, 0.3f) }, + { Vector2dF(0.3f, 0.3f), -Vector2dF(-0.3f, -0.3f) }, + { Vector2dF(-0.3f, 0.3f), -Vector2dF(0.3f, -0.3f) }, + { Vector2dF(0.3f, -0.3f), -Vector2dF(-0.3f, 0.3f) } + }; + + for (size_t i = 0; i < base::size(float_tests); ++i) + EXPECT_EQ(float_tests[i].expected.ToString(), + float_tests[i].actual.ToString()); +} + +TEST(Vector2dTest, Scale) { + float double_values[][4] = { + { 4.5f, 1.2f, 3.3f, 5.6f }, + { 4.5f, -1.2f, 3.3f, 5.6f }, + { 4.5f, 1.2f, 3.3f, -5.6f }, + { 4.5f, 1.2f, -3.3f, -5.6f }, + { -4.5f, 1.2f, 3.3f, 5.6f }, + { -4.5f, 1.2f, 0, 5.6f }, + { -4.5f, 1.2f, 3.3f, 0 }, + { 4.5f, 0, 3.3f, 5.6f }, + { 0, 1.2f, 3.3f, 5.6f } + }; + + for (size_t i = 0; i < base::size(double_values); ++i) { + Vector2dF v(double_values[i][0], double_values[i][1]); + v.Scale(double_values[i][2], double_values[i][3]); + EXPECT_EQ(v.x(), double_values[i][0] * double_values[i][2]); + EXPECT_EQ(v.y(), double_values[i][1] * double_values[i][3]); + + Vector2dF v2 = ScaleVector2d( + gfx::Vector2dF(double_values[i][0], double_values[i][1]), + double_values[i][2], double_values[i][3]); + EXPECT_EQ(double_values[i][0] * double_values[i][2], v2.x()); + EXPECT_EQ(double_values[i][1] * double_values[i][3], v2.y()); + } + + float single_values[][3] = { + { 4.5f, 1.2f, 3.3f }, + { 4.5f, -1.2f, 3.3f }, + { 4.5f, 1.2f, 3.3f }, + { 4.5f, 1.2f, -3.3f }, + { -4.5f, 1.2f, 3.3f }, + { -4.5f, 1.2f, 0 }, + { -4.5f, 1.2f, 3.3f }, + { 4.5f, 0, 3.3f }, + { 0, 1.2f, 3.3f } + }; + + for (size_t i = 0; i < base::size(single_values); ++i) { + Vector2dF v(single_values[i][0], single_values[i][1]); + v.Scale(single_values[i][2]); + EXPECT_EQ(v.x(), single_values[i][0] * single_values[i][2]); + EXPECT_EQ(v.y(), single_values[i][1] * single_values[i][2]); + + Vector2dF v2 = ScaleVector2d( + gfx::Vector2dF(double_values[i][0], double_values[i][1]), + double_values[i][2]); + EXPECT_EQ(single_values[i][0] * single_values[i][2], v2.x()); + EXPECT_EQ(single_values[i][1] * single_values[i][2], v2.y()); + } +} + +TEST(Vector2dTest, Length) { + int int_values[][2] = { + { 0, 0 }, + { 10, 20 }, + { 20, 10 }, + { -10, -20 }, + { -20, 10 }, + { 10, -20 }, + }; + + for (size_t i = 0; i < base::size(int_values); ++i) { + int v0 = int_values[i][0]; + int v1 = int_values[i][1]; + double length_squared = + static_cast(v0) * v0 + static_cast(v1) * v1; + double length = std::sqrt(length_squared); + Vector2d vector(v0, v1); + EXPECT_EQ(static_cast(length_squared), vector.LengthSquared()); + EXPECT_EQ(static_cast(length), vector.Length()); + } + + float float_values[][2] = { + { 0, 0 }, + { 10.5f, 20.5f }, + { 20.5f, 10.5f }, + { -10.5f, -20.5f }, + { -20.5f, 10.5f }, + { 10.5f, -20.5f }, + // A large vector that fails if the Length function doesn't use + // double precision internally. + { 1236278317862780234892374893213178027.12122348904204230f, + 335890352589839028212313231225425134332.38123f }, + }; + + for (size_t i = 0; i < base::size(float_values); ++i) { + double v0 = float_values[i][0]; + double v1 = float_values[i][1]; + double length_squared = + static_cast(v0) * v0 + static_cast(v1) * v1; + double length = std::sqrt(length_squared); + Vector2dF vector(v0, v1); + EXPECT_DOUBLE_EQ(length_squared, vector.LengthSquared()); + EXPECT_FLOAT_EQ(static_cast(length), vector.Length()); + } +} + +TEST(Vector2dTest, ClampVector2d) { + Vector2d a; + + a = Vector2d(3, 5); + EXPECT_EQ(Vector2d(3, 5).ToString(), a.ToString()); + a.SetToMax(Vector2d(2, 4)); + EXPECT_EQ(Vector2d(3, 5).ToString(), a.ToString()); + a.SetToMax(Vector2d(3, 5)); + EXPECT_EQ(Vector2d(3, 5).ToString(), a.ToString()); + a.SetToMax(Vector2d(4, 2)); + EXPECT_EQ(Vector2d(4, 5).ToString(), a.ToString()); + a.SetToMax(Vector2d(8, 10)); + EXPECT_EQ(Vector2d(8, 10).ToString(), a.ToString()); + + a.SetToMin(Vector2d(9, 11)); + EXPECT_EQ(Vector2d(8, 10).ToString(), a.ToString()); + a.SetToMin(Vector2d(8, 10)); + EXPECT_EQ(Vector2d(8, 10).ToString(), a.ToString()); + a.SetToMin(Vector2d(11, 9)); + EXPECT_EQ(Vector2d(8, 9).ToString(), a.ToString()); + a.SetToMin(Vector2d(7, 11)); + EXPECT_EQ(Vector2d(7, 9).ToString(), a.ToString()); + a.SetToMin(Vector2d(3, 5)); + EXPECT_EQ(Vector2d(3, 5).ToString(), a.ToString()); +} + +TEST(Vector2dTest, ClampVector2dF) { + Vector2dF a; + + a = Vector2dF(3.5f, 5.5f); + EXPECT_EQ(Vector2dF(3.5f, 5.5f).ToString(), a.ToString()); + a.SetToMax(Vector2dF(2.5f, 4.5f)); + EXPECT_EQ(Vector2dF(3.5f, 5.5f).ToString(), a.ToString()); + a.SetToMax(Vector2dF(3.5f, 5.5f)); + EXPECT_EQ(Vector2dF(3.5f, 5.5f).ToString(), a.ToString()); + a.SetToMax(Vector2dF(4.5f, 2.5f)); + EXPECT_EQ(Vector2dF(4.5f, 5.5f).ToString(), a.ToString()); + a.SetToMax(Vector2dF(8.5f, 10.5f)); + EXPECT_EQ(Vector2dF(8.5f, 10.5f).ToString(), a.ToString()); + + a.SetToMin(Vector2dF(9.5f, 11.5f)); + EXPECT_EQ(Vector2dF(8.5f, 10.5f).ToString(), a.ToString()); + a.SetToMin(Vector2dF(8.5f, 10.5f)); + EXPECT_EQ(Vector2dF(8.5f, 10.5f).ToString(), a.ToString()); + a.SetToMin(Vector2dF(11.5f, 9.5f)); + EXPECT_EQ(Vector2dF(8.5f, 9.5f).ToString(), a.ToString()); + a.SetToMin(Vector2dF(7.5f, 11.5f)); + EXPECT_EQ(Vector2dF(7.5f, 9.5f).ToString(), a.ToString()); + a.SetToMin(Vector2dF(3.5f, 5.5f)); + EXPECT_EQ(Vector2dF(3.5f, 5.5f).ToString(), a.ToString()); +} + +TEST(Vector2dTest, IntegerOverflow) { + int int_max = std::numeric_limits::max(); + int int_min = std::numeric_limits::min(); + + Vector2d max_vector(int_max, int_max); + Vector2d min_vector(int_min, int_min); + Vector2d test; + + test = Vector2d(); + test += Vector2d(int_max, int_max); + EXPECT_EQ(test, max_vector); + + test = Vector2d(); + test += Vector2d(int_min, int_min); + EXPECT_EQ(test, min_vector); + + test = Vector2d(10, 20); + test += Vector2d(int_max, int_max); + EXPECT_EQ(test, max_vector); + + test = Vector2d(-10, -20); + test += Vector2d(int_min, int_min); + EXPECT_EQ(test, min_vector); + + test = Vector2d(); + test -= Vector2d(int_max, int_max); + EXPECT_EQ(test, Vector2d(-int_max, -int_max)); + + test = Vector2d(); + test -= Vector2d(int_min, int_min); + EXPECT_EQ(test, max_vector); + + test = Vector2d(10, 20); + test -= Vector2d(int_min, int_min); + EXPECT_EQ(test, max_vector); + + test = Vector2d(-10, -20); + test -= Vector2d(int_max, int_max); + EXPECT_EQ(test, min_vector); +} + +} // namespace gfx diff --git a/geometry/vector3d_f.cc b/geometry/vector3d_f.cc new file mode 100644 index 000000000000..d538a57f0d7b --- /dev/null +++ b/geometry/vector3d_f.cc @@ -0,0 +1,111 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/vector3d_f.h" + +#include + +#include "base/strings/stringprintf.h" +#include "ui/gfx/geometry/angle_conversions.h" + +namespace { +const double kEpsilon = 1.0e-6; +} + +namespace gfx { + +std::string Vector3dF::ToString() const { + return base::StringPrintf("[%f %f %f]", x_, y_, z_); +} + +bool Vector3dF::IsZero() const { + return x_ == 0 && y_ == 0 && z_ == 0; +} + +void Vector3dF::Add(const Vector3dF& other) { + x_ += other.x_; + y_ += other.y_; + z_ += other.z_; +} + +void Vector3dF::Subtract(const Vector3dF& other) { + x_ -= other.x_; + y_ -= other.y_; + z_ -= other.z_; +} + +double Vector3dF::LengthSquared() const { + return static_cast(x_) * x_ + static_cast(y_) * y_ + + static_cast(z_) * z_; +} + +float Vector3dF::Length() const { + return static_cast(std::sqrt(LengthSquared())); +} + +void Vector3dF::Scale(float x_scale, float y_scale, float z_scale) { + x_ *= x_scale; + y_ *= y_scale; + z_ *= z_scale; +} + +void Vector3dF::Cross(const Vector3dF& other) { + double dx = x_; + double dy = y_; + double dz = z_; + float x = static_cast(dy * other.z() - dz * other.y()); + float y = static_cast(dz * other.x() - dx * other.z()); + float z = static_cast(dx * other.y() - dy * other.x()); + x_ = x; + y_ = y; + z_ = z; +} + +bool Vector3dF::GetNormalized(Vector3dF* out) const { + double length_squared = LengthSquared(); + *out = *this; + if (length_squared < kEpsilon * kEpsilon) + return false; + out->Scale(1 / sqrt(length_squared)); + return true; +} + +float DotProduct(const Vector3dF& lhs, const Vector3dF& rhs) { + return lhs.x() * rhs.x() + lhs.y() * rhs.y() + lhs.z() * rhs.z(); +} + +Vector3dF ScaleVector3d(const Vector3dF& v, + float x_scale, + float y_scale, + float z_scale) { + Vector3dF scaled_v(v); + scaled_v.Scale(x_scale, y_scale, z_scale); + return scaled_v; +} + +float AngleBetweenVectorsInDegrees(const gfx::Vector3dF& base, + const gfx::Vector3dF& other) { + // Clamp the resulting value to prevent potential NANs from floating point + // precision issues. + return gfx::RadToDeg(std::acos(fmax( + fmin(gfx::DotProduct(base, other) / base.Length() / other.Length(), 1.f), + -1.f))); +} + +float ClockwiseAngleBetweenVectorsInDegrees(const gfx::Vector3dF& base, + const gfx::Vector3dF& other, + const gfx::Vector3dF& normal) { + float angle = AngleBetweenVectorsInDegrees(base, other); + gfx::Vector3dF cross(base); + cross.Cross(other); + + // If the dot product of this cross product is normal, it means that the + // shortest angle between |base| and |other| was counterclockwise with respect + // to the surface represented by |normal| and this angle must be reversed. + if (gfx::DotProduct(cross, normal) > 0.0f) + angle = 360.0f - angle; + return angle; +} + +} // namespace gfx diff --git a/geometry/vector3d_f.h b/geometry/vector3d_f.h new file mode 100644 index 000000000000..8238a165712d --- /dev/null +++ b/geometry/vector3d_f.h @@ -0,0 +1,155 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Defines a simple float vector class. This class is used to indicate a +// distance in two dimensions between two points. Subtracting two points should +// produce a vector, and adding a vector to a point produces the point at the +// vector's distance from the original point. + +#ifndef UI_GFX_GEOMETRY_VECTOR3D_F_H_ +#define UI_GFX_GEOMETRY_VECTOR3D_F_H_ + +#include +#include + +#include "ui/gfx/geometry/geometry_export.h" +#include "ui/gfx/geometry/vector2d_f.h" + +namespace gfx { + +class GEOMETRY_EXPORT Vector3dF { + public: + constexpr Vector3dF() : x_(0), y_(0), z_(0) {} + constexpr Vector3dF(float x, float y, float z) : x_(x), y_(y), z_(z) {} + + constexpr explicit Vector3dF(const Vector2dF& other) + : x_(other.x()), y_(other.y()), z_(0) {} + + constexpr float x() const { return x_; } + void set_x(float x) { x_ = x; } + + constexpr float y() const { return y_; } + void set_y(float y) { y_ = y; } + + constexpr float z() const { return z_; } + void set_z(float z) { z_ = z; } + + // True if all components of the vector are 0. + bool IsZero() const; + + // Add the components of the |other| vector to the current vector. + void Add(const Vector3dF& other); + // Subtract the components of the |other| vector from the current vector. + void Subtract(const Vector3dF& other); + + void operator+=(const Vector3dF& other) { Add(other); } + void operator-=(const Vector3dF& other) { Subtract(other); } + + void SetToMin(const Vector3dF& other) { + x_ = x_ <= other.x_ ? x_ : other.x_; + y_ = y_ <= other.y_ ? y_ : other.y_; + z_ = z_ <= other.z_ ? z_ : other.z_; + } + + void SetToMax(const Vector3dF& other) { + x_ = x_ >= other.x_ ? x_ : other.x_; + y_ = y_ >= other.y_ ? y_ : other.y_; + z_ = z_ >= other.z_ ? z_ : other.z_; + } + + // Gives the square of the diagonal length of the vector. + double LengthSquared() const; + // Gives the diagonal length of the vector. + float Length() const; + + // Scale all components of the vector by |scale|. + void Scale(float scale) { Scale(scale, scale, scale); } + // Scale the each component of the vector by the given scale factors. + void Scale(float x_scale, float y_scale, float z_scale); + + // Take the cross product of this vector with |other| and become the result. + void Cross(const Vector3dF& other); + + // |out| is assigned a unit-length vector in the direction of |this| iff + // this function returns true. It can return false if |this| is too short. + bool GetNormalized(Vector3dF* out) const; + + std::string ToString() const; + + private: + float x_; + float y_; + float z_; +}; + +inline bool operator==(const Vector3dF& lhs, const Vector3dF& rhs) { + return lhs.x() == rhs.x() && lhs.y() == rhs.y() && lhs.z() == rhs.z(); +} + +inline bool operator!=(const Vector3dF& lhs, const Vector3dF& rhs) { + return !(lhs == rhs); +} + +inline Vector3dF operator-(const Vector3dF& v) { + return Vector3dF(-v.x(), -v.y(), -v.z()); +} + +inline Vector3dF operator+(const Vector3dF& lhs, const Vector3dF& rhs) { + Vector3dF result = lhs; + result.Add(rhs); + return result; +} + +inline Vector3dF operator-(const Vector3dF& lhs, const Vector3dF& rhs) { + Vector3dF result = lhs; + result.Add(-rhs); + return result; +} + +// Return the cross product of two vectors. +inline Vector3dF CrossProduct(const Vector3dF& lhs, const Vector3dF& rhs) { + Vector3dF result = lhs; + result.Cross(rhs); + return result; +} + +// Return the dot product of two vectors. +GEOMETRY_EXPORT float DotProduct(const Vector3dF& lhs, const Vector3dF& rhs); + +// Return a vector that is |v| scaled by the given scale factors along each +// axis. +GEOMETRY_EXPORT Vector3dF ScaleVector3d(const Vector3dF& v, + float x_scale, + float y_scale, + float z_scale); + +// Return a vector that is |v| scaled by the components of |s| +inline Vector3dF ScaleVector3d(const Vector3dF& v, const Vector3dF& s) { + return ScaleVector3d(v, s.x(), s.y(), s.z()); +} + +// Return a vector that is |v| scaled by the given scale factor. +inline Vector3dF ScaleVector3d(const Vector3dF& v, float scale) { + return ScaleVector3d(v, scale, scale, scale); +} + +// Returns the angle between |base| and |other| in degrees. +GEOMETRY_EXPORT float AngleBetweenVectorsInDegrees(const gfx::Vector3dF& base, + const gfx::Vector3dF& other); + +// Returns the clockwise angle between |base| and |other| where |normal| is the +// normal of the virtual surface to measure clockwise according to. +GEOMETRY_EXPORT float ClockwiseAngleBetweenVectorsInDegrees( + const gfx::Vector3dF& base, + const gfx::Vector3dF& other, + const gfx::Vector3dF& normal); + +// This is declared here for use in gtest-based unit tests but is defined in +// the //ui/gfx:test_support target. Depend on that to use this in your unit +// test. This should not be used in production code - call ToString() instead. +void PrintTo(const Vector3dF& vector, ::std::ostream* os); + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_VECTOR3D_F_H_ diff --git a/geometry/vector3d_unittest.cc b/geometry/vector3d_unittest.cc new file mode 100644 index 000000000000..1eab6a68cf7c --- /dev/null +++ b/geometry/vector3d_unittest.cc @@ -0,0 +1,349 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include + +#include "base/cxx17_backports.h" +#include "build/build_config.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/geometry/vector3d_f.h" + +namespace gfx { + +TEST(Vector3dTest, IsZero) { + gfx::Vector3dF float_zero(0, 0, 0); + gfx::Vector3dF float_nonzero(0.1f, -0.1f, 0.1f); + + EXPECT_TRUE(float_zero.IsZero()); + EXPECT_FALSE(float_nonzero.IsZero()); +} + +TEST(Vector3dTest, Add) { + gfx::Vector3dF f1(3.1f, 5.1f, 2.7f); + gfx::Vector3dF f2(4.3f, -1.3f, 8.1f); + + const struct { + gfx::Vector3dF expected; + gfx::Vector3dF actual; + } float_tests[] = { + { gfx::Vector3dF(3.1F, 5.1F, 2.7f), f1 + gfx::Vector3dF() }, + { gfx::Vector3dF(3.1f + 4.3f, 5.1f - 1.3f, 2.7f + 8.1f), f1 + f2 }, + { gfx::Vector3dF(3.1f - 4.3f, 5.1f + 1.3f, 2.7f - 8.1f), f1 - f2 } + }; + + for (size_t i = 0; i < base::size(float_tests); ++i) + EXPECT_EQ(float_tests[i].expected.ToString(), + float_tests[i].actual.ToString()); +} + +TEST(Vector3dTest, Negative) { + const struct { + gfx::Vector3dF expected; + gfx::Vector3dF actual; + } float_tests[] = { + { gfx::Vector3dF(-0.0f, -0.0f, -0.0f), -gfx::Vector3dF(0, 0, 0) }, + { gfx::Vector3dF(-0.3f, -0.3f, -0.3f), -gfx::Vector3dF(0.3f, 0.3f, 0.3f) }, + { gfx::Vector3dF(0.3f, 0.3f, 0.3f), -gfx::Vector3dF(-0.3f, -0.3f, -0.3f) }, + { gfx::Vector3dF(-0.3f, 0.3f, -0.3f), -gfx::Vector3dF(0.3f, -0.3f, 0.3f) }, + { gfx::Vector3dF(0.3f, -0.3f, -0.3f), -gfx::Vector3dF(-0.3f, 0.3f, 0.3f) }, + { gfx::Vector3dF(-0.3f, -0.3f, 0.3f), -gfx::Vector3dF(0.3f, 0.3f, -0.3f) } + }; + + for (size_t i = 0; i < base::size(float_tests); ++i) + EXPECT_EQ(float_tests[i].expected.ToString(), + float_tests[i].actual.ToString()); +} + +TEST(Vector3dTest, Scale) { + float triple_values[][6] = { + { 4.5f, 1.2f, 1.8f, 3.3f, 5.6f, 4.2f }, + { 4.5f, -1.2f, -1.8f, 3.3f, 5.6f, 4.2f }, + { 4.5f, 1.2f, -1.8f, 3.3f, 5.6f, 4.2f }, + { 4.5f, -1.2f -1.8f, 3.3f, 5.6f, 4.2f }, + + { 4.5f, 1.2f, 1.8f, 3.3f, -5.6f, -4.2f }, + { 4.5f, 1.2f, 1.8f, -3.3f, -5.6f, -4.2f }, + { 4.5f, 1.2f, -1.8f, 3.3f, -5.6f, -4.2f }, + { 4.5f, 1.2f, -1.8f, -3.3f, -5.6f, -4.2f }, + + { -4.5f, 1.2f, 1.8f, 3.3f, 5.6f, 4.2f }, + { -4.5f, 1.2f, 1.8f, 0, 5.6f, 4.2f }, + { -4.5f, 1.2f, -1.8f, 3.3f, 5.6f, 4.2f }, + { -4.5f, 1.2f, -1.8f, 0, 5.6f, 4.2f }, + + { -4.5f, 1.2f, 1.8f, 3.3f, 0, 4.2f }, + { 4.5f, 0, 1.8f, 3.3f, 5.6f, 4.2f }, + { -4.5f, 1.2f, -1.8f, 3.3f, 0, 4.2f }, + { 4.5f, 0, -1.8f, 3.3f, 5.6f, 4.2f }, + { -4.5f, 1.2f, 1.8f, 3.3f, 5.6f, 0 }, + { -4.5f, 1.2f, -1.8f, 3.3f, 5.6f, 0 }, + + { 0, 1.2f, 0, 3.3f, 5.6f, 4.2f }, + { 0, 1.2f, 1.8f, 3.3f, 5.6f, 4.2f } + }; + + for (size_t i = 0; i < base::size(triple_values); ++i) { + gfx::Vector3dF v(triple_values[i][0], + triple_values[i][1], + triple_values[i][2]); + v.Scale(triple_values[i][3], triple_values[i][4], triple_values[i][5]); + EXPECT_EQ(triple_values[i][0] * triple_values[i][3], v.x()); + EXPECT_EQ(triple_values[i][1] * triple_values[i][4], v.y()); + EXPECT_EQ(triple_values[i][2] * triple_values[i][5], v.z()); + + Vector3dF v2 = ScaleVector3d( + gfx::Vector3dF(triple_values[i][0], + triple_values[i][1], + triple_values[i][2]), + triple_values[i][3], triple_values[i][4], triple_values[i][5]); + EXPECT_EQ(triple_values[i][0] * triple_values[i][3], v2.x()); + EXPECT_EQ(triple_values[i][1] * triple_values[i][4], v2.y()); + EXPECT_EQ(triple_values[i][2] * triple_values[i][5], v2.z()); + } + + float single_values[][4] = { + { 4.5f, 1.2f, 1.8f, 3.3f }, + { 4.5f, -1.2f, 1.8f, 3.3f }, + { 4.5f, 1.2f, -1.8f, 3.3f }, + { 4.5f, -1.2f, -1.8f, 3.3f }, + { -4.5f, 1.2f, 3.3f }, + { -4.5f, 1.2f, 0 }, + { -4.5f, 1.2f, 1.8f, 3.3f }, + { -4.5f, 1.2f, 1.8f, 0 }, + { 4.5f, 0, 1.8f, 3.3f }, + { 0, 1.2f, 1.8f, 3.3f }, + { 4.5f, 0, 1.8f, 3.3f }, + { 0, 1.2f, 1.8f, 3.3f }, + { 4.5f, 1.2f, 0, 3.3f }, + { 4.5f, 1.2f, 0, 3.3f } + }; + + for (size_t i = 0; i < base::size(single_values); ++i) { + gfx::Vector3dF v(single_values[i][0], + single_values[i][1], + single_values[i][2]); + v.Scale(single_values[i][3]); + EXPECT_EQ(single_values[i][0] * single_values[i][3], v.x()); + EXPECT_EQ(single_values[i][1] * single_values[i][3], v.y()); + EXPECT_EQ(single_values[i][2] * single_values[i][3], v.z()); + + Vector3dF v2 = ScaleVector3d( + gfx::Vector3dF(single_values[i][0], + single_values[i][1], + single_values[i][2]), + single_values[i][3]); + EXPECT_EQ(single_values[i][0] * single_values[i][3], v2.x()); + EXPECT_EQ(single_values[i][1] * single_values[i][3], v2.y()); + EXPECT_EQ(single_values[i][2] * single_values[i][3], v2.z()); + } +} + +TEST(Vector3dTest, Length) { + float float_values[][3] = { + { 0, 0, 0 }, + { 10.5f, 20.5f, 8.5f }, + { 20.5f, 10.5f, 8.5f }, + { 8.5f, 20.5f, 10.5f }, + { 10.5f, 8.5f, 20.5f }, + { -10.5f, -20.5f, -8.5f }, + { -20.5f, 10.5f, -8.5f }, + { -8.5f, -20.5f, -10.5f }, + { -10.5f, -8.5f, -20.5f }, + { 10.5f, -20.5f, 8.5f }, + { -10.5f, 20.5f, 8.5f }, + { 10.5f, -20.5f, -8.5f }, + { -10.5f, 20.5f, -8.5f }, + // A large vector that fails if the Length function doesn't use + // double precision internally. + { 1236278317862780234892374893213178027.12122348904204230f, + 335890352589839028212313231225425134332.38123f, + 27861786423846742743236423478236784678.236713617231f } + }; + + for (size_t i = 0; i < base::size(float_values); ++i) { + double v0 = float_values[i][0]; + double v1 = float_values[i][1]; + double v2 = float_values[i][2]; + double length_squared = + static_cast(v0) * v0 + + static_cast(v1) * v1 + + static_cast(v2) * v2; + double length = std::sqrt(length_squared); + gfx::Vector3dF vector(v0, v1, v2); + EXPECT_DOUBLE_EQ(length_squared, vector.LengthSquared()); + EXPECT_FLOAT_EQ(static_cast(length), vector.Length()); + } +} + +TEST(Vector3dTest, DotProduct) { + const struct { + float expected; + gfx::Vector3dF input1; + gfx::Vector3dF input2; + } tests[] = { + { 0, gfx::Vector3dF(1, 0, 0), gfx::Vector3dF(0, 1, 1) }, + { 0, gfx::Vector3dF(0, 1, 0), gfx::Vector3dF(1, 0, 1) }, + { 0, gfx::Vector3dF(0, 0, 1), gfx::Vector3dF(1, 1, 0) }, + + { 3, gfx::Vector3dF(1, 1, 1), gfx::Vector3dF(1, 1, 1) }, + + { 1.2f, gfx::Vector3dF(1.2f, -1.2f, 1.2f), gfx::Vector3dF(1, 1, 1) }, + { 1.2f, gfx::Vector3dF(1, 1, 1), gfx::Vector3dF(1.2f, -1.2f, 1.2f) }, + + { 38.72f, + gfx::Vector3dF(1.1f, 2.2f, 3.3f), gfx::Vector3dF(4.4f, 5.5f, 6.6f) } + }; + + for (size_t i = 0; i < base::size(tests); ++i) { + float actual = gfx::DotProduct(tests[i].input1, tests[i].input2); + EXPECT_EQ(tests[i].expected, actual); + } +} + +TEST(Vector3dTest, CrossProduct) { + const struct { + gfx::Vector3dF expected; + gfx::Vector3dF input1; + gfx::Vector3dF input2; + } tests[] = { + { Vector3dF(), Vector3dF(), Vector3dF(1, 1, 1) }, + { Vector3dF(), Vector3dF(1, 1, 1), Vector3dF() }, + { Vector3dF(), Vector3dF(1, 1, 1), Vector3dF(1, 1, 1) }, + { Vector3dF(), + Vector3dF(1.6f, 10.6f, -10.6f), + Vector3dF(1.6f, 10.6f, -10.6f) }, + + { Vector3dF(1, -1, 0), Vector3dF(1, 1, 1), Vector3dF(0, 0, 1) }, + { Vector3dF(-1, 0, 1), Vector3dF(1, 1, 1), Vector3dF(0, 1, 0) }, + { Vector3dF(0, 1, -1), Vector3dF(1, 1, 1), Vector3dF(1, 0, 0) }, + + { Vector3dF(-1, 1, 0), Vector3dF(0, 0, 1), Vector3dF(1, 1, 1) }, + { Vector3dF(1, 0, -1), Vector3dF(0, 1, 0), Vector3dF(1, 1, 1) }, + { Vector3dF(0, -1, 1), Vector3dF(1, 0, 0), Vector3dF(1, 1, 1) } + }; + + for (size_t i = 0; i < base::size(tests); ++i) { + SCOPED_TRACE(i); + Vector3dF actual = gfx::CrossProduct(tests[i].input1, tests[i].input2); + EXPECT_EQ(tests[i].expected.ToString(), actual.ToString()); + } +} + +TEST(Vector3dFTest, ClampVector3dF) { + Vector3dF a; + + a = Vector3dF(3.5f, 5.5f, 7.5f); + EXPECT_EQ(Vector3dF(3.5f, 5.5f, 7.5f).ToString(), a.ToString()); + a.SetToMax(Vector3dF(2, 4.5f, 6.5f)); + EXPECT_EQ(Vector3dF(3.5f, 5.5f, 7.5f).ToString(), a.ToString()); + a.SetToMax(Vector3dF(3.5f, 5.5f, 7.5f)); + EXPECT_EQ(Vector3dF(3.5f, 5.5f, 7.5f).ToString(), a.ToString()); + a.SetToMax(Vector3dF(4.5f, 2, 6.5f)); + EXPECT_EQ(Vector3dF(4.5f, 5.5f, 7.5f).ToString(), a.ToString()); + a.SetToMax(Vector3dF(3.5f, 6.5f, 6.5f)); + EXPECT_EQ(Vector3dF(4.5f, 6.5f, 7.5f).ToString(), a.ToString()); + a.SetToMax(Vector3dF(3.5f, 5.5f, 8.5f)); + EXPECT_EQ(Vector3dF(4.5f, 6.5f, 8.5f).ToString(), a.ToString()); + a.SetToMax(Vector3dF(8.5f, 10.5f, 12.5f)); + EXPECT_EQ(Vector3dF(8.5f, 10.5f, 12.5f).ToString(), a.ToString()); + + a.SetToMin(Vector3dF(9.5f, 11.5f, 13.5f)); + EXPECT_EQ(Vector3dF(8.5f, 10.5f, 12.5f).ToString(), a.ToString()); + a.SetToMin(Vector3dF(8.5f, 10.5f, 12.5f)); + EXPECT_EQ(Vector3dF(8.5f, 10.5f, 12.5f).ToString(), a.ToString()); + a.SetToMin(Vector3dF(7.5f, 11.5f, 13.5f)); + EXPECT_EQ(Vector3dF(7.5f, 10.5f, 12.5f).ToString(), a.ToString()); + a.SetToMin(Vector3dF(9.5f, 9.5f, 13.5f)); + EXPECT_EQ(Vector3dF(7.5f, 9.5f, 12.5f).ToString(), a.ToString()); + a.SetToMin(Vector3dF(9.5f, 11.5f, 11.5f)); + EXPECT_EQ(Vector3dF(7.5f, 9.5f, 11.5f).ToString(), a.ToString()); + a.SetToMin(Vector3dF(3.5f, 5.5f, 7.5f)); + EXPECT_EQ(Vector3dF(3.5f, 5.5f, 7.5f).ToString(), a.ToString()); +} + +TEST(Vector3dTest, AngleBetweenVectorsInDegress) { + const struct { + float expected; + gfx::Vector3dF input1; + gfx::Vector3dF input2; + } tests[] = {{0, gfx::Vector3dF(0, 1, 0), gfx::Vector3dF(0, 1, 0)}, + {90, gfx::Vector3dF(0, 1, 0), gfx::Vector3dF(0, 0, 1)}, + {45, gfx::Vector3dF(0, 1, 0), + gfx::Vector3dF(0, 0.70710678188f, 0.70710678188f)}, + {180, gfx::Vector3dF(0, 1, 0), gfx::Vector3dF(0, -1, 0)}, + // Two vectors that are sufficiently close enough together to + // trigger an issue that produces NANs if the value passed to + // acos is not clamped due to floating point precision. + {0, gfx::Vector3dF(0, -0.990842f, -0.003177f), + gfx::Vector3dF(0, -0.999995f, -0.003124f)}}; + + for (size_t i = 0; i < base::size(tests); ++i) { + float actual = + gfx::AngleBetweenVectorsInDegrees(tests[i].input1, tests[i].input2); + EXPECT_FLOAT_EQ(tests[i].expected, actual); + actual = + gfx::AngleBetweenVectorsInDegrees(tests[i].input2, tests[i].input1); + EXPECT_FLOAT_EQ(tests[i].expected, actual); + } +} + +TEST(Vector3dTest, ClockwiseAngleBetweenVectorsInDegress) { + const struct { + float expected; + gfx::Vector3dF input1; + gfx::Vector3dF input2; + } tests[] = { + {0, gfx::Vector3dF(0, 1, 0), gfx::Vector3dF(0, 1, 0)}, + {90, gfx::Vector3dF(0, 1, 0), gfx::Vector3dF(0, 0, -1)}, + {45, + gfx::Vector3dF(0, -1, 0), + gfx::Vector3dF(0, -0.70710678188f, 0.70710678188f)}, + {180, gfx::Vector3dF(0, -1, 0), gfx::Vector3dF(0, 1, 0)}, + {270, gfx::Vector3dF(0, 1, 0), gfx::Vector3dF(0, 0, 1)}, + }; + + const gfx::Vector3dF normal_vector(1.0f, 0.0f, 0.0f); + + for (size_t i = 0; i < base::size(tests); ++i) { + float actual = gfx::ClockwiseAngleBetweenVectorsInDegrees( + tests[i].input1, tests[i].input2, normal_vector); + EXPECT_FLOAT_EQ(tests[i].expected, actual); + actual = -gfx::ClockwiseAngleBetweenVectorsInDegrees( + tests[i].input2, tests[i].input1, normal_vector); + if (actual < 0.0f) + actual += 360.0f; + EXPECT_FLOAT_EQ(tests[i].expected, actual); + } +} + +TEST(Vector3dTest, GetNormalized) { + const struct { + bool expected; + gfx::Vector3dF v; + gfx::Vector3dF normalized; + } tests[] = { + {false, gfx::Vector3dF(0, 0, 0), gfx::Vector3dF(0, 0, 0)}, + {false, + gfx::Vector3dF(std::numeric_limits::min(), + std::numeric_limits::min(), + std::numeric_limits::min()), + gfx::Vector3dF(std::numeric_limits::min(), + std::numeric_limits::min(), + std::numeric_limits::min())}, + {true, gfx::Vector3dF(1, 0, 0), gfx::Vector3dF(1, 0, 0)}, + {true, gfx::Vector3dF(std::numeric_limits::max(), 0, 0), + gfx::Vector3dF(1, 0, 0)}, + }; + + for (size_t i = 0; i < base::size(tests); ++i) { + gfx::Vector3dF n; + EXPECT_EQ(tests[i].expected, tests[i].v.GetNormalized(&n)); + EXPECT_EQ(tests[i].normalized.ToString(), n.ToString()); + } +} + +} // namespace gfx diff --git a/geometry_skia_export.h b/geometry_skia_export.h new file mode 100644 index 000000000000..c68198234765 --- /dev/null +++ b/geometry_skia_export.h @@ -0,0 +1,29 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_SKIA_EXPORT_H_ +#define UI_GFX_GEOMETRY_SKIA_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(GEOMETRY_SKIA_IMPLEMENTATION) +#define GEOMETRY_SKIA_EXPORT __declspec(dllexport) +#else +#define GEOMETRY_SKIA_EXPORT __declspec(dllimport) +#endif // defined(GEOMETRY_SKIA_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(GEOMETRY_SKIA_IMPLEMENTATION) +#define GEOMETRY_SKIA_EXPORT __attribute__((visibility("default"))) +#else +#define GEOMETRY_SKIA_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define GEOMETRY_SKIA_EXPORT +#endif + +#endif // UI_GFX_GEOMETRY_SKIA_EXPORT_H_ diff --git a/gfx_export.h b/gfx_export.h new file mode 100644 index 000000000000..20c8bb1681d1 --- /dev/null +++ b/gfx_export.h @@ -0,0 +1,29 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GFX_EXPORT_H_ +#define UI_GFX_GFX_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(GFX_IMPLEMENTATION) +#define GFX_EXPORT __declspec(dllexport) +#else +#define GFX_EXPORT __declspec(dllimport) +#endif // defined(GFX_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(GFX_IMPLEMENTATION) +#define GFX_EXPORT __attribute__((visibility("default"))) +#else +#define GFX_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define GFX_EXPORT +#endif + +#endif // UI_GFX_GFX_EXPORT_H_ diff --git a/gfx_skia_export.h b/gfx_skia_export.h new file mode 100644 index 000000000000..5bd3572d10b9 --- /dev/null +++ b/gfx_skia_export.h @@ -0,0 +1,29 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GFX_SKIA_EXPORT_H_ +#define UI_GFX_GFX_SKIA_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(GFX_SKIA_IMPLEMENTATION) +#define GFX_SKIA_EXPORT __declspec(dllexport) +#else +#define GFX_SKIA_EXPORT __declspec(dllimport) +#endif // defined(GEOMETRY_SKIA_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(GFX_SKIA_IMPLEMENTATION) +#define GFX_SKIA_EXPORT __attribute__((visibility("default"))) +#else +#define GFX_SKIA_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define GFX_SKIA_EXPORT +#endif + +#endif // UI_GFX_GFX_SKIA_EXPORT_H_ diff --git a/gpu_extra_info.cc b/gpu_extra_info.cc new file mode 100644 index 000000000000..90747bf44777 --- /dev/null +++ b/gpu_extra_info.cc @@ -0,0 +1,23 @@ +// Copyright (c) 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/gpu_extra_info.h" + +namespace gfx { + +ANGLEFeature::ANGLEFeature() = default; +ANGLEFeature::ANGLEFeature(const ANGLEFeature& other) = default; +ANGLEFeature::ANGLEFeature(ANGLEFeature&& other) = default; +ANGLEFeature::~ANGLEFeature() = default; +ANGLEFeature& ANGLEFeature::operator=(const ANGLEFeature& other) = default; +ANGLEFeature& ANGLEFeature::operator=(ANGLEFeature&& other) = default; + +GpuExtraInfo::GpuExtraInfo() = default; +GpuExtraInfo::GpuExtraInfo(const GpuExtraInfo&) = default; +GpuExtraInfo::GpuExtraInfo(GpuExtraInfo&&) = default; +GpuExtraInfo::~GpuExtraInfo() = default; +GpuExtraInfo& GpuExtraInfo::operator=(const GpuExtraInfo&) = default; +GpuExtraInfo& GpuExtraInfo::operator=(GpuExtraInfo&&) = default; + +} // namespace gfx diff --git a/gpu_extra_info.h b/gpu_extra_info.h new file mode 100644 index 000000000000..c8951450003d --- /dev/null +++ b/gpu_extra_info.h @@ -0,0 +1,75 @@ +// Copyright (c) 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GPU_EXTRA_INFO_H_ +#define UI_GFX_GPU_EXTRA_INFO_H_ + +#include +#include + +#include "ui/gfx/buffer_types.h" +#include "ui/gfx/gfx_export.h" + +#if defined(USE_OZONE) +#include "ui/ozone/buildflags.h" +#if BUILDFLAG(OZONE_PLATFORM_X11) +#define USE_OZONE_PLATFORM_X11 +#endif +#endif + +#if defined(USE_X11) || defined(USE_OZONE_PLATFORM_X11) +#include "ui/gfx/x/xproto.h" +#endif + +namespace gfx { + +// Specification of a feature that can be enabled/disable in ANGLE +struct GFX_EXPORT ANGLEFeature { + ANGLEFeature(); + ANGLEFeature(const ANGLEFeature& other); + ANGLEFeature(ANGLEFeature&& other); + ~ANGLEFeature(); + ANGLEFeature& operator=(const ANGLEFeature& other); + ANGLEFeature& operator=(ANGLEFeature&& other); + + // Name of the feature in camel_case. + std::string name; + + // Name of the category that the feature belongs to. + std::string category; + + // One sentence description of the feature, why it's available. + std::string description; + + // Full link to cr/angle bug if applicable. + std::string bug; + + // Status, can be "enabled" or "disabled". + std::string status; + + // Condition, contains the condition that set 'status'. + std::string condition; +}; +using ANGLEFeatures = std::vector; + +struct GFX_EXPORT GpuExtraInfo { + GpuExtraInfo(); + GpuExtraInfo(const GpuExtraInfo&); + GpuExtraInfo(GpuExtraInfo&&); + ~GpuExtraInfo(); + GpuExtraInfo& operator=(const GpuExtraInfo&); + GpuExtraInfo& operator=(GpuExtraInfo&&); + + // List of the currently available ANGLE features. May be empty if not + // applicable. + ANGLEFeatures angle_features; + +#if defined(USE_X11) || defined(USE_OZONE_PLATFORM_X11) + std::vector gpu_memory_buffer_support_x11; +#endif +}; + +} // namespace gfx + +#endif // UI_GFX_GPU_EXTRA_INFO_H_ diff --git a/gpu_fence.cc b/gpu_fence.cc new file mode 100644 index 000000000000..e52f60776236 --- /dev/null +++ b/gpu_fence.cc @@ -0,0 +1,101 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/gpu_fence.h" + +#include "base/logging.h" +#include "base/notreached.h" +#include "base/time/time.h" + +#if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID) +#include +#endif + +namespace gfx { + +GpuFence::GpuFence(GpuFenceHandle fence_handle) + : fence_handle_(std::move(fence_handle)) {} + +GpuFence::~GpuFence() = default; + +GpuFence::GpuFence(GpuFence&& other) = default; + +GpuFence& GpuFence::operator=(GpuFence&& other) = default; + +const GpuFenceHandle& GpuFence::GetGpuFenceHandle() const { + return fence_handle_; +} + +ClientGpuFence GpuFence::AsClientGpuFence() { + return reinterpret_cast(this); +} + +// static +GpuFence* GpuFence::FromClientGpuFence(ClientGpuFence gpu_fence) { + return reinterpret_cast(gpu_fence); +} + +void GpuFence::Wait() { + if (fence_handle_.is_null()) { + return; + } + +#if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID) + static const int kInfiniteSyncWaitTimeout = -1; + DCHECK_GE(fence_handle_.owned_fd.get(), 0); + if (sync_wait(fence_handle_.owned_fd.get(), kInfiniteSyncWaitTimeout) < 0) { + LOG(FATAL) << "Failed while waiting for gpu fence fd"; + } +#else + NOTREACHED(); +#endif +} + +// static +GpuFence::FenceStatus GpuFence::GetStatusChangeTime(int fd, + base::TimeTicks* time) { + DCHECK_NE(fd, -1); +#if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID) + auto info = + std::unique_ptr{ + sync_fence_info(fd), sync_fence_info_free}; + if (!info) { + LOG(ERROR) << "sync_fence_info returned null for fd : " << fd; + return FenceStatus::kInvalid; + } + + // Not signalled yet. + if (info->status != 1) { + return FenceStatus::kNotSignaled; + } + + uint64_t timestamp_ns = 0u; + struct sync_pt_info* pt_info = nullptr; + while ((pt_info = sync_pt_info(info.get(), pt_info))) + timestamp_ns = std::max(timestamp_ns, pt_info->timestamp_ns); + + if (timestamp_ns == 0u) { + LOG(ERROR) << "No timestamp provided from sync_pt_info for fd : " << fd; + return FenceStatus::kInvalid; + } + *time = base::TimeTicks() + base::Nanoseconds(timestamp_ns); + return FenceStatus::kSignaled; +#endif + NOTREACHED(); + return FenceStatus::kInvalid; +} + +base::TimeTicks GpuFence::GetMaxTimestamp() const { + base::TimeTicks timestamp; +#if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID) + FenceStatus status = + GetStatusChangeTime(fence_handle_.owned_fd.get(), ×tamp); + DCHECK_EQ(status, FenceStatus::kSignaled); + return timestamp; +#endif + NOTREACHED(); + return timestamp; +} + +} // namespace gfx diff --git a/gpu_fence.h b/gpu_fence.h new file mode 100644 index 000000000000..eb71d7f6062c --- /dev/null +++ b/gpu_fence.h @@ -0,0 +1,59 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GPU_FENCE_H_ +#define UI_GFX_GPU_FENCE_H_ + +#include "base/macros.h" +#include "build/build_config.h" +#include "ui/gfx/gfx_export.h" +#include "ui/gfx/gpu_fence_handle.h" + +extern "C" typedef struct _ClientGpuFence* ClientGpuFence; + +namespace base { +class TimeTicks; +} // namespace base + +namespace gfx { + +// GpuFence objects own a GpuFenceHandle and release the resources in it when +// going out of scope as appropriate. +class GFX_EXPORT GpuFence { + public: + // Constructor takes ownership of the source handle's resources. + explicit GpuFence(GpuFenceHandle handle); + GpuFence() = delete; + GpuFence(GpuFence&& other); + GpuFence& operator=(GpuFence&& other); + + GpuFence(const GpuFence&) = delete; + GpuFence& operator=(const GpuFence&) = delete; + + ~GpuFence(); + + // Returns a const reference to the underlying GpuFenceHandle + // owned by GpuFence. If you'd like a duplicated handle for use + // with IPC, call the Clone method on the returned handle. + const GpuFenceHandle& GetGpuFenceHandle() const; + + // Casts for use with the GLES interface. + ClientGpuFence AsClientGpuFence(); + static GpuFence* FromClientGpuFence(ClientGpuFence gpu_fence); + + // Wait for the GpuFence to become ready. + void Wait(); + + enum FenceStatus { kSignaled, kNotSignaled, kInvalid }; + static FenceStatus GetStatusChangeTime(int fd, base::TimeTicks* time); + + base::TimeTicks GetMaxTimestamp() const; + + private: + gfx::GpuFenceHandle fence_handle_; +}; + +} // namespace gfx + +#endif // UI_GFX_GPU_FENCE_H_ diff --git a/gpu_fence_handle.cc b/gpu_fence_handle.cc new file mode 100644 index 000000000000..85a6e5b98ff8 --- /dev/null +++ b/gpu_fence_handle.cc @@ -0,0 +1,82 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/gpu_fence_handle.h" + +#include "base/debug/alias.h" +#include "base/notreached.h" + +#if defined(OS_POSIX) +#include + +#include "base/posix/eintr_wrapper.h" +#endif + +#if defined(OS_FUCHSIA) +#include "base/fuchsia/fuchsia_logging.h" +#endif + +#if defined(OS_WIN) +#include +#include "base/process/process_handle.h" +#endif + +namespace gfx { + +GpuFenceHandle::GpuFenceHandle() = default; + +GpuFenceHandle::GpuFenceHandle(GpuFenceHandle&& other) = default; + +GpuFenceHandle& GpuFenceHandle::operator=(GpuFenceHandle&& other) = default; + +GpuFenceHandle::~GpuFenceHandle() = default; + +bool GpuFenceHandle::is_null() const { +#if defined(OS_POSIX) + return !owned_fd.is_valid(); +#elif defined(OS_FUCHSIA) + return !owned_event.is_valid(); +#elif defined(OS_WIN) + return !owned_handle.IsValid(); +#else + return true; +#endif +} + +GpuFenceHandle GpuFenceHandle::Clone() const { + if (is_null()) + return GpuFenceHandle(); + + gfx::GpuFenceHandle handle; +#if defined(OS_POSIX) + const int duped_handle = HANDLE_EINTR(dup(owned_fd.get())); + if (duped_handle < 0) + return GpuFenceHandle(); + handle.owned_fd = base::ScopedFD(duped_handle); +#elif defined(OS_FUCHSIA) + zx_status_t status = + owned_event.duplicate(ZX_RIGHT_SAME_RIGHTS, &handle.owned_event); + if (status != ZX_OK) { + ZX_DLOG(ERROR, status) << "zx_handle_duplicate"; + return GpuFenceHandle(); + } +#elif defined(OS_WIN) + const base::ProcessHandle process = ::GetCurrentProcess(); + HANDLE duplicated_handle = INVALID_HANDLE_VALUE; + const BOOL result = + ::DuplicateHandle(process, owned_handle.Get(), process, + &duplicated_handle, 0, FALSE, DUPLICATE_SAME_ACCESS); + if (!result) { + const DWORD last_error = ::GetLastError(); + base::debug::Alias(&last_error); + CHECK(false); + } + handle.owned_handle.Set(duplicated_handle); +#else + NOTREACHED(); +#endif + return handle; +} + +} // namespace gfx diff --git a/gpu_fence_handle.h b/gpu_fence_handle.h new file mode 100644 index 000000000000..3e4abef74f79 --- /dev/null +++ b/gpu_fence_handle.h @@ -0,0 +1,54 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GPU_FENCE_HANDLE_H_ +#define UI_GFX_GPU_FENCE_HANDLE_H_ + +#include "base/macros.h" +#include "build/build_config.h" +#include "ui/gfx/gfx_export.h" + +#if defined(OS_POSIX) +#include "base/files/scoped_file.h" +#endif + +#if defined(OS_FUCHSIA) +#include +#endif + +#if defined(OS_WIN) +#include "base/win/scoped_handle.h" +#endif + +namespace gfx { + +struct GFX_EXPORT GpuFenceHandle { + GpuFenceHandle(const GpuFenceHandle&) = delete; + GpuFenceHandle& operator=(const GpuFenceHandle&) = delete; + + GpuFenceHandle(); + GpuFenceHandle(GpuFenceHandle&& other); + GpuFenceHandle& operator=(GpuFenceHandle&& other); + ~GpuFenceHandle(); + + bool is_null() const; + + // Returns an instance of |handle| which can be sent over IPC. This duplicates + // the handle so that IPC code can take ownership of it without invalidating + // |handle| itself. + GpuFenceHandle Clone() const; + + // TODO(crbug.com/1142962): Make this a class instead of struct. +#if defined(OS_POSIX) + base::ScopedFD owned_fd; +#elif defined(OS_FUCHSIA) + zx::event owned_event; +#elif defined(OS_WIN) + base::win::ScopedHandle owned_handle; +#endif +}; + +} // namespace gfx + +#endif // UI_GFX_GPU_FENCE_HANDLE_H_ diff --git a/gpu_memory_buffer.cc b/gpu_memory_buffer.cc new file mode 100644 index 000000000000..44b2667960da --- /dev/null +++ b/gpu_memory_buffer.cc @@ -0,0 +1,72 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/gpu_memory_buffer.h" + +#include "base/logging.h" +#include "ui/gfx/generic_shared_memory_id.h" + +#if defined(OS_WIN) +#include +#include "base/win/scoped_handle.h" +#endif + +namespace gfx { + +#if defined(OS_WIN) +namespace { +base::win::ScopedHandle CloneDXGIHandle(HANDLE handle) { + HANDLE target_handle = nullptr; + if (!::DuplicateHandle(GetCurrentProcess(), handle, GetCurrentProcess(), + &target_handle, 0, FALSE, DUPLICATE_SAME_ACCESS)) { + DVLOG(1) << "Error duplicating GMB DXGI handle. error=" << GetLastError(); + } + return base::win::ScopedHandle(target_handle); +} +} // namespace +#endif + +GpuMemoryBufferHandle::GpuMemoryBufferHandle() = default; + +#if defined(OS_ANDROID) +GpuMemoryBufferHandle::GpuMemoryBufferHandle( + base::android::ScopedHardwareBufferHandle handle) + : type(GpuMemoryBufferType::ANDROID_HARDWARE_BUFFER), + android_hardware_buffer(std::move(handle)) {} +#endif + +// TODO(crbug.com/863011): Reset |type| and possibly the handles on the +// moved-from object. +GpuMemoryBufferHandle::GpuMemoryBufferHandle(GpuMemoryBufferHandle&& other) = + default; + +GpuMemoryBufferHandle& GpuMemoryBufferHandle::operator=( + GpuMemoryBufferHandle&& other) = default; + +GpuMemoryBufferHandle::~GpuMemoryBufferHandle() = default; + +GpuMemoryBufferHandle GpuMemoryBufferHandle::Clone() const { + GpuMemoryBufferHandle handle; + handle.type = type; + handle.id = id; + handle.region = region.Duplicate(); + handle.offset = offset; + handle.stride = stride; +#if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_FUCHSIA) + handle.native_pixmap_handle = CloneHandleForIPC(native_pixmap_handle); +#elif defined(OS_MAC) + handle.io_surface = io_surface; +#elif defined(OS_WIN) + handle.dxgi_handle = CloneDXGIHandle(dxgi_handle.Get()); +#elif defined(OS_ANDROID) + NOTIMPLEMENTED(); +#endif + return handle; +} + +void GpuMemoryBuffer::SetColorSpace(const ColorSpace& color_space) {} + +void GpuMemoryBuffer::SetHDRMetadata(const HDRMetadata& hdr_metadata) {} + +} // namespace gfx diff --git a/gpu_memory_buffer.h b/gpu_memory_buffer.h new file mode 100644 index 000000000000..9d6fc35af33c --- /dev/null +++ b/gpu_memory_buffer.h @@ -0,0 +1,151 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GPU_MEMORY_BUFFER_H_ +#define UI_GFX_GPU_MEMORY_BUFFER_H_ + +#include +#include + +#include "base/memory/unsafe_shared_memory_region.h" +#include "build/build_config.h" +#include "ui/gfx/buffer_types.h" +#include "ui/gfx/generic_shared_memory_id.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/gfx_export.h" +#include "ui/gfx/hdr_metadata.h" + +#if defined(USE_OZONE) || defined(OS_LINUX) || defined(OS_CHROMEOS) +#include "ui/gfx/native_pixmap_handle.h" +#elif defined(OS_MAC) +#include "ui/gfx/mac/io_surface.h" +#elif defined(OS_WIN) +#include "base/win/scoped_handle.h" +#elif defined(OS_ANDROID) +#include "base/android/scoped_hardware_buffer_handle.h" +#endif + +extern "C" typedef struct _ClientBuffer* ClientBuffer; + +namespace base { +namespace trace_event { +class ProcessMemoryDump; +class MemoryAllocatorDumpGuid; +} // namespace trace_event +} // namespace base + +namespace gfx { + +class ColorSpace; + +enum GpuMemoryBufferType { + EMPTY_BUFFER, + SHARED_MEMORY_BUFFER, + IO_SURFACE_BUFFER, + NATIVE_PIXMAP, + DXGI_SHARED_HANDLE, + ANDROID_HARDWARE_BUFFER, + GPU_MEMORY_BUFFER_TYPE_LAST = ANDROID_HARDWARE_BUFFER +}; + +using GpuMemoryBufferId = GenericSharedMemoryId; + +// TODO(crbug.com/863011): Convert this to a proper class to ensure the state is +// always consistent, particularly that the only one handle is set at the same +// time and it corresponds to |type|. +struct GFX_EXPORT GpuMemoryBufferHandle { + GpuMemoryBufferHandle(); +#if defined(OS_ANDROID) + explicit GpuMemoryBufferHandle( + base::android::ScopedHardwareBufferHandle handle); +#endif + GpuMemoryBufferHandle(GpuMemoryBufferHandle&& other); + GpuMemoryBufferHandle& operator=(GpuMemoryBufferHandle&& other); + ~GpuMemoryBufferHandle(); + GpuMemoryBufferHandle Clone() const; + bool is_null() const { return type == EMPTY_BUFFER; } + GpuMemoryBufferType type = GpuMemoryBufferType::EMPTY_BUFFER; + GpuMemoryBufferId id{0}; + base::UnsafeSharedMemoryRegion region; + uint32_t offset = 0; + int32_t stride = 0; +#if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_FUCHSIA) + NativePixmapHandle native_pixmap_handle; +#elif defined(OS_MAC) + ScopedIOSurface io_surface; +#elif defined(OS_WIN) + base::win::ScopedHandle dxgi_handle; +#elif defined(OS_ANDROID) + base::android::ScopedHardwareBufferHandle android_hardware_buffer; +#endif +}; + +// This interface typically correspond to a type of shared memory that is also +// shared with the GPU. A GPU memory buffer can be written to directly by +// regular CPU code, but can also be read by the GPU. +class GFX_EXPORT GpuMemoryBuffer { + public: + virtual ~GpuMemoryBuffer() {} + + // Maps each plane of the buffer into the client's address space so it can be + // written to by the CPU. This call may block, for instance if the GPU needs + // to finish accessing the buffer or if CPU caches need to be synchronized. + // Returns false on failure. + virtual bool Map() = 0; + + // Returns a pointer to the memory address of a plane. Buffer must have been + // successfully mapped using a call to Map() before calling this function. + virtual void* memory(size_t plane) = 0; + + // Unmaps the buffer. It's illegal to use any pointer returned by memory() + // after this has been called. + virtual void Unmap() = 0; + + // Returns the size in pixels of the first plane of the buffer. + virtual Size GetSize() const = 0; + + // Returns the format for the buffer. + virtual BufferFormat GetFormat() const = 0; + + // Fills the stride in bytes for each plane of the buffer. The stride of + // plane K is stored at index K-1 of the |stride| array. + virtual int stride(size_t plane) const = 0; + + // Set the color space in which this buffer should be interpreted when used + // as an overlay. Note that this will not impact texturing from the buffer. + virtual void SetColorSpace(const ColorSpace& color_space); + + // Set the HDR metadata for use when this buffer is used as an overlay. Note + // that this will not impact texturing from the buffer. + virtual void SetHDRMetadata(const HDRMetadata& hdr_metadata); + + // Returns a unique identifier associated with buffer. + virtual GpuMemoryBufferId GetId() const = 0; + + // Returns the type of this buffer. + virtual GpuMemoryBufferType GetType() const = 0; + + // Returns a platform specific handle for this buffer which in particular can + // be sent over IPC. This duplicates file handles as appropriate, so that a + // caller takes ownership of the returned handle. + virtual GpuMemoryBufferHandle CloneHandle() const = 0; + + // Type-checking downcast routine. + virtual ClientBuffer AsClientBuffer() = 0; + + // Dumps information about the memory backing the GpuMemoryBuffer to |pmd|. + // The memory usage is attributed to |buffer_dump_guid|. + // |tracing_process_id| uniquely identifies the process owning the memory. + // |importance| is relevant only for the cases of co-ownership, the memory + // gets attributed to the owner with the highest importance. + virtual void OnMemoryDump( + base::trace_event::ProcessMemoryDump* pmd, + const base::trace_event::MemoryAllocatorDumpGuid& buffer_dump_guid, + uint64_t tracing_process_id, + int importance) const = 0; +}; + +} // namespace gfx + +#endif // UI_GFX_GPU_MEMORY_BUFFER_H_ diff --git a/half_float.cc b/half_float.cc new file mode 100644 index 000000000000..a7c66961c169 --- /dev/null +++ b/half_float.cc @@ -0,0 +1,17 @@ +// Copyright (c) 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/half_float.h" + +namespace gfx { + +void FloatToHalfFloat(const float* input, HalfFloat* output, size_t num) { + for (size_t i = 0; i < num; i++) { + float tmp = input[i] * 1.9259299444e-34f; + uint32_t tmp2 = *reinterpret_cast(&tmp) + (1 << 12); + output[i] = (tmp2 & 0x80000000UL) >> 16 | (tmp2 >> 13); + } +} + +} // namespace gfx diff --git a/half_float.h b/half_float.h new file mode 100644 index 000000000000..c5e48ce7b92a --- /dev/null +++ b/half_float.h @@ -0,0 +1,23 @@ +// Copyright (c) 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_HALF_FLOAT_H_ +#define UI_GFX_HALF_FLOAT_H_ + +#include +#include + +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +typedef uint16_t HalfFloat; + +// Floats are expected to be within +/- 65535.0; +GFX_EXPORT void FloatToHalfFloat(const float* input, + HalfFloat* output, + size_t num); +} // namespace gfx + +#endif // UI_GFX_HALF_FLOAT_H_ diff --git a/half_float_unittest.cc b/half_float_unittest.cc new file mode 100644 index 000000000000..d6a1965662b1 --- /dev/null +++ b/half_float_unittest.cc @@ -0,0 +1,88 @@ +// Copyright (c) 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/cxx17_backports.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/half_float.h" + +namespace gfx { + +class HalfFloatTest : public testing::Test { + public: + union FloatUIntUnion { + // this must come first for the initializations below to work + uint32_t fUInt; + float fFloat; + }; + + // Convert an IEEE 754 half-float to a float value + // that we can do math on. + float FromHalfFloat(HalfFloat half_float) { + int sign = (half_float & 0x8000) ? -1 : 1; + int exponent = (half_float >> 10) & 0x1F; + int fraction = half_float & 0x3FF; + if (exponent == 0) { + return powf(2.0f, -24.0f) * fraction; + } else if (exponent == 0x1F) { + return sign * 1000000000000.0f; + } else { + return pow(2.0f, exponent - 25) * (0x400 + fraction); + } + } + + HalfFloat ConvertTruth(float f) { + if (f < 0.0) + return 0x8000 | ConvertTruth(-f); + int max = 0x8000; + int min = 0; + while (max - min > 1) { + int mid = (min + max) >> 1; + if (FromHalfFloat(mid) > f) { + max = mid; + } else { + min = mid; + } + } + float low = FromHalfFloat(min); + float high = FromHalfFloat(min + 1); + if (f - low <= high - f) { + return min; + } else { + return min + 1; + } + } + + HalfFloat Convert(float f) { + HalfFloat ret; + FloatToHalfFloat(&f, &ret, 1); + return ret; + } +}; + +TEST_F(HalfFloatTest, NoCrashTest) { + Convert(nanf("")); + Convert(1.0E30f); + Convert(-1.0E30f); + Convert(1.0E-30f); + Convert(-1.0E-30f); +} + +TEST_F(HalfFloatTest, SimpleTest) { + static float test[] = { + 0.0f, 1.0f, 10.0f, 1000.0f, 65503.0f, + 1.0E-3f, 1.0E-6f, 1.0E-20f, 1.0E-44f, + }; + for (size_t i = 0; i < base::size(test); i++) { + EXPECT_EQ(ConvertTruth(test[i]), Convert(test[i])) << " float = " + << test[i]; + if (test[i] != 0.0) { + EXPECT_EQ(ConvertTruth(-test[i]), Convert(-test[i])) << " float = " + << -test[i]; + } + } +} + +} // namespace diff --git a/harfbuzz_font_skia.cc b/harfbuzz_font_skia.cc new file mode 100644 index 000000000000..7b13a183ea40 --- /dev/null +++ b/harfbuzz_font_skia.cc @@ -0,0 +1,306 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/harfbuzz_font_skia.h" + +#include +#include + +#include +#include + +#include "base/check_op.h" +#include "base/containers/mru_cache.h" +#include "base/lazy_instance.h" +#include "base/macros.h" +#include "base/no_destructor.h" +#include "build/build_config.h" +#include "third_party/skia/include/core/SkFont.h" +#include "third_party/skia/include/core/SkTypeface.h" +#include "ui/gfx/render_text.h" +#include "ui/gfx/skia_util.h" + +namespace gfx { + +namespace { + +class TypefaceData; + +// Maps from code points to glyph indices in a font. +using GlyphCache = std::map; + +// Wraps a custom user data attached to a hb_font object. Font data provider for +// HarfBuzz using Skia. Copied from Blink. +// TODO(ckocagil): Eliminate the duplication. http://crbug.com/368375 +struct FontData { + explicit FontData(GlyphCache* glyph_cache) : glyph_cache_(glyph_cache) {} + + SkFont font_; + GlyphCache* glyph_cache_; +}; + +// Deletes the object at the given pointer after casting it to the given type. +template +void DeleteByType(void* data) { + Type* typed_data = reinterpret_cast(data); + delete typed_data; +} + +template +void DeleteArrayByType(void* data) { + Type* typed_data = reinterpret_cast(data); + delete[] typed_data; +} + +// Outputs the |width| and |extents| of the glyph with index |codepoint| in +// |paint|'s font. +void GetGlyphWidthAndExtents(const SkFont& font, + hb_codepoint_t codepoint, + hb_position_t* width, + hb_glyph_extents_t* extents) { + DCHECK_LE(codepoint, std::numeric_limits::max()); + + SkScalar sk_width; + SkRect sk_bounds; + uint16_t glyph = static_cast(codepoint); + + font.getWidths(&glyph, 1, &sk_width, &sk_bounds); + if (width) + *width = SkiaScalarToHarfBuzzUnits(sk_width); + if (extents) { + // Invert y-axis because Skia is y-grows-down but we set up HarfBuzz to be + // y-grows-up. + extents->x_bearing = SkiaScalarToHarfBuzzUnits(sk_bounds.fLeft); + extents->y_bearing = SkiaScalarToHarfBuzzUnits(-sk_bounds.fTop); + extents->width = SkiaScalarToHarfBuzzUnits(sk_bounds.width()); + extents->height = SkiaScalarToHarfBuzzUnits(-sk_bounds.height()); + } +} + +// Writes the |glyph| index for the given |unicode| code point. Returns whether +// the glyph exists, i.e. it is not a missing glyph. +hb_bool_t GetGlyph(hb_font_t* font, + void* data, + hb_codepoint_t unicode, + hb_codepoint_t variation_selector, + hb_codepoint_t* glyph, + void* user_data) { + FontData* font_data = reinterpret_cast(data); + GlyphCache* cache = font_data->glyph_cache_; + + GlyphCache::iterator iter = cache->find(unicode); + if (iter == cache->end()) { + auto result = cache->insert( + std::make_pair(unicode, font_data->font_.unicharToGlyph(unicode))); + DCHECK(result.second); + iter = result.first; + } + + *glyph = iter->second; + return !!*glyph; +} + +hb_bool_t GetNominalGlyph(hb_font_t* font, + void* data, + hb_codepoint_t unicode, + hb_codepoint_t* glyph, + void* user_data) { + return GetGlyph(font, data, unicode, 0, glyph, user_data); +} + +// Returns the horizontal advance value of the |glyph|. +hb_position_t GetGlyphHorizontalAdvance(hb_font_t* font, + void* data, + hb_codepoint_t glyph, + void* user_data) { + FontData* font_data = reinterpret_cast(data); + hb_position_t advance = 0; + + GetGlyphWidthAndExtents(font_data->font_, glyph, &advance, 0); + return advance; +} + +hb_bool_t GetGlyphHorizontalOrigin(hb_font_t* font, + void* data, + hb_codepoint_t glyph, + hb_position_t* x, + hb_position_t* y, + void* user_data) { + // Just return true, like the HarfBuzz-FreeType implementation. + return true; +} + +hb_position_t GetGlyphKerning(FontData* font_data, + hb_codepoint_t first_glyph, + hb_codepoint_t second_glyph) { + SkTypeface* typeface = font_data->font_.getTypeface(); + const uint16_t glyphs[2] = { static_cast(first_glyph), + static_cast(second_glyph) }; + int32_t kerning_adjustments[1] = { 0 }; + + if (!typeface->getKerningPairAdjustments(glyphs, 2, kerning_adjustments)) + return 0; + + SkScalar upm = SkIntToScalar(typeface->getUnitsPerEm()); + SkScalar size = font_data->font_.getSize(); + return SkiaScalarToHarfBuzzUnits(SkIntToScalar(kerning_adjustments[0]) * + size / upm); +} + +hb_position_t GetGlyphHorizontalKerning(hb_font_t* font, + void* data, + hb_codepoint_t left_glyph, + hb_codepoint_t right_glyph, + void* user_data) { + FontData* font_data = reinterpret_cast(data); + return GetGlyphKerning(font_data, left_glyph, right_glyph); +} + +hb_position_t GetGlyphVerticalKerning(hb_font_t* font, + void* data, + hb_codepoint_t top_glyph, + hb_codepoint_t bottom_glyph, + void* user_data) { + FontData* font_data = reinterpret_cast(data); + return GetGlyphKerning(font_data, top_glyph, bottom_glyph); +} + +// Writes the |extents| of |glyph|. +hb_bool_t GetGlyphExtents(hb_font_t* font, + void* data, + hb_codepoint_t glyph, + hb_glyph_extents_t* extents, + void* user_data) { + FontData* font_data = reinterpret_cast(data); + + GetGlyphWidthAndExtents(font_data->font_, glyph, 0, extents); + return true; +} + +class FontFuncs { + public: + FontFuncs() : font_funcs_(hb_font_funcs_create()) { + hb_font_funcs_set_variation_glyph_func(font_funcs_, GetGlyph, 0, 0); + hb_font_funcs_set_nominal_glyph_func(font_funcs_, GetNominalGlyph, 0, 0); + hb_font_funcs_set_glyph_h_advance_func( + font_funcs_, GetGlyphHorizontalAdvance, 0, 0); + hb_font_funcs_set_glyph_h_kerning_func( + font_funcs_, GetGlyphHorizontalKerning, 0, 0); + hb_font_funcs_set_glyph_h_origin_func( + font_funcs_, GetGlyphHorizontalOrigin, 0, 0); + hb_font_funcs_set_glyph_v_kerning_func( + font_funcs_, GetGlyphVerticalKerning, 0, 0); + hb_font_funcs_set_glyph_extents_func( + font_funcs_, GetGlyphExtents, 0, 0); + hb_font_funcs_make_immutable(font_funcs_); + } + + FontFuncs(const FontFuncs&) = delete; + FontFuncs& operator=(const FontFuncs&) = delete; + + ~FontFuncs() { + hb_font_funcs_destroy(font_funcs_); + } + + hb_font_funcs_t* get() { return font_funcs_; } + + private: + hb_font_funcs_t* font_funcs_; +}; + +base::LazyInstance::Leaky g_font_funcs = LAZY_INSTANCE_INITIALIZER; + +// Returns the raw data of the font table |tag|. +hb_blob_t* GetFontTable(hb_face_t* face, hb_tag_t tag, void* user_data) { + SkTypeface* typeface = reinterpret_cast(user_data); + + const size_t table_size = typeface->getTableSize(tag); + if (!table_size) + return 0; + + std::unique_ptr buffer(new char[table_size]); + if (!buffer) + return 0; + size_t actual_size = typeface->getTableData(tag, 0, table_size, buffer.get()); + if (table_size != actual_size) + return 0; + + char* buffer_raw = buffer.release(); + return hb_blob_create(buffer_raw, static_cast(table_size), + HB_MEMORY_MODE_WRITABLE, buffer_raw, + DeleteArrayByType); +} + +// For a given skia typeface, maps to its harfbuzz face and its glyphs cache. +class TypefaceData { + public: + explicit TypefaceData(sk_sp skia_face) : sk_typeface_(skia_face) { + face_ = hb_face_create_for_tables(GetFontTable, skia_face.get(), nullptr); + DCHECK(face_); + } + + TypefaceData(TypefaceData&& data) { + face_ = data.face_; + glyphs_ = std::move(data.glyphs_); + data.face_ = nullptr; + } + + TypefaceData(const TypefaceData&) = delete; + TypefaceData& operator=(const TypefaceData&) = delete; + + ~TypefaceData() { hb_face_destroy(face_); } + + hb_face_t* face() { return face_; } + GlyphCache* glyphs() { return &glyphs_; } + + private: + TypefaceData() = delete; + + GlyphCache glyphs_; + hb_face_t* face_ = nullptr; + + // The skia typeface must outlive |face_| since it's being used by harfbuzz. + sk_sp sk_typeface_; +}; + +} // namespace + +// Creates a HarfBuzz font from the given Skia face and text size. +hb_font_t* CreateHarfBuzzFont(sk_sp skia_face, + SkScalar text_size, + const FontRenderParams& params, + bool subpixel_rendering_suppressed) { + // A cache from Skia font to harfbuzz typeface information. + using TypefaceCache = base::MRUCache; + + constexpr int kTypefaceCacheSize = 64; + static base::NoDestructor face_caches(kTypefaceCacheSize); + + TypefaceCache* typeface_cache = face_caches.get(); + TypefaceCache::iterator typeface_data = + typeface_cache->Get(skia_face->uniqueID()); + if (typeface_data == typeface_cache->end()) { + TypefaceData new_typeface_data(skia_face); + typeface_data = typeface_cache->Put(skia_face->uniqueID(), + std::move(new_typeface_data)); + } + + DCHECK(typeface_data->second.face()); + hb_font_t* harfbuzz_font = hb_font_create(typeface_data->second.face()); + + const int scale = SkiaScalarToHarfBuzzUnits(text_size); + hb_font_set_scale(harfbuzz_font, scale, scale); + FontData* hb_font_data = new FontData(typeface_data->second.glyphs()); + hb_font_data->font_.setTypeface(std::move(skia_face)); + hb_font_data->font_.setSize(text_size); + // TODO(ckocagil): Do we need to update these params later? + internal::ApplyRenderParams(params, subpixel_rendering_suppressed, + &hb_font_data->font_); + hb_font_set_funcs(harfbuzz_font, g_font_funcs.Get().get(), hb_font_data, + DeleteByType); + hb_font_make_immutable(harfbuzz_font); + return harfbuzz_font; +} + +} // namespace gfx diff --git a/harfbuzz_font_skia.h b/harfbuzz_font_skia.h new file mode 100644 index 000000000000..abc6c79387ab --- /dev/null +++ b/harfbuzz_font_skia.h @@ -0,0 +1,25 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_HARFBUZZ_FONT_SKIA_H_ +#define UI_GFX_HARFBUZZ_FONT_SKIA_H_ + +#include "third_party/skia/include/core/SkRefCnt.h" +#include "third_party/skia/include/core/SkScalar.h" +#include "ui/gfx/font_render_params.h" + +#include + +class SkTypeface; + +namespace gfx { + +hb_font_t* CreateHarfBuzzFont(sk_sp skia_face, + SkScalar text_size, + const FontRenderParams& params, + bool subpixel_rendering_suppressed); + +} // namespace gfx + +#endif // UI_GFX_HARFBUZZ_FONT_SKIA_H_ diff --git a/hdr_metadata.cc b/hdr_metadata.cc new file mode 100644 index 000000000000..ed20c0fa7c5d --- /dev/null +++ b/hdr_metadata.cc @@ -0,0 +1,19 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/hdr_metadata.h" + +namespace gfx { + +ColorVolumeMetadata::ColorVolumeMetadata() = default; +ColorVolumeMetadata::ColorVolumeMetadata(const ColorVolumeMetadata& rhs) = + default; +ColorVolumeMetadata& ColorVolumeMetadata::operator=( + const ColorVolumeMetadata& rhs) = default; + +HDRMetadata::HDRMetadata() = default; +HDRMetadata::HDRMetadata(const HDRMetadata& rhs) = default; +HDRMetadata& HDRMetadata::operator=(const HDRMetadata& rhs) = default; + +} // namespace gfx diff --git a/hdr_metadata.h b/hdr_metadata.h new file mode 100644 index 000000000000..5fc03196ce4e --- /dev/null +++ b/hdr_metadata.h @@ -0,0 +1,74 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_HDR_METADATA_H_ +#define UI_GFX_HDR_METADATA_H_ + +#include "ui/gfx/geometry/point_f.h" +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +// SMPTE ST 2086 color volume metadata. +struct GFX_EXPORT ColorVolumeMetadata { + using Chromaticity = PointF; + Chromaticity primary_r; + Chromaticity primary_g; + Chromaticity primary_b; + Chromaticity white_point; + float luminance_max = 0; + float luminance_min = 0; + + ColorVolumeMetadata(); + ColorVolumeMetadata(const ColorVolumeMetadata& rhs); + ColorVolumeMetadata& operator=(const ColorVolumeMetadata& rhs); + + bool operator==(const ColorVolumeMetadata& rhs) const { + return ((primary_r == rhs.primary_r) && (primary_g == rhs.primary_g) && + (primary_b == rhs.primary_b) && (white_point == rhs.white_point) && + (luminance_max == rhs.luminance_max) && + (luminance_min == rhs.luminance_min)); + } +}; + +// HDR metadata common for HDR10 and WebM/VP9-based HDR formats. +struct GFX_EXPORT HDRMetadata { + ColorVolumeMetadata color_volume_metadata; + // Max content light level (CLL), i.e. maximum brightness level present in the + // stream), in nits. + unsigned max_content_light_level = 0; + // Max frame-average light level (FALL), i.e. maximum average brightness of + // the brightest frame in the stream), in nits. + unsigned max_frame_average_light_level = 0; + + HDRMetadata(); + HDRMetadata(const HDRMetadata& rhs); + HDRMetadata& operator=(const HDRMetadata& rhs); + + bool IsValid() const { + return !((max_content_light_level == 0) && + (max_frame_average_light_level == 0) && + (color_volume_metadata == ColorVolumeMetadata())); + } + + bool operator==(const HDRMetadata& rhs) const { + return ( + (max_content_light_level == rhs.max_content_light_level) && + (max_frame_average_light_level == rhs.max_frame_average_light_level) && + (color_volume_metadata == rhs.color_volume_metadata)); + } +}; + +// HDR metadata types as described in +// https://w3c.github.io/media-capabilities/#enumdef-hdrmetadatatype +enum class HdrMetadataType { + kNone, + kSmpteSt2086, + kSmpteSt2094_10, + kSmpteSt2094_40, +}; + +} // namespace gfx + +#endif // UI_GFX_HDR_METADATA_H_ diff --git a/hdr_static_metadata.cc b/hdr_static_metadata.cc new file mode 100644 index 000000000000..b5b3f0ad8041 --- /dev/null +++ b/hdr_static_metadata.cc @@ -0,0 +1,16 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/hdr_static_metadata.h" + +namespace gfx { + +HDRStaticMetadata::HDRStaticMetadata() = default; +HDRStaticMetadata::HDRStaticMetadata(double max, double max_avg, double min) + : max(max), max_avg(max_avg), min(min) {} +HDRStaticMetadata::HDRStaticMetadata(const HDRStaticMetadata& rhs) = default; +HDRStaticMetadata& HDRStaticMetadata::operator=(const HDRStaticMetadata& rhs) = + default; + +} // namespace gfx diff --git a/hdr_static_metadata.h b/hdr_static_metadata.h new file mode 100644 index 000000000000..035d3ac1e6c3 --- /dev/null +++ b/hdr_static_metadata.h @@ -0,0 +1,40 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_HDR_STATIC_METADATA_H_ +#define UI_GFX_HDR_STATIC_METADATA_H_ + +#include "ui/gfx/color_space_export.h" + +namespace gfx { + +// This structure is used to define the HDR static capabilities of a display. +// Reflects CEA 861.G-2018, Sec.7.5.13, "HDR Static Metadata Data Block" +// A value of 0.0 in any of the fields means that it's not indicated. +struct COLOR_SPACE_EXPORT HDRStaticMetadata { + // "Desired Content Max Luminance Data. This is the content’s absolute peak + // luminance (in cd/m2) (likely only in a small area of the screen) that the + // display prefers for optimal content rendering." + double max; + // "Desired Content Max Frame-average Luminance. This is the content’s max + // frame-average luminance (in cd/m2) that the display prefers for optimal + // content rendering." + double max_avg; + // "Desired Content Min Luminance. This is the minimum value of the content + // (in cd/m2) that the display prefers for optimal content rendering." + double min; + + HDRStaticMetadata(); + HDRStaticMetadata(double max, double max_avg, double min); + HDRStaticMetadata(const HDRStaticMetadata& rhs); + HDRStaticMetadata& operator=(const HDRStaticMetadata& rhs); + + bool operator==(const HDRStaticMetadata& rhs) const { + return ((max == rhs.max) && (max_avg == rhs.max_avg) && (min == rhs.min)); + } +}; + +} // namespace gfx + +#endif // UI_GFX_HDR_STATIC_METADATA_H_ diff --git a/icc_profile.cc b/icc_profile.cc new file mode 100644 index 000000000000..94f0e7c89611 --- /dev/null +++ b/icc_profile.cc @@ -0,0 +1,228 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/icc_profile.h" + +#include +#include + +#include "base/command_line.h" +#include "base/containers/mru_cache.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/synchronization/lock.h" +#include "third_party/skia/include/core/SkColorSpace.h" +#include "third_party/skia/include/third_party/skcms/skcms.h" +#include "ui/gfx/skia_color_space_util.h" + +namespace gfx { + +namespace { + +static const size_t kMaxCachedICCProfiles = 16; + +// An MRU cache mapping data to ICCProfile objects, to avoid re-parsing +// profiles every time they are read. +using DataToProfileCacheBase = base::MRUCache, ICCProfile>; +class DataToProfileCache : public DataToProfileCacheBase { + public: + DataToProfileCache() : DataToProfileCacheBase(kMaxCachedICCProfiles) {} +}; +base::LazyInstance::Leaky g_data_to_profile_cache = + LAZY_INSTANCE_INITIALIZER; + +// Lock that must be held to access |g_data_to_profile_cache|. +base::LazyInstance::Leaky g_icc_profile_lock = + LAZY_INSTANCE_INITIALIZER; + +} // namespace + +void ICCProfile::Internals::Initialize() { + // Start out with no parametric data. + if (data_.empty()) + return; + + // Parse the profile. + skcms_ICCProfile profile; + if (!skcms_Parse(data_.data(), data_.size(), &profile)) { + DLOG(ERROR) << "Failed to parse ICC profile."; + return; + } + + // We have seen many users with profiles that don't have a D50 white point. + // Windows appears to detect these profiles, and not use them for OS drawing. + // It still returns them when we query the system for the installed profile. + // For consistency (and to match old behavior) we reject these profiles on + // all platforms. + // https://crbug.com/847024 + const skcms_Matrix3x3& m(profile.toXYZD50); + float wX = m.vals[0][0] + m.vals[0][1] + m.vals[0][2]; + float wY = m.vals[1][0] + m.vals[1][1] + m.vals[1][2]; + float wZ = m.vals[2][0] + m.vals[2][1] + m.vals[2][2]; + static const float kD50_WhitePoint[3] = { 0.96420f, 1.00000f, 0.82491f }; + if (fabsf(wX - kD50_WhitePoint[0]) > 0.04f || + fabsf(wY - kD50_WhitePoint[1]) > 0.04f || + fabsf(wZ - kD50_WhitePoint[2]) > 0.04f) { + return; + } + + // At this point, the profile is considered valid. We still need to determine + // if it's representable with a parametric transfer function. + is_valid_ = true; + + // Extract the primary matrix, and assume that transfer function is sRGB until + // we get something more precise. + to_XYZD50_ = profile.toXYZD50; + transfer_fn_ = SkNamedTransferFn::kSRGB; + + // Coerce it into a rasterization destination (if possible). If the profile + // can't be approximated accurately, then use an sRGB transfer function and + // return failure. We will continue to use the gamut from this profile. + if (!skcms_MakeUsableAsDestinationWithSingleCurve(&profile)) { + DLOG(ERROR) << "Parsed ICC profile but can't make usable as destination, " + "using sRGB gamma"; + return; + } + + // If SkColorSpace will treat the gamma as that of sRGB, then use the named + // constants. + sk_sp sk_color_space = SkColorSpace::Make(profile); + if (!sk_color_space) { + DLOG(ERROR) << "Parsed ICC profile but cannot create SkColorSpace from it, " + "using sRGB gamma."; + return; + } + + // We were able to get a parametric representation of the transfer function. + is_parametric_ = true; + + if (sk_color_space->gammaCloseToSRGB()) + return; + + // We assume that if we accurately approximated the profile, then the + // single-curve version (which may have higher error) is also okay. If we + // want to maintain the distinction between accurate and inaccurate profiles, + // we could check to see if the single-curve version is/ approximately equal + // to the original (or to the multi-channel approximation). + transfer_fn_ = profile.trc[0].parametric; +} + +ICCProfile::ICCProfile() = default; +ICCProfile::ICCProfile(ICCProfile&& other) = default; +ICCProfile::ICCProfile(const ICCProfile& other) = default; +ICCProfile& ICCProfile::operator=(ICCProfile&& other) = default; +ICCProfile& ICCProfile::operator=(const ICCProfile& other) = default; +ICCProfile::~ICCProfile() = default; + +bool ICCProfile::operator==(const ICCProfile& other) const { + if (!internals_ && !other.internals_) + return true; + if (internals_ && other.internals_) { + return internals_->data_ == other.internals_->data_; + } + return false; +} + +bool ICCProfile::operator!=(const ICCProfile& other) const { + return !(*this == other); +} + +bool ICCProfile::IsValid() const { + return internals_ ? internals_->is_valid_ : false; +} + +std::vector ICCProfile::GetData() const { + return internals_ ? internals_->data_ : std::vector(); +} + +// static +ICCProfile ICCProfile::FromData(const void* data_as_void, size_t size) { + const char* data_as_byte = reinterpret_cast(data_as_void); + std::vector data(data_as_byte, data_as_byte + size); + + base::AutoLock lock(g_icc_profile_lock.Get()); + + // See if there is already an entry with the same data. If so, return that + // entry. If not, parse the data. + ICCProfile icc_profile; + auto found_by_data = g_data_to_profile_cache.Get().Get(data); + if (found_by_data != g_data_to_profile_cache.Get().end()) { + icc_profile = found_by_data->second; + } else { + icc_profile.internals_ = base::MakeRefCounted(std::move(data)); + } + + // Insert the profile into all caches. + g_data_to_profile_cache.Get().Put(icc_profile.internals_->data_, icc_profile); + + return icc_profile; +} + +ColorSpace ICCProfile::GetColorSpace() const { + if (!internals_ || !internals_->is_valid_) + return ColorSpace(); + + return ColorSpace(ColorSpace::PrimaryID::CUSTOM, + ColorSpace::TransferID::CUSTOM, ColorSpace::MatrixID::RGB, + ColorSpace::RangeID::FULL, &internals_->to_XYZD50_, + &internals_->transfer_fn_); +} + +ColorSpace ICCProfile::GetPrimariesOnlyColorSpace() const { + if (!internals_ || !internals_->is_valid_) + return ColorSpace(); + + return ColorSpace(ColorSpace::PrimaryID::CUSTOM, + ColorSpace::TransferID::IEC61966_2_1, + ColorSpace::MatrixID::RGB, ColorSpace::RangeID::FULL, + &internals_->to_XYZD50_, nullptr); +} + +bool ICCProfile::IsColorSpaceAccurate() const { + if (!internals_) + return false; + + if (!internals_->is_valid_) + return false; + + return internals_->is_parametric_; +} + +// static +ICCProfile ICCProfile::FromColorSpace(const ColorSpace& color_space) { + if (!color_space.IsValid()) { + return ICCProfile(); + } + if (color_space.GetMatrixID() != ColorSpace::MatrixID::RGB) { + DLOG(ERROR) << "Not creating non-RGB ICCProfile"; + return ICCProfile(); + } + if (color_space.GetRangeID() != ColorSpace::RangeID::FULL) { + DLOG(ERROR) << "Not creating non-full-range ICCProfile"; + return ICCProfile(); + } + skcms_Matrix3x3 to_XYZD50_matrix; + color_space.GetPrimaryMatrix(&to_XYZD50_matrix); + skcms_TransferFunction fn; + if (!color_space.GetTransferFunction(&fn)) { + DLOG(ERROR) << "Failed to get ColorSpace transfer function for ICCProfile."; + return ICCProfile(); + } + sk_sp data = SkWriteICCProfile(fn, to_XYZD50_matrix); + if (!data) { + DLOG(ERROR) << "Failed to create SkICC."; + return ICCProfile(); + } + return FromData(data->data(), data->size()); +} + +ICCProfile::Internals::Internals(std::vector data) + : data_(std::move(data)) { + // Parse the ICC profile + Initialize(); +} + +ICCProfile::Internals::~Internals() {} + +} // namespace gfx diff --git a/icc_profile.h b/icc_profile.h new file mode 100644 index 000000000000..542f703fbf93 --- /dev/null +++ b/icc_profile.h @@ -0,0 +1,107 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_ICC_PROFILE_H_ +#define UI_GFX_ICC_PROFILE_H_ + +#include +#include +#include + +#include "base/gtest_prod_util.h" +#include "base/memory/ref_counted.h" +#include "third_party/skia/include/third_party/skcms/skcms.h" +#include "ui/gfx/color_space.h" + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size); + +namespace IPC { +template +struct ParamTraits; +} // namespace IPC + +namespace gfx { + +// Used to represent a full ICC profile, usually retrieved from a monitor. It +// can be lossily compressed into a ColorSpace object. This structure should +// only be sent from higher-privilege processes to lower-privilege processes, +// as parsing this structure is not secure. +class COLOR_SPACE_EXPORT ICCProfile { + public: + ICCProfile(); + ICCProfile(ICCProfile&& other); + ICCProfile(const ICCProfile& other); + ICCProfile& operator=(ICCProfile&& other); + ICCProfile& operator=(const ICCProfile& other); + ~ICCProfile(); + bool operator==(const ICCProfile& other) const; + bool operator!=(const ICCProfile& other) const; + + // Returns true if this profile was successfully parsed by SkICC and will + // return a valid ColorSpace. + bool IsValid() const; + + // Create directly from profile data. This function should be called only + // in the browser process (and the results from there sent to other + // processes). + static ICCProfile FromData(const void* icc_profile, size_t size); + + // Create a profile for a color space. Returns an invalid profile if the + // specified space is not expressable as an ICCProfile. + static ICCProfile FromColorSpace(const gfx::ColorSpace& color_space); + + // Return a ColorSpace that best represents this ICCProfile. + ColorSpace GetColorSpace() const; + + // Return a ColorSpace with the primaries from this ICCProfile and an + // sRGB transfer function. + ColorSpace GetPrimariesOnlyColorSpace() const; + + // Returns true if GetColorSpace returns an accurate representation of this + // ICCProfile. This could be false if the result of GetColorSpace had to + // approximate transfer functions. + bool IsColorSpaceAccurate() const; + + // Return the data for the profile. + std::vector GetData() const; + + private: + class Internals : public base::RefCountedThreadSafe { + public: + explicit Internals(std::vector); + + const std::vector data_; + + // True iff we can create a valid ColorSpace (and ColorTransform) from this + // object. The transform may be LUT-based (using an SkColorSpaceXform to + // compute the lut). + bool is_valid_ = false; + + // True iff |to_XYZD50_| and |transfer_fn_| are accurate representations of + // the data in this profile. In this case ColorTransforms created from this + // profile will be analytic and not LUT-based. + bool is_parametric_ = false; + + // The best-fit parametric primaries and transfer function. + skcms_Matrix3x3 to_XYZD50_; + skcms_TransferFunction transfer_fn_; + + protected: + friend class base::RefCountedThreadSafe; + void Initialize(); + virtual ~Internals(); + }; + scoped_refptr internals_; + + FRIEND_TEST_ALL_PREFIXES(SimpleColorSpace, BT709toSRGBICC); + FRIEND_TEST_ALL_PREFIXES(SimpleColorSpace, GetColorSpace); + friend int ::LLVMFuzzerTestOneInput(const uint8_t*, size_t); + friend class ColorSpace; + friend class ColorTransformInternal; + friend struct IPC::ParamTraits; +}; + +} // namespace gfx + +#endif // UI_GFX_ICC_PROFILE_H_ diff --git a/icc_profile_unittest.cc b/icc_profile_unittest.cc new file mode 100644 index 000000000000..29c4ff8bbe75 --- /dev/null +++ b/icc_profile_unittest.cc @@ -0,0 +1,138 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/icc_profile.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/color_space.h" +#include "ui/gfx/skia_color_space_util.h" +#include "ui/gfx/test/icc_profiles.h" + +namespace gfx { + +TEST(ICCProfile, Conversions) { + ICCProfile icc_profile = ICCProfileForTestingColorSpin(); + ColorSpace color_space_from_icc_profile = icc_profile.GetColorSpace(); + + ICCProfile icc_profile_from_color_space = + ICCProfile::FromColorSpace(color_space_from_icc_profile); + EXPECT_TRUE(icc_profile_from_color_space.IsValid()); + EXPECT_NE(icc_profile, icc_profile_from_color_space); +} + +TEST(ICCProfile, SRGB) { + ICCProfile icc_profile = ICCProfileForTestingSRGB(); + ColorSpace color_space = ColorSpace::CreateSRGB(); + sk_sp sk_color_space = SkColorSpace::MakeSRGB(); + + // The ICC profile parser should note that this is SRGB. + EXPECT_EQ(icc_profile.GetColorSpace(), ColorSpace::CreateSRGB()); + EXPECT_EQ(icc_profile.GetColorSpace().ToSkColorSpace().get(), + sk_color_space.get()); + // The generated color space should recognize that this is SRGB. + EXPECT_EQ(color_space.ToSkColorSpace().get(), sk_color_space.get()); +} + +TEST(ICCProfile, Equality) { + ICCProfile spin_profile = ICCProfileForTestingColorSpin(); + ICCProfile adobe_profile = ICCProfileForTestingAdobeRGB(); + EXPECT_TRUE(spin_profile == spin_profile); + EXPECT_FALSE(spin_profile != spin_profile); + EXPECT_FALSE(spin_profile == adobe_profile); + EXPECT_TRUE(spin_profile != adobe_profile); + + gfx::ColorSpace spin_space = spin_profile.GetColorSpace(); + gfx::ColorSpace adobe_space = adobe_profile.GetColorSpace(); + EXPECT_TRUE(spin_space == spin_space); + EXPECT_FALSE(spin_space != spin_space); + EXPECT_FALSE(spin_space == adobe_space); + EXPECT_TRUE(spin_space != adobe_space); + + EXPECT_TRUE(!!spin_space.ToSkColorSpace()); + EXPECT_TRUE(!!adobe_space.ToSkColorSpace()); + EXPECT_FALSE(SkColorSpace::Equals( + spin_space.ToSkColorSpace().get(), + adobe_space.ToSkColorSpace().get())); +} + +TEST(ICCProfile, ParametricVersusExactInaccurate) { + // This ICC profile has three transfer functions that differ significantly, + // but ICCProfiles are always either invalid or considered accurate (and in + // this case, each curve is approximated, so the profile is "accurate"). + // See comments in ICCProfile::Internals::Analyze. + ICCProfile multi_tr_fn = ICCProfileForTestingNoAnalyticTrFn(); + EXPECT_TRUE(multi_tr_fn.IsColorSpaceAccurate()); + + // We are capable of generating a parametric approximation. + ICCProfile profile; + profile = ICCProfile::FromColorSpace(multi_tr_fn.GetColorSpace()); + EXPECT_TRUE(profile.IsValid()); + EXPECT_NE(profile, multi_tr_fn); +} + +TEST(ICCProfile, ParametricVersusExactOvershoot) { + // This ICC profile has a transfer function with T(1) that is greater than 1 + // in the approximation, but is still close enough to be considered accurate. + ICCProfile overshoot = ICCProfileForTestingOvershoot(); + EXPECT_TRUE(overshoot.IsColorSpaceAccurate()); + + ICCProfile profile; + profile = ICCProfile::FromColorSpace(overshoot.GetColorSpace()); + EXPECT_TRUE(profile.IsValid()); + EXPECT_NE(profile, overshoot); +} + +TEST(ICCProfile, ParametricVersusExactAdobe) { + // This ICC profile is precisely represented by the parametric color space. + ICCProfile accurate = ICCProfileForTestingAdobeRGB(); + EXPECT_TRUE(accurate.IsColorSpaceAccurate()); + + ICCProfile profile; + profile = ICCProfile::FromColorSpace(accurate.GetColorSpace()); + EXPECT_TRUE(profile.IsValid()); + EXPECT_NE(profile, accurate); +} + +TEST(ICCProfile, ParametricVersusExactA2B) { + // This ICC profile has only an A2B representation. We cannot transform to + // A2B only ICC profiles, so this should be marked as invalid. + ICCProfile a2b = ICCProfileForTestingA2BOnly(); + EXPECT_FALSE(a2b.GetColorSpace().IsValid()); + + // Even though it is invalid, it should not be equal to the empty constructor + EXPECT_NE(a2b, gfx::ICCProfile()); +} + +TEST(ICCProfile, GarbageData) { + std::vector bad_data(10 * 1024); + const char* bad_data_string = "deadbeef"; + for (size_t i = 0; i < bad_data.size(); ++i) + bad_data[i] = bad_data_string[i % 8]; + ICCProfile garbage_profile = + ICCProfile::FromData(bad_data.data(), bad_data.size()); + EXPECT_FALSE(garbage_profile.IsValid()); + EXPECT_FALSE(garbage_profile.GetColorSpace().IsValid()); + + ICCProfile default_ctor_profile; + EXPECT_FALSE(default_ctor_profile.IsValid()); + EXPECT_FALSE(default_ctor_profile.GetColorSpace().IsValid()); +} + +TEST(ICCProfile, GenericRGB) { + ColorSpace icc_profile = ICCProfileForTestingGenericRGB().GetColorSpace(); + ColorSpace color_space(ColorSpace::PrimaryID::APPLE_GENERIC_RGB, + ColorSpace::TransferID::GAMMA18); + + skia::Matrix44 icc_profile_matrix; + skia::Matrix44 color_space_matrix; + + icc_profile.GetPrimaryMatrix(&icc_profile_matrix); + color_space.GetPrimaryMatrix(&color_space_matrix); + + skia::Matrix44 eye; + icc_profile_matrix.invert(&eye); + eye.postConcat(color_space_matrix); + EXPECT_TRUE(SkMatrixIsApproximatelyIdentity(eye)); +} + +} // namespace gfx diff --git a/icon_util.cc b/icon_util.cc new file mode 100644 index 000000000000..7da3e2bc0798 --- /dev/null +++ b/icon_util.cc @@ -0,0 +1,703 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/icon_util.h" + +#include "base/check_op.h" +#include "base/cxx17_backports.h" +#include "base/files/file_util.h" +#include "base/files/important_file_writer.h" +#include "base/notreached.h" +#include "base/trace_event/trace_event.h" +#include "base/win/resource_util.h" +#include "base/win/scoped_gdi_object.h" +#include "base/win/scoped_handle.h" +#include "base/win/scoped_hdc.h" +#include "skia/ext/image_operations.h" +#include "skia/ext/skia_utils_win.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/image/image.h" +#include "ui/gfx/image/image_family.h" +#include "ui/gfx/skbitmap_operations.h" + +namespace { + +// Used for indicating that the .ico contains an icon (rather than a cursor) +// image. This value is set in the |idType| field of the ICONDIR structure. +const int kResourceTypeIcon = 1; + +struct ScopedICONINFO : ICONINFO { + ScopedICONINFO() { + hbmColor = NULL; + hbmMask = NULL; + } + + ~ScopedICONINFO() { + if (hbmColor) + ::DeleteObject(hbmColor); + if (hbmMask) + ::DeleteObject(hbmMask); + } +}; + +// Creates a new ImageFamily, |resized_image_family|, based on the images in +// |image_family|, but containing images of specific dimensions desirable for +// Windows icons. For each desired image dimension, it chooses the most +// appropriate image for that size, and resizes it to the desired size. +// Returns true on success, false on failure. Failure can occur if +// |image_family| is empty, all images in the family have size 0x0, or an image +// has no allocated pixel data. +// |resized_image_family| must be empty. +bool BuildResizedImageFamily(const gfx::ImageFamily& image_family, + gfx::ImageFamily* resized_image_family) { + DCHECK(resized_image_family); + DCHECK(resized_image_family->empty()); + + // Determine whether there is an image bigger than 48x48 (kMediumIconSize). + const gfx::Image* biggest = + image_family.GetBest(IconUtil::kLargeIconSize, IconUtil::kLargeIconSize); + if (!biggest || biggest->IsEmpty()) { + // Either |image_family| is empty, or all images have size 0x0. + return false; + } + + bool has_bigger_than_medium = biggest->Width() > IconUtil::kMediumIconSize || + biggest->Height() > IconUtil::kMediumIconSize; + + for (size_t i = 0; i < IconUtil::kNumIconDimensions; ++i) { + int dimension = IconUtil::kIconDimensions[i]; + // Windows' "Large icons" view displays icons at full size only if there is + // a 256x256 (kLargeIconSize) image in the .ico file. Otherwise, it shrinks + // icons to 48x48 (kMediumIconSize). Therefore, if there is no source icon + // larger than 48x48, do not create any images larger than 48x48. + // kIconDimensions is sorted in ascending order, so it is safe to break + // here. + if (!has_bigger_than_medium && dimension > IconUtil::kMediumIconSize) + break; + + gfx::Image resized = image_family.CreateExact(dimension, dimension); + if (resized.IsEmpty()) { + // An error occurred in CreateExact (typically because the image had the + // wrong pixel format). + return false; + } + + resized_image_family->Add(resized); + } + return true; +} + +// Creates a set of bitmaps from an image family. +// All images smaller than 256x256 are converted to SkBitmaps, and inserted into +// |bitmaps| in order of aspect ratio (thinnest to widest), and then ascending +// size order. If an image of exactly 256x256 is specified, it is converted into +// PNG format and stored in |png_bytes|. Images with width or height larger than +// 256 are ignored. +// |bitmaps| must be an empty vector, and not NULL. +// Returns true on success, false on failure. This fails if any image in +// |image_family| is not a 32-bit ARGB image, or is otherwise invalid. +void ConvertImageFamilyToBitmaps( + const gfx::ImageFamily& image_family, + std::vector* bitmaps, + scoped_refptr* png_bytes) { + DCHECK(bitmaps != NULL); + DCHECK(bitmaps->empty()); + + for (gfx::ImageFamily::const_iterator it = image_family.begin(); + it != image_family.end(); ++it) { + const gfx::Image& image = *it; + + // All images should have one of the kIconDimensions sizes. + DCHECK_GT(image.Width(), 0); + DCHECK_LE(image.Width(), IconUtil::kLargeIconSize); + DCHECK_GT(image.Height(), 0); + DCHECK_LE(image.Height(), IconUtil::kLargeIconSize); + + SkBitmap bitmap = image.AsBitmap(); + CHECK_EQ(bitmap.colorType(), kN32_SkColorType); + CHECK(!bitmap.isNull()); + + // Special case: Icons exactly 256x256 are stored in PNG format. + if (image.Width() == IconUtil::kLargeIconSize && + image.Height() == IconUtil::kLargeIconSize) { + *png_bytes = image.As1xPNGBytes(); + } else { + bitmaps->push_back(bitmap); + } + } +} + +} // namespace + +// The icon images appear in the icon file in same order in which their +// corresponding dimensions appear in this array, so it is important to keep +// this array sorted. Also note that the maximum icon image size we can handle +// is 256 by 256. See: +// http://msdn.microsoft.com/en-us/library/windows/desktop/aa511280.aspx#size +const int IconUtil::kIconDimensions[] = { + 8, // Recommended by the MSDN as a nice to have icon size. + 10, // Used by the Shell (e.g. for shortcuts). + 14, // Recommended by the MSDN as a nice to have icon size. + 16, // Toolbar, Application and Shell icon sizes. + 22, // Recommended by the MSDN as a nice to have icon size. + 24, // Used by the Shell (e.g. for shortcuts). + 32, // Toolbar, Dialog and Wizard icon size. + 40, // Quick Launch. + 48, // Alt+Tab icon size. + 64, // Recommended by the MSDN as a nice to have icon size. + 96, // Recommended by the MSDN as a nice to have icon size. + 128, // Used by the Shell (e.g. for shortcuts). + 256 // Used by Vista onwards for large icons. +}; + +const size_t IconUtil::kNumIconDimensions = base::size(kIconDimensions); +const size_t IconUtil::kNumIconDimensionsUpToMediumSize = 9; + +base::win::ScopedHICON IconUtil::CreateHICONFromSkBitmap( + const SkBitmap& bitmap) { + // Only 32 bit ARGB bitmaps are supported. We also try to perform as many + // validations as we can on the bitmap. + if ((bitmap.colorType() != kN32_SkColorType) || + (bitmap.width() <= 0) || (bitmap.height() <= 0) || + (bitmap.getPixels() == NULL)) + return base::win::ScopedHICON(); + + // We start by creating a DIB which we'll use later on in order to create + // the HICON. We use BITMAPV5HEADER since the bitmap we are about to convert + // may contain an alpha channel and the V5 header allows us to specify the + // alpha mask for the DIB. + BITMAPV5HEADER bitmap_header; + InitializeBitmapHeader(&bitmap_header, bitmap.width(), bitmap.height()); + + void* bits = NULL; + HBITMAP dib; + + { + base::win::ScopedGetDC hdc(NULL); + dib = ::CreateDIBSection(hdc, reinterpret_cast(&bitmap_header), + DIB_RGB_COLORS, &bits, NULL, 0); + } + if (!dib || !bits) + return base::win::ScopedHICON(); + + memcpy(bits, bitmap.getPixels(), bitmap.width() * bitmap.height() * 4); + + // Icons are generally created using an AND and XOR masks where the AND + // specifies boolean transparency (the pixel is either opaque or + // transparent) and the XOR mask contains the actual image pixels. If the XOR + // mask bitmap has an alpha channel, the AND monochrome bitmap won't + // actually be used for computing the pixel transparency. Even though all our + // bitmap has an alpha channel, Windows might not agree when all alpha values + // are zero. So the monochrome bitmap is created with all pixels transparent + // for this case. Otherwise, it is created with all pixels opaque. + bool bitmap_has_alpha_channel = + PixelsHaveAlpha(static_cast(bitmap.getPixels()), + bitmap.width() * bitmap.height()); + + std::unique_ptr mask_bits; + if (!bitmap_has_alpha_channel) { + // Bytes per line with paddings to make it word alignment. + size_t bytes_per_line = (bitmap.width() + 0xF) / 16 * 2; + size_t mask_bits_size = bytes_per_line * bitmap.height(); + + mask_bits = std::make_unique(mask_bits_size); + DCHECK(mask_bits.get()); + + // Make all pixels transparent. + memset(mask_bits.get(), 0xFF, mask_bits_size); + } + + HBITMAP mono_bitmap = ::CreateBitmap(bitmap.width(), bitmap.height(), 1, 1, + reinterpret_cast(mask_bits.get())); + DCHECK(mono_bitmap); + + ICONINFO icon_info; + icon_info.fIcon = TRUE; + icon_info.xHotspot = 0; + icon_info.yHotspot = 0; + icon_info.hbmMask = mono_bitmap; + icon_info.hbmColor = dib; + base::win::ScopedHICON icon(CreateIconIndirect(&icon_info)); + ::DeleteObject(dib); + ::DeleteObject(mono_bitmap); + return icon; +} + +SkBitmap IconUtil::CreateSkBitmapFromHICON(HICON icon, const gfx::Size& s) { + // We start with validating parameters. + if (!icon || s.IsEmpty()) + return SkBitmap(); + ScopedICONINFO icon_info; + if (!::GetIconInfo(icon, &icon_info)) + return SkBitmap(); + if (!icon_info.fIcon) + return SkBitmap(); + return CreateSkBitmapFromHICONHelper(icon, s); +} + +// static +std::unique_ptr IconUtil::CreateImageFamilyFromIconResource( + HMODULE module, + int resource_id) { + // Read the resource directly so we can get the icon image sizes. This data + // will also be used to directly get the PNG bytes for large images. + void* icon_dir_data = NULL; + size_t icon_dir_size = 0; + if (!base::win::GetResourceFromModule(module, resource_id, RT_GROUP_ICON, + &icon_dir_data, &icon_dir_size)) { + return nullptr; + } + DCHECK(icon_dir_data); + DCHECK_GE(icon_dir_size, sizeof(GRPICONDIR)); + + const GRPICONDIR* icon_dir = + reinterpret_cast(icon_dir_data); + std::unique_ptr result(new gfx::ImageFamily); + for (size_t i = 0; i < icon_dir->idCount; ++i) { + const GRPICONDIRENTRY* entry = &icon_dir->idEntries[i]; + if (entry->bWidth != 0 || entry->bHeight != 0) { + // Ignore the low-bit-depth versions of the icon. + if (entry->wBitCount != 32) + continue; + + // For everything except the Vista+ 256x256 icons, use |LoadImage()|. + base::win::ScopedHICON icon_handle(static_cast(LoadImage( + module, MAKEINTRESOURCE(resource_id), IMAGE_ICON, entry->bWidth, + entry->bHeight, LR_DEFAULTCOLOR | LR_DEFAULTSIZE))); + result->Add(gfx::Image::CreateFrom1xBitmap( + IconUtil::CreateSkBitmapFromHICON(icon_handle.get()))); + } else { + // 256x256 icons are stored with width and height set to 0. + // See: http://en.wikipedia.org/wiki/ICO_(file_format) + void* png_data = NULL; + size_t png_size = 0; + if (!base::win::GetResourceFromModule(module, entry->nID, RT_ICON, + &png_data, &png_size)) { + return nullptr; + } + DCHECK(png_data); + DCHECK_EQ(png_size, entry->dwBytesInRes); + + result->Add(gfx::Image::CreateFrom1xPNGBytes( + new base::RefCountedStaticMemory(png_data, png_size))); + } + } + return result; +} + +SkBitmap IconUtil::CreateSkBitmapFromHICON(HICON icon) { + // We start with validating parameters. + if (!icon) + return SkBitmap(); + + ScopedICONINFO icon_info; + BITMAP bitmap_info = { 0 }; + + if (!::GetIconInfo(icon, &icon_info)) + return SkBitmap(); + + if (!::GetObject(icon_info.hbmMask, sizeof(bitmap_info), &bitmap_info)) + return SkBitmap(); + + // For non-color cursors, the mask contains both an AND and an XOR mask and + // the height includes both. Thus, the mask width is the same as image width, + // but we need to divide mask height by 2 to get the image height. + const int height = bitmap_info.bmHeight / (icon_info.hbmColor ? 1 : 2); + gfx::Size icon_size(bitmap_info.bmWidth, height); + return CreateSkBitmapFromHICONHelper(icon, icon_size); +} + +base::win::ScopedHICON IconUtil::CreateCursorFromSkBitmap( + const SkBitmap& bitmap, + const gfx::Point& hotspot) { + if (bitmap.empty()) + return base::win::ScopedHICON(); + + // Only 32 bit ARGB bitmaps are supported. + if (bitmap.colorType() != kN32_SkColorType) { + NOTIMPLEMENTED() << " unsupported color type: " << bitmap.colorType(); + return base::win::ScopedHICON(); + } + + BITMAPINFO icon_bitmap_info = {}; + skia::CreateBitmapHeaderForN32SkBitmap( + bitmap, reinterpret_cast(&icon_bitmap_info)); + + base::win::ScopedGetDC dc(NULL); + base::win::ScopedCreateDC working_dc(CreateCompatibleDC(dc)); + base::win::ScopedGDIObject bitmap_handle( + CreateDIBSection(dc, + &icon_bitmap_info, + DIB_RGB_COLORS, + 0, + 0, + 0)); + SetDIBits(0, bitmap_handle.get(), 0, bitmap.height(), bitmap.getPixels(), + &icon_bitmap_info, DIB_RGB_COLORS); + + HBITMAP old_bitmap = reinterpret_cast( + SelectObject(working_dc.Get(), bitmap_handle.get())); + SetBkMode(working_dc.Get(), TRANSPARENT); + SelectObject(working_dc.Get(), old_bitmap); + + base::win::ScopedGDIObject mask( + CreateBitmap(bitmap.width(), bitmap.height(), 1, 1, NULL)); + ICONINFO ii = {0}; + ii.fIcon = FALSE; + ii.xHotspot = hotspot.x(); + ii.yHotspot = hotspot.y(); + ii.hbmMask = mask.get(); + ii.hbmColor = bitmap_handle.get(); + + return base::win::ScopedHICON(CreateIconIndirect(&ii)); +} + +gfx::Point IconUtil::GetHotSpotFromHICON(HICON icon) { + ScopedICONINFO icon_info; + gfx::Point hotspot; + if (::GetIconInfo(icon, &icon_info)) + hotspot = gfx::Point(icon_info.xHotspot, icon_info.yHotspot); + + return hotspot; +} + +// static +SkBitmap IconUtil::CreateSkBitmapFromHICONHelper(HICON icon, + const gfx::Size& s) { + DCHECK(icon); + DCHECK(!s.IsEmpty()); + + // Allocating memory for the SkBitmap object. We are going to create an ARGB + // bitmap so we should set the configuration appropriately. + SkBitmap bitmap; + bitmap.allocN32Pixels(s.width(), s.height()); + bitmap.eraseARGB(0, 0, 0, 0); + + // Now we should create a DIB so that we can use ::DrawIconEx in order to + // obtain the icon's image. + BITMAPV5HEADER h; + InitializeBitmapHeader(&h, s.width(), s.height()); + HDC hdc = ::GetDC(NULL); + uint32_t* bits; + HBITMAP dib = ::CreateDIBSection(hdc, reinterpret_cast(&h), + DIB_RGB_COLORS, reinterpret_cast(&bits), NULL, 0); + DCHECK(dib); + HDC dib_dc = CreateCompatibleDC(hdc); + ::ReleaseDC(NULL, hdc); + DCHECK(dib_dc); + HGDIOBJ old_obj = ::SelectObject(dib_dc, dib); + + // Windows icons are defined using two different masks. The XOR mask, which + // represents the icon image and an AND mask which is a monochrome bitmap + // which indicates the transparency of each pixel. + // + // To make things more complex, the icon image itself can be an ARGB bitmap + // and therefore contain an alpha channel which specifies the transparency + // for each pixel. Unfortunately, there is no easy way to determine whether + // or not a bitmap has an alpha channel and therefore constructing the bitmap + // for the icon is nothing but straightforward. + // + // The idea is to read the AND mask but use it only if we know for sure that + // the icon image does not have an alpha channel. The only way to tell if the + // bitmap has an alpha channel is by looking through the pixels and checking + // whether there are non-zero alpha bytes. + // + // We start by drawing the AND mask into our DIB. + size_t num_pixels = s.GetArea(); + memset(bits, 0, num_pixels * 4); + ::DrawIconEx(dib_dc, 0, 0, icon, s.width(), s.height(), 0, NULL, DI_MASK); + + // Capture boolean opacity. We may not use it if we find out the bitmap has + // an alpha channel. + std::unique_ptr opaque(new bool[num_pixels]); + for (size_t i = 0; i < num_pixels; ++i) + opaque[i] = !bits[i]; + + // Then draw the image itself which is really the XOR mask. + memset(bits, 0, num_pixels * 4); + ::DrawIconEx(dib_dc, 0, 0, icon, s.width(), s.height(), 0, NULL, DI_NORMAL); + memcpy(bitmap.getPixels(), static_cast(bits), num_pixels * 4); + + // Finding out whether the bitmap has an alpha channel. + bool bitmap_has_alpha_channel = PixelsHaveAlpha( + static_cast(bitmap.getPixels()), num_pixels); + + // If the bitmap does not have an alpha channel, we need to build it using + // the previously captured AND mask. Otherwise, we are done. + if (!bitmap_has_alpha_channel) { + uint32_t* p = static_cast(bitmap.getPixels()); + for (size_t i = 0; i < num_pixels; ++p, ++i) { + DCHECK_EQ((*p & 0xff000000), 0u); + if (opaque[i]) + *p |= 0xff000000; + else + *p &= 0x00ffffff; + } + } + + ::SelectObject(dib_dc, old_obj); + ::DeleteObject(dib); + ::DeleteDC(dib_dc); + + return bitmap; +} + +// static +bool IconUtil::CreateIconFileFromImageFamily( + const gfx::ImageFamily& image_family, + const base::FilePath& icon_path, + WriteType write_type) { + // Creating a set of bitmaps corresponding to the icon images we'll end up + // storing in the icon file. Each bitmap is created by resizing the most + // appropriate image from |image_family| to the desired size. + gfx::ImageFamily resized_image_family; + if (!BuildResizedImageFamily(image_family, &resized_image_family)) + return false; + + std::vector bitmaps; + scoped_refptr png_bytes; + ConvertImageFamilyToBitmaps(resized_image_family, &bitmaps, &png_bytes); + + // Guaranteed true because BuildResizedImageFamily will provide at least one + // image < 256x256. + DCHECK(!bitmaps.empty()); + // ICONDIR's idCount is a WORD, so check for overflow. + DCHECK_LE(bitmaps.size(), + static_cast(USHRT_MAX - (png_bytes.get() ? 1 : 0))); + WORD bitmap_count = + static_cast(bitmaps.size()); // Not including PNG image. + // Including PNG image, if any. + WORD image_count = bitmap_count + (png_bytes.get() ? 1 : 0); + + // Computing the total size of the buffer we need in order to store the + // images in the desired icon format. + size_t buffer_size = ComputeIconFileBufferSize(bitmaps); + // Account for the bytes needed for the PNG entry. + if (png_bytes.get()) + buffer_size += sizeof(ICONDIRENTRY) + png_bytes->size(); + + // Setting the information in the structures residing within the buffer. + // First, we set the information which doesn't require iterating through the + // bitmap set and then we set the bitmap specific structures. In the latter + // step we also copy the actual bits. + std::vector buffer(buffer_size); + ICONDIR* icon_dir = reinterpret_cast(&buffer[0]); + icon_dir->idType = kResourceTypeIcon; + icon_dir->idCount = image_count; + // - 1 because there is already one ICONDIRENTRY in ICONDIR. + DWORD icon_dir_count = image_count - 1; + + DWORD offset = sizeof(ICONDIR) + (sizeof(ICONDIRENTRY) * icon_dir_count); + for (size_t i = 0; i < bitmap_count; i++) { + ICONIMAGE* image = reinterpret_cast(&buffer[offset]); + DCHECK_LT(offset, buffer_size); + size_t icon_image_size = 0; + SetSingleIconImageInformation(bitmaps[i], i, icon_dir, image, offset, + &icon_image_size); + DCHECK_GT(icon_image_size, 0U); + offset += icon_image_size; + } + + // Add the PNG entry, if necessary. + if (png_bytes.get()) { + ICONDIRENTRY* entry = &icon_dir->idEntries[bitmap_count]; + entry->bWidth = 0; + entry->bHeight = 0; + entry->wPlanes = 1; + entry->wBitCount = 32; + entry->dwBytesInRes = static_cast(png_bytes->size()); + entry->dwImageOffset = offset; + memcpy(&buffer[offset], png_bytes->front(), png_bytes->size()); + offset += png_bytes->size(); + } + + DCHECK_EQ(offset, buffer_size); + + if (write_type == NORMAL_WRITE) { + if (base::WriteFile(icon_path, buffer)) + return true; + bool delete_success = base::DeleteFile(icon_path); + DCHECK(delete_success); + return false; + } + + std::string data(buffer.begin(), buffer.end()); + return base::ImportantFileWriter::WriteFileAtomically(icon_path, data); +} + +bool IconUtil::PixelsHaveAlpha(const uint32_t* pixels, size_t num_pixels) { + for (const uint32_t* end = pixels + num_pixels; pixels != end; ++pixels) { + if ((*pixels & 0xff000000) != 0) + return true; + } + + return false; +} + +void IconUtil::InitializeBitmapHeader(BITMAPV5HEADER* header, int width, + int height) { + DCHECK(header); + memset(header, 0, sizeof(BITMAPV5HEADER)); + header->bV5Size = sizeof(BITMAPV5HEADER); + + // Note that icons are created using top-down DIBs so we must negate the + // value used for the icon's height. + header->bV5Width = width; + header->bV5Height = -height; + header->bV5Planes = 1; + header->bV5Compression = BI_RGB; + + // Initializing the bitmap format to 32 bit ARGB. + header->bV5BitCount = 32; + header->bV5RedMask = 0x00FF0000; + header->bV5GreenMask = 0x0000FF00; + header->bV5BlueMask = 0x000000FF; + header->bV5AlphaMask = 0xFF000000; + + // Use the system color space. The default value is LCS_CALIBRATED_RGB, which + // causes us to crash if we don't specify the approprite gammas, etc. See + // and + // . + header->bV5CSType = LCS_WINDOWS_COLOR_SPACE; + + // Use a valid value for bV5Intent as 0 is not a valid one. + // + header->bV5Intent = LCS_GM_IMAGES; +} + +void IconUtil::SetSingleIconImageInformation(const SkBitmap& bitmap, + size_t index, + ICONDIR* icon_dir, + ICONIMAGE* icon_image, + DWORD image_offset, + size_t* image_byte_count) { + DCHECK(icon_dir != NULL); + DCHECK(icon_image != NULL); + DCHECK_GT(image_offset, 0U); + DCHECK(image_byte_count != NULL); + DCHECK_LT(bitmap.width(), kLargeIconSize); + DCHECK_LT(bitmap.height(), kLargeIconSize); + + // We start by computing certain image values we'll use later on. + size_t xor_mask_size; + DWORD bytes_in_resource; + ComputeBitmapSizeComponents(bitmap, + &xor_mask_size, + &bytes_in_resource); + + icon_dir->idEntries[index].bWidth = static_cast(bitmap.width()); + icon_dir->idEntries[index].bHeight = static_cast(bitmap.height()); + icon_dir->idEntries[index].wPlanes = 1; + icon_dir->idEntries[index].wBitCount = 32; + icon_dir->idEntries[index].dwBytesInRes = bytes_in_resource; + icon_dir->idEntries[index].dwImageOffset = image_offset; + icon_image->icHeader.biSize = sizeof(BITMAPINFOHEADER); + + // The width field in the BITMAPINFOHEADER structure accounts for the height + // of both the AND mask and the XOR mask so we need to multiply the bitmap's + // height by 2. The same does NOT apply to the width field. + icon_image->icHeader.biHeight = bitmap.height() * 2; + icon_image->icHeader.biWidth = bitmap.width(); + icon_image->icHeader.biPlanes = 1; + icon_image->icHeader.biBitCount = 32; + + // We use a helper function for copying to actual bits from the SkBitmap + // object into the appropriate space in the buffer. We use a helper function + // (rather than just copying the bits) because there is no way to specify the + // orientation (bottom-up vs. top-down) of a bitmap residing in a .ico file. + // Thus, if we just copy the bits, we'll end up with a bottom up bitmap in + // the .ico file which will result in the icon being displayed upside down. + // The helper function copies the image into the buffer one scanline at a + // time. + // + // Note that we don't need to initialize the AND mask since the memory + // allocated for the icon data buffer was initialized to zero. The icon we + // create will therefore use an AND mask containing only zeros, which is OK + // because the underlying image has an alpha channel. An AND mask containing + // only zeros essentially means we'll initially treat all the pixels as + // opaque. + unsigned char* image_addr = reinterpret_cast(icon_image); + unsigned char* xor_mask_addr = image_addr + sizeof(BITMAPINFOHEADER); + + // Make sure pixels are not premultiplied by alpha. + SkBitmap unpremul_bitmap = SkBitmapOperations::UnPreMultiply(bitmap); + CopySkBitmapBitsIntoIconBuffer(unpremul_bitmap, xor_mask_addr, xor_mask_size); + + *image_byte_count = bytes_in_resource; +} + +void IconUtil::CopySkBitmapBitsIntoIconBuffer(const SkBitmap& bitmap, + unsigned char* buffer, + size_t buffer_size) { + unsigned char* bitmap_ptr = static_cast(bitmap.getPixels()); + size_t bitmap_size = bitmap.height() * bitmap.width() * 4; + DCHECK_EQ(buffer_size, bitmap_size); + for (size_t i = 0; i < bitmap_size; i += bitmap.width() * 4) { + memcpy(buffer + bitmap_size - bitmap.width() * 4 - i, + bitmap_ptr + i, + bitmap.width() * 4); + } +} + +size_t IconUtil::ComputeIconFileBufferSize(const std::vector& set) { + DCHECK(!set.empty()); + + // We start by counting the bytes for the structures that don't depend on the + // number of icon images. Note that sizeof(ICONDIR) already accounts for a + // single ICONDIRENTRY structure, which is why we subtract one from the + // number of bitmaps. + size_t total_buffer_size = sizeof(ICONDIR); + size_t bitmap_count = set.size(); + total_buffer_size += sizeof(ICONDIRENTRY) * (bitmap_count - 1); + // May not have all icon sizes, but must have at least up to medium icon size. + DCHECK_GE(bitmap_count, kNumIconDimensionsUpToMediumSize); + + // Add the bitmap specific structure sizes. + for (size_t i = 0; i < bitmap_count; i++) { + size_t xor_mask_size; + DWORD bytes_in_resource; + ComputeBitmapSizeComponents(set[i], + &xor_mask_size, + &bytes_in_resource); + total_buffer_size += bytes_in_resource; + } + return total_buffer_size; +} + +void IconUtil::ComputeBitmapSizeComponents(const SkBitmap& bitmap, + size_t* xor_mask_size, + DWORD* bytes_in_resource) { + // The XOR mask size is easy to calculate since we only deal with 32bpp + // images. + *xor_mask_size = bitmap.width() * bitmap.height() * 4; + + // Computing the AND mask is a little trickier since it is a monochrome + // bitmap (regardless of the number of bits per pixels used in the XOR mask). + // There are two things we must make sure we do when computing the AND mask + // size: + // + // 1. Make sure the right number of bytes is allocated for each AND mask + // scan line in case the number of pixels in the image is not divisible by + // 8. For example, in a 15X15 image, 15 / 8 is one byte short of + // containing the number of bits we need in order to describe a single + // image scan line so we need to add a byte. Thus, we need 2 bytes instead + // of 1 for each scan line. + // + // 2. Make sure each scan line in the AND mask is 4 byte aligned (so that the + // total icon image has a 4 byte alignment). In the 15X15 image example + // above, we can not use 2 bytes so we increase it to the next multiple of + // 4 which is 4. + // + // Once we compute the size for a singe AND mask scan line, we multiply that + // number by the image height in order to get the total number of bytes for + // the AND mask. Thus, for a 15X15 image, we need 15 * 4 which is 60 bytes + // for the monochrome bitmap representing the AND mask. + size_t and_line_length = (bitmap.width() + 7) >> 3; + and_line_length = (and_line_length + 3) & ~3; + size_t and_mask_size = and_line_length * bitmap.height(); + size_t masks_size = *xor_mask_size + and_mask_size; + *bytes_in_resource = + static_cast(masks_size + sizeof(BITMAPINFOHEADER)); +} diff --git a/icon_util.h b/icon_util.h new file mode 100644 index 000000000000..22b2e725e0e4 --- /dev/null +++ b/icon_util.h @@ -0,0 +1,272 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_ICON_UTIL_H_ +#define UI_GFX_ICON_UTIL_H_ + +#include +#include +#include +#include +#include + +#include "base/macros.h" +#include "base/win/scoped_gdi_object.h" +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/gfx_export.h" + +namespace base { +class FilePath; +} + +namespace gfx { +class ImageFamily; +class Size; +} +class SkBitmap; + +/////////////////////////////////////////////////////////////////////////////// +// +// The IconUtil class contains helper functions for manipulating Windows icons. +// The class interface contains methods for converting an HICON handle into an +// SkBitmap object and vice versa. The class can also create a .ico file given +// a PNG image contained in an SkBitmap object. The following code snippet +// shows an example usage of IconUtil::CreateHICONFromSkBitmap(): +// +// SkBitmap bitmap; +// +// // Fill |bitmap| with valid data +// bitmap.setConfig(...); +// bitmap.allocPixels(); +// +// ... +// +// // Convert the bitmap into a Windows HICON +// base::win::ScopedHICON icon(IconUtil::CreateHICONFromSkBitmap(bitmap)); +// if (!icon.is_valid()) { +// // Handle error +// ... +// } +// +// // Use the icon with a WM_SETICON message +// ::SendMessage(hwnd, WM_SETICON, static_cast(ICON_BIG), +// reinterpret_cast(icon.get())); +// +/////////////////////////////////////////////////////////////////////////////// +class GFX_EXPORT IconUtil { + public: + // ATOMIC_WRITE ensures that a partially written icon won't be created even if + // Chrome crashes part way through, but ATOMIC_WRITE is more expensive than + // NORMAL_WRITE. See CreateIconFileFromImageFamily. ATOMIC_WRITE is the + // default for historical reasons. + enum WriteType { ATOMIC_WRITE, NORMAL_WRITE }; + // The size of the large icon entries in .ico files on Windows Vista+. + enum { kLargeIconSize = 256 }; + // The size of icons in the medium icons view on Windows Vista+. This is the + // maximum size Windows will display an icon that does not have a 256x256 + // image, even at the large or extra large icons views. + enum { kMediumIconSize = 48 }; + + // The dimensions for icon images in Windows icon files. All sizes are square; + // that is, the value 48 means a 48x48 pixel image. Sizes are listed in + // ascending order. + static const int kIconDimensions[]; + + // The number of elements in kIconDimensions. + static const size_t kNumIconDimensions; + // The number of elements in kIconDimensions <= kMediumIconSize. + static const size_t kNumIconDimensionsUpToMediumSize; + + // Prevent clients from instantiating objects of that class. + IconUtil() = delete; + IconUtil(const IconUtil&) = delete; + IconUtil& operator=(const IconUtil&) = delete; + + // Given an SkBitmap object, the function converts the bitmap to a Windows + // icon and returns the corresponding HICON handle. If the function cannot + // convert the bitmap, NULL is returned. + // + // The client is responsible for destroying the icon when it is no longer + // needed by calling ::DestroyIcon(). + static base::win::ScopedHICON CreateHICONFromSkBitmap(const SkBitmap& bitmap); + + // Given a valid HICON handle representing an icon, this function converts + // the icon into an SkBitmap object containing an ARGB bitmap using the + // dimensions specified in |s|. |s| must specify valid dimensions (both + // width() an height() must be greater than zero). If the function cannot + // convert the icon to a bitmap (most probably due to an invalid parameter), + // the returned SkBitmap's isNull() method will return true. + static SkBitmap CreateSkBitmapFromHICON(HICON icon, const gfx::Size& s); + + // Loads an icon resource as a SkBitmap for the specified |size| from a + // loaded .dll or .exe |module|. Supports loading smaller icon sizes as well + // as the Vista+ 256x256 PNG icon size. If the icon could not be loaded or + // found, returns a NULL scoped_ptr. + static std::unique_ptr CreateImageFamilyFromIconResource( + HMODULE module, + int resource_id); + + // Given a valid HICON handle representing an icon, this function converts + // the icon into an SkBitmap object containing an ARGB bitmap using the + // dimensions of HICON. If the function cannot convert the icon to a bitmap + // (most probably due to an invalid parameter), the returned SkBitmap's + // isNull() method will return true. + static SkBitmap CreateSkBitmapFromHICON(HICON icon); + + // Creates Windows .ico file at |icon_path|. The icon file is created with + // multiple BMP representations at varying predefined dimensions (by resizing + // an appropriately sized image from |image_family|) because Windows uses + // different image sizes when loading icons, depending on where the icon is + // drawn (ALT+TAB window, desktop shortcut, Quick Launch, etc.). + // + // If |image_family| contains an image larger than 48x48, the resulting icon + // will contain all sizes up to 256x256. The 256x256 image will be stored in + // PNG format inside the .ico file. If not, the resulting icon will contain + // all sizes up to 48x48. + // + // The function returns true on success and false otherwise. Returns false if + // |image_family| is empty. + static bool CreateIconFileFromImageFamily( + const gfx::ImageFamily& image_family, + const base::FilePath& icon_path, + WriteType write_type = ATOMIC_WRITE); + + // Creates a cursor of the specified size from the SkBitmap passed in. + // Returns the cursor on success or NULL on failure. + static base::win::ScopedHICON CreateCursorFromSkBitmap( + const SkBitmap& bitmap, + const gfx::Point& hotspot); + + // Given a valid HICON handle representing an icon, this function retrieves + // the hot spot of the icon. + static gfx::Point GetHotSpotFromHICON(HICON icon); + + private: + // The icon format is published in the MSDN but there is no definition of + // the icon file structures in any of the Windows header files so we need to + // define these structure within the class. We must make sure we use 2 byte + // packing so that the structures are laid out properly within the file. + // See: http://msdn.microsoft.com/en-us/library/ms997538.aspx +#pragma pack(push) +#pragma pack(2) + + // ICONDIRENTRY contains meta data for an individual icon image within a + // .ico file. + struct ICONDIRENTRY { + BYTE bWidth; + BYTE bHeight; + BYTE bColorCount; + BYTE bReserved; + WORD wPlanes; + WORD wBitCount; + DWORD dwBytesInRes; + DWORD dwImageOffset; + }; + + // ICONDIR Contains information about all the icon images contained within a + // single .ico file. + struct ICONDIR { + WORD idReserved; + WORD idType; + WORD idCount; + ICONDIRENTRY idEntries[1]; + }; + + // GRPICONDIRENTRY contains meta data for an individual icon image within a + // RT_GROUP_ICON resource in an .exe or .dll. + struct GRPICONDIRENTRY { + BYTE bWidth; + BYTE bHeight; + BYTE bColorCount; + BYTE bReserved; + WORD wPlanes; + WORD wBitCount; + DWORD dwBytesInRes; + WORD nID; + }; + + // GRPICONDIR Contains information about all the icon images contained within + // a RT_GROUP_ICON resource in an .exe or .dll. + struct GRPICONDIR { + WORD idReserved; + WORD idType; + WORD idCount; + GRPICONDIRENTRY idEntries[1]; + }; + + // Contains the actual icon image. + struct ICONIMAGE { + BITMAPINFOHEADER icHeader; + RGBQUAD icColors[1]; + BYTE icXOR[1]; + BYTE icAND[1]; + }; +#pragma pack(pop) + + friend class IconUtilTest; + + // Returns true if any pixel in the given pixels buffer has an non-zero alpha. + static bool PixelsHaveAlpha(const uint32_t* pixels, size_t num_pixels); + + // A helper function that initializes a BITMAPV5HEADER structure with a set + // of values. + static void InitializeBitmapHeader(BITMAPV5HEADER* header, int width, + int height); + + // Given a single SkBitmap object and pointers to the corresponding icon + // structures within the icon data buffer, this function sets the image + // information (dimensions, color depth, etc.) in the icon structures and + // also copies the underlying icon image into the appropriate location. + // The width and height of |bitmap| must be < 256. + // (Note that the 256x256 icon is treated specially, as a PNG, and should not + // use this method.) + // + // The function will set the data pointed to by |image_byte_count| with the + // number of image bytes written to the buffer. Note that the number of bytes + // includes only the image data written into the memory pointed to by + // |icon_image|. + static void SetSingleIconImageInformation(const SkBitmap& bitmap, + size_t index, + ICONDIR* icon_dir, + ICONIMAGE* icon_image, + DWORD image_offset, + size_t* image_byte_count); + + // Copies the bits of an SkBitmap object into a buffer holding the bits of + // the corresponding image for an icon within the .ico file. + static void CopySkBitmapBitsIntoIconBuffer(const SkBitmap& bitmap, + unsigned char* buffer, + size_t buffer_size); + + // Given a set of bitmaps with varying dimensions, this function computes + // the amount of memory needed in order to store the bitmaps as image icons + // in a .ico file. + static size_t ComputeIconFileBufferSize(const std::vector& set); + + // A helper function for computing various size components of a given bitmap. + // The different sizes can be used within the various .ico file structures. + // + // |xor_mask_size| - the size, in bytes, of the XOR mask in the ICONIMAGE + // structure. + // |and_mask_size| - the size, in bytes, of the AND mask in the ICONIMAGE + // structure. + // |bytes_in_resource| - the total number of bytes set in the ICONIMAGE + // structure. This value is equal to the sum of the + // bytes in the AND mask and the XOR mask plus the size + // of the BITMAPINFOHEADER structure. Note that since + // only 32bpp are handled by the IconUtil class, the + // icColors field in the ICONIMAGE structure is ignored + // and is not accounted for when computing the + // different size components. + static void ComputeBitmapSizeComponents(const SkBitmap& bitmap, + size_t* xor_mask_size, + DWORD* bytes_in_resource); + + // A helper function of CreateSkBitmapFromHICON. + static SkBitmap CreateSkBitmapFromHICONHelper(HICON icon, + const gfx::Size& s); +}; + +#endif // UI_GFX_ICON_UTIL_H_ diff --git a/icon_util_unittest.cc b/icon_util_unittest.cc new file mode 100644 index 000000000000..21c228ed9faf --- /dev/null +++ b/icon_util_unittest.cc @@ -0,0 +1,431 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/icon_util.h" + +#include + +#include +#include + +#include "base/files/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/path_service.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/icon_util_unittests_resource.h" +#include "ui/gfx/image/image.h" +#include "ui/gfx/image/image_family.h" + +namespace { + +static const char kSmallIconName[] = "16_X_16_icon.ico"; +static const char kLargeIconName[] = "128_X_128_icon.ico"; +static const char kTempIconFilename[] = "temp_test_icon.ico"; + +} // namespace + +class IconUtilTest : public testing::Test { + public: + using ScopedHICON = base::win::ScopedHICON; + + void SetUp() override { + ASSERT_TRUE(base::PathService::Get(base::DIR_SOURCE_ROOT, &test_data_dir_)); + test_data_dir_ = test_data_dir_.Append(FILE_PATH_LITERAL("ui")) + .Append(FILE_PATH_LITERAL("gfx")) + .Append(FILE_PATH_LITERAL("test")) + .Append(FILE_PATH_LITERAL("data")) + .Append(FILE_PATH_LITERAL("icon_util")); + ASSERT_TRUE(base::PathExists(test_data_dir_)); + + ASSERT_TRUE(temp_directory_.CreateUniqueTempDir()); + } + + static const int kSmallIconWidth = 16; + static const int kSmallIconHeight = 16; + static const int kLargeIconWidth = 128; + static const int kLargeIconHeight = 128; + + // Given a file name for an .ico file and an image dimensions, this + // function loads the icon and returns an HICON handle. + ScopedHICON LoadIconFromFile(const base::FilePath& filename, + int width, + int height) { + HICON icon = static_cast(LoadImage(NULL, + filename.value().c_str(), + IMAGE_ICON, + width, + height, + LR_LOADTRANSPARENT | LR_LOADFROMFILE)); + return ScopedHICON(icon); + } + + SkBitmap CreateBlackSkBitmap(int width, int height) { + SkBitmap bitmap; + bitmap.allocN32Pixels(width, height); + // Setting the pixels to transparent-black. + memset(bitmap.getPixels(), 0, width * height * 4); + return bitmap; + } + + // Loads an .ico file from |icon_filename| and asserts that it contains all of + // the expected icon sizes up to and including |max_icon_size|, and no other + // icons. If |max_icon_size| >= 256, this tests for a 256x256 PNG icon entry. + void CheckAllIconSizes(const base::FilePath& icon_filename, + int max_icon_size); + + protected: + // The root directory for test files. This should be treated as read-only. + base::FilePath test_data_dir_; + + // Directory for creating files by this test. + base::ScopedTempDir temp_directory_; +}; + +void IconUtilTest::CheckAllIconSizes(const base::FilePath& icon_filename, + int max_icon_size) { + ASSERT_TRUE(base::PathExists(icon_filename)); + + // Determine how many icons to expect, based on |max_icon_size|. + int expected_num_icons = 0; + for (size_t i = 0; i < IconUtil::kNumIconDimensions; ++i) { + if (IconUtil::kIconDimensions[i] > max_icon_size) + break; + ++expected_num_icons; + } + + // First, use the Windows API to load the icon, a basic validity test. + EXPECT_TRUE(LoadIconFromFile(icon_filename, kSmallIconWidth, kSmallIconHeight) + .is_valid()); + + // Read the file completely into memory. + std::string icon_data; + ASSERT_TRUE(base::ReadFileToString(icon_filename, &icon_data)); + ASSERT_GE(icon_data.length(), sizeof(IconUtil::ICONDIR)); + + // Ensure that it has exactly the expected number and sizes of icons, in the + // expected order. This matches each entry of the loaded file's icon directory + // with the corresponding element of kIconDimensions. + // Also extracts the 256x256 entry as png_entry. + const IconUtil::ICONDIR* icon_dir = + reinterpret_cast(icon_data.data()); + EXPECT_EQ(expected_num_icons, icon_dir->idCount); + ASSERT_GE(IconUtil::kNumIconDimensions, icon_dir->idCount); + ASSERT_GE(icon_data.length(), + sizeof(IconUtil::ICONDIR) + + icon_dir->idCount * sizeof(IconUtil::ICONDIRENTRY)); + const IconUtil::ICONDIRENTRY* png_entry = NULL; + for (size_t i = 0; i < icon_dir->idCount; ++i) { + const IconUtil::ICONDIRENTRY* entry = &icon_dir->idEntries[i]; + // Mod 256 because as a special case in ICONDIRENTRY, the value 0 represents + // a width or height of 256. + int expected_size = IconUtil::kIconDimensions[i] % 256; + EXPECT_EQ(expected_size, static_cast(entry->bWidth)); + EXPECT_EQ(expected_size, static_cast(entry->bHeight)); + if (entry->bWidth == 0 && entry->bHeight == 0) { + EXPECT_EQ(NULL, png_entry); + png_entry = entry; + } + } + + if (max_icon_size >= 256) { + ASSERT_TRUE(png_entry); + + // Convert the PNG entry data back to a SkBitmap to ensure it's valid. + ASSERT_GE(icon_data.length(), + png_entry->dwImageOffset + png_entry->dwBytesInRes); + const unsigned char* png_bytes = reinterpret_cast( + icon_data.data() + png_entry->dwImageOffset); + gfx::Image image = gfx::Image::CreateFrom1xPNGBytes( + png_bytes, png_entry->dwBytesInRes); + SkBitmap bitmap = image.AsBitmap(); + EXPECT_EQ(256, bitmap.width()); + EXPECT_EQ(256, bitmap.height()); + } +} + +// The following test case makes sure IconUtil::SkBitmapFromHICON fails +// gracefully when called with invalid input parameters. +TEST_F(IconUtilTest, TestIconToBitmapInvalidParameters) { + base::FilePath icon_filename = test_data_dir_.AppendASCII(kSmallIconName); + gfx::Size icon_size(kSmallIconWidth, kSmallIconHeight); + ScopedHICON icon( + LoadIconFromFile(icon_filename, icon_size.width(), icon_size.height())); + ASSERT_TRUE(icon.is_valid()); + + // Invalid size parameter. + gfx::Size invalid_icon_size(kSmallIconHeight, 0); + EXPECT_TRUE(IconUtil::CreateSkBitmapFromHICON(icon.get(), invalid_icon_size) + .isNull()); + + // Invalid icon. + EXPECT_TRUE(IconUtil::CreateSkBitmapFromHICON(nullptr, icon_size).isNull()); + + // The following code should succeed. + EXPECT_FALSE( + IconUtil::CreateSkBitmapFromHICON(icon.get(), icon_size).drawsNothing()); +} + +// The following test case makes sure IconUtil::CreateHICONFromSkBitmap fails +// gracefully when called with invalid input parameters. +TEST_F(IconUtilTest, TestBitmapToIconInvalidParameters) { + ScopedHICON icon; + std::unique_ptr bitmap; + + // Wrong bitmap format. + bitmap = std::make_unique(); + ASSERT_NE(bitmap.get(), static_cast(NULL)); + bitmap->setInfo(SkImageInfo::MakeA8(kSmallIconWidth, kSmallIconHeight)); + icon = IconUtil::CreateHICONFromSkBitmap(*bitmap); + EXPECT_FALSE(icon.is_valid()); + + // Invalid bitmap size. + bitmap = std::make_unique(); + ASSERT_NE(bitmap.get(), static_cast(NULL)); + bitmap->setInfo(SkImageInfo::MakeN32Premul(0, 0)); + icon = IconUtil::CreateHICONFromSkBitmap(*bitmap); + EXPECT_FALSE(icon.is_valid()); + + // Valid bitmap configuration but no pixels allocated. + bitmap = std::make_unique(); + ASSERT_NE(bitmap.get(), static_cast(NULL)); + bitmap->setInfo(SkImageInfo::MakeN32Premul(kSmallIconWidth, + kSmallIconHeight)); + icon = IconUtil::CreateHICONFromSkBitmap(*bitmap); + EXPECT_FALSE(icon.is_valid()); +} + +// The following test case makes sure IconUtil::CreateIconFileFromImageFamily +// fails gracefully when called with invalid input parameters. +TEST_F(IconUtilTest, TestCreateIconFileInvalidParameters) { + gfx::ImageFamily image_family; + base::FilePath valid_icon_filename = + temp_directory_.GetPath().AppendASCII(kTempIconFilename); + base::FilePath invalid_icon_filename = + temp_directory_.GetPath().AppendASCII("<>?.ico"); + + // Invalid file name. + SkBitmap bitmap; + image_family.clear(); + bitmap.allocN32Pixels(1, 1); + image_family.Add(gfx::Image::CreateFrom1xBitmap(bitmap)); + EXPECT_FALSE(IconUtil::CreateIconFileFromImageFamily(image_family, + invalid_icon_filename)); + EXPECT_FALSE(base::PathExists(invalid_icon_filename)); +} + +// This test case makes sure IconUtil::CreateIconFileFromImageFamily fails if +// the image family is empty or invalid. +TEST_F(IconUtilTest, TestCreateIconFileEmptyImageFamily) { + base::FilePath icon_filename = + temp_directory_.GetPath().AppendASCII(kTempIconFilename); + + // Empty image family. + EXPECT_FALSE(IconUtil::CreateIconFileFromImageFamily(gfx::ImageFamily(), + icon_filename)); + EXPECT_FALSE(base::PathExists(icon_filename)); + + // Image family with only an empty image. + gfx::ImageFamily image_family; + image_family.Add(gfx::Image()); + EXPECT_FALSE(IconUtil::CreateIconFileFromImageFamily(image_family, + icon_filename)); + EXPECT_FALSE(base::PathExists(icon_filename)); +} + +// This test case makes sure that when we load an icon from disk and convert +// the HICON into a bitmap, the bitmap has the expected format and dimensions. +TEST_F(IconUtilTest, TestCreateSkBitmapFromHICON) { + base::FilePath small_icon_filename = + test_data_dir_.AppendASCII(kSmallIconName); + gfx::Size small_icon_size(kSmallIconWidth, kSmallIconHeight); + ScopedHICON small_icon(LoadIconFromFile( + small_icon_filename, small_icon_size.width(), small_icon_size.height())); + ASSERT_TRUE(small_icon.is_valid()); + SkBitmap bitmap = + IconUtil::CreateSkBitmapFromHICON(small_icon.get(), small_icon_size); + ASSERT_FALSE(bitmap.isNull()); + EXPECT_EQ(bitmap.width(), small_icon_size.width()); + EXPECT_EQ(bitmap.height(), small_icon_size.height()); + EXPECT_EQ(bitmap.colorType(), kN32_SkColorType); + + base::FilePath large_icon_filename = + test_data_dir_.AppendASCII(kLargeIconName); + gfx::Size large_icon_size(kLargeIconWidth, kLargeIconHeight); + ScopedHICON large_icon(LoadIconFromFile( + large_icon_filename, large_icon_size.width(), large_icon_size.height())); + ASSERT_TRUE(large_icon.is_valid()); + bitmap = IconUtil::CreateSkBitmapFromHICON(large_icon.get(), large_icon_size); + ASSERT_FALSE(bitmap.isNull()); + EXPECT_EQ(bitmap.width(), large_icon_size.width()); + EXPECT_EQ(bitmap.height(), large_icon_size.height()); + EXPECT_EQ(bitmap.colorType(), kN32_SkColorType); +} + +// This test case makes sure that when an HICON is created from an SkBitmap, +// the returned handle is valid and refers to an icon with the expected +// dimensions color depth etc. +TEST_F(IconUtilTest, TestBasicCreateHICONFromSkBitmap) { + SkBitmap bitmap = CreateBlackSkBitmap(kSmallIconWidth, kSmallIconHeight); + ScopedHICON icon(IconUtil::CreateHICONFromSkBitmap(bitmap)); + EXPECT_TRUE(icon.is_valid()); + ICONINFO icon_info; + ASSERT_TRUE(GetIconInfo(icon.get(), &icon_info)); + EXPECT_TRUE(icon_info.fIcon); + + // Now that have the icon information, we should obtain the specification of + // the icon's bitmap and make sure it matches the specification of the + // SkBitmap we started with. + // + // The bitmap handle contained in the icon information is a handle to a + // compatible bitmap so we need to call ::GetDIBits() in order to retrieve + // the bitmap's header information. + BITMAPINFO bitmap_info; + ::ZeroMemory(&bitmap_info, sizeof(BITMAPINFO)); + bitmap_info.bmiHeader.biSize = sizeof(BITMAPINFO); + HDC hdc = ::GetDC(NULL); + int result = ::GetDIBits(hdc, + icon_info.hbmColor, + 0, + kSmallIconWidth, + NULL, + &bitmap_info, + DIB_RGB_COLORS); + ASSERT_GT(result, 0); + EXPECT_EQ(bitmap_info.bmiHeader.biWidth, kSmallIconWidth); + EXPECT_EQ(bitmap_info.bmiHeader.biHeight, kSmallIconHeight); + EXPECT_EQ(bitmap_info.bmiHeader.biPlanes, 1); + EXPECT_EQ(bitmap_info.bmiHeader.biBitCount, 32); + ::ReleaseDC(NULL, hdc); +} + +// This test case makes sure that CreateIconFileFromImageFamily creates a +// valid .ico file given an ImageFamily, and appropriately creates all icon +// sizes from the given input. +TEST_F(IconUtilTest, TestCreateIconFileFromImageFamily) { + gfx::ImageFamily image_family; + base::FilePath icon_filename = + temp_directory_.GetPath().AppendASCII(kTempIconFilename); + + // Test with only a 16x16 icon. Should only scale up to 48x48. + image_family.Add(gfx::Image::CreateFrom1xBitmap( + CreateBlackSkBitmap(kSmallIconWidth, kSmallIconHeight))); + ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family, + icon_filename)); + CheckAllIconSizes(icon_filename, 48); + + // Test with a 48x48 icon. Should only scale down. + image_family.Add(gfx::Image::CreateFrom1xBitmap(CreateBlackSkBitmap(48, 48))); + ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family, + icon_filename)); + CheckAllIconSizes(icon_filename, 48); + + // Test with a 64x64 icon. Should scale up to 256x256. + image_family.Add(gfx::Image::CreateFrom1xBitmap(CreateBlackSkBitmap(64, 64))); + ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family, + icon_filename)); + CheckAllIconSizes(icon_filename, 256); + + // Test with a 256x256 icon. Should include the 256x256 in the output. + image_family.Add(gfx::Image::CreateFrom1xBitmap( + CreateBlackSkBitmap(256, 256))); + ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family, + icon_filename)); + CheckAllIconSizes(icon_filename, 256); + + // Test with a 49x49 icon. Should scale up to 256x256, but exclude the + // original 49x49 representation from the output. + image_family.clear(); + image_family.Add(gfx::Image::CreateFrom1xBitmap(CreateBlackSkBitmap(49, 49))); + ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family, + icon_filename)); + CheckAllIconSizes(icon_filename, 256); + + // Test with a non-square 16x32 icon. Should scale up to 48, but exclude the + // original 16x32 representation from the output. + image_family.clear(); + image_family.Add(gfx::Image::CreateFrom1xBitmap(CreateBlackSkBitmap(16, 32))); + ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family, + icon_filename)); + CheckAllIconSizes(icon_filename, 48); + + // Test with a non-square 32x49 icon. Should scale up to 256, but exclude the + // original 32x49 representation from the output. + image_family.Add(gfx::Image::CreateFrom1xBitmap(CreateBlackSkBitmap(32, 49))); + ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family, + icon_filename)); + CheckAllIconSizes(icon_filename, 256); + + // Test with an empty and non-empty image. + // The empty image should be ignored. + image_family.clear(); + image_family.Add(gfx::Image()); + image_family.Add(gfx::Image::CreateFrom1xBitmap(CreateBlackSkBitmap(16, 16))); + ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family, + icon_filename)); + CheckAllIconSizes(icon_filename, 48); +} + +TEST_F(IconUtilTest, TestCreateImageFamilyFromIconResource) { + HMODULE module = GetModuleHandle(NULL); + std::unique_ptr family( + IconUtil::CreateImageFamilyFromIconResource(module, IDR_MAINFRAME)); + ASSERT_TRUE(family.get()); + EXPECT_FALSE(family->empty()); + std::vector images; + for (const auto& image : *family) + images.push_back(image); + + // Assert that the family contains all of the images from the icon resource. + EXPECT_EQ(5u, images.size()); + EXPECT_EQ(16, images[0].Width()); + EXPECT_EQ(24, images[1].Width()); + EXPECT_EQ(32, images[2].Width()); + EXPECT_EQ(48, images[3].Width()); + EXPECT_EQ(256, images[4].Width()); +} + +// This tests that kNumIconDimensionsUpToMediumSize has the correct value. +TEST_F(IconUtilTest, TestNumIconDimensionsUpToMediumSize) { + ASSERT_LE(IconUtil::kNumIconDimensionsUpToMediumSize, + IconUtil::kNumIconDimensions); + EXPECT_EQ(IconUtil::kMediumIconSize, + IconUtil::kIconDimensions[ + IconUtil::kNumIconDimensionsUpToMediumSize - 1]); +} + +TEST_F(IconUtilTest, TestTransparentIcon) { + base::FilePath icon_filename = + temp_directory_.GetPath().AppendASCII(kTempIconFilename); + int size = 48; + auto semi_transparent_red = SkColorSetARGB(0x77, 0xFF, 0x00, 0x00); + + // Create a bitmap with a semi transparent red dot. + SkBitmap bitmap; + bitmap.allocN32Pixels(size, size, false); + EXPECT_EQ(bitmap.alphaType(), kPremul_SkAlphaType); + { + SkCanvas canvas(bitmap, SkSurfaceProps{}); + canvas.drawColor(SK_ColorWHITE); + SkPaint paint; + paint.setColor(semi_transparent_red); + paint.setBlendMode(SkBlendMode::kSrc); + canvas.drawPoint(1, 1, paint); + } + + // Create icon from that bitmap. + gfx::ImageFamily image_family; + image_family.Add(gfx::Image::CreateFrom1xBitmap(bitmap)); + ASSERT_TRUE( + IconUtil::CreateIconFileFromImageFamily(image_family, icon_filename)); + + // Load icon and check that dot has same color. + ScopedHICON icon(LoadIconFromFile(icon_filename, size, size)); + ASSERT_TRUE(icon.is_valid()); + SkBitmap bitmap_loaded = + IconUtil::CreateSkBitmapFromHICON(icon.get(), gfx::Size(size, size)); + EXPECT_EQ(bitmap_loaded.getColor(1, 1), semi_transparent_red); +} diff --git a/icon_util_unittests.ico b/icon_util_unittests.ico new file mode 100644 index 000000000000..8cd57a122992 Binary files /dev/null and b/icon_util_unittests.ico differ diff --git a/icon_util_unittests.rc b/icon_util_unittests.rc new file mode 100644 index 000000000000..a44a001bc0bd --- /dev/null +++ b/icon_util_unittests.rc @@ -0,0 +1,36 @@ +// Microsoft Visual C++ generated resource script. +// +#include "icon_util_unittests_resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#define APSTUDIO_HIDDEN_SYMBOLS +#include "windows.h" +#undef APSTUDIO_HIDDEN_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDR_MAINFRAME ICON "icon_util_unittests.ico" + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// diff --git a/icon_util_unittests_resource.h b/icon_util_unittests_resource.h new file mode 100644 index 000000000000..560a0de9aef8 --- /dev/null +++ b/icon_util_unittests_resource.h @@ -0,0 +1,10 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_ICON_UTIL_UNITTESTS_RESOURCE_H_ +#define UI_GFX_ICON_UTIL_UNITTESTS_RESOURCE_H_ + +#define IDR_MAINFRAME 101 + +#endif // UI_GFX_ICON_UTIL_UNITTESTS_RESOURCE_H_ \ No newline at end of file diff --git a/image/OWNERS b/image/OWNERS new file mode 100644 index 000000000000..23dbb5a958f9 --- /dev/null +++ b/image/OWNERS @@ -0,0 +1,4 @@ +rsesek@chromium.org + +# ImageSkia related classes except for _mac/_ios stuff +per-file image_skia*=oshima@chromium.org diff --git a/image/buffer_w_stream.cc b/image/buffer_w_stream.cc new file mode 100644 index 000000000000..ea9747f74d6e --- /dev/null +++ b/image/buffer_w_stream.cc @@ -0,0 +1,29 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/image/buffer_w_stream.h" + +#include + +namespace gfx { + +BufferWStream::BufferWStream() = default; + +BufferWStream::~BufferWStream() = default; + +std::vector BufferWStream::TakeBuffer() { + return std::move(result_); +} + +bool BufferWStream::write(const void* buffer, size_t size) { + const uint8_t* bytes = reinterpret_cast(buffer); + result_.insert(result_.end(), bytes, bytes + size); + return true; +} + +size_t BufferWStream::bytesWritten() const { + return result_.size(); +} + +} // namespace gfx diff --git a/image/buffer_w_stream.h b/image/buffer_w_stream.h new file mode 100644 index 000000000000..43b555f64816 --- /dev/null +++ b/image/buffer_w_stream.h @@ -0,0 +1,38 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_IMAGE_BUFFER_W_STREAM_H_ +#define UI_GFX_IMAGE_BUFFER_W_STREAM_H_ + +#include +#include + +#include "third_party/skia/include/core/SkStream.h" +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +// Writes bytes to a std::vector that can be fetched. This is used to record the +// output of skia image encoding. +class GFX_EXPORT BufferWStream : public SkWStream { + public: + BufferWStream(); + BufferWStream(const BufferWStream&) = delete; + BufferWStream& operator=(const BufferWStream&) = delete; + ~BufferWStream() override; + + // Returns the output buffer by moving. + std::vector TakeBuffer(); + + // SkWStream: + bool write(const void* buffer, size_t size) override; + size_t bytesWritten() const override; + + private: + std::vector result_; +}; + +} // namespace gfx + +#endif // UI_GFX_IMAGE_BUFFER_W_STREAM_H_ diff --git a/image/buffer_w_stream_unittest.cc b/image/buffer_w_stream_unittest.cc new file mode 100644 index 000000000000..1d7e02c1e818 --- /dev/null +++ b/image/buffer_w_stream_unittest.cc @@ -0,0 +1,54 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/image/buffer_w_stream.h" + +#include +#include +#include + +#include "base/ranges/algorithm.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace gfx { +namespace { + +using BufferWStreamTest = testing::Test; + +TEST(BufferWStreamTest, WriteBuffer) { + BufferWStream stream; + + std::vector buffer; + for (uint8_t i = 0; i < 10; i++) { + buffer.push_back(i); + } + ASSERT_TRUE(stream.write(buffer.data(), buffer.size())); + std::vector buffer_from_stream = stream.TakeBuffer(); + EXPECT_TRUE(base::ranges::equal(buffer, buffer_from_stream)); + + std::vector buffer2; + for (uint8_t i = 5; i > 0; i--) { + buffer2.push_back(i); + } + + // Checks that the stream is cleared after TakeBuffer() so that it won't + // contain old data. + ASSERT_TRUE(stream.write(buffer2.data(), buffer2.size())); + buffer_from_stream = stream.TakeBuffer(); + EXPECT_TRUE(base::ranges::equal(buffer2, buffer_from_stream)); + + // Checks that calling write() twice works expectedly. + ASSERT_TRUE(stream.write(buffer.data(), buffer.size())); + ASSERT_TRUE(stream.write(buffer2.data(), buffer2.size())); + buffer_from_stream = stream.TakeBuffer(); + EXPECT_TRUE(base::ranges::equal(buffer.begin(), buffer.end(), + buffer_from_stream.begin(), + buffer_from_stream.begin() + buffer.size())); + EXPECT_TRUE(base::ranges::equal(buffer2.begin(), buffer2.end(), + buffer_from_stream.begin() + buffer.size(), + buffer_from_stream.end())); +} + +} // namespace +} // namespace gfx diff --git a/image/canvas_image_source.cc b/image/canvas_image_source.cc new file mode 100644 index 000000000000..13274ed84492 --- /dev/null +++ b/image/canvas_image_source.cc @@ -0,0 +1,80 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/image/canvas_image_source.h" + +#include "base/check_op.h" +#include "cc/paint/display_item_list.h" +#include "cc/paint/record_paint_canvas.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/geometry/insets.h" +#include "ui/gfx/geometry/size_conversions.h" +#include "ui/gfx/image/image_skia.h" +#include "ui/gfx/switches.h" + +namespace gfx { + +namespace { + +class PaddedImageSource : public CanvasImageSource { + public: + PaddedImageSource(const ImageSkia& image, const Insets& insets) + : CanvasImageSource(Size(image.width() + insets.width(), + image.height() + insets.height())), + image_(image), + insets_(insets) {} + + PaddedImageSource(const PaddedImageSource&) = delete; + PaddedImageSource& operator=(const PaddedImageSource&) = delete; + + // CanvasImageSource: + void Draw(Canvas* canvas) override { + canvas->DrawImageInt(image_, insets_.left(), insets_.top()); + } + + private: + const ImageSkia image_; + const Insets insets_; +}; + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// +// CanvasImageSource + +// static +ImageSkia CanvasImageSource::CreatePadded(const ImageSkia& image, + const Insets& insets) { + return MakeImageSkia(image, insets); +} + +CanvasImageSource::CanvasImageSource(const Size& size) : size_(size) {} + +ImageSkiaRep CanvasImageSource::GetImageForScale(float scale) { + scoped_refptr display_item_list = + base::MakeRefCounted( + cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer); + display_item_list->StartPaint(); + + SizeF size_in_pixel = ScaleSize(SizeF(size_), scale); + cc::RecordPaintCanvas record_canvas( + display_item_list.get(), + SkRect::MakeWH(SkFloatToScalar(size_in_pixel.width()), + SkFloatToScalar(size_in_pixel.height()))); + gfx::Canvas canvas(&record_canvas, scale); +#if DCHECK_IS_ON() + Rect clip_rect; + DCHECK(canvas.GetClipBounds(&clip_rect)); + DCHECK(clip_rect.Contains(gfx::Rect(ToCeiledSize(size_in_pixel)))); +#endif + canvas.Scale(scale, scale); + Draw(&canvas); + + display_item_list->EndPaintOfPairedEnd(); + display_item_list->Finalize(); + return ImageSkiaRep(display_item_list->ReleaseAsRecord(), + gfx::ScaleToCeiledSize(size_, scale), scale); +} + +} // namespace gfx diff --git a/image/canvas_image_source.h b/image/canvas_image_source.h new file mode 100644 index 000000000000..18aeb581307c --- /dev/null +++ b/image/canvas_image_source.h @@ -0,0 +1,63 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_IMAGE_CANVAS_IMAGE_SOURCE_H_ +#define UI_GFX_IMAGE_CANVAS_IMAGE_SOURCE_H_ + +#include + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/gfx_export.h" +#include "ui/gfx/image/image_skia.h" +#include "ui/gfx/image/image_skia_source.h" + +namespace gfx { +class Canvas; +class ImageSkiaRep; +class Insets; + +// CanvasImageSource is useful if you need to generate an image for a scale +// factor using Canvas. It creates a new Canvas with target scale factor and +// generates ImageSkiaRep when drawing is completed. +class GFX_EXPORT CanvasImageSource : public ImageSkiaSource { + public: + // Factory function to create an ImageSkia from a CanvasImageSource. Example: + // ImageSkia my_image = + // CanvasImageSource::MakeImageSkia(param1, param2); + template + static ImageSkia MakeImageSkia(Args&&... args) { + auto source = std::make_unique(std::forward(args)...); + Size size = source->size(); + return ImageSkia(std::move(source), size); + } + + // Creates a Image containing |image| with transparent padding around the + // edges as specified by |insets|. + static ImageSkia CreatePadded(const ImageSkia& image, const Insets& insets); + + explicit CanvasImageSource(const Size& size); + + CanvasImageSource(const CanvasImageSource&) = delete; + CanvasImageSource& operator=(const CanvasImageSource&) = delete; + + ~CanvasImageSource() override {} + + // Called when a new image needs to be drawn for a scale factor. + virtual void Draw(Canvas* canvas) = 0; + + // Returns the size of images in DIP that this source will generate. + const Size& size() const { return size_; } + + // Overridden from ImageSkiaSource. + ImageSkiaRep GetImageForScale(float scale) override; + + protected: + const Size size_; +}; + +} // namespace gfx + +#endif // UI_GFX_IMAGE_CANVAS_IMAGE_SOURCE_H_ diff --git a/image/image.cc b/image/image.cc new file mode 100644 index 000000000000..8852ae9a4a0c --- /dev/null +++ b/image/image.cc @@ -0,0 +1,648 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/image/image.h" + +#include +#include +#include + +#include "base/check_op.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/notreached.h" +#include "build/build_config.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/image/image_platform.h" +#include "ui/gfx/image/image_png_rep.h" +#include "ui/gfx/image/image_skia.h" + +#if defined(OS_IOS) +#include "base/mac/foundation_util.h" +#include "ui/gfx/image/image_skia_util_ios.h" +#elif defined(OS_MAC) +#include "base/mac/foundation_util.h" +#include "base/mac/mac_util.h" +#include "ui/gfx/image/image_skia_util_mac.h" +#endif + +namespace gfx { + +namespace { + +using RepresentationMap = + std::map>; + +} // namespace + +namespace internal { + +class ImageRepPNG; +class ImageRepSkia; +class ImageRepCocoa; +class ImageRepCocoaTouch; + +// An ImageRep is the object that holds the backing memory for an Image. Each +// RepresentationType has an ImageRep subclass that is responsible for freeing +// the memory that the ImageRep holds. When an ImageRep is created, it expects +// to take ownership of the image, without having to retain it or increase its +// reference count. +class ImageRep { + public: + explicit ImageRep(Image::RepresentationType rep) : type_(rep) {} + + // Deletes the associated pixels of an ImageRep. + virtual ~ImageRep() {} + + // Cast helpers ("fake RTTI"). + const ImageRepPNG* AsImageRepPNG() const { + CHECK_EQ(type_, Image::kImageRepPNG); + return reinterpret_cast(this); + } + ImageRepPNG* AsImageRepPNG() { + return const_cast( + static_cast(this)->AsImageRepPNG()); + } + + const ImageRepSkia* AsImageRepSkia() const { + CHECK_EQ(type_, Image::kImageRepSkia); + return reinterpret_cast(this); + } + ImageRepSkia* AsImageRepSkia() { + return const_cast( + static_cast(this)->AsImageRepSkia()); + } + +#if defined(OS_IOS) + const ImageRepCocoaTouch* AsImageRepCocoaTouch() const { + CHECK_EQ(type_, Image::kImageRepCocoaTouch); + return reinterpret_cast(this); + } + ImageRepCocoaTouch* AsImageRepCocoaTouch() { + return const_cast( + static_cast(this)->AsImageRepCocoaTouch()); + } +#elif defined(OS_MAC) + const ImageRepCocoa* AsImageRepCocoa() const { + CHECK_EQ(type_, Image::kImageRepCocoa); + return reinterpret_cast(this); + } + ImageRepCocoa* AsImageRepCocoa() { + return const_cast( + static_cast(this)->AsImageRepCocoa()); + } +#endif + + Image::RepresentationType type() const { return type_; } + + virtual int Width() const = 0; + virtual int Height() const = 0; + virtual gfx::Size Size() const = 0; + + private: + Image::RepresentationType type_; +}; + +class ImageRepPNG : public ImageRep { + public: + ImageRepPNG() : ImageRep(Image::kImageRepPNG) { + } + + explicit ImageRepPNG(const std::vector& image_png_reps) + : ImageRep(Image::kImageRepPNG), image_png_reps_(image_png_reps) {} + + ImageRepPNG(const ImageRepPNG&) = delete; + ImageRepPNG& operator=(const ImageRepPNG&) = delete; + + ~ImageRepPNG() override {} + + int Width() const override { return Size().width(); } + + int Height() const override { return Size().height(); } + + gfx::Size Size() const override { + // Read the PNG data to get the image size, caching it. + if (!size_cache_) { + for (auto it = image_reps().begin(); it != image_reps().end(); ++it) { + if (it->scale == 1.0f) { + size_cache_ = it->Size(); + return *size_cache_; + } + } + size_cache_ = gfx::Size(); + } + + return *size_cache_; + } + + const std::vector& image_reps() const { return image_png_reps_; } + + private: + std::vector image_png_reps_; + + // Cached to avoid having to parse the raw data multiple times. + mutable absl::optional size_cache_; +}; + +class ImageRepSkia : public ImageRep { + public: + explicit ImageRepSkia(ImageSkia image) + : ImageRep(Image::kImageRepSkia), image_(image) {} + + ImageRepSkia(const ImageRepSkia&) = delete; + ImageRepSkia& operator=(const ImageRepSkia&) = delete; + + ~ImageRepSkia() override {} + + int Width() const override { return image_.width(); } + + int Height() const override { return image_.height(); } + + gfx::Size Size() const override { return image_.size(); } + + const ImageSkia* image() const { return &image_; } + ImageSkia* image() { return &image_; } + + private: + ImageSkia image_; +}; + +#if defined(OS_IOS) +class ImageRepCocoaTouch : public ImageRep { + public: + explicit ImageRepCocoaTouch(UIImage* image) + : ImageRep(Image::kImageRepCocoaTouch), + image_(image) { + CHECK(image_); + base::mac::NSObjectRetain(image_); + } + + ImageRepCocoaTouch(const ImageRepCocoaTouch&) = delete; + ImageRepCocoaTouch& operator=(const ImageRepCocoaTouch&) = delete; + + ~ImageRepCocoaTouch() override { + base::mac::NSObjectRelease(image_); + image_ = nil; + } + + int Width() const override { return Size().width(); } + + int Height() const override { return Size().height(); } + + gfx::Size Size() const override { return internal::UIImageSize(image_); } + + UIImage* image() const { return image_; } + + private: + UIImage* image_; +}; +#elif defined(OS_MAC) +class ImageRepCocoa : public ImageRep { + public: + explicit ImageRepCocoa(NSImage* image) + : ImageRep(Image::kImageRepCocoa), + image_(image) { + CHECK(image_); + base::mac::NSObjectRetain(image_); + } + + ImageRepCocoa(const ImageRepCocoa&) = delete; + ImageRepCocoa& operator=(const ImageRepCocoa&) = delete; + + ~ImageRepCocoa() override { + base::mac::NSObjectRelease(image_); + image_ = nil; + } + + int Width() const override { return Size().width(); } + + int Height() const override { return Size().height(); } + + gfx::Size Size() const override { return internal::NSImageSize(image_); } + + NSImage* image() const { return image_; } + + private: + NSImage* image_; +}; +#endif // defined(OS_MAC) + +// The Storage class acts similarly to the pixels in a SkBitmap: the Image +// class holds a refptr instance of Storage, which in turn holds all the +// ImageReps. This way, the Image can be cheaply copied. +// +// This class is deliberately not RefCountedThreadSafe. Making it so does not +// solve threading issues, as gfx::Image and its internal classes are +// themselves not threadsafe. +class ImageStorage : public base::RefCounted { + public: + explicit ImageStorage(Image::RepresentationType default_type) + : default_representation_type_(default_type) +#if defined(OS_MAC) + , + default_representation_color_space_( + base::mac::GetGenericRGBColorSpace()) +#endif // defined(OS_MAC) + { + } + + ImageStorage(const ImageStorage&) = delete; + ImageStorage& operator=(const ImageStorage&) = delete; + + Image::RepresentationType default_representation_type() const { + DCHECK(IsOnValidSequence()); + return default_representation_type_; + } + + bool HasRepresentation(Image::RepresentationType type) const { + DCHECK(IsOnValidSequence()); + return representations_.count(type) != 0; + } + + size_t RepresentationCount() const { + DCHECK(IsOnValidSequence()); + return representations_.size(); + } + + const ImageRep* GetRepresentation(Image::RepresentationType rep_type, + bool must_exist) const { + DCHECK(IsOnValidSequence()); + RepresentationMap::const_iterator it = representations_.find(rep_type); + if (it == representations_.end()) { + CHECK(!must_exist); + return nullptr; + } + return it->second.get(); + } + + const ImageRep* AddRepresentation(std::unique_ptr rep) const { + DCHECK(IsOnValidSequence()); + Image::RepresentationType type = rep->type(); + auto result = representations_.emplace(type, std::move(rep)); + + // insert should not fail (implies that there was already a representation + // of that type in the map). + CHECK(result.second) << "type was already in map."; + + return result.first->second.get(); + } + +#if defined(OS_MAC) + void set_default_representation_color_space(CGColorSpaceRef color_space) { + DCHECK(IsOnValidSequence()); + default_representation_color_space_ = color_space; + } + CGColorSpaceRef default_representation_color_space() const { + DCHECK(IsOnValidSequence()); + return default_representation_color_space_; + } +#endif // defined(OS_MAC) + + private: + friend class base::RefCounted; + + ~ImageStorage() {} + + // The type of image that was passed to the constructor. This key will always + // exist in the |representations_| map. + Image::RepresentationType default_representation_type_; + +#if defined(OS_MAC) + // The default representation's colorspace. This is used for converting to + // NSImage. This field exists to compensate for PNGCodec not writing or + // reading colorspace ancillary chunks. (sRGB, iCCP). + // Not owned. + CGColorSpaceRef default_representation_color_space_; +#endif // defined(OS_MAC) + + // All the representations of an Image. Size will always be at least one, with + // more for any converted representations. + mutable RepresentationMap representations_; +}; + +} // namespace internal + +Image::Image() { + // |storage_| is null for empty Images. +} + +Image::Image(const std::vector& image_reps) { + // Do not store obviously invalid ImagePNGReps. + std::vector filtered; + for (size_t i = 0; i < image_reps.size(); ++i) { + if (image_reps[i].raw_data.get() && image_reps[i].raw_data->size()) + filtered.push_back(image_reps[i]); + } + + if (filtered.empty()) + return; + + storage_ = new internal::ImageStorage(Image::kImageRepPNG); + AddRepresentation(std::make_unique(filtered)); +} + +Image::Image(const ImageSkia& image) { + if (!image.isNull()) { + storage_ = new internal::ImageStorage(Image::kImageRepSkia); + AddRepresentation(std::make_unique(image)); + } +} + +#if defined(OS_IOS) +Image::Image(UIImage* image) { + if (image) { + storage_ = new internal::ImageStorage(Image::kImageRepCocoaTouch); + AddRepresentation(std::make_unique(image)); + } +} +#elif defined(OS_MAC) +Image::Image(NSImage* image) { + if (image) { + storage_ = new internal::ImageStorage(Image::kImageRepCocoa); + AddRepresentation(std::make_unique(image)); + } +} +#endif + +Image::Image(const Image& other) = default; + +Image::Image(Image&& other) noexcept = default; + +Image& Image::operator=(const Image& other) = default; + +Image& Image::operator=(Image&& other) noexcept = default; + +Image::~Image() {} + +bool Image::operator==(const Image& other) const { + return storage_ == other.storage_; +} + +// static +Image Image::CreateFrom1xBitmap(const SkBitmap& bitmap) { + return Image(ImageSkia::CreateFrom1xBitmap(bitmap)); +} + +// static +Image Image::CreateFrom1xPNGBytes(const unsigned char* input, + size_t input_size) { + if (input_size == 0u) + return Image(); + + scoped_refptr raw_data(new base::RefCountedBytes()); + raw_data->data().assign(input, input + input_size); + + return CreateFrom1xPNGBytes(raw_data); +} + +Image Image::CreateFrom1xPNGBytes( + const scoped_refptr& input) { + if (!input.get() || input->size() == 0u) + return Image(); + + std::vector image_reps; + image_reps.push_back(ImagePNGRep(input, 1.0f)); + return Image(image_reps); +} + +const SkBitmap* Image::ToSkBitmap() const { + // Possibly create and cache an intermediate ImageRepSkia. + return ToImageSkia()->bitmap(); +} + +const ImageSkia* Image::ToImageSkia() const { + const internal::ImageRep* rep = GetRepresentation(kImageRepSkia, false); + if (!rep) { + std::unique_ptr scoped_rep; + switch (DefaultRepresentationType()) { + case kImageRepPNG: { + const internal::ImageRepPNG* png_rep = + GetRepresentation(kImageRepPNG, true)->AsImageRepPNG(); + scoped_rep = std::make_unique( + internal::ImageSkiaFromPNG(png_rep->image_reps())); + break; + } +#if defined(OS_IOS) + case kImageRepCocoaTouch: { + const internal::ImageRepCocoaTouch* native_rep = + GetRepresentation(kImageRepCocoaTouch, true) + ->AsImageRepCocoaTouch(); + scoped_rep = std::make_unique( + ImageSkia(ImageSkiaFromUIImage(native_rep->image()))); + break; + } +#elif defined(OS_MAC) + case kImageRepCocoa: { + const internal::ImageRepCocoa* native_rep = + GetRepresentation(kImageRepCocoa, true)->AsImageRepCocoa(); + scoped_rep = std::make_unique( + ImageSkia(ImageSkiaFromNSImage(native_rep->image()))); + break; + } +#endif + default: + NOTREACHED(); + } + CHECK(scoped_rep); + rep = AddRepresentation(std::move(scoped_rep)); + } + return rep->AsImageRepSkia()->image(); +} + +#if defined(OS_IOS) +UIImage* Image::ToUIImage() const { + const internal::ImageRep* rep = GetRepresentation(kImageRepCocoaTouch, false); + if (!rep) { + std::unique_ptr scoped_rep; + switch (DefaultRepresentationType()) { + case kImageRepPNG: { + const internal::ImageRepPNG* png_rep = + GetRepresentation(kImageRepPNG, true)->AsImageRepPNG(); + scoped_rep = std::make_unique( + internal::UIImageFromPNG(png_rep->image_reps())); + break; + } + case kImageRepSkia: { + const internal::ImageRepSkia* skia_rep = + GetRepresentation(kImageRepSkia, true)->AsImageRepSkia(); + UIImage* image = UIImageFromImageSkia(*skia_rep->image()); + scoped_rep = std::make_unique(image); + break; + } + default: + NOTREACHED(); + } + CHECK(scoped_rep); + rep = AddRepresentation(std::move(scoped_rep)); + } + return rep->AsImageRepCocoaTouch()->image(); +} +#elif defined(OS_MAC) +NSImage* Image::ToNSImage() const { + const internal::ImageRep* rep = GetRepresentation(kImageRepCocoa, false); + if (!rep) { + std::unique_ptr scoped_rep; + CGColorSpaceRef default_representation_color_space = + storage()->default_representation_color_space(); + + switch (DefaultRepresentationType()) { + case kImageRepPNG: { + const internal::ImageRepPNG* png_rep = + GetRepresentation(kImageRepPNG, true)->AsImageRepPNG(); + scoped_rep = + std::make_unique(internal::NSImageFromPNG( + png_rep->image_reps(), default_representation_color_space)); + break; + } + case kImageRepSkia: { + const internal::ImageRepSkia* skia_rep = + GetRepresentation(kImageRepSkia, true)->AsImageRepSkia(); + NSImage* image = NSImageFromImageSkiaWithColorSpace(*skia_rep->image(), + default_representation_color_space); + scoped_rep = std::make_unique(image); + break; + } + default: + NOTREACHED(); + } + CHECK(scoped_rep); + rep = AddRepresentation(std::move(scoped_rep)); + } + return rep->AsImageRepCocoa()->image(); +} +#endif + +scoped_refptr Image::As1xPNGBytes() const { + if (IsEmpty()) + return new base::RefCountedBytes(); + + const internal::ImageRep* rep = GetRepresentation(kImageRepPNG, false); + + if (rep) { + const std::vector& image_png_reps = + rep->AsImageRepPNG()->image_reps(); + for (size_t i = 0; i < image_png_reps.size(); ++i) { + if (image_png_reps[i].scale == 1.0f) + return image_png_reps[i].raw_data; + } + return new base::RefCountedBytes(); + } + + scoped_refptr png_bytes; + switch (DefaultRepresentationType()) { +#if defined(OS_IOS) + case kImageRepCocoaTouch: { + const internal::ImageRepCocoaTouch* cocoa_touch_rep = + GetRepresentation(kImageRepCocoaTouch, true)->AsImageRepCocoaTouch(); + png_bytes = internal::Get1xPNGBytesFromUIImage( + cocoa_touch_rep->image()); + break; + } +#elif defined(OS_MAC) + case kImageRepCocoa: { + const internal::ImageRepCocoa* cocoa_rep = + GetRepresentation(kImageRepCocoa, true)->AsImageRepCocoa(); + png_bytes = internal::Get1xPNGBytesFromNSImage(cocoa_rep->image()); + break; + } +#endif + case kImageRepSkia: { + const internal::ImageRepSkia* skia_rep = + GetRepresentation(kImageRepSkia, true)->AsImageRepSkia(); + png_bytes = internal::Get1xPNGBytesFromImageSkia(skia_rep->image()); + break; + } + default: + NOTREACHED(); + } + if (!png_bytes.get() || !png_bytes->size()) { + // Add an ImageRepPNG with no data such that the conversion is not + // attempted each time we want the PNG bytes. + AddRepresentation(base::WrapUnique(new internal::ImageRepPNG())); + return new base::RefCountedBytes(); + } + + // Do not insert representations for scale factors other than 1x even if + // they are available because: + // - Only the 1x PNG bytes can be accessed. + // - ImageRepPNG is not used as an intermediate type in converting to a + // final type eg (converting from ImageRepSkia to ImageRepPNG to get an + // ImageRepCocoa). + std::vector image_png_reps; + image_png_reps.push_back(ImagePNGRep(png_bytes, 1.0f)); + AddRepresentation( + base::WrapUnique(new internal::ImageRepPNG(image_png_reps))); + return png_bytes; +} + +SkBitmap Image::AsBitmap() const { + return IsEmpty() ? SkBitmap() : *ToSkBitmap(); +} + +ImageSkia Image::AsImageSkia() const { + return IsEmpty() ? ImageSkia() : *ToImageSkia(); +} + +#if defined(OS_MAC) +NSImage* Image::AsNSImage() const { + return IsEmpty() ? nil : ToNSImage(); +} +#endif + +bool Image::HasRepresentation(RepresentationType type) const { + return storage() && storage()->HasRepresentation(type); +} + +size_t Image::RepresentationCount() const { + return storage() ? storage()->RepresentationCount() : 0; +} + +bool Image::IsEmpty() const { + return RepresentationCount() == 0; +} + +int Image::Width() const { + if (IsEmpty()) + return 0; + return GetRepresentation(DefaultRepresentationType(), true)->Width(); +} + +int Image::Height() const { + if (IsEmpty()) + return 0; + return GetRepresentation(DefaultRepresentationType(), true)->Height(); +} + +gfx::Size Image::Size() const { + if (IsEmpty()) + return gfx::Size(); + return GetRepresentation(DefaultRepresentationType(), true)->Size(); +} + +#if defined(OS_MAC) +void Image::SetSourceColorSpace(CGColorSpaceRef color_space) { + if (storage()) + storage()->set_default_representation_color_space(color_space); +} +#endif // defined(OS_MAC) + +Image::RepresentationType Image::DefaultRepresentationType() const { + CHECK(storage()); + return storage()->default_representation_type(); +} + +const internal::ImageRep* Image::GetRepresentation(RepresentationType rep_type, + bool must_exist) const { + CHECK(storage()); + return storage()->GetRepresentation(rep_type, must_exist); +} + +const internal::ImageRep* Image::AddRepresentation( + std::unique_ptr rep) const { + CHECK(storage()); + return storage()->AddRepresentation(std::move(rep)); +} + +} // namespace gfx diff --git a/image/image.h b/image/image.h new file mode 100644 index 000000000000..52ad9fd20674 --- /dev/null +++ b/image/image.h @@ -0,0 +1,194 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// An Image wraps an image any flavor, be it platform-native GdkBitmap/NSImage, +// or a SkBitmap. This also provides easy conversion to other image types +// through operator overloading. It will cache the converted representations +// internally to prevent double-conversion. +// +// The lifetime of both the initial representation and any converted ones are +// tied to the lifetime of the Image's internal storage. To allow Images to be +// cheaply passed around by value, the actual image data is stored in a ref- +// counted member. When all Images referencing this storage are deleted, the +// actual representations are deleted, too. +// +// Images can be empty, in which case they have no backing representation. +// Attempting to use an empty Image will result in a crash. + +#ifndef UI_GFX_IMAGE_IMAGE_H_ +#define UI_GFX_IMAGE_IMAGE_H_ + +#include + +#include +#include +#include + +#include "base/memory/ref_counted_memory.h" +#include "base/memory/scoped_policy.h" +#include "build/build_config.h" +#include "ui/gfx/gfx_export.h" +#include "ui/gfx/native_widget_types.h" + +#if defined(OS_MAC) +typedef struct CGColorSpace* CGColorSpaceRef; +#endif + +class SkBitmap; + +namespace gfx { +struct ImagePNGRep; +class ImageSkia; +class Size; + +namespace internal { +class ImageRep; +class ImageStorage; +} + +class GFX_EXPORT Image { + public: + enum RepresentationType { + kImageRepCocoa, + kImageRepCocoaTouch, + kImageRepSkia, + kImageRepPNG, + }; + + // Creates an empty image with no representations. + Image(); + + // Creates a new image by copying the raw PNG-encoded input for use as the + // default representation. + explicit Image(const std::vector& image_reps); + + // Creates a new image by copying the ImageSkia for use as the default + // representation. + explicit Image(const ImageSkia& image); + +#if defined(OS_IOS) + // Retains |image|. + explicit Image(UIImage* image); +#elif defined(OS_MAC) + // Retains |image|. + explicit Image(NSImage* image); +#endif + + // Initializes a new Image by AddRef()ing |other|'s internal storage. + Image(const Image& other); + + // Moves a reference from |other| to the new image without changing the + // reference count. + Image(Image&& other) noexcept; + + // Copies a reference to |other|'s storage. + Image& operator=(const Image& other); + + // Moves a reference from |other|'s storage without changing the reference + // count. + Image& operator=(Image&& other) noexcept; + + // Deletes the image and, if the only owner of the storage, all of its cached + // representations. + ~Image(); + + // True iff both images are backed by the same storage. + bool operator==(const Image& other) const; + + // Creates an image from the passed in 1x bitmap. + // WARNING: The resulting image will be pixelated when painted on a high + // density display. + static Image CreateFrom1xBitmap(const SkBitmap& bitmap); + + // Creates an image from the PNG encoded input. + // For example (from an std::vector): + // std::vector png = ...; + // gfx::Image image = + // Image::CreateFrom1xPNGBytes(&png.front(), png.size()); + static Image CreateFrom1xPNGBytes(const unsigned char* input, + size_t input_size); + + // Creates an image from the PNG encoded input. + static Image CreateFrom1xPNGBytes( + const scoped_refptr& input); + + // Converts the Image to the desired representation and stores it internally. + // The returned result is a weak pointer owned by and scoped to the life of + // the Image. Must only be called if IsEmpty() is false. + const SkBitmap* ToSkBitmap() const; + const ImageSkia* ToImageSkia() const; +#if defined(OS_IOS) + UIImage* ToUIImage() const; +#elif defined(OS_MAC) + NSImage* ToNSImage() const; +#endif + + // Returns the raw PNG-encoded data for the bitmap at 1x. If the data is + // unavailable, either because the image has no data for 1x or because it is + // empty, an empty RefCountedBytes object is returned. NULL is never + // returned. + scoped_refptr As1xPNGBytes() const; + + // Same as ToSkBitmap(), but returns a null SkBitmap if this image is empty. + SkBitmap AsBitmap() const; + + // Same as ToImageSkia(), but returns an empty ImageSkia if this + // image is empty. + ImageSkia AsImageSkia() const; + + // Same as ToNSImage(), but returns nil if this image is empty. +#if defined(OS_MAC) + NSImage* AsNSImage() const; +#endif + + // Inspects the representations map to see if the given type exists. + bool HasRepresentation(RepresentationType type) const; + + // Returns the number of representations. + size_t RepresentationCount() const; + + // Returns true if this Image has no representations. + bool IsEmpty() const; + + // Width and height of image in DIP coordinate system. + int Width() const; + int Height() const; + gfx::Size Size() const; + +#if defined(OS_MAC) + // Set the default representation's color space. This is used for converting + // to NSImage. This is used to compensate for PNGCodec not writing or reading + // colorspace ancillary chunks. (sRGB, iCCP). + void SetSourceColorSpace(CGColorSpaceRef color_space); +#endif // defined(OS_MAC) + + private: + // Returns the type of the default representation. + RepresentationType DefaultRepresentationType() const; + + // Returns the ImageRep of the appropriate type or NULL if there is no + // representation of that type (and must_exist is false). + const internal::ImageRep* GetRepresentation(RepresentationType rep_type, + bool must_exist) const; + + // Stores a representation into the map. A representation of that type must + // not already be in the map. Returns a pointer to the representation stored + // inside the map. + const internal::ImageRep* AddRepresentation( + std::unique_ptr rep) const; + + // Getter should be used internally (unless a handle to the scoped_refptr is + // needed) instead of directly accessing |storage_|, to ensure logical + // constness is upheld. + const internal::ImageStorage* storage() const { return storage_.get(); } + internal::ImageStorage* storage() { return storage_.get(); } + + // Internal class that holds all the representations. This allows the Image to + // be cheaply copied. + scoped_refptr storage_; +}; + +} // namespace gfx + +#endif // UI_GFX_IMAGE_IMAGE_H_ diff --git a/image/image_family.cc b/image/image_family.cc new file mode 100644 index 000000000000..e6fce1d20ff0 --- /dev/null +++ b/image/image_family.cc @@ -0,0 +1,164 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/image/image_family.h" + +#include + +#include "skia/ext/image_operations.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/image/image.h" +#include "ui/gfx/image/image_skia.h" + +namespace gfx { + +ImageFamily::const_iterator::const_iterator() {} + +ImageFamily::const_iterator::const_iterator(const const_iterator& other) + : map_iterator_(other.map_iterator_) {} + +ImageFamily::const_iterator::const_iterator( + const std::map::const_iterator& other) + : map_iterator_(other) {} + +ImageFamily::const_iterator::~const_iterator() {} + +ImageFamily::ImageFamily() {} +ImageFamily::ImageFamily(ImageFamily&& other) = default; +ImageFamily::~ImageFamily() {} + +ImageFamily& ImageFamily::operator=(ImageFamily&& other) = default; + +ImageFamily ImageFamily::Clone() const { + ImageFamily clone; + clone.map_ = map_; + return clone; +} + +void ImageFamily::Add(const gfx::Image& image) { + gfx::Size size = image.Size(); + if (size.IsEmpty()) { + map_[MapKey(1.0f, 0)] = image; + } else { + float aspect = static_cast(size.width()) / size.height(); + DCHECK_GT(aspect, 0.0f); + map_[MapKey(aspect, size.width())] = image; + } +} + +void ImageFamily::Add(const gfx::ImageSkia& image_skia) { + Add(gfx::Image(image_skia)); +} + +const gfx::Image* ImageFamily::GetBest(int width, int height) const { + if (map_.empty()) + return NULL; + + // If either |width| or |height| is 0, both are. + float desired_aspect; + if (height == 0 || width == 0) { + desired_aspect = 1.0f; + height = 0; + width = 0; + } else { + desired_aspect = static_cast(width) / height; + } + DCHECK_GT(desired_aspect, 0.0f); + + float closest_aspect = GetClosestAspect(desired_aspect); + + // If thinner than desired, search for images with width such that the + // corresponding height is greater than or equal to the desired |height|. + int desired_width = closest_aspect <= desired_aspect ? + width : static_cast(ceilf(height * closest_aspect)); + + // Get the best-sized image with the aspect ratio. + return GetWithExactAspect(closest_aspect, desired_width); +} + +float ImageFamily::GetClosestAspect(float desired_aspect) const { + // Find the two aspect ratios on either side of |desired_aspect|. + auto greater_or_equal = map_.lower_bound(MapKey(desired_aspect, 0)); + // Early exit optimization if there is an exact match. + if (greater_or_equal != map_.end() && + greater_or_equal->first.aspect() == desired_aspect) { + return desired_aspect; + } + + // No exact match; |greater_or_equal| will point to the first image with + // aspect ratio >= |desired_aspect|, and |less_than| will point to the last + // image with aspect ratio < |desired_aspect|. + if (greater_or_equal != map_.begin()) { + auto less_than = greater_or_equal; + --less_than; + float thinner_aspect = less_than->first.aspect(); + DCHECK_GT(thinner_aspect, 0.0f); + DCHECK_LT(thinner_aspect, desired_aspect); + if (greater_or_equal != map_.end()) { + float wider_aspect = greater_or_equal->first.aspect(); + DCHECK_GT(wider_aspect, desired_aspect); + if ((wider_aspect / desired_aspect) < (desired_aspect / thinner_aspect)) + return wider_aspect; + } + return thinner_aspect; + } else { + // No aspect ratio is less than or equal to |desired_aspect|. + DCHECK(greater_or_equal != map_.end()); + float wider_aspect = greater_or_equal->first.aspect(); + DCHECK_GT(wider_aspect, desired_aspect); + return wider_aspect; + } +} + +const gfx::Image* ImageFamily::GetBest(const gfx::Size& size) const { + return GetBest(size.width(), size.height()); +} + +gfx::Image ImageFamily::CreateExact(int width, int height) const { + // Resize crashes if width or height is 0, so just return an empty image. + if (width == 0 || height == 0) + return gfx::Image(); + + const gfx::Image* image = GetBest(width, height); + if (!image) + return gfx::Image(); + + if (image->Width() == width && image->Height() == height) { + // Make a copy at gfx::ImageSkia level, so that resulting image's ref count + // is not racy to |image|. Since this function can run on a different thread + // than the thread |image| created on, we should not touch the + // non-thread-safe ref count in gfx::Image here. + return gfx::Image(image->AsImageSkia()); + } + + SkBitmap bitmap = image->AsBitmap(); + SkBitmap resized_bitmap = skia::ImageOperations::Resize( + bitmap, skia::ImageOperations::RESIZE_LANCZOS3, width, height); + return gfx::Image::CreateFrom1xBitmap(resized_bitmap); +} + +gfx::Image ImageFamily::CreateExact(const gfx::Size& size) const { + return CreateExact(size.width(), size.height()); +} + +const gfx::Image* ImageFamily::GetWithExactAspect(float aspect, + int width) const { + // Find the two images of given aspect ratio on either side of |width|. + auto greater_or_equal = map_.lower_bound(MapKey(aspect, width)); + if (greater_or_equal != map_.end() && + greater_or_equal->first.aspect() == aspect) { + // We have found the smallest image of the same size or greater. + return &greater_or_equal->second; + } + + DCHECK(greater_or_equal != map_.begin()); + auto less_than = greater_or_equal; + --less_than; + // This must be true because there must be at least one image with |aspect|. + DCHECK_EQ(less_than->first.aspect(), aspect); + // We have found the largest image smaller than desired. + return &less_than->second; +} + +} // namespace gfx diff --git a/image/image_family.h b/image/image_family.h new file mode 100644 index 000000000000..f87442665866 --- /dev/null +++ b/image/image_family.h @@ -0,0 +1,184 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_IMAGE_IMAGE_FAMILY_H_ +#define UI_GFX_IMAGE_IMAGE_FAMILY_H_ + +#include +#include +#include + +#include "ui/gfx/gfx_export.h" +#include "ui/gfx/image/image.h" + +namespace gfx { +class ImageSkia; +class Size; + +// A collection of images at different sizes. The images should be different +// representations of the same basic concept (for example, an icon) at various +// sizes and (optionally) aspect ratios. A method is provided for finding the +// most appropriate image to fit in a given rectangle. +// +// NOTE: This is not appropriate for storing an image at a single logical pixel +// size, with high-DPI bitmap versions; use an Image or ImageSkia for that. Each +// image in an ImageFamily should have a different logical size (and may also +// include high-DPI representations). +class GFX_EXPORT ImageFamily { + private: + // An pair. + // A 0x0 image has aspect ratio 1.0. 0xN and Nx0 images are treated as 0x0. + struct MapKey : std::pair { + MapKey(float aspect, int width) + : std::pair(aspect, width) {} + + float aspect() const { return first; } + + int width() const { return second; } + }; + + public: + // Type for iterating over all images in the family, in order. + // Dereferencing this iterator returns a gfx::Image. + class GFX_EXPORT const_iterator : + std::iterator { + public: + const_iterator(); + + const_iterator(const const_iterator& other); + + ~const_iterator(); + + const_iterator& operator++() { + ++map_iterator_; + return *this; + } + + const_iterator operator++(int /*unused*/) { + const_iterator result(*this); + ++(*this); + return result; + } + + const_iterator& operator--() { + --map_iterator_; + return *this; + } + + const_iterator operator--(int /*unused*/) { + const_iterator result(*this); + --(*this); + return result; + } + + bool operator==(const const_iterator& other) const { + return map_iterator_ == other.map_iterator_; + } + + bool operator!=(const const_iterator& other) const { + return map_iterator_ != other.map_iterator_; + } + + const gfx::Image& operator*() const { + return map_iterator_->second; + } + + const gfx::Image* operator->() const { + return &**this; + } + + private: + friend class ImageFamily; + + explicit const_iterator( + const std::map::const_iterator& other); + + std::map::const_iterator map_iterator_; + }; + + ImageFamily(); + ImageFamily(ImageFamily&& other); + + // Even though the Images in the family are copyable (reference-counted), the + // family itself should not be implicitly copied, as it would result in a + // shallow clone of the entire map and updates to many reference counts. + // ImageFamily can be explicitly Clone()d, but std::move is preferred. + ImageFamily(const ImageFamily&) = delete; + ImageFamily& operator=(const ImageFamily&) = delete; + + ~ImageFamily(); + + ImageFamily& operator=(ImageFamily&& other); + + // Gets an iterator to the first image. + const_iterator begin() const { return const_iterator(map_.begin()); } + // Gets an iterator to one after the last image. + const_iterator end() const { return const_iterator(map_.end()); } + + // Determines whether the image family has no images in it. + bool empty() const { return map_.empty(); } + + // Removes all images from the family. + void clear() { return map_.clear(); } + + // Creates a shallow copy of the family. The Images inside share their backing + // store with the original Images. + ImageFamily Clone() const; + + // Adds an image to the family. If another image is already present at the + // same size, it will be overwritten. + void Add(const gfx::Image& image); + + // Adds an image to the family. If another image is already present at the + // same size, it will be overwritten. + void Add(const gfx::ImageSkia& image_skia); + + // Gets the best image to use in a rectangle of |width|x|height|. + // Gets an image at the same aspect ratio as |width|:|height|, if possible, or + // if not, the closest aspect ratio. Among images of that aspect ratio, + // returns the smallest image with both its width and height bigger or equal + // to the requested size. If none exists, returns the largest image of that + // aspect ratio. If there are no images in the family, returns NULL. + const gfx::Image* GetBest(int width, int height) const; + + // Gets the best image to use in a rectangle of |size|. + // Gets an image at the same aspect ratio as |size.width()|:|size.height()|, + // if possible, or if not, the closest aspect ratio. Among images of that + // aspect ratio, returns the smallest image with both its width and height + // bigger or equal to the requested size. If none exists, returns the largest + // image of that aspect ratio. If there are no images in the family, returns + // NULL. + const gfx::Image* GetBest(const gfx::Size& size) const; + + // Gets an image of size |width|x|height|. If no image of that exact size + // exists, chooses the nearest larger image using GetBest() and scales it to + // the desired size. If there are no images in the family, returns an empty + // image. + gfx::Image CreateExact(int width, int height) const; + + // Gets an image of size |size|. If no image of that exact size exists, + // chooses the nearest larger image using GetBest() and scales it to the + // desired size. If there are no images in the family, returns an empty image. + gfx::Image CreateExact(const gfx::Size& size) const; + + private: + // Find the closest aspect ratio in the map to |desired_aspect|. + // Ties are broken by the thinner aspect. + // |map_| must not be empty. |desired_aspect| must be > 0.0. + float GetClosestAspect(float desired_aspect) const; + + // Gets an image with aspect ratio |aspect|, at the best size for |width|. + // Returns the smallest image of aspect ratio |aspect| with its width bigger + // or equal to |width|. If none exists, returns the largest image of aspect + // ratio |aspect|. Behavior is undefined if there is not at least one image in + // |map_| of aspect ratio |aspect|. + const gfx::Image* GetWithExactAspect(float aspect, int width) const; + + // Map from (aspect ratio, width) to image. + std::map map_; +}; + +} // namespace gfx + +#endif // UI_GFX_IMAGE_IMAGE_FAMILY_H_ diff --git a/image/image_family_unittest.cc b/image/image_family_unittest.cc new file mode 100644 index 000000000000..4bd2dc6f52c6 --- /dev/null +++ b/image/image_family_unittest.cc @@ -0,0 +1,249 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkImageInfo.h" +#include "ui/gfx/image/image.h" +#include "ui/gfx/image/image_family.h" +#include "ui/gfx/image/image_skia.h" +#include "ui/gfx/image/image_unittest_util.h" + +namespace { + +namespace gt = gfx::test; + +// Tests that |image| != NULL, and has the given width and height. +// This is a macro instead of a function, so that the correct line numbers are +// reported when a test fails. +#define EXPECT_IMAGE_NON_NULL_AND_SIZE(image, expected_width, expected_height) \ +do { \ + const gfx::Image* image_ = image; \ + EXPECT_TRUE(image_); \ + EXPECT_EQ(expected_width, image_->Width()); \ + EXPECT_EQ(expected_height, image_->Height()); \ +} while(0) + +// Tests that |image| has the given width and height. +// This is a macro instead of a function, so that the correct line numbers are +// reported when a test fails. +#define EXPECT_IMAGE_SIZE(image, expected_width, expected_height) \ +do { \ + const gfx::Image& image_ = image; \ + EXPECT_FALSE(image_.IsEmpty()); \ + EXPECT_EQ(expected_width, image_.Width()); \ + EXPECT_EQ(expected_height, image_.Height()); \ +} while(0) + +class ImageFamilyTest : public testing::Test { + public: + // Construct an ImageFamily. Implicitly tests Add and Empty. + void SetUp() override { + EXPECT_TRUE(image_family_.empty()); + + // Aspect ratio 1:1. + image_family_.Add(gt::CreateImageSkia(32, 32)); + EXPECT_FALSE(image_family_.empty()); + image_family_.Add(gt::CreateImageSkia(16, 16)); + image_family_.Add(gt::CreateImageSkia(64, 64)); + // Duplicate (should override previous one). + // Insert an Image directly, instead of an ImageSkia. + gfx::Image image(gt::CreateImageSkia(32, 32)); + image_family_.Add(image); + // Aspect ratio 1:4. + image_family_.Add(gt::CreateImageSkia(3, 12)); + image_family_.Add(gt::CreateImageSkia(12, 48)); + // Aspect ratio 4:1. + image_family_.Add(gt::CreateImageSkia(512, 128)); + image_family_.Add(gt::CreateImageSkia(256, 64)); + + EXPECT_FALSE(image_family_.empty()); + } + + gfx::ImageFamily image_family_; +}; + +TEST_F(ImageFamilyTest, Clear) { + image_family_.clear(); + EXPECT_TRUE(image_family_.empty()); +} + +TEST_F(ImageFamilyTest, MoveConstructor) { + gfx::ImageFamily family(std::move(image_family_)); + EXPECT_TRUE(image_family_.empty()); + EXPECT_FALSE(family.empty()); +} + +TEST_F(ImageFamilyTest, MoveAssignment) { + gfx::ImageFamily family; + EXPECT_TRUE(family.empty()); + family = std::move(image_family_); + EXPECT_TRUE(image_family_.empty()); + EXPECT_FALSE(family.empty()); +} + +TEST_F(ImageFamilyTest, Clone) { + gfx::ImageFamily family = image_family_.Clone(); + EXPECT_FALSE(image_family_.empty()); + EXPECT_FALSE(family.empty()); +} + +// Tests iteration over an ImageFamily. +TEST_F(ImageFamilyTest, Iteration) { + gfx::ImageFamily::const_iterator it = image_family_.begin(); + gfx::ImageFamily::const_iterator end = image_family_.end(); + + // Expect iteration in order of aspect ratio (from thinnest to widest), then + // size. + EXPECT_TRUE(it != end); + EXPECT_EQ(gfx::Size(3, 12), it->Size()); + ++it; + EXPECT_TRUE(it != end); + EXPECT_EQ(gfx::Size(12, 48), it->Size()); + it++; // Test post-increment. + EXPECT_TRUE(it != end); + EXPECT_EQ(gfx::Size(16, 16), it->Size()); + ++it; + EXPECT_TRUE(it != end); + EXPECT_EQ(gfx::Size(32, 32), it->Size()); + --it; // Test decrement + EXPECT_TRUE(it != end); + EXPECT_EQ(gfx::Size(16, 16), it->Size()); + ++it; + ++it; + EXPECT_TRUE(it != end); + EXPECT_EQ(gfx::Size(64, 64), (*it).Size()); // Test operator*. + ++it; + EXPECT_TRUE(it != end); + EXPECT_EQ(gfx::Size(256, 64), it->Size()); + ++it; + EXPECT_TRUE(it != end); + EXPECT_EQ(gfx::Size(512, 128), it->Size()); + ++it; + + EXPECT_TRUE(it == end); +} + +TEST_F(ImageFamilyTest, GetBest) { + // Get on an empty family. + gfx::ImageFamily empty_family; + EXPECT_TRUE(empty_family.empty()); + EXPECT_FALSE(empty_family.GetBest(32, 32)); + EXPECT_FALSE(empty_family.GetBest(0, 32)); + EXPECT_FALSE(empty_family.GetBest(32, 0)); + + // Get various aspect ratios and sizes on the sample family. + + // 0x0 (expect the smallest square image). + EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(0, 0), 16, 16); + // GetBest(0, N) or GetBest(N, 0) should be treated the same as GetBest(0, 0). + EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(0, 16), 16, 16); + EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(0, 64), 16, 16); + EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(16, 0), 16, 16); + EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(64, 0), 16, 16); + + // Thinner than thinnest image. + EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(2, 12), 3, 12); + EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(2, 13), 12, 48); + EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(10, 60), 12, 48); + + // Between two images' aspect ratio. + // Note: Testing the boundary around 1:2 and 2:1, half way to 1:4 and 4:1. + // Ties are broken by favouring the thinner aspect ratio. + EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(63, 32), 64, 64); + EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(64, 32), 64, 64); + EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(65, 32), 256, 64); + EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(32, 63), 64, 64); + EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(32, 64), 12, 48); + EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(32, 65), 12, 48); + + // Exact match aspect ratio. + // Exact match size. + EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(32, 32), 32, 32); + // Slightly smaller. + EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(31, 31), 32, 32); + // Much smaller. + EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(17, 17), 32, 32); + // Exact match size. + EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(16, 16), 16, 16); + // Smaller than any image. + EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(3, 3), 16, 16); + // Larger than any image. + EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(512, 512), 64, 64); + // 1:4 aspect ratio. + EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(16, 64), 12, 48); + EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(2, 8), 3, 12); + // 4:1 aspect ratio. + EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(64, 16), 256, 64); + EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(260, 65), 512, 128); + + // Wider than widest image. + EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(255, 51), 256, 64); + EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(260, 52), 512, 128); + EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(654, 129), 512, 128); +} + +TEST_F(ImageFamilyTest, CreateExact) { + // CreateExact on an empty family. + gfx::ImageFamily empty_family; + EXPECT_TRUE(empty_family.empty()); + EXPECT_TRUE(empty_family.CreateExact(32, 32).IsEmpty()); + EXPECT_TRUE(empty_family.CreateExact(0, 32).IsEmpty()); + EXPECT_TRUE(empty_family.CreateExact(32, 0).IsEmpty()); + + // CreateExact on a family with only empty images results in an empty image, + // despite the requested image size. + gfx::ImageFamily family_with_empty_image; + family_with_empty_image.Add(gfx::Image()); + EXPECT_FALSE(family_with_empty_image.empty()); + EXPECT_TRUE(family_with_empty_image.CreateExact(32, 32).IsEmpty()); + + // CreateExact on various aspect ratios and sizes on the sample family. + + // Targeting an image with width and/or height of 0 results in empty image. + EXPECT_TRUE(image_family_.CreateExact(0, 0).IsEmpty()); + EXPECT_TRUE(image_family_.CreateExact(0, 64).IsEmpty()); + EXPECT_TRUE(image_family_.CreateExact(64, 0).IsEmpty()); + + // Thinner than thinnest image. + EXPECT_IMAGE_SIZE(image_family_.CreateExact(2, 12), 2, 12); + + // Exact match aspect ratio. + // Exact match size. + EXPECT_IMAGE_SIZE(image_family_.CreateExact(32, 32), 32, 32); + // Much smaller. + EXPECT_IMAGE_SIZE(image_family_.CreateExact(17, 17), 17, 17); + // Smaller than any image. + EXPECT_IMAGE_SIZE(image_family_.CreateExact(3, 3), 3, 3); + // Larger than any image. + EXPECT_IMAGE_SIZE(image_family_.CreateExact(512, 512), 512, 512); + + // Wider than widest image. + EXPECT_IMAGE_SIZE(image_family_.CreateExact(255, 51), 255, 51); +} + +// Test adding and looking up images with 0 width and height. +TEST_F(ImageFamilyTest, ZeroWidthAndHeight) { + // An empty Image. Should be considered to have 0 width and height. + image_family_.Add(gfx::Image()); + // Images with 0 width OR height should be treated the same as an image with 0 + // width AND height (in fact, the ImageSkias should be indistinguishable). + image_family_.Add(gt::CreateImageSkia(32, 0)); + image_family_.Add(gt::CreateImageSkia(0, 32)); + + EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(0, 0), 0, 0); + EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(1, 1), 16, 16); + EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(32, 32), 32, 32); + + // GetBest(0, N) or GetBest(N, 0) should be treated the same as GetBest(0, 0). + EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(0, 1), 0, 0); + EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(0, 32), 0, 0); + EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(1, 0), 0, 0); + EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(32, 0), 0, 0); + + EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(1, 32), 12, 48); + EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(32, 1), 256, 64); +} + +} // namespace diff --git a/image/image_generic.cc b/image/image_generic.cc new file mode 100644 index 000000000000..f598390ccb80 --- /dev/null +++ b/image/image_generic.cc @@ -0,0 +1,126 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/image/image_platform.h" + +#include +#include +#include + +#include "base/logging.h" +#include "ui/gfx/codec/png_codec.h" +#include "ui/gfx/image/image_skia_source.h" + +namespace gfx { +namespace internal { + +namespace { + +// Returns a 16x16 red image to visually show error in decoding PNG. +ImageSkia GetErrorImageSkia() { + SkBitmap bitmap; + bitmap.allocN32Pixels(16, 16); + bitmap.eraseARGB(0xff, 0xff, 0, 0); + return ImageSkia(ImageSkiaRep(bitmap, 1.0f)); +} + +class PNGImageSource : public ImageSkiaSource { + public: + PNGImageSource() {} + + PNGImageSource(const PNGImageSource&) = delete; + PNGImageSource& operator=(const PNGImageSource&) = delete; + + ~PNGImageSource() override {} + + ImageSkiaRep GetImageForScale(float scale) override { + if (image_skia_reps_.empty()) + return ImageSkiaRep(); + + const ImageSkiaRep* rep = nullptr; + // gfx::ImageSkia passes one of the resource scale factors. The source + // should return: + // 1) The ImageSkiaRep with the highest scale if all available + // scales are smaller than |scale|. + // 2) The ImageSkiaRep with the smallest one that is larger than |scale|. + for (auto iter = image_skia_reps_.begin(); iter != image_skia_reps_.end(); + ++iter) { + if ((*iter).scale() == scale) + return (*iter); + if (!rep || rep->scale() < (*iter).scale()) + rep = &(*iter); + if (rep->scale() >= scale) + break; + } + return rep ? *rep : ImageSkiaRep(); + } + + const gfx::Size size() const { return size_; } + + bool AddPNGData(const ImagePNGRep& png_rep) { + const gfx::ImageSkiaRep rep = ToImageSkiaRep(png_rep); + if (rep.is_null()) + return false; + if (size_.IsEmpty()) + size_ = gfx::Size(rep.GetWidth(), rep.GetHeight()); + image_skia_reps_.insert(rep); + return true; + } + + static ImageSkiaRep ToImageSkiaRep(const ImagePNGRep& png_rep) { + scoped_refptr raw_data = png_rep.raw_data; + CHECK(raw_data.get()); + SkBitmap bitmap; + if (!PNGCodec::Decode(raw_data->front(), raw_data->size(), &bitmap)) { + LOG(ERROR) << "Unable to decode PNG for " << png_rep.scale << "."; + return ImageSkiaRep(); + } + return ImageSkiaRep(bitmap, png_rep.scale); + } + + private: + struct Compare { + bool operator()(const ImageSkiaRep& rep1, const ImageSkiaRep& rep2) const { + return rep1.scale() < rep2.scale(); + } + }; + + typedef std::set ImageSkiaRepSet; + ImageSkiaRepSet image_skia_reps_; + gfx::Size size_; +}; + +} // namespace + +ImageSkia ImageSkiaFromPNG(const std::vector& image_png_reps) { + if (image_png_reps.empty()) + return GetErrorImageSkia(); + std::unique_ptr image_source(new PNGImageSource); + + for (size_t i = 0; i < image_png_reps.size(); ++i) { + if (!image_source->AddPNGData(image_png_reps[i])) + return GetErrorImageSkia(); + } + const gfx::Size& size = image_source->size(); + DCHECK(!size.IsEmpty()); + if (size.IsEmpty()) + return GetErrorImageSkia(); + return ImageSkia(std::move(image_source), size); +} + +scoped_refptr Get1xPNGBytesFromImageSkia( + const ImageSkia* image_skia) { + ImageSkiaRep image_skia_rep = image_skia->GetRepresentation(1.0f); + + scoped_refptr png_bytes(new base::RefCountedBytes()); + if (image_skia_rep.scale() != 1.0f || + !PNGCodec::EncodeBGRASkBitmap(image_skia_rep.GetBitmap(), false, + &png_bytes->data())) { + return nullptr; + } + return png_bytes; +} + +} // namespace internal +} // namespace gfx diff --git a/image/image_ios.mm b/image/image_ios.mm new file mode 100644 index 000000000000..a2401315830d --- /dev/null +++ b/image/image_ios.mm @@ -0,0 +1,134 @@ +// Copyright 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/image/image_platform.h" + +#include +#import +#include +#include + +#include "base/logging.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/mac/scoped_nsobject.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/image/image_png_rep.h" +#include "ui/gfx/image/image_skia.h" +#include "ui/gfx/image/image_skia_util_ios.h" + +namespace gfx { +namespace internal { + +namespace { + +// Returns a 16x16 red UIImage to visually show when a UIImage cannot be +// created from PNG data. Logs error as well. +// Caller takes ownership of returned UIImage. +UIImage* CreateErrorUIImage(float scale) { + LOG(ERROR) << "Unable to decode PNG into UIImage."; + base::ScopedCFTypeRef color_space( + CGColorSpaceCreateDeviceRGB()); + base::ScopedCFTypeRef context(CGBitmapContextCreate( + nullptr, // Allow CG to allocate memory. + 16, // width + 16, // height + 8, // bitsPerComponent + 0, // CG will calculate by default. + color_space, kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host)); + CGContextSetRGBFillColor(context, 1.0, 0.0, 0.0, 1.0); + CGContextFillRect(context, CGRectMake(0.0, 0.0, 16, 16)); + base::ScopedCFTypeRef cg_image( + CGBitmapContextCreateImage(context)); + return [[UIImage imageWithCGImage:cg_image.get() + scale:scale + orientation:UIImageOrientationUp] retain]; +} + +// Converts from ImagePNGRep to UIImage. +UIImage* CreateUIImageFromImagePNGRep(const gfx::ImagePNGRep& image_png_rep) { + float scale = image_png_rep.scale; + scoped_refptr png = image_png_rep.raw_data; + CHECK(png.get()); + NSData* data = [NSData dataWithBytes:png->front() length:png->size()]; + UIImage* image = [[UIImage alloc] initWithData:data scale:scale]; + return image ? image : CreateErrorUIImage(scale); +} + +} // namespace + +scoped_refptr Get1xPNGBytesFromUIImage( + UIImage* uiimage) { + NSData* data = UIImagePNGRepresentation(uiimage); + + if ([data length] == 0) + return nullptr; + + scoped_refptr png_bytes( + new base::RefCountedBytes()); + png_bytes->data().resize([data length]); + [data getBytes:&png_bytes->data().at(0) length:[data length]]; + return png_bytes; +} + +UIImage* UIImageFromPNG(const std::vector& image_png_reps) { + float ideal_scale = ImageSkia::GetMaxSupportedScale(); + + if (image_png_reps.empty()) + return CreateErrorUIImage(ideal_scale); + + // Find best match for |ideal_scale|. + float smallest_diff = std::numeric_limits::max(); + size_t closest_index = 0u; + for (size_t i = 0; i < image_png_reps.size(); ++i) { + float scale = image_png_reps[i].scale; + float diff = std::abs(ideal_scale - scale); + if (diff < smallest_diff) { + smallest_diff = diff; + closest_index = i; + } + } + + return + [CreateUIImageFromImagePNGRep(image_png_reps[closest_index]) autorelease]; +} + +scoped_refptr Get1xPNGBytesFromImageSkia( + const ImageSkia* skia) { + // iOS does not expose libpng, so conversion from ImageSkia to PNG must go + // through UIImage. + // TODO(rohitrao): Rewrite the callers of this function to save the UIImage + // representation in the gfx::Image. If we're generating it, we might as well + // hold on to it. + const gfx::ImageSkiaRep& image_skia_rep = skia->GetRepresentation(1.0f); + if (image_skia_rep.scale() != 1.0f) + return nullptr; + + UIImage* image = UIImageFromImageSkiaRep(image_skia_rep); + return Get1xPNGBytesFromUIImage(image); +} + +ImageSkia ImageSkiaFromPNG( + const std::vector& image_png_reps) { + // iOS does not expose libpng, so conversion from PNG to ImageSkia must go + // through UIImage. + ImageSkia image_skia; + for (size_t i = 0; i < image_png_reps.size(); ++i) { + base::scoped_nsobject uiimage( + CreateUIImageFromImagePNGRep(image_png_reps[i])); + gfx::ImageSkiaRep image_skia_rep = ImageSkiaRepOfScaleFromUIImage( + uiimage, image_png_reps[i].scale); + if (!image_skia_rep.is_null()) + image_skia.AddRepresentation(image_skia_rep); + } + return image_skia; +} + +gfx::Size UIImageSize(UIImage* image) { + int width = static_cast(image.size.width); + int height = static_cast(image.size.height); + return gfx::Size(width, height); +} + +} // namespace internal +} // namespace gfx diff --git a/image/image_ios_unittest.mm b/image/image_ios_unittest.mm new file mode 100644 index 000000000000..c3cd6395ae42 --- /dev/null +++ b/image/image_ios_unittest.mm @@ -0,0 +1,112 @@ +// Copyright (c) 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import +#include +#import + +#include "base/cxx17_backports.h" +#include "base/mac/scoped_cftyperef.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/image/image.h" +#include "ui/gfx/image/image_skia.h" + +namespace { + +// Helper function to return a UIImage with the given size and scale. +UIImage* UIImageWithSizeAndScale(CGFloat width, CGFloat height, CGFloat scale) { + CGSize target_size = CGSizeMake(width * scale, height * scale); + + // Create a UIImage directly from a CGImage in order to control the exact + // pixel size of the underlying image. + base::ScopedCFTypeRef color_space( + CGColorSpaceCreateDeviceRGB()); + base::ScopedCFTypeRef context(CGBitmapContextCreate( + NULL, + target_size.width, + target_size.height, + 8, + target_size.width * 4, + color_space, + kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host)); + + CGRect target_rect = CGRectMake(0, 0, + target_size.width, target_size.height); + CGContextSetFillColorWithColor(context, [[UIColor redColor] CGColor]); + CGContextFillRect(context, target_rect); + + base::ScopedCFTypeRef cg_image( + CGBitmapContextCreateImage(context)); + return [UIImage imageWithCGImage:cg_image + scale:scale + orientation:UIImageOrientationUp]; +} + + +class ImageIOSTest : public testing::Test { + public: + ImageIOSTest() {} + + ImageIOSTest(const ImageIOSTest&) = delete; + ImageIOSTest& operator=(const ImageIOSTest&) = delete; + + ~ImageIOSTest() override {} + + void SetUp() override { + original_scale_factors_ = gfx::ImageSkia::GetSupportedScales(); + } + + void TearDown() override { + gfx::ImageSkia::SetSupportedScales(original_scale_factors_); + } + + private: + // Used to save and restore the scale factors in effect before this test. + std::vector original_scale_factors_; +}; + +// Tests image conversion when the scale factor of the source image is not in +// the list of supported scale factors. +TEST_F(ImageIOSTest, ImageConversionWithUnsupportedScaleFactor) { + const CGFloat kWidth = 200; + const CGFloat kHeight = 100; + const CGFloat kTestScales[3] = { 1.0f, 2.0f, 3.0f }; + + for (size_t i = 0; i < base::size(kTestScales); ++i) { + for (size_t j = 0; j < base::size(kTestScales); ++j) { + const CGFloat source_scale = kTestScales[i]; + const CGFloat supported_scale = kTestScales[j]; + + // Set the supported scale for testing. + std::vector supported_scales; + supported_scales.push_back(supported_scale); + gfx::ImageSkia::SetSupportedScales(supported_scales); + + // Create an UIImage with the appropriate source_scale. + UIImage* ui_image = + UIImageWithSizeAndScale(kWidth, kHeight, source_scale); + ASSERT_EQ(kWidth, ui_image.size.width); + ASSERT_EQ(kHeight, ui_image.size.height); + ASSERT_EQ(source_scale, ui_image.scale); + + // Convert to SkBitmap and test its size. + gfx::Image to_skbitmap([ui_image retain]); + const SkBitmap* bitmap = to_skbitmap.ToSkBitmap(); + ASSERT_TRUE(bitmap != NULL); + EXPECT_EQ(kWidth * supported_scale, bitmap->width()); + EXPECT_EQ(kHeight * supported_scale, bitmap->height()); + + // Convert to ImageSkia and test its size. + gfx::Image to_imageskia([ui_image retain]); + const gfx::ImageSkia* imageskia = to_imageskia.ToImageSkia(); + EXPECT_EQ(kWidth, imageskia->width()); + EXPECT_EQ(kHeight, imageskia->height()); + + // TODO(rohitrao): Convert from ImageSkia back to UIImage. This should + // scale the image based on the current set of supported scales. + } + } +} + +} // namespace diff --git a/image/image_mac.mm b/image/image_mac.mm new file mode 100644 index 000000000000..939099fe4c3d --- /dev/null +++ b/image/image_mac.mm @@ -0,0 +1,119 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/image/image_platform.h" + +#import +#include + +#include "base/debug/dump_without_crashing.h" +#include "base/logging.h" +#include "base/mac/scoped_nsobject.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/image/image_png_rep.h" + +namespace gfx { +namespace internal { + +namespace { + +// Returns a 16x16 red NSImage to visually show when a NSImage cannot be +// created from PNG data. +// Caller takes ownership of the returned NSImage. +NSImage* GetErrorNSImage() { + NSRect rect = NSMakeRect(0, 0, 16, 16); + NSImage* image = [[NSImage alloc] initWithSize:rect.size]; + [image lockFocus]; + [[NSColor colorWithDeviceRed:1.0 green:0.0 blue:0.0 alpha:1.0] set]; + NSRectFill(rect); + [image unlockFocus]; + return image; +} + +} // namespace + +scoped_refptr Get1xPNGBytesFromNSImage( + NSImage* nsimage) { + DCHECK(nsimage); + CGImageRef cg_image = [nsimage CGImageForProposedRect:NULL + context:nil + hints:nil]; + if (!cg_image) { + // TODO(crbug.com/1271762): Look at DumpWithoutCrashing() reports to figure + // out what's going on here. + return scoped_refptr(); + } + base::scoped_nsobject ns_bitmap( + [[NSBitmapImageRep alloc] initWithCGImage:cg_image]); + NSData* ns_data = [ns_bitmap representationUsingType:NSPNGFileType + properties:@{}]; + const unsigned char* bytes = + static_cast([ns_data bytes]); + scoped_refptr refcounted_bytes( + new base::RefCountedBytes()); + refcounted_bytes->data().assign(bytes, bytes + [ns_data length]); + return refcounted_bytes; +} + +NSImage* NSImageFromPNG(const std::vector& image_png_reps, + CGColorSpaceRef color_space) { + if (image_png_reps.empty()) { + LOG(ERROR) << "Unable to decode PNG."; + return GetErrorNSImage(); + } + + base::scoped_nsobject image; + for (size_t i = 0; i < image_png_reps.size(); ++i) { + scoped_refptr png = image_png_reps[i].raw_data; + CHECK(png.get()); + base::scoped_nsobject ns_data( + [[NSData alloc] initWithBytes:png->front() length:png->size()]); + base::scoped_nsobject ns_image_rep( + [[NSBitmapImageRep alloc] initWithData:ns_data]); + if (!ns_image_rep) { + LOG(ERROR) << "Unable to decode PNG at " + << image_png_reps[i].scale + << "."; + return GetErrorNSImage(); + } + + // PNGCodec ignores colorspace related ancillary chunks (sRGB, iCCP). Ignore + // colorspace information when decoding directly from PNG to an NSImage so + // that the conversions: PNG -> SkBitmap -> NSImage and PNG -> NSImage + // produce visually similar results. + CGColorSpaceModel decoded_color_space_model = CGColorSpaceGetModel( + [[ns_image_rep colorSpace] CGColorSpace]); + CGColorSpaceModel color_space_model = CGColorSpaceGetModel(color_space); + if (decoded_color_space_model == color_space_model) { + base::scoped_nsobject ns_color_space( + [[NSColorSpace alloc] initWithCGColorSpace:color_space]); + NSBitmapImageRep* ns_retagged_image_rep = + [ns_image_rep + bitmapImageRepByRetaggingWithColorSpace:ns_color_space]; + if (ns_retagged_image_rep && ns_retagged_image_rep != ns_image_rep) + ns_image_rep.reset([ns_retagged_image_rep retain]); + } + + if (!image.get()) { + float scale = image_png_reps[i].scale; + NSSize image_size = NSMakeSize([ns_image_rep pixelsWide] / scale, + [ns_image_rep pixelsHigh] / scale); + image.reset([[NSImage alloc] initWithSize:image_size]); + } + [image addRepresentation:ns_image_rep]; + } + + return image.autorelease(); +} + +gfx::Size NSImageSize(NSImage* image) { + NSSize size = [image size]; + int width = static_cast(size.width); + int height = static_cast(size.height); + return gfx::Size(width, height); +} + +} // namespace internal +} // namespace gfx + diff --git a/image/image_mac_unittest.mm b/image/image_mac_unittest.mm new file mode 100644 index 000000000000..226b61dc97cd --- /dev/null +++ b/image/image_mac_unittest.mm @@ -0,0 +1,209 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +#include "base/mac/scoped_nsobject.h" +#include "base/macros.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/image/image.h" +#include "ui/gfx/image/image_png_rep.h" +#include "ui/gfx/image/image_skia.h" +#include "ui/gfx/image/image_skia_util_mac.h" +#include "ui/gfx/image/image_unittest_util.h" + +namespace { + +// Returns true if the structure of |ns_image| matches the structure +// described by |width|, |height|, and |scales|. +// The structure matches if: +// - |ns_image| is not nil. +// - |ns_image| has NSImageReps of |scales|. +// - Each of the NSImageReps has a pixel size of [|ns_image| size] * +// scale. +bool NSImageStructureMatches( + NSImage* ns_image, + int width, + int height, + const std::vector& scales) { + if (!ns_image || + [ns_image size].width != width || + [ns_image size].height != height || + [ns_image representations].count != scales.size()) { + return false; + } + + for (size_t i = 0; i < scales.size(); ++i) { + float scale = scales[i]; + bool found_match = false; + for (size_t j = 0; j < [ns_image representations].count; ++j) { + NSImageRep* ns_image_rep = [ns_image representations][j]; + if (ns_image_rep && + [ns_image_rep pixelsWide] == width * scale && + [ns_image_rep pixelsHigh] == height * scale) { + found_match = true; + break; + } + } + if (!found_match) + return false; + } + return true; +} + +void BitmapImageRep(int width, int height, + NSBitmapImageRep** image_rep) { + *image_rep = [[[NSBitmapImageRep alloc] + initWithBitmapDataPlanes:NULL + pixelsWide:width + pixelsHigh:height + bitsPerSample:8 + samplesPerPixel:3 + hasAlpha:NO + isPlanar:NO + colorSpaceName:NSDeviceRGBColorSpace + bitmapFormat:0 + bytesPerRow:0 + bitsPerPixel:0] + autorelease]; + unsigned char* image_rep_data = [*image_rep bitmapData]; + for (int i = 0; i < width * height * 3; ++i) + image_rep_data[i] = 255; +} + +class ImageMacTest : public testing::Test { + public: + ImageMacTest() { + gfx::ImageSkia::SetSupportedScales(gfx::test::Get1xAnd2xScales()); + } + + ImageMacTest(const ImageMacTest&) = delete; + ImageMacTest& operator=(const ImageMacTest&) = delete; +}; + +namespace gt = gfx::test; + +TEST_F(ImageMacTest, MultiResolutionNSImageToImageSkia) { + const int kWidth1x = 10; + const int kHeight1x = 12; + const int kWidth2x = 20; + const int kHeight2x = 24; + + NSBitmapImageRep* ns_image_rep1; + BitmapImageRep(kWidth1x, kHeight1x, &ns_image_rep1); + NSBitmapImageRep* ns_image_rep2; + BitmapImageRep(kWidth2x, kHeight2x, &ns_image_rep2); + base::scoped_nsobject ns_image( + [[NSImage alloc] initWithSize:NSMakeSize(kWidth1x, kHeight1x)]); + [ns_image addRepresentation:ns_image_rep1]; + [ns_image addRepresentation:ns_image_rep2]; + + gfx::Image image(ns_image); + + EXPECT_EQ(1u, image.RepresentationCount()); + + const gfx::ImageSkia* image_skia = image.ToImageSkia(); + + std::vector scales; + scales.push_back(1.0f); + scales.push_back(2.0f); + EXPECT_TRUE(gt::ImageSkiaStructureMatches(*image_skia, kWidth1x, kHeight1x, + scales)); + + // ToImageSkia should create a second representation. + EXPECT_EQ(2u, image.RepresentationCount()); +} + +// Test that convertng to an ImageSkia from an NSImage with scale factors +// other than 1x and 2x results in an ImageSkia with scale factors 1x and +// 2x; +TEST_F(ImageMacTest, UnalignedMultiResolutionNSImageToImageSkia) { + const int kWidth1x = 10; + const int kHeight1x= 12; + const int kWidth4x = 40; + const int kHeight4x = 48; + + NSBitmapImageRep* ns_image_rep4; + BitmapImageRep(kWidth4x, kHeight4x, &ns_image_rep4); + base::scoped_nsobject ns_image( + [[NSImage alloc] initWithSize:NSMakeSize(kWidth1x, kHeight1x)]); + [ns_image addRepresentation:ns_image_rep4]; + + gfx::Image image(ns_image); + + EXPECT_EQ(1u, image.RepresentationCount()); + + const gfx::ImageSkia* image_skia = image.ToImageSkia(); + + std::vector scales; + scales.push_back(1.0f); + scales.push_back(2.0f); + EXPECT_TRUE(gt::ImageSkiaStructureMatches(*image_skia, kWidth1x, kHeight1x, + scales)); + + // ToImageSkia should create a second representation. + EXPECT_EQ(2u, image.RepresentationCount()); +} + +TEST_F(ImageMacTest, MultiResolutionImageSkiaToNSImage) { + const int kWidth1x = 10; + const int kHeight1x= 12; + const int kWidth2x = 20; + const int kHeight2x = 24; + + gfx::ImageSkia image_skia; + image_skia.AddRepresentation(gfx::ImageSkiaRep( + gt::CreateBitmap(kWidth1x, kHeight1x), 1.0f)); + image_skia.AddRepresentation(gfx::ImageSkiaRep( + gt::CreateBitmap(kWidth2x, kHeight2x), 2.0f)); + + gfx::Image image(image_skia); + + EXPECT_EQ(1u, image.RepresentationCount()); + EXPECT_EQ(2u, image.ToImageSkia()->image_reps().size()); + + NSImage* ns_image = image.ToNSImage(); + + std::vector scales; + scales.push_back(1.0f); + scales.push_back(2.0f); + EXPECT_TRUE(NSImageStructureMatches(ns_image, kWidth1x, kHeight1x, scales)); + + // Request for NSImage* should create a second representation. + EXPECT_EQ(2u, image.RepresentationCount()); +} + +TEST_F(ImageMacTest, MultiResolutionPNGToNSImage) { + const int kSize1x = 25; + const int kSize2x = 50; + + scoped_refptr bytes1x = gt::CreatePNGBytes(kSize1x); + scoped_refptr bytes2x = gt::CreatePNGBytes(kSize2x); + std::vector image_png_reps; + image_png_reps.push_back(gfx::ImagePNGRep(bytes1x, 1.0f)); + image_png_reps.push_back(gfx::ImagePNGRep(bytes2x, 2.0f)); + + gfx::Image image(image_png_reps); + + NSImage* ns_image = image.ToNSImage(); + std::vector scales; + scales.push_back(1.0f); + scales.push_back(2.0f); + EXPECT_TRUE(NSImageStructureMatches(ns_image, kSize1x, kSize1x, scales)); + + // Converting from PNG to NSImage should not go through ImageSkia. + EXPECT_FALSE(image.HasRepresentation(gfx::Image::kImageRepSkia)); + + // Convert to ImageSkia to check pixel contents of NSImageReps. + gfx::ImageSkia image_skia = gfx::ImageSkiaFromNSImage(ns_image); + EXPECT_TRUE(gt::ArePNGBytesCloseToBitmap( + *bytes1x, image_skia.GetRepresentation(1.0f).GetBitmap(), + gt::MaxColorSpaceConversionColorShift())); + EXPECT_TRUE(gt::ArePNGBytesCloseToBitmap( + *bytes2x, image_skia.GetRepresentation(2.0f).GetBitmap(), + gt::MaxColorSpaceConversionColorShift())); +} + +} // namespace diff --git a/image/image_platform.h b/image/image_platform.h new file mode 100644 index 000000000000..3e37e8cdfe7c --- /dev/null +++ b/image/image_platform.h @@ -0,0 +1,56 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file declares platform-specific helper functions in gfx::internal for +// use by image.cc. +// +// The functions are implemented in image_generic.cc (all platforms other than +// iOS), image_ios.mm and image_mac.mm. + +#ifndef UI_GFX_IMAGE_IMAGE_PLATFORM_H_ +#define UI_GFX_IMAGE_IMAGE_PLATFORM_H_ + +#include +#include + +#include "base/memory/ref_counted.h" +#include "base/memory/ref_counted_memory.h" +#include "build/build_config.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/image/image_png_rep.h" +#include "ui/gfx/image/image_skia.h" + +#if defined(OS_IOS) +#include "base/mac/foundation_util.h" +#include "ui/gfx/image/image_skia_util_ios.h" +#elif defined(OS_MAC) +#include "base/mac/foundation_util.h" +#include "base/mac/mac_util.h" +#include "ui/gfx/image/image_skia_util_mac.h" +#endif + +namespace gfx { +namespace internal { + +#if defined(OS_IOS) +scoped_refptr Get1xPNGBytesFromUIImage( + UIImage* uiimage); +UIImage* UIImageFromPNG(const std::vector& image_png_reps); +gfx::Size UIImageSize(UIImage* image); +#elif defined(OS_MAC) +scoped_refptr Get1xPNGBytesFromNSImage( + NSImage* nsimage); +NSImage* NSImageFromPNG(const std::vector& image_png_reps, + CGColorSpaceRef color_space); +gfx::Size NSImageSize(NSImage* image); +#endif // defined(OS_MAC) + +ImageSkia ImageSkiaFromPNG(const std::vector& image_png_reps); +scoped_refptr Get1xPNGBytesFromImageSkia( + const ImageSkia* image_skia); + +} // namespace internal +} // namespace gfx + +#endif // UI_GFX_IMAGE_IMAGE_PLATFORM_H_ diff --git a/image/image_png_rep.cc b/image/image_png_rep.cc new file mode 100644 index 000000000000..4115baf19826 --- /dev/null +++ b/image/image_png_rep.cc @@ -0,0 +1,40 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/image/image_png_rep.h" + +#include "base/logging.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/gfx/codec/png_codec.h" +#include "ui/gfx/geometry/size.h" + +namespace gfx { + +ImagePNGRep::ImagePNGRep() = default; + +ImagePNGRep::ImagePNGRep(const scoped_refptr& data, + float data_scale) + : raw_data(data), + scale(data_scale) { +} + +ImagePNGRep::ImagePNGRep(const ImagePNGRep& other) = default; + +ImagePNGRep::~ImagePNGRep() { +} + +gfx::Size ImagePNGRep::Size() const { + // The only way to get the width and height of a raw PNG stream, at least + // using the gfx::PNGCodec API, is to decode the whole thing. + CHECK(raw_data.get()); + SkBitmap bitmap; + if (!gfx::PNGCodec::Decode(raw_data->front(), raw_data->size(), + &bitmap)) { + LOG(ERROR) << "Unable to decode PNG."; + return gfx::Size(0, 0); + } + return gfx::Size(bitmap.width(), bitmap.height()); +} + +} // namespace gfx diff --git a/image/image_png_rep.h b/image/image_png_rep.h new file mode 100644 index 000000000000..0a75f197d31e --- /dev/null +++ b/image/image_png_rep.h @@ -0,0 +1,36 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_IMAGE_IMAGE_PNG_REP_H_ +#define UI_GFX_IMAGE_IMAGE_PNG_REP_H_ + +#include "base/memory/ref_counted_memory.h" +#include "ui/gfx/gfx_export.h" + +namespace gfx { +class Size; + +// An ImagePNGRep represents a bitmap's png encoded data and the scale factor it +// was intended for. +struct GFX_EXPORT ImagePNGRep { + public: + ImagePNGRep(); + ImagePNGRep(const scoped_refptr& data, + float data_scale); + ImagePNGRep(const ImagePNGRep& other); + ~ImagePNGRep(); + + // Width and height of the image, in pixels. + // If the image is invalid, returns gfx::Size(0, 0). + // Warning: This operation processes the entire image stream, so its result + // should be cached if it is needed multiple times. + gfx::Size Size() const; + + scoped_refptr raw_data; + float scale = 1.0f; +}; + +} // namespace gfx + +#endif // UI_GFX_IMAGE_IMAGE_PNG_REP_H_ diff --git a/image/image_skia.cc b/image/image_skia.cc new file mode 100644 index 000000000000..a8d768007d08 --- /dev/null +++ b/image/image_skia.cc @@ -0,0 +1,554 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/image/image_skia.h" + +#include + +#include +#include +#include +#include + +#include "base/check_op.h" +#include "base/command_line.h" +#include "base/memory/ptr_util.h" +#include "base/no_destructor.h" +#include "base/sequence_checker.h" +#include "build/build_config.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/geometry/size_conversions.h" +#include "ui/gfx/geometry/skia_conversions.h" +#include "ui/gfx/image/image_skia_operations.h" +#include "ui/gfx/image/image_skia_source.h" +#include "ui/gfx/switches.h" + +namespace gfx { +namespace { + +// static +gfx::ImageSkiaRep& NullImageRep() { + static base::NoDestructor null_image_rep; + return *null_image_rep; +} + +std::vector* g_supported_scales = NULL; + +// The difference to fall back to the smaller scale factor rather than the +// larger one. For example, assume 1.20 is requested but only 1.0 and 2.0 are +// supported. In that case, not fall back to 2.0 but 1.0, and then expand +// the image to 1.25. +const float kFallbackToSmallerScaleDiff = 0.20f; + +// Maps to the closest supported scale. Returns an exact match, a smaller +// scale within 0.2 units, the nearest larger scale, or the min/max +// supported scale. +float MapToSupportedScale(float scale) { + for (float supported_scale : *g_supported_scales) { + if (supported_scale + kFallbackToSmallerScaleDiff >= scale) + return supported_scale; + } + return g_supported_scales->back(); +} + +} // namespace + +namespace internal { +namespace { + +ImageSkiaRep ScaleImageSkiaRep(const ImageSkiaRep& rep, float target_scale) { + if (rep.is_null() || rep.scale() == target_scale) + return rep; + + gfx::Size scaled_size = + gfx::ScaleToCeiledSize(rep.pixel_size(), target_scale / rep.scale()); + return ImageSkiaRep( + skia::ImageOperations::Resize(rep.GetBitmap(), + skia::ImageOperations::RESIZE_LANCZOS3, + scaled_size.width(), scaled_size.height()), + target_scale); +} + +} // namespace + +// A helper class such that ImageSkia can be cheaply copied. ImageSkia holds a +// refptr instance of ImageSkiaStorage, which in turn holds all of ImageSkia's +// information. +// The ImageSkia, and this class, are designed to be thread-safe in their const +// methods, but also are bound to a single sequence for mutating methods. +// NOTE: The FindRepresentation() method const and thread-safe *iff* it is +// called with `fetch_new_image` set to true. Otherwise it may mutate the +// class, which is not thread-safe. Internally, mutation is bound to a single +// sequence with a `base::SequenceChecker`. +class ImageSkiaStorage : public base::RefCountedThreadSafe { + public: + ImageSkiaStorage(std::unique_ptr source, + const gfx::Size& size); + ImageSkiaStorage(std::unique_ptr source, float scale); + + ImageSkiaStorage(const ImageSkiaStorage&) = delete; + ImageSkiaStorage& operator=(const ImageSkiaStorage&) = delete; + + bool has_source() const { return source_ != nullptr; } + std::vector& image_reps() { return image_reps_; } + const gfx::Size& size() const { return size_; } + bool read_only() const { return read_only_; } + + void DeleteSource(); + void SetReadOnly(); + void DetachFromSequence(); + + // Checks if the current thread can safely modify the storage. + bool CanModify() const; + + // Checks if the current thread can safely read the storage. + bool CanRead() const; + + // Add a new representation. This checks if the scale of the added image + // is not 1.0f, and mark the existing rep as scaled to make + // the image high DPI aware. + void AddRepresentation(const ImageSkiaRep& image); + + // Returns whether the underlying image source can provide a representation at + // any scale. In this case, the caller is guaranteed that + // FindRepresentation(..., true) will always succeed. + bool HasRepresentationAtAllScales() const; + + // Returns the iterator of the image rep whose density best matches + // |scale|. If the image for the |scale| doesn't exist in the storage and + // |storage| is set, it fetches new image by calling + // |ImageSkiaSource::GetImageForScale|. There are two modes to deal with + // arbitrary scale factors. + // 1: Invoke GetImageForScale with requested scale and if the source + // returns the image with different scale (if the image doesn't exist in + // resource, for example), it will fallback to closest image rep. + // 2: Invoke GetImageForScale with the closest known scale to the requested + // one and rescale the image. + // Right now only Windows uses 2 and other platforms use 1 by default. + // TODO(mukai, oshima): abandon 1 code path and use 2 for every platforms. + std::vector::const_iterator FindRepresentation( + float scale, + bool fetch_new_image) const; + + private: + friend class base::RefCountedThreadSafe; + + virtual ~ImageSkiaStorage(); + + // Each entry in here has a different scale and is returned when looking for + // an ImageSkiaRep of that scale. + std::vector image_reps_; + + // If no ImageSkiaRep exists in `image_reps_` for a given scale, the `source_` + // is queried to produce an ImageSkiaRep at that scale. + std::unique_ptr source_; + + // Size of the image in DIP. + gfx::Size size_; + + bool read_only_; + + // This isn't using SEQUENCE_CHECKER() macros because we use the sequence + // checker outside of DCHECKs to make branching decisions. + base::SequenceChecker sequence_checker_; // nocheck +}; + +ImageSkiaStorage::ImageSkiaStorage(std::unique_ptr source, + const gfx::Size& size) + : source_(std::move(source)), size_(size), read_only_(false) {} + +ImageSkiaStorage::ImageSkiaStorage(std::unique_ptr source, + float scale) + : source_(std::move(source)), read_only_(false) { + DCHECK(source_); + auto it = FindRepresentation(scale, true); + if (it == image_reps_.end() || it->is_null()) + source_.reset(); + else + size_.SetSize(it->GetWidth(), it->GetHeight()); +} + +void ImageSkiaStorage::DeleteSource() { + source_.reset(); +} + +void ImageSkiaStorage::SetReadOnly() { + read_only_ = true; +} + +void ImageSkiaStorage::DetachFromSequence() { + sequence_checker_.DetachFromSequence(); +} + +bool ImageSkiaStorage::CanModify() const { + return !read_only_ && sequence_checker_.CalledOnValidSequence(); +} + +bool ImageSkiaStorage::CanRead() const { + return (read_only_ && !source_) || sequence_checker_.CalledOnValidSequence(); +} + +void ImageSkiaStorage::AddRepresentation(const ImageSkiaRep& image) { + // Explicitly adding a representation makes no sense for images that + // inherently have representations at all scales already. + DCHECK(!HasRepresentationAtAllScales()); + + if (image.scale() != 1.0f) { + ImageSkia::ImageSkiaReps::iterator it; + for (it = image_reps_.begin(); it < image_reps_.end(); ++it) { + if (it->unscaled()) { + DCHECK_EQ(1.0f, it->scale()); + *it = ImageSkiaRep(it->GetBitmap(), it->scale()); + break; + } + } + } + image_reps_.push_back(image); +} + +bool ImageSkiaStorage::HasRepresentationAtAllScales() const { + return source_ && source_->HasRepresentationAtAllScales(); +} + +std::vector::const_iterator ImageSkiaStorage::FindRepresentation( + float scale, + bool fetch_new_image) const { + auto closest_iter = image_reps_.end(); + auto exact_iter = image_reps_.end(); + float smallest_diff = std::numeric_limits::max(); + for (auto it = image_reps_.begin(); it < image_reps_.end(); ++it) { + if (it->scale() == scale) { + // found exact match + fetch_new_image = false; + if (it->is_null()) + continue; + exact_iter = it; + break; + } + float diff = std::abs(it->scale() - scale); + if (diff < smallest_diff && !it->is_null()) { + closest_iter = it; + smallest_diff = diff; + } + } + + if (fetch_new_image && source_) { + DCHECK(sequence_checker_.CalledOnValidSequence()) + << "An ImageSkia with the source must be accessed by the same " + "sequence."; + // This method is const and thread-safe, unless `fetch_new_image` is true, + // in which case the method is no longer considered const and we ensure + // that it is used in this way on a single sequence at a time with the above + // `sequence_checker_`. + auto* mutable_this = const_cast(this); + + ImageSkiaRep image; + float resource_scale = scale; + if (!HasRepresentationAtAllScales() && g_supported_scales) + resource_scale = MapToSupportedScale(scale); + if (scale != resource_scale) { + auto iter = FindRepresentation(resource_scale, fetch_new_image); + CHECK(iter != image_reps_.end()); + image = iter->unscaled() ? (*iter) : ScaleImageSkiaRep(*iter, scale); + } else { + image = source_->GetImageForScale(scale); + // Image may be missing for the specified scale in some cases, such like + // looking up 2x resources but the 2x resource pack is missing. Fall back + // to 1x and re-scale it. + if (image.is_null() && scale != 1.0f) + image = ScaleImageSkiaRep(source_->GetImageForScale(1.0f), scale); + } + + // If the source returned the new image, store it. + if (!image.is_null() && + std::find_if(image_reps_.begin(), image_reps_.end(), + [&image](const ImageSkiaRep& rep) { + return rep.scale() == image.scale(); + }) == image_reps_.end()) { + mutable_this->image_reps_.push_back(image); + } + + // image_reps_ should have the exact much now, or we will fallback + // to the new closest value. We pass false to prevent the generation step + // from running again and repeating the recursion. + return FindRepresentation(scale, false); + } + return exact_iter != image_reps_.end() ? exact_iter : closest_iter; +} + +ImageSkiaStorage::~ImageSkiaStorage() = default; + +} // internal + +ImageSkia::ImageSkia() {} + +ImageSkia::ImageSkia(std::unique_ptr source, + const gfx::Size& size) + : storage_( + base::MakeRefCounted(std::move(source), + size)) { + DCHECK(storage_->has_source()); + // No other thread has reference to this, so it's safe to detach the sequence. + DetachStorageFromSequence(); +} + +ImageSkia::ImageSkia(std::unique_ptr source, float scale) + : storage_( + base::MakeRefCounted(std::move(source), + scale)) { + if (!storage_->has_source()) + storage_ = nullptr; + // No other thread has reference to this, so it's safe to detach the sequence. + DetachStorageFromSequence(); +} + +ImageSkia::ImageSkia(const ImageSkiaRep& image_rep) { + DCHECK(!image_rep.is_null()); + Init(image_rep); + // No other thread has reference to this, so it's safe to detach the sequence. + DetachStorageFromSequence(); +} + +ImageSkia::ImageSkia(const ImageSkia& other) : storage_(other.storage_) { +} + +ImageSkia& ImageSkia::operator=(const ImageSkia& other) { + storage_ = other.storage_; + return *this; +} + +ImageSkia::~ImageSkia() { +} + +// static +void ImageSkia::SetSupportedScales(const std::vector& supported_scales) { + if (g_supported_scales != NULL) + delete g_supported_scales; + g_supported_scales = new std::vector(supported_scales); + std::sort(g_supported_scales->begin(), g_supported_scales->end()); +} + +// static +const std::vector& ImageSkia::GetSupportedScales() { + DCHECK(g_supported_scales != NULL); + return *g_supported_scales; +} + +// static +float ImageSkia::GetMaxSupportedScale() { + return g_supported_scales->back(); +} + +// static +ImageSkia ImageSkia::CreateFromBitmap(const SkBitmap& bitmap, float scale) { + // An uninitialized/empty/null bitmap makes a null ImageSkia. + if (bitmap.drawsNothing()) + return ImageSkia(); + return ImageSkia(ImageSkiaRep(bitmap, scale)); +} + +// static +ImageSkia ImageSkia::CreateFrom1xBitmap(const SkBitmap& bitmap) { + // An uninitialized/empty/null bitmap makes a null ImageSkia. + if (bitmap.drawsNothing()) + return ImageSkia(); + return ImageSkia(ImageSkiaRep(bitmap, 0.0f)); +} + +ImageSkia ImageSkia::DeepCopy() const { + ImageSkia copy; + if (isNull()) + return copy; + + CHECK(CanRead()); + + std::vector& reps = storage_->image_reps(); + for (auto iter = reps.begin(); iter != reps.end(); ++iter) { + copy.AddRepresentation(*iter); + } + // The copy has its own storage. Detach the copy from the current + // sequence so that other sequences can use this. + if (!copy.isNull()) + copy.storage_->DetachFromSequence(); + return copy; +} + +bool ImageSkia::BackedBySameObjectAs(const gfx::ImageSkia& other) const { + return storage_.get() == other.storage_.get(); +} + +const void* ImageSkia::GetBackingObject() const { + return storage_.get(); +} + +void ImageSkia::AddRepresentation(const ImageSkiaRep& image_rep) { + DCHECK(!image_rep.is_null()); + // TODO(oshima): This method should be called |SetRepresentation| + // and replace the existing rep if there is already one with the + // same scale so that we can guarantee that a ImageSkia instance contains only + // one image rep per scale. This is not possible now as ImageLoader currently + // stores need this feature, but this needs to be fixed. + if (isNull()) { + Init(image_rep); + } else { + CHECK(CanModify()); + // If someone is adding ImageSkia explicitly, check if we should + // make the image high DPI aware. + storage_->AddRepresentation(image_rep); + } +} + +void ImageSkia::RemoveRepresentation(float scale) { + if (isNull()) + return; + CHECK(CanModify()); + + ImageSkiaReps& image_reps = storage_->image_reps(); + auto it = storage_->FindRepresentation(scale, false); + if (it != image_reps.end() && it->scale() == scale) + image_reps.erase(it); +} + +bool ImageSkia::HasRepresentation(float scale) const { + if (isNull()) + return false; + CHECK(CanRead()); + + // This check is not only faster than FindRepresentation(), it's important for + // getting the right answer in cases of image types that are not based on + // discrete preset underlying representations, which otherwise might report + // "false" for this if GetRepresentation() has not yet been called for this + // |scale|. + if (storage_->HasRepresentationAtAllScales()) + return true; + + auto it = storage_->FindRepresentation(scale, false); + return (it != storage_->image_reps().end() && it->scale() == scale); +} + +const ImageSkiaRep& ImageSkia::GetRepresentation(float scale) const { + if (isNull()) + return NullImageRep(); + + CHECK(CanRead()); + + auto it = storage_->FindRepresentation(scale, true); + if (it == storage_->image_reps().end()) + return NullImageRep(); + + return *it; +} + +void ImageSkia::SetReadOnly() { + CHECK(storage_.get()); + storage_->SetReadOnly(); + DetachStorageFromSequence(); +} + +void ImageSkia::MakeThreadSafe() { + CHECK(storage_.get()); + EnsureRepsForSupportedScales(); + // Delete source as we no longer needs it. + if (storage_.get()) + storage_->DeleteSource(); + storage_->SetReadOnly(); + CHECK(IsThreadSafe()); +} + +bool ImageSkia::IsThreadSafe() const { + return !storage_.get() || (storage_->read_only() && !storage_->has_source()); +} + +int ImageSkia::width() const { + return isNull() ? 0 : storage_->size().width(); +} + +gfx::Size ImageSkia::size() const { + return gfx::Size(width(), height()); +} + +int ImageSkia::height() const { + return isNull() ? 0 : storage_->size().height(); +} + +std::vector ImageSkia::image_reps() const { + if (isNull()) + return std::vector(); + + CHECK(CanRead()); + + ImageSkiaReps internal_image_reps = storage_->image_reps(); + // Create list of image reps to return, skipping null image reps which were + // added for caching purposes only. + ImageSkiaReps image_reps; + for (auto it = internal_image_reps.begin(); it != internal_image_reps.end(); + ++it) { + if (!it->is_null()) + image_reps.push_back(*it); + } + + return image_reps; +} + +void ImageSkia::EnsureRepsForSupportedScales() const { + DCHECK(g_supported_scales != NULL); + // Don't check ReadOnly because the source may generate images even for read + // only ImageSkia. Concurrent access will be protected by + // |DCHECK(sequence_checker_.CalledOnValidSequence())| in FindRepresentation. + if (storage_.get() && storage_->has_source()) { + for (std::vector::const_iterator it = g_supported_scales->begin(); + it != g_supported_scales->end(); ++it) + storage_->FindRepresentation(*it, true); + } +} + +void ImageSkia::RemoveUnsupportedRepresentationsForScale(float scale) { + for (const ImageSkiaRep& image_rep_to_test : image_reps()) { + const float test_scale = image_rep_to_test.scale(); + if (test_scale != scale && MapToSupportedScale(test_scale) == scale) + RemoveRepresentation(test_scale); + } +} + +void ImageSkia::Init(const ImageSkiaRep& image_rep) { + DCHECK(!image_rep.is_null()); + storage_ = new internal::ImageSkiaStorage( + NULL, gfx::Size(image_rep.GetWidth(), image_rep.GetHeight())); + storage_->image_reps().push_back(image_rep); +} + +const SkBitmap& ImageSkia::GetBitmap() const { + if (isNull()) { + // Callers expect a ImageSkiaRep even if it is |isNull()|. + // TODO(pkotwicz): Fix this. + return NullImageRep().GetBitmap(); + } + + // TODO(oshima): This made a few tests flaky on Windows. + // Fix the root cause and re-enable this. crbug.com/145623. +#if !defined(OS_WIN) + CHECK(CanRead()); +#endif + + auto it = storage_->FindRepresentation(1.0f, true); + if (it != storage_->image_reps().end()) + return it->GetBitmap(); + return NullImageRep().GetBitmap(); +} + +bool ImageSkia::CanRead() const { + return !storage_.get() || storage_->CanRead(); +} + +bool ImageSkia::CanModify() const { + return !storage_.get() || storage_->CanModify(); +} + +void ImageSkia::DetachStorageFromSequence() { + if (storage_.get()) + storage_->DetachFromSequence(); +} + +} // namespace gfx diff --git a/image/image_skia.h b/image/image_skia.h new file mode 100644 index 000000000000..156c8283cbac --- /dev/null +++ b/image/image_skia.h @@ -0,0 +1,198 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_IMAGE_IMAGE_SKIA_H_ +#define UI_GFX_IMAGE_IMAGE_SKIA_H_ + +#include +#include + +#include "base/gtest_prod_util.h" +#include "base/memory/ref_counted.h" +#include "ui/gfx/gfx_export.h" +#include "ui/gfx/image/image_skia_rep.h" + +namespace gfx { +class ImageSkiaSource; +class Size; + +namespace internal { +class ImageSkiaStorage; +} // namespace internal + +namespace test { +class TestOnThread; +} + +// Container for the same image at different densities, similar to NSImage. +// Image height and width are in DIP (Density Indepent Pixel) coordinates. +// +// ImageSkia should be used whenever possible instead of SkBitmap. +// Functions that mutate the image should operate on the gfx::ImageSkiaRep +// returned from ImageSkia::GetRepresentation, not on ImageSkia. +// +// NOTE: This class should *not* be used to store multiple logical sizes of an +// image (e.g., small, medium and large versions of an icon); use an ImageFamily +// for that. An ImageSkia represents an image of a single logical size, with +// potentially many different densities for high-DPI displays. +// +// ImageSkia is cheap to copy and intentionally supports copy semantics. +class GFX_EXPORT ImageSkia { + public: + typedef std::vector ImageSkiaReps; + + // Creates an instance with no bitmaps. + ImageSkia(); + + // Creates an instance that will use the |source| to get the image + // for scale factors. |size| specifes the size of the image in DIP. + ImageSkia(std::unique_ptr source, const gfx::Size& size); + + // Creates an instance that uses the |source|. The constructor loads the image + // at |scale| and uses its dimensions to calculate the size in DIP. + ImageSkia(std::unique_ptr source, float scale); + + explicit ImageSkia(const gfx::ImageSkiaRep& image_rep); + + // Copies a reference to |other|'s storage. + ImageSkia(const ImageSkia& other); + + // Copies a reference to |other|'s storage. + ImageSkia& operator=(const ImageSkia& other); + + ~ImageSkia(); + + // Changes the value of GetSupportedScales() to |scales|. + static void SetSupportedScales(const std::vector& scales); + + // Returns a vector with the scale factors which are supported by this + // platform, in ascending order. + static const std::vector& GetSupportedScales(); + + // Returns the maximum scale supported by this platform. + static float GetMaxSupportedScale(); + + // Creates an image from the passed in bitmap, which is designed for display + // at the device scale factor given in `scale`. The DIP width and height will + // be based on that scale factor. A scale factor of 0 is equivalent to + // calling CreateFrom1xBitmap(), which indicates the bitmap is not scaled. + // The `bitmap`, if present, will be made immutable. If the `bitmap` is + // uninitialized, empty, or null then the returned ImageSkia will be + // default-constructed and empty. + // WARNING: If the device scale factory differs from the scale given here, + // the resulting image will be pixelated when displayed. + static ImageSkia CreateFromBitmap(const SkBitmap& bitmap, float scale); + + // Creates an image from the passed in bitmap. The DIP width and height will + // be based on scale factor of 1x. The `bitmap`, if present, will be made + // immutable. If the bitmap is uninitialized, empty, or null then the + // returned ImageSkia will be default-constructed and empty. + // WARNING: The resulting image will be pixelated when painted on a high + // density display. + static ImageSkia CreateFrom1xBitmap(const SkBitmap& bitmap); + + // Returns a deep copy of this ImageSkia which has its own storage with + // the ImageSkiaRep instances that this ImageSkia currently has. + // This can be safely passed to and manipulated by another thread. + // Note that this does NOT generate ImageSkiaReps from its source. + // If you want to create a deep copy with ImageSkiaReps for supported + // scale factors, you need to explicitly call + // |EnsureRepsForSupportedScales()| first. + ImageSkia DeepCopy() const; + + // Returns true if this object is backed by the same ImageSkiaStorage as + // |other|. Will also return true if both images are isNull(). + bool BackedBySameObjectAs(const gfx::ImageSkia& other) const; + + // Returns a pointer that identifies the backing ImageSkiaStorage. Comparing + // the results of this method from two ImageSkia objects is equivalent to + // using BackedBySameObjectAs(). + const void* GetBackingObject() const; + + // Adds |image_rep| to the image reps contained by this object. + void AddRepresentation(const gfx::ImageSkiaRep& image_rep); + + // Removes the image rep of |scale| if present. + void RemoveRepresentation(float scale); + + // Returns true if the object owns an image rep whose density matches + // |scale| exactly. + bool HasRepresentation(float scale) const; + + // Returns the image rep whose density best matches |scale|. + // Returns a null image rep if the object contains no image reps. + const gfx::ImageSkiaRep& GetRepresentation(float scale) const; + + // Make the ImageSkia instance read-only. Note that this only prevent + // modification from client code, and the storage may still be + // modified by the source if any (thus, it's not thread safe). This + // detaches the storage from currently accessing sequence, so its safe + // to pass it to another sequence as long as it is accessed only by that + // sequence. If this ImageSkia's storage will be accessed by multiple + // sequences, use |MakeThreadSafe()| method. + void SetReadOnly(); + + // Make the image thread safe by making the storage read only and remove + // its source if any. All ImageSkia that shares the same storage will also + // become thread safe. Note that in order to make it 100% thread safe, + // this must be called before it's been passed to another sequence. + void MakeThreadSafe(); + bool IsThreadSafe() const; + + // Returns true if this is a null object. + bool isNull() const { return storage_.get() == NULL; } + + // Width and height of image in DIP coordinate system. + int width() const; + int height() const; + gfx::Size size() const; + + // Returns pointer to 1x bitmap contained by this object. If there is no 1x + // bitmap, the bitmap whose scale factor is closest to 1x is returned. + // This function should only be used in unittests and on platforms which do + // not support scale factors other than 1x. + // TODO(pkotwicz): Return null SkBitmap when the object has no 1x bitmap. + const SkBitmap* bitmap() const { return &GetBitmap(); } + + // Returns a vector with the image reps contained in this object. + // There is no guarantee that this will return all images rep for + // supported scale factors. + std::vector image_reps() const; + + // When the source is available, generates all ImageReps for + // supported scale factors. This method is defined as const as + // the state change in the storage is agnostic to the caller. + void EnsureRepsForSupportedScales() const; + + // Clears cached representations for non-supported scale factors that are + // based on |scale|. + void RemoveUnsupportedRepresentationsForScale(float scale); + + private: + friend class test::TestOnThread; + FRIEND_TEST_ALL_PREFIXES(ImageSkiaTest, EmptyOnThreadTest); + FRIEND_TEST_ALL_PREFIXES(ImageSkiaTest, StaticOnThreadTest); + FRIEND_TEST_ALL_PREFIXES(ImageSkiaTest, SourceOnThreadTest); + + // Initialize ImageSkiaStorage with passed in parameters. + // If the image rep's bitmap is empty, ImageStorage is set to NULL. + void Init(const gfx::ImageSkiaRep& image_rep); + + const SkBitmap& GetBitmap() const; + + // Checks if the current sequence can read/modify the ImageSkia. + bool CanRead() const; + bool CanModify() const; + + // Detach the storage from the currently assigned sequence + // so that other sequence can access the storage. + void DetachStorageFromSequence(); + + // A refptr so that ImageRepSkia can be copied cheaply. + scoped_refptr storage_; +}; + +} // namespace gfx + +#endif // UI_GFX_IMAGE_IMAGE_SKIA_H_ diff --git a/image/image_skia_operations.cc b/image/image_skia_operations.cc new file mode 100644 index 000000000000..b691b2f3acc0 --- /dev/null +++ b/image/image_skia_operations.cc @@ -0,0 +1,766 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/image/image_skia_operations.h" + +#include +#include + +#include "base/command_line.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/numerics/safe_conversions.h" +#include "skia/ext/image_operations.h" +#include "third_party/skia/include/core/SkClipOp.h" +#include "third_party/skia/include/core/SkDrawLooper.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/color_palette.h" +#include "ui/gfx/geometry/insets.h" +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/point_conversions.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/rect_conversions.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/geometry/size_conversions.h" +#include "ui/gfx/geometry/skia_conversions.h" +#include "ui/gfx/image/canvas_image_source.h" +#include "ui/gfx/image/image_skia.h" +#include "ui/gfx/image/image_skia_rep.h" +#include "ui/gfx/image/image_skia_source.h" +#include "ui/gfx/skbitmap_operations.h" +#include "ui/gfx/skia_paint_util.h" + +namespace gfx { +namespace { + +gfx::Size DIPToPixelSize(gfx::Size dip_size, float scale) { + return ScaleToCeiledSize(dip_size, scale); +} + +gfx::Rect DIPToPixelBounds(gfx::Rect dip_bounds, float scale) { + return gfx::Rect(ScaleToFlooredPoint(dip_bounds.origin(), scale), + DIPToPixelSize(dip_bounds.size(), scale)); +} + +// Returns an image rep for the ImageSkiaSource to return to visually indicate +// an error. +ImageSkiaRep GetErrorImageRep(float scale, const gfx::Size& pixel_size) { + SkBitmap bitmap; + bitmap.allocN32Pixels(pixel_size.width(), pixel_size.height()); + bitmap.eraseColor(kPlaceholderColor); + return gfx::ImageSkiaRep(bitmap, scale); +} + +// A base image source class that creates an image from two source images. +// This class guarantees that two ImageSkiaReps have have the same pixel size. +class BinaryImageSource : public gfx::ImageSkiaSource { + protected: + BinaryImageSource(const ImageSkia& first, + const ImageSkia& second, + const char* source_name) + : first_(first), + second_(second), + source_name_(source_name) { + } + + BinaryImageSource(const BinaryImageSource&) = delete; + BinaryImageSource& operator=(const BinaryImageSource&) = delete; + + ~BinaryImageSource() override {} + + // gfx::ImageSkiaSource overrides: + ImageSkiaRep GetImageForScale(float scale) override { + ImageSkiaRep first_rep = first_.GetRepresentation(scale); + if (first_rep.is_null()) + return first_rep; + ImageSkiaRep second_rep = second_.GetRepresentation(scale); + if (second_rep.is_null()) + return second_rep; + + if (first_rep.pixel_size() != second_rep.pixel_size()) { + DCHECK_NE(first_rep.scale(), second_rep.scale()); + if (first_rep.scale() == second_rep.scale()) { + LOG(ERROR) << "ImageSkiaRep size mismatch in " << source_name_; + return GetErrorImageRep(first_rep.scale(),first_rep.pixel_size()); + } + first_rep = first_.GetRepresentation(1.0f); + second_rep = second_.GetRepresentation(1.0f); + DCHECK_EQ(first_rep.pixel_width(), second_rep.pixel_width()); + DCHECK_EQ(first_rep.pixel_height(), second_rep.pixel_height()); + if (first_rep.pixel_size() != second_rep.pixel_size()) { + LOG(ERROR) << "ImageSkiaRep size mismatch in " << source_name_; + return GetErrorImageRep(first_rep.scale(), first_rep.pixel_size()); + } + } else { + DCHECK_EQ(first_rep.scale(), second_rep.scale()); + } + return CreateImageSkiaRep(first_rep, second_rep); + } + + // Creates a final image from two ImageSkiaReps. The pixel size of + // the two images are guaranteed to be the same. + virtual ImageSkiaRep CreateImageSkiaRep( + const ImageSkiaRep& first_rep, + const ImageSkiaRep& second_rep) const = 0; + + private: + const ImageSkia first_; + const ImageSkia second_; + // The name of a class that implements the BinaryImageSource. + // The subclass is responsible for managing the memory. + const char* source_name_; +}; + +class BlendingImageSource : public BinaryImageSource { + public: + BlendingImageSource(const ImageSkia& first, + const ImageSkia& second, + double alpha) + : BinaryImageSource(first, second, "BlendingImageSource"), + alpha_(alpha) { + } + + BlendingImageSource(const BlendingImageSource&) = delete; + BlendingImageSource& operator=(const BlendingImageSource&) = delete; + + ~BlendingImageSource() override {} + + // BinaryImageSource overrides: + ImageSkiaRep CreateImageSkiaRep( + const ImageSkiaRep& first_rep, + const ImageSkiaRep& second_rep) const override { + SkBitmap blended = SkBitmapOperations::CreateBlendedBitmap( + first_rep.GetBitmap(), second_rep.GetBitmap(), alpha_); + return ImageSkiaRep(blended, first_rep.scale()); + } + + private: + double alpha_; +}; + +class SuperimposedImageSource : public gfx::CanvasImageSource { + public: + SuperimposedImageSource(const ImageSkia& first, const ImageSkia& second) + : gfx::CanvasImageSource(first.size()), first_(first), second_(second) {} + + SuperimposedImageSource(const SuperimposedImageSource&) = delete; + SuperimposedImageSource& operator=(const SuperimposedImageSource&) = delete; + + ~SuperimposedImageSource() override {} + + // gfx::CanvasImageSource override. + void Draw(Canvas* canvas) override { + canvas->DrawImageInt(first_, 0, 0); + canvas->DrawImageInt(second_, + (first_.width() - second_.width()) / 2, + (first_.height() - second_.height()) / 2); + } + + private: + const ImageSkia first_; + const ImageSkia second_; +}; + +class TransparentImageSource : public gfx::ImageSkiaSource { + public: + TransparentImageSource(const ImageSkia& image, double alpha) + : image_(image), + alpha_(alpha) { + } + + TransparentImageSource(const TransparentImageSource&) = delete; + TransparentImageSource& operator=(const TransparentImageSource&) = delete; + + ~TransparentImageSource() override {} + + private: + // gfx::ImageSkiaSource overrides: + ImageSkiaRep GetImageForScale(float scale) override { + ImageSkiaRep image_rep = image_.GetRepresentation(scale); + if (image_rep.is_null()) + return image_rep; + + SkBitmap alpha; + alpha.allocN32Pixels(image_rep.pixel_width(), + image_rep.pixel_height()); + alpha.eraseColor(SkColorSetA(SK_ColorBLACK, SK_AlphaOPAQUE * alpha_)); + return ImageSkiaRep( + SkBitmapOperations::CreateMaskedBitmap(image_rep.GetBitmap(), alpha), + image_rep.scale()); + } + + ImageSkia image_; + double alpha_; +}; + +class MaskedImageSource : public BinaryImageSource { + public: + MaskedImageSource(const ImageSkia& rgb, const ImageSkia& alpha) + : BinaryImageSource(rgb, alpha, "MaskedImageSource") { + } + + MaskedImageSource(const MaskedImageSource&) = delete; + MaskedImageSource& operator=(const MaskedImageSource&) = delete; + + ~MaskedImageSource() override {} + + // BinaryImageSource overrides: + ImageSkiaRep CreateImageSkiaRep( + const ImageSkiaRep& first_rep, + const ImageSkiaRep& second_rep) const override { + return ImageSkiaRep(SkBitmapOperations::CreateMaskedBitmap( + first_rep.GetBitmap(), second_rep.GetBitmap()), + first_rep.scale()); + } +}; + +class TiledImageSource : public gfx::ImageSkiaSource { + public: + TiledImageSource(const ImageSkia& source, + int src_x, int src_y, + int dst_w, int dst_h) + : source_(source), + src_x_(src_x), + src_y_(src_y), + dst_w_(dst_w), + dst_h_(dst_h) { + } + + TiledImageSource(const TiledImageSource&) = delete; + TiledImageSource& operator=(const TiledImageSource&) = delete; + + ~TiledImageSource() override {} + + // gfx::ImageSkiaSource overrides: + ImageSkiaRep GetImageForScale(float scale) override { + ImageSkiaRep source_rep = source_.GetRepresentation(scale); + if (source_rep.is_null()) + return source_rep; + + gfx::Rect bounds = DIPToPixelBounds(gfx::Rect(src_x_, src_y_, dst_w_, + dst_h_), source_rep.scale()); + return ImageSkiaRep(SkBitmapOperations::CreateTiledBitmap( + source_rep.GetBitmap(), bounds.x(), bounds.y(), + bounds.width(), bounds.height()), + source_rep.scale()); + } + + private: + const ImageSkia source_; + const int src_x_; + const int src_y_; + const int dst_w_; + const int dst_h_; +}; + +class HSLImageSource : public gfx::ImageSkiaSource { + public: + HSLImageSource(const ImageSkia& image, + const color_utils::HSL& hsl_shift) + : image_(image), + hsl_shift_(hsl_shift) { + } + + HSLImageSource(const HSLImageSource&) = delete; + HSLImageSource& operator=(const HSLImageSource&) = delete; + + ~HSLImageSource() override {} + + // gfx::ImageSkiaSource overrides: + ImageSkiaRep GetImageForScale(float scale) override { + ImageSkiaRep image_rep = image_.GetRepresentation(scale); + if (image_rep.is_null()) + return image_rep; + + return gfx::ImageSkiaRep(SkBitmapOperations::CreateHSLShiftedBitmap( + image_rep.GetBitmap(), hsl_shift_), + image_rep.scale()); + } + + private: + const gfx::ImageSkia image_; + const color_utils::HSL hsl_shift_; +}; + +// ImageSkiaSource which uses SkBitmapOperations::CreateButtonBackground +// to generate image reps for the target image. The image and mask can be +// diferent sizes (crbug.com/171725). +class ButtonImageSource: public gfx::ImageSkiaSource { + public: + ButtonImageSource(SkColor color, + const ImageSkia& image, + const ImageSkia& mask) + : color_(color), + image_(image), + mask_(mask) { + } + + ButtonImageSource(const ButtonImageSource&) = delete; + ButtonImageSource& operator=(const ButtonImageSource&) = delete; + + ~ButtonImageSource() override {} + + // gfx::ImageSkiaSource overrides: + ImageSkiaRep GetImageForScale(float scale) override { + ImageSkiaRep image_rep = image_.GetRepresentation(scale); + if (image_rep.is_null()) + return image_rep; + ImageSkiaRep mask_rep = mask_.GetRepresentation(scale); + if (mask_rep.is_null()) + return image_rep; + + if (image_rep.scale() != mask_rep.scale()) { + image_rep = image_.GetRepresentation(1.0f); + mask_rep = mask_.GetRepresentation(1.0f); + } + return gfx::ImageSkiaRep( + SkBitmapOperations::CreateButtonBackground( + color_, image_rep.GetBitmap(), mask_rep.GetBitmap()), + image_rep.scale()); + } + + private: + const SkColor color_; + const ImageSkia image_; + const ImageSkia mask_; +}; + +// ImageSkiaSource which uses SkBitmap::extractSubset to generate image reps +// for the target image. +class ExtractSubsetImageSource: public gfx::ImageSkiaSource { + public: + ExtractSubsetImageSource(const gfx::ImageSkia& image, + const gfx::Rect& subset_bounds) + : image_(image), + subset_bounds_(subset_bounds) { + } + + ExtractSubsetImageSource(const ExtractSubsetImageSource&) = delete; + ExtractSubsetImageSource& operator=(const ExtractSubsetImageSource&) = delete; + + ~ExtractSubsetImageSource() override {} + + // gfx::ImageSkiaSource overrides: + ImageSkiaRep GetImageForScale(float scale) override { + ImageSkiaRep image_rep = image_.GetRepresentation(scale); + if (image_rep.is_null()) + return image_rep; + + SkIRect subset_bounds_in_pixel = RectToSkIRect( + DIPToPixelBounds(subset_bounds_, image_rep.scale())); + SkBitmap dst; + bool success = + image_rep.GetBitmap().extractSubset(&dst, subset_bounds_in_pixel); + DCHECK(success); + return gfx::ImageSkiaRep(dst, image_rep.scale()); + } + + private: + const gfx::ImageSkia image_; + const gfx::Rect subset_bounds_; +}; + +// ResizeSource resizes relevant image reps in |source| to |target_dip_size| +// for requested scale factors. +class ResizeSource : public ImageSkiaSource { + public: + ResizeSource(const ImageSkia& source, + skia::ImageOperations::ResizeMethod method, + const Size& target_dip_size) + : source_(source), + resize_method_(method), + target_dip_size_(target_dip_size) { + } + + ResizeSource(const ResizeSource&) = delete; + ResizeSource& operator=(const ResizeSource&) = delete; + + ~ResizeSource() override {} + + // gfx::ImageSkiaSource overrides: + ImageSkiaRep GetImageForScale(float scale) override { + const ImageSkiaRep& image_rep = source_.GetRepresentation(scale); + if (image_rep.is_null()) + return image_rep; + if (image_rep.GetWidth() == target_dip_size_.width() && + image_rep.GetHeight() == target_dip_size_.height()) + return image_rep; + + const Size target_pixel_size = DIPToPixelSize(target_dip_size_, scale); + const SkBitmap resized = skia::ImageOperations::Resize( + image_rep.GetBitmap(), resize_method_, target_pixel_size.width(), + target_pixel_size.height()); + if (resized.colorType() == kUnknown_SkColorType) + return ImageSkiaRep(); + return ImageSkiaRep(resized, scale); + } + + private: + const ImageSkia source_; + skia::ImageOperations::ResizeMethod resize_method_; + const Size target_dip_size_; +}; + +// DropShadowSource generates image reps with drop shadow for image reps in +// |source| that represent requested scale factors. +class DropShadowSource : public ImageSkiaSource { + public: + DropShadowSource(const ImageSkia& source, const ShadowValues& shadows_in_dip) + : source_(source), shadows_in_dip_(shadows_in_dip) {} + + DropShadowSource(const DropShadowSource&) = delete; + DropShadowSource& operator=(const DropShadowSource&) = delete; + + ~DropShadowSource() override {} + + // gfx::ImageSkiaSource overrides: + ImageSkiaRep GetImageForScale(float scale) override { + const ImageSkiaRep& image_rep = source_.GetRepresentation(scale); + if (image_rep.is_null()) + return image_rep; + + ShadowValues shadows_in_pixel; + for (size_t i = 0; i < shadows_in_dip_.size(); ++i) + shadows_in_pixel.push_back(shadows_in_dip_[i].Scale(scale)); + + const SkBitmap shadow_bitmap = SkBitmapOperations::CreateDropShadow( + image_rep.GetBitmap(), shadows_in_pixel); + return ImageSkiaRep(shadow_bitmap, image_rep.scale()); + } + + private: + const ImageSkia source_; + const ShadowValues shadows_in_dip_; +}; + +// An image source that is 1px wide, suitable for tiling horizontally. +class HorizontalShadowSource : public CanvasImageSource { + public: + HorizontalShadowSource(const std::vector& shadows, + bool fades_down) + : CanvasImageSource(Size(1, GetHeightForShadows(shadows))), + shadows_(shadows), + fades_down_(fades_down) {} + + HorizontalShadowSource(const HorizontalShadowSource&) = delete; + HorizontalShadowSource& operator=(const HorizontalShadowSource&) = delete; + + ~HorizontalShadowSource() override {} + + // CanvasImageSource overrides: + void Draw(Canvas* canvas) override { + cc::PaintFlags flags; + flags.setLooper(CreateShadowDrawLooper(shadows_)); + canvas->DrawRect(RectF(0, fades_down_ ? -1 : size().height(), 1, 1), flags); + } + + private: + static int GetHeightForShadows(const std::vector& shadows) { + int height = 0; + for (const auto& shadow : shadows) { + height = + std::max(height, shadow.y() + base::ClampCeil(shadow.blur() / 2)); + } + return height; + } + + const std::vector shadows_; + + // The orientation of the shadow (true for shadows that emanate downwards). + bool fades_down_; +}; + +// RotatedSource generates image reps that are rotations of those in +// |source| that represent requested scale factors. +class RotatedSource : public ImageSkiaSource { + public: + RotatedSource(const ImageSkia& source, + SkBitmapOperations::RotationAmount rotation) + : source_(source), + rotation_(rotation) { + } + + RotatedSource(const RotatedSource&) = delete; + RotatedSource& operator=(const RotatedSource&) = delete; + + ~RotatedSource() override {} + + // gfx::ImageSkiaSource overrides: + ImageSkiaRep GetImageForScale(float scale) override { + const ImageSkiaRep& image_rep = source_.GetRepresentation(scale); + if (image_rep.is_null()) + return image_rep; + + const SkBitmap rotated_bitmap = + SkBitmapOperations::Rotate(image_rep.GetBitmap(), rotation_); + return ImageSkiaRep(rotated_bitmap, image_rep.scale()); + } + + private: + const ImageSkia source_; + const SkBitmapOperations::RotationAmount rotation_; +}; + +class IconWithBadgeSource : public gfx::CanvasImageSource { + public: + IconWithBadgeSource(const ImageSkia& icon, const ImageSkia& badge) + : gfx::CanvasImageSource(icon.size()), icon_(icon), badge_(badge) {} + + IconWithBadgeSource(const IconWithBadgeSource&) = delete; + IconWithBadgeSource& operator=(const IconWithBadgeSource&) = delete; + + ~IconWithBadgeSource() override {} + + // gfx::CanvasImageSource override. + void Draw(Canvas* canvas) override { + canvas->DrawImageInt(icon_, 0, 0); + canvas->DrawImageInt(badge_, (icon_.width() - badge_.width()), + (icon_.height() - badge_.height())); + } + + private: + const ImageSkia icon_; + const ImageSkia badge_; +}; + +// ImageSkiaSource which uses SkBitmapOperations::CreateColorMask +// to generate image reps for the target image. +class ColorMaskSource : public gfx::ImageSkiaSource { + public: + ColorMaskSource(const ImageSkia& image, SkColor color) + : image_(image), color_(color) {} + + ColorMaskSource(const ColorMaskSource&) = delete; + ColorMaskSource& operator=(const ColorMaskSource&) = delete; + + ~ColorMaskSource() override {} + + // gfx::ImageSkiaSource overrides: + ImageSkiaRep GetImageForScale(float scale) override { + ImageSkiaRep image_rep = image_.GetRepresentation(scale); + if (image_rep.is_null()) + return image_rep; + + return ImageSkiaRep( + SkBitmapOperations::CreateColorMask(image_rep.GetBitmap(), color_), + image_rep.scale()); + } + + private: + const ImageSkia image_; + const SkColor color_; +}; + +// Image source to create an image with a circle background. +class ImageWithCircleBackgroundSource : public gfx::CanvasImageSource { + public: + ImageWithCircleBackgroundSource(int radius, + SkColor color, + const gfx::ImageSkia& image) + : gfx::CanvasImageSource(gfx::Size(radius * 2, radius * 2)), + radius_(radius), + color_(color), + image_(image) {} + + ImageWithCircleBackgroundSource(const ImageWithCircleBackgroundSource&) = + delete; + ImageWithCircleBackgroundSource& operator=( + const ImageWithCircleBackgroundSource&) = delete; + + ~ImageWithCircleBackgroundSource() override = default; + + // gfx::CanvasImageSource: + void Draw(gfx::Canvas* canvas) override { + cc::PaintFlags flags; + flags.setAntiAlias(true); + flags.setStyle(cc::PaintFlags::kFill_Style); + flags.setColor(color_); + canvas->DrawCircle(gfx::Point(radius_, radius_), radius_, flags); + const int x = radius_ - image_.width() / 2; + const int y = radius_ - image_.height() / 2; + canvas->DrawImageInt(image_, x, y); + } + + private: + const int radius_; + const SkColor color_; + const gfx::ImageSkia image_; +}; +} // namespace + +// static +ImageSkia ImageSkiaOperations::CreateBlendedImage(const ImageSkia& first, + const ImageSkia& second, + double alpha) { + if (first.isNull() || second.isNull()) + return ImageSkia(); + + return ImageSkia(std::make_unique(first, second, alpha), + first.size()); +} + +// static +ImageSkia ImageSkiaOperations::CreateSuperimposedImage( + const ImageSkia& first, + const ImageSkia& second) { + if (first.isNull() || second.isNull()) + return ImageSkia(); + + return ImageSkia(std::make_unique(first, second), + first.size()); +} + +// static +ImageSkia ImageSkiaOperations::CreateTransparentImage(const ImageSkia& image, + double alpha) { + if (image.isNull()) + return ImageSkia(); + + return ImageSkia(std::make_unique(image, alpha), + image.size()); +} + +// static +ImageSkia ImageSkiaOperations::CreateMaskedImage(const ImageSkia& rgb, + const ImageSkia& alpha) { + if (rgb.isNull() || alpha.isNull()) + return ImageSkia(); + + return ImageSkia(std::make_unique(rgb, alpha), rgb.size()); +} + +// static +ImageSkia ImageSkiaOperations::CreateTiledImage(const ImageSkia& source, + int src_x, int src_y, + int dst_w, int dst_h) { + if (source.isNull()) + return ImageSkia(); + + return ImageSkia( + std::make_unique(source, src_x, src_y, dst_w, dst_h), + gfx::Size(dst_w, dst_h)); +} + +// static +ImageSkia ImageSkiaOperations::CreateHSLShiftedImage( + const ImageSkia& image, + const color_utils::HSL& hsl_shift) { + if (image.isNull()) + return ImageSkia(); + + return ImageSkia(std::make_unique(image, hsl_shift), + image.size()); +} + +// static +ImageSkia ImageSkiaOperations::CreateButtonBackground(SkColor color, + const ImageSkia& image, + const ImageSkia& mask) { + if (image.isNull() || mask.isNull()) + return ImageSkia(); + + return ImageSkia(std::make_unique(color, image, mask), + mask.size()); +} + +// static +ImageSkia ImageSkiaOperations::ExtractSubset(const ImageSkia& image, + const Rect& subset_bounds) { + gfx::Rect clipped_bounds = + gfx::IntersectRects(subset_bounds, gfx::Rect(image.size())); + if (image.isNull() || clipped_bounds.IsEmpty()) + return ImageSkia(); + if (clipped_bounds == gfx::Rect(image.size())) + return image; + + return ImageSkia( + std::make_unique(image, clipped_bounds), + clipped_bounds.size()); +} + +// static +ImageSkia ImageSkiaOperations::CreateResizedImage( + const ImageSkia& source, + skia::ImageOperations::ResizeMethod method, + const Size& target_dip_size) { + if (source.isNull()) + return ImageSkia(); + if (source.size() == target_dip_size) + return source; + + return ImageSkia( + std::make_unique(source, method, target_dip_size), + target_dip_size); +} + +// static +ImageSkia ImageSkiaOperations::CreateImageWithDropShadow( + const ImageSkia& source, + const ShadowValues& shadows) { + if (source.isNull()) + return ImageSkia(); + + const gfx::Insets shadow_padding = -gfx::ShadowValue::GetMargin(shadows); + gfx::Size shadow_image_size = source.size(); + shadow_image_size.Enlarge(shadow_padding.width(), + shadow_padding.height()); + return ImageSkia(std::make_unique(source, shadows), + shadow_image_size); +} + +// static +ImageSkia ImageSkiaOperations::CreateHorizontalShadow( + const std::vector& shadows, + bool fades_down) { + auto* source = new HorizontalShadowSource(shadows, fades_down); + return ImageSkia(base::WrapUnique(source), source->size()); +} + +// static +ImageSkia ImageSkiaOperations::CreateRotatedImage( + const ImageSkia& source, + SkBitmapOperations::RotationAmount rotation) { + if (source.isNull()) + return ImageSkia(); + + return ImageSkia(std::make_unique(source, rotation), + SkBitmapOperations::ROTATION_180_CW == rotation + ? source.size() + : gfx::Size(source.height(), source.width())); +} + +// static +ImageSkia ImageSkiaOperations::CreateIconWithBadge(const ImageSkia& icon, + const ImageSkia& badge) { + if (icon.isNull()) + return ImageSkia(); + + if (badge.isNull()) + return icon; + + return ImageSkia(std::make_unique(icon, badge), + icon.size()); +} + +// static +ImageSkia ImageSkiaOperations::CreateColorMask(const ImageSkia& image, + SkColor color) { + if (image.isNull()) + return ImageSkia(); + + return ImageSkia(std::make_unique(image, color), + image.size()); +} + +ImageSkia ImageSkiaOperations::CreateImageWithCircleBackground( + int radius, + SkColor color, + const ImageSkia& image) { + DCHECK_GE(radius * 2, image.width()); + DCHECK_GE(radius * 2, image.height()); + return gfx::CanvasImageSource::MakeImageSkia( + radius, color, image); +} +} // namespace gfx diff --git a/image/image_skia_operations.h b/image/image_skia_operations.h new file mode 100644 index 000000000000..a25afd429d91 --- /dev/null +++ b/image/image_skia_operations.h @@ -0,0 +1,128 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_IMAGE_IMAGE_SKIA_OPERATIONS_H_ +#define UI_GFX_IMAGE_IMAGE_SKIA_OPERATIONS_H_ + +#include "skia/ext/image_operations.h" +#include "third_party/skia/include/core/SkDrawLooper.h" +#include "third_party/skia/include/core/SkPaint.h" +#include "third_party/skia/include/core/SkRRect.h" +#include "ui/gfx/color_utils.h" +#include "ui/gfx/gfx_export.h" +#include "ui/gfx/shadow_value.h" +#include "ui/gfx/skbitmap_operations.h" + +namespace gfx { +class ImageSkia; +class Rect; +class Size; + +class GFX_EXPORT ImageSkiaOperations { + public: + // Create an image that is a blend of two others. The alpha argument + // specifies the opacity of the second imag. The provided image must + // use the kARGB_8888_Config config and be of equal dimensions. + static ImageSkia CreateBlendedImage(const ImageSkia& first, + const ImageSkia& second, + double alpha); + + // Creates an image that is the original image with opacity set to |alpha|. + static ImageSkia CreateTransparentImage(const ImageSkia& image, double alpha); + + // Creates new image by painting first and second image respectively. + // The second image is centered in respect to the first image. + static ImageSkia CreateSuperimposedImage(const ImageSkia& first, + const ImageSkia& second); + + // Create an image that is the original image masked out by the mask defined + // in the alpha image. The images must use the kARGB_8888_Config config and + // be of equal dimensions. + static ImageSkia CreateMaskedImage(const ImageSkia& first, + const ImageSkia& alpha); + + // Create an image that is cropped from another image. This is special + // because it tiles the original image, so your coordinates can extend + // outside the bounds of the original image. + static ImageSkia CreateTiledImage(const ImageSkia& image, + int src_x, int src_y, + int dst_w, int dst_h); + + // Shift an image's HSL values. The shift values are in the range of 0-1, + // with the option to specify -1 for 'no change'. The shift values are + // defined as: + // hsl_shift[0] (hue): The absolute hue value for the image - 0 and 1 map + // to 0 and 360 on the hue color wheel (red). + // hsl_shift[1] (saturation): A saturation shift for the image, with the + // following key values: + // 0 = remove all color. + // 0.5 = leave unchanged. + // 1 = fully saturate the image. + // hsl_shift[2] (lightness): A lightness shift for the image, with the + // following key values: + // 0 = remove all lightness (make all pixels black). + // 0.5 = leave unchanged. + // 1 = full lightness (make all pixels white). + static ImageSkia CreateHSLShiftedImage(const gfx::ImageSkia& image, + const color_utils::HSL& hsl_shift); + + // Creates a button background image by compositing the color and image + // together, then applying the mask. This is a highly specialized composite + // operation that is the equivalent of drawing a background in |color|, + // tiling |image| over the top, and then masking the result out with |mask|. + // The images must use kARGB_8888_Config config. + static ImageSkia CreateButtonBackground(SkColor color, + const gfx::ImageSkia& image, + const gfx::ImageSkia& mask); + + // Returns an image which is a subset of |image| with bounds |subset_bounds|. + // The |image| cannot use kA1_Config config. + static ImageSkia ExtractSubset(const gfx::ImageSkia& image, + const gfx::Rect& subset_bounds); + + // Creates an image by resizing |source| to given |target_dip_size|. + static ImageSkia CreateResizedImage(const ImageSkia& source, + skia::ImageOperations::ResizeMethod methd, + const Size& target_dip_size); + + // Creates an image with drop shadow defined in |shadows| for |source|. + static ImageSkia CreateImageWithDropShadow(const ImageSkia& source, + const ShadowValues& shadows); + + // Creates an image that is 1dp wide, suitable for tiling horizontally to + // create a drop shadow effect. The purpose of tiling a static image is to + // avoid repeatedly asking Skia to draw a shadow. + static ImageSkia CreateHorizontalShadow( + const std::vector& shadows, + bool fades_down); + + // Creates an image which is a rotation of the |source|. |rotation| is the + // amount of clockwise rotation in degrees. + static ImageSkia CreateRotatedImage( + const ImageSkia& source, + SkBitmapOperations::RotationAmount rotation); + + // Creates an icon by painting the second icon as a badge to the first one. + // The second icon is in the right corner of the first icon. If the icon + // is valid and the badge is not, the icon will be returned. + static ImageSkia CreateIconWithBadge(const ImageSkia& icon, + const ImageSkia& badge); + + // Creates an image by combining |image| and color |color|. + // The image must use the kARGB_8888_Config config. + static ImageSkia CreateColorMask(const gfx::ImageSkia& image, SkColor color); + + // Creates an image with a circle background. |color| and |radius| is the + // color and radius of the circle background. + static ImageSkia CreateImageWithCircleBackground(int radius, + SkColor color, + const ImageSkia& image); + + private: + ImageSkiaOperations(); // Class for scoping only. +}; + +} // namespace gfx + +#endif // UI_GFX_IMAGE_IMAGE_SKIA_OPERATIONS_H_ diff --git a/image/image_skia_operations_unittest.cc b/image/image_skia_operations_unittest.cc new file mode 100644 index 000000000000..473e2d511684 --- /dev/null +++ b/image/image_skia_operations_unittest.cc @@ -0,0 +1,23 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/image/image_skia_operations.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/image/image_skia.h" + +namespace gfx { +namespace { + +TEST(ImageSkiaOperationsTest, ResizeFailure) { + ImageSkia image(ImageSkiaRep(gfx::Size(10, 10), 1.f)); + + // Try to resize to empty. This isn't a valid resize and fails gracefully. + ImageSkia resized = ImageSkiaOperations::CreateResizedImage( + image, skia::ImageOperations::RESIZE_BEST, gfx::Size()); + EXPECT_TRUE(resized.GetRepresentation(1.0f).is_null()); +} + +} // namespace +} // namespace gfx diff --git a/image/image_skia_rep.h b/image/image_skia_rep.h new file mode 100644 index 000000000000..4f9d6b1bbc68 --- /dev/null +++ b/image/image_skia_rep.h @@ -0,0 +1,16 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_IMAGE_IMAGE_SKIA_REP_H_ +#define UI_GFX_IMAGE_IMAGE_SKIA_REP_H_ + +#include "build/build_config.h" + +#if defined(OS_IOS) +#include "ui/gfx/image/image_skia_rep_ios.h" +#else +#include "ui/gfx/image/image_skia_rep_default.h" +#endif // defined(OS_IOS) + +#endif // UI_GFX_IMAGE_IMAGE_SKIA_REP_H_ diff --git a/image/image_skia_rep_default.cc b/image/image_skia_rep_default.cc new file mode 100644 index 000000000000..66c76570087b --- /dev/null +++ b/image/image_skia_rep_default.cc @@ -0,0 +1,113 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/image/image_skia_rep_default.h" + +#include "base/check_op.h" +#include "base/notreached.h" +#include "cc/paint/display_item_list.h" +#include "cc/paint/record_paint_canvas.h" +#include "cc/paint/skia_paint_canvas.h" +#include "skia/ext/legacy_display_globals.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "ui/gfx/color_palette.h" + +namespace gfx { + +ImageSkiaRep::ImageSkiaRep() + : type_(ImageRepType::kImageTypeDrawable), scale_(0.0f) {} + +ImageSkiaRep::ImageSkiaRep(const gfx::Size& size, float scale) + : type_(ImageRepType::kImageTypeBitmap), scale_(scale) { + bitmap_.allocN32Pixels(static_cast(size.width() * this->scale()), + static_cast(size.height() * this->scale())); + bitmap_.eraseColor(kPlaceholderColor); + bitmap_.setImmutable(); + pixel_size_.SetSize(bitmap_.width(), bitmap_.height()); + paint_image_ = cc::PaintImage::CreateFromBitmap(bitmap_); +} + +ImageSkiaRep::ImageSkiaRep(const SkBitmap& src, float scale) + : type_(ImageRepType::kImageTypeBitmap), + pixel_size_(gfx::Size(src.width(), src.height())), + bitmap_(src), + scale_(scale) { + CHECK_EQ(bitmap_.colorType(), kN32_SkColorType); + DCHECK(!bitmap_.drawsNothing()); + bitmap_.setImmutable(); + paint_image_ = cc::PaintImage::CreateFromBitmap(src); +} + +ImageSkiaRep::ImageSkiaRep(sk_sp paint_record, + const gfx::Size& pixel_size, + float scale) + : paint_record_(std::move(paint_record)), + type_(ImageRepType::kImageTypeDrawable), + pixel_size_(pixel_size), + scale_(scale) { + DCHECK(!pixel_size.IsEmpty()); +} + +ImageSkiaRep::ImageSkiaRep(const ImageSkiaRep& other) + : paint_image_(other.paint_image_), + paint_record_(other.paint_record_), + type_(other.type_), + pixel_size_(other.pixel_size_), + bitmap_(other.bitmap_), + scale_(other.scale_) {} + +ImageSkiaRep::~ImageSkiaRep() {} + +int ImageSkiaRep::GetWidth() const { + return static_cast(pixel_width() / scale()); +} + +int ImageSkiaRep::GetHeight() const { + return static_cast(pixel_height() / scale()); +} + +sk_sp ImageSkiaRep::GetPaintRecord() const { + DCHECK(type_ == ImageRepType::kImageTypeBitmap || !is_null()); + // If this image rep is of |kImageTypeDrawable| then it must have a paint + // record. + if (type_ == ImageRepType::kImageTypeDrawable || paint_record_) + return paint_record_; + + // If this ImageRep was generated using a bitmap then it may not have a + // paint record generated for it yet. We would have to generate it now. + scoped_refptr display_item_list = + base::MakeRefCounted( + cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer); + + cc::RecordPaintCanvas record_canvas( + display_item_list.get(), SkRect::MakeIWH(pixel_width(), pixel_height())); + + display_item_list->StartPaint(); + record_canvas.drawImage(paint_image(), 0, 0); + display_item_list->EndPaintOfPairedEnd(); + display_item_list->Finalize(); + + paint_record_ = display_item_list->ReleaseAsRecord(); + return paint_record_; +} + +const SkBitmap& ImageSkiaRep::GetBitmap() const { + if (type_ == ImageRepType::kImageTypeDrawable && bitmap_.isNull() && + paint_record_) { + // TODO(malaykeshav): Add a NOTREACHED() once all instances of this call + // path is removed from the code base. + + // A request for bitmap was made even though this ImageSkiaRep is sourced + // form a drawable(e.g. CanvasImageSource). This should not be happenning + // as it forces a rasterization on the UI thread. + bitmap_.allocN32Pixels(pixel_width(), pixel_height()); + bitmap_.eraseColor(SK_ColorTRANSPARENT); + SkCanvas canvas(bitmap_, skia::LegacyDisplayGlobals::GetSkSurfaceProps()); + paint_record_->Playback(&canvas); + bitmap_.setImmutable(); + } + return bitmap_; +} + +} // namespace gfx diff --git a/image/image_skia_rep_default.h b/image/image_skia_rep_default.h new file mode 100644 index 000000000000..651ff76da31a --- /dev/null +++ b/image/image_skia_rep_default.h @@ -0,0 +1,102 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_IMAGE_IMAGE_SKIA_REP_DEFAULT_H_ +#define UI_GFX_IMAGE_IMAGE_SKIA_REP_DEFAULT_H_ + +#include "build/build_config.h" +#include "cc/paint/paint_image.h" +#include "cc/paint/paint_op_buffer.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +// An ImageSkiaRep represents an image and the scale factor it is intended for. +// 0.0f scale is used to indicate that this ImageSkiaRep is used for unscaled +// (ImageSkia does not automatically scale the image). +// TODO(malaykeshav): Support transport of PaintRecord across mojo. This would +// require adding inline serialization support for PaintRecords. +class GFX_EXPORT ImageSkiaRep { + public: + // Create null bitmap. + ImageSkiaRep(); + + // Note: This is for testing purpose only. + // Creates a bitmap with kARGB_8888_Config config with given |size| in DIP. + // This allocates pixels in the bitmap. It is guaranteed that the data in the + // bitmap are initialized but the actual values are undefined. + // Specifying 0 scale means the image is for unscaled image (unscaled() + // returns true, and scale() returns 1.0f). + ImageSkiaRep(const gfx::Size& size, float scale); + + // Creates an ImageSkiaRep that holds the `src` bitmap, which is created for + // display at the given device scale factor. Takes ownership of a reference to + // the SkBitmap's backing store. The `src` bitmap may not be uninitialized, + // null or empty; in that case the default constructor should be used + // instead. + ImageSkiaRep(const SkBitmap& src, float scale); + + // Creates an image rep backed by a paint record of given size and scale. This + // is used when the image representation is sourced from a drawable such as + // CanvasImageSource. The `size` must not be empty; in that case the default + // constructor should be used instead. + ImageSkiaRep(sk_sp paint_record, + const gfx::Size& size, + float scale); + + ImageSkiaRep(const ImageSkiaRep& other); + ~ImageSkiaRep(); + + // Get width and height of the image in pixels. + int pixel_width() const { return pixel_size_.width(); } + int pixel_height() const { return pixel_size_.height(); } + const Size& pixel_size() const { return pixel_size_; } + + // Get width and height of the image in DIP. + int GetWidth() const; + int GetHeight() const; + + // Retrieves the scale for which this image is a representation of. + float scale() const { return unscaled() ? 1.0f : scale_; } + bool unscaled() const { return scale_ == 0.0f; } + + bool is_null() const { + return type_ == ImageRepType::kImageTypeBitmap ? bitmap_.isNull() + : !paint_record_; + } + + // Returns the backing bitmap when the image representation is sourced from a + // bitmap. If this is a |kImageTypeDrawable| then it will generate(and cache) + // a bitmap. + const SkBitmap& GetBitmap() const; + + // Returns the backing drawable as a PaintRecord. Use this when the type of + // ImageRep is |kImageTypeDrawable|. + sk_sp GetPaintRecord() const; + + const cc::PaintImage& paint_image() const { return paint_image_; } + bool has_paint_image() const { return !!paint_image_; } + + private: + enum class ImageRepType { + kImageTypeBitmap, // When the source image is rasterized. (Bitmaps, PNGs) + kImageTypeDrawable // When the source image is a drawable generated by a + // CanvasImageSource. + }; + + // TODO(malaykeshav): Remove when migration is complete and it is safe. + cc::PaintImage paint_image_; + mutable sk_sp paint_record_; + ImageRepType type_; + + Size pixel_size_; + mutable SkBitmap bitmap_; + float scale_; +}; + +} // namespace gfx + +#endif // UI_GFX_IMAGE_IMAGE_SKIA_REP_DEFAULT_H_ diff --git a/image/image_skia_rep_ios.cc b/image/image_skia_rep_ios.cc new file mode 100644 index 000000000000..5fb5925c47a9 --- /dev/null +++ b/image/image_skia_rep_ios.cc @@ -0,0 +1,50 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/image/image_skia_rep_ios.h" + +#include "base/check_op.h" +#include "ui/gfx/color_palette.h" + +namespace gfx { + +ImageSkiaRep::ImageSkiaRep() : scale_(0.0f) {} + +ImageSkiaRep::ImageSkiaRep(const gfx::Size& size, float scale) : scale_(scale) { + bitmap_.allocN32Pixels(static_cast(size.width() * this->scale()), + static_cast(size.height() * this->scale())); + bitmap_.eraseColor(kPlaceholderColor); + bitmap_.setImmutable(); + pixel_size_.SetSize(bitmap_.width(), bitmap_.height()); +} + +ImageSkiaRep::ImageSkiaRep(const SkBitmap& src, float scale) + : pixel_size_(gfx::Size(src.width(), src.height())), + bitmap_(src), + scale_(scale) { + CHECK_EQ(src.colorType(), kN32_SkColorType); + DCHECK(!bitmap_.drawsNothing()); + bitmap_.setImmutable(); +} + +ImageSkiaRep::ImageSkiaRep(const ImageSkiaRep& other) + : pixel_size_(other.pixel_size_), + bitmap_(other.bitmap_), + scale_(other.scale_) {} + +ImageSkiaRep::~ImageSkiaRep() {} + +int ImageSkiaRep::GetWidth() const { + return static_cast(pixel_width() / scale()); +} + +int ImageSkiaRep::GetHeight() const { + return static_cast(pixel_height() / scale()); +} + +const SkBitmap& ImageSkiaRep::GetBitmap() const { + return bitmap_; +} + +} // namespace gfx diff --git a/image/image_skia_rep_ios.h b/image/image_skia_rep_ios.h new file mode 100644 index 000000000000..51e8f8473c0e --- /dev/null +++ b/image/image_skia_rep_ios.h @@ -0,0 +1,67 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_IMAGE_IMAGE_SKIA_REP_IOS_H_ +#define UI_GFX_IMAGE_IMAGE_SKIA_REP_IOS_H_ + +#include "build/build_config.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +// An ImageSkiaRep represents an image and the scale factor it is intended for. +// 0.0f scale is used to indicate that this ImageSkiaRep is used for unscaled +// image (ImageSkia does not automatically scale the image). +// iOS does not support cc's PaintOpBuffer and instead uses cocoa frameworks +// image formats. +class GFX_EXPORT ImageSkiaRep { + public: + // Create null bitmap. + ImageSkiaRep(); + + // Note: This is for testing purpose only. + // Creates a bitmap with kARGB_8888_Config config with given |size| in DIP. + // This allocates pixels in the bitmap. It is guaranteed that the data in the + // bitmap are initialized but the actual values are undefined. + // Specifying 0 scale means the image is for unscaled image. (unscaled() + // returns truen, and scale() returns 1.0f;) + ImageSkiaRep(const gfx::Size& size, float scale); + + // Creates a bitmap with given scale. + // Adds ref to |src|. + ImageSkiaRep(const SkBitmap& src, float scale); + ImageSkiaRep(const ImageSkiaRep& other); + + ~ImageSkiaRep(); + + // Get width and height of the image in pixels. + int pixel_width() const { return bitmap_.width(); } + int pixel_height() const { return bitmap_.height(); } + Size pixel_size() const { return gfx::Size(pixel_width(), pixel_height()); } + + // Get width and height of the image in DIP. + int GetWidth() const; + int GetHeight() const; + + // Retrieves the scale for which this image is a representation of. + float scale() const { return unscaled() ? 1.0f : scale_; } + bool unscaled() const { return scale_ == 0.0f; } + + bool is_null() const { return bitmap_.isNull(); } + + // Returns the backing bitmap when the image representation is sourced from a + // bitmap. + const SkBitmap& GetBitmap() const; + + private: + Size pixel_size_; + SkBitmap bitmap_; + float scale_; +}; + +} // namespace gfx + +#endif // UI_GFX_IMAGE_IMAGE_SKIA_REP_IOS_H_ diff --git a/image/image_skia_source.cc b/image/image_skia_source.cc new file mode 100644 index 000000000000..53bc4271ca78 --- /dev/null +++ b/image/image_skia_source.cc @@ -0,0 +1,13 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/image/image_skia_source.h" + +namespace gfx { + +ImageSkiaSource::~ImageSkiaSource() {} + +bool ImageSkiaSource::HasRepresentationAtAllScales() const { return false; } + +} // namespace gfx diff --git a/image/image_skia_source.h b/image/image_skia_source.h new file mode 100644 index 000000000000..9ffc135f5466 --- /dev/null +++ b/image/image_skia_source.h @@ -0,0 +1,32 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_IMAGE_IMAGE_SKIA_SOURCE_H_ +#define UI_GFX_IMAGE_IMAGE_SKIA_SOURCE_H_ + + +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +class ImageSkiaRep; + +class GFX_EXPORT ImageSkiaSource { + public: + virtual ~ImageSkiaSource(); + + // Returns the ImageSkiaRep for the given |scale|. ImageSkia caches the + // returned ImageSkiaRep and calls this method only if it doesn't have + // ImageSkiaRep for given |scale|. There is no need for the implementation to + // cache the image. + virtual gfx::ImageSkiaRep GetImageForScale(float scale) = 0; + + // Subclasses should override this to return true when they are capable of + // providing an exact representation at any desired scale factor. + virtual bool HasRepresentationAtAllScales() const; +}; + +} // namespace gfx + +#endif // UI_GFX_IMAGE_IMAGE_SKIA_SOURCE_H_ diff --git a/image/image_skia_unittest.cc b/image/image_skia_unittest.cc new file mode 100644 index 000000000000..12b19b95e7c4 --- /dev/null +++ b/image/image_skia_unittest.cc @@ -0,0 +1,637 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/image/image_skia.h" + +#include + +#include "base/check_op.h" +#include "base/command_line.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/threading/simple_thread.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/image/image_skia_rep.h" +#include "ui/gfx/image/image_skia_source.h" +#include "ui/gfx/switches.h" + +namespace gfx { + +namespace { + +class FixedSource : public ImageSkiaSource { + public: + explicit FixedSource(const ImageSkiaRep& image) : image_(image) {} + + FixedSource(const FixedSource&) = delete; + FixedSource& operator=(const FixedSource&) = delete; + + ~FixedSource() override {} + + ImageSkiaRep GetImageForScale(float scale) override { return image_; } + + private: + ImageSkiaRep image_; +}; + +class FixedScaleSource : public ImageSkiaSource { + public: + explicit FixedScaleSource(const ImageSkiaRep& image) : image_(image) {} + + FixedScaleSource(const FixedScaleSource&) = delete; + FixedScaleSource& operator=(const FixedScaleSource&) = delete; + + ~FixedScaleSource() override {} + + ImageSkiaRep GetImageForScale(float scale) override { + if (!image_.unscaled() && image_.scale() != scale) + return ImageSkiaRep(); + return image_; + } + + private: + ImageSkiaRep image_; +}; + +class DynamicSource : public ImageSkiaSource { + public: + explicit DynamicSource(const gfx::Size& size) + : size_(size), + last_requested_scale_(0.0f) {} + + DynamicSource(const DynamicSource&) = delete; + DynamicSource& operator=(const DynamicSource&) = delete; + + ~DynamicSource() override {} + + ImageSkiaRep GetImageForScale(float scale) override { + last_requested_scale_ = scale; + return gfx::ImageSkiaRep(size_, scale); + } + + float GetLastRequestedScaleAndReset() { + float result = last_requested_scale_; + last_requested_scale_ = 0.0f; + return result; + } + + private: + gfx::Size size_; + float last_requested_scale_; +}; + +class NullSource: public ImageSkiaSource { + public: + NullSource() { + } + + NullSource(const NullSource&) = delete; + NullSource& operator=(const NullSource&) = delete; + + ~NullSource() override {} + + ImageSkiaRep GetImageForScale(float scale) override { + return gfx::ImageSkiaRep(); + } +}; + +} // namespace + +namespace test { +class TestOnThread : public base::SimpleThread { + public: + explicit TestOnThread(ImageSkia* image_skia) + : SimpleThread("image_skia_on_thread"), + image_skia_(image_skia), + can_read_(false), + can_modify_(false) { + } + + TestOnThread(const TestOnThread&) = delete; + TestOnThread& operator=(const TestOnThread&) = delete; + + void Run() override { + can_read_ = image_skia_->CanRead(); + can_modify_ = image_skia_->CanModify(); + if (can_read_) + image_skia_->image_reps(); + } + + void StartAndJoin() { + Start(); + Join(); + } + + bool can_read() const { return can_read_; } + + bool can_modify() const { return can_modify_; } + + private: + ImageSkia* image_skia_; + + bool can_read_; + bool can_modify_; +}; + +} // namespace test + +class ImageSkiaTest : public testing::Test { + public: + ImageSkiaTest() { + // In the test, we assume that we support 1.0f and 2.0f DSFs. + old_scales_ = ImageSkia::GetSupportedScales(); + + // Sets the list of scale factors supported by resource bundle. + std::vector supported_scales; + supported_scales.push_back(1.0f); + supported_scales.push_back(2.0f); + ImageSkia::SetSupportedScales(supported_scales); + } + + ImageSkiaTest(const ImageSkiaTest&) = delete; + ImageSkiaTest& operator=(const ImageSkiaTest&) = delete; + + ~ImageSkiaTest() override { ImageSkia::SetSupportedScales(old_scales_); } + + private: + std::vector old_scales_; +}; + +TEST_F(ImageSkiaTest, FixedSource) { + ImageSkiaRep image(Size(100, 200), 0.0f); + ImageSkia image_skia(std::make_unique(image), Size(100, 200)); + EXPECT_EQ(0U, image_skia.image_reps().size()); + + const ImageSkiaRep& result_100p = image_skia.GetRepresentation(1.0f); + EXPECT_EQ(100, result_100p.GetWidth()); + EXPECT_EQ(200, result_100p.GetHeight()); + EXPECT_EQ(1.0f, result_100p.scale()); + EXPECT_EQ(1U, image_skia.image_reps().size()); + + const ImageSkiaRep& result_200p = image_skia.GetRepresentation(2.0f); + + EXPECT_EQ(100, result_200p.GetWidth()); + EXPECT_EQ(200, result_200p.GetHeight()); + EXPECT_EQ(100, result_200p.pixel_width()); + EXPECT_EQ(200, result_200p.pixel_height()); + EXPECT_EQ(1.0f, result_200p.scale()); + EXPECT_EQ(1U, image_skia.image_reps().size()); + + const ImageSkiaRep& result_300p = image_skia.GetRepresentation(3.0f); + + EXPECT_EQ(100, result_300p.GetWidth()); + EXPECT_EQ(200, result_300p.GetHeight()); + EXPECT_EQ(100, result_300p.pixel_width()); + EXPECT_EQ(200, result_300p.pixel_height()); + EXPECT_EQ(1.0f, result_300p.scale()); + EXPECT_EQ(1U, image_skia.image_reps().size()); + + // Get the representation again and make sure it doesn't + // generate new image skia rep. + image_skia.GetRepresentation(1.0f); + image_skia.GetRepresentation(2.0f); + image_skia.GetRepresentation(3.0f); + EXPECT_EQ(1U, image_skia.image_reps().size()); +} + +TEST_F(ImageSkiaTest, FixedScaledSource) { + ImageSkiaRep image(Size(100, 200), 1.0f); + ImageSkia image_skia(std::make_unique(image), + Size(100, 200)); + EXPECT_EQ(0U, image_skia.image_reps().size()); + + const ImageSkiaRep& result_100p = image_skia.GetRepresentation(1.0f); + EXPECT_EQ(100, result_100p.GetWidth()); + EXPECT_EQ(200, result_100p.GetHeight()); + EXPECT_EQ(1.0f, result_100p.scale()); + EXPECT_EQ(1U, image_skia.image_reps().size()); + + // 2.0f data doesn't exist, then it falls back to 1.0f and rescale it. + const ImageSkiaRep& result_200p = image_skia.GetRepresentation(2.0f); + + EXPECT_EQ(100, result_200p.GetWidth()); + EXPECT_EQ(200, result_200p.GetHeight()); + EXPECT_EQ(200, result_200p.pixel_width()); + EXPECT_EQ(400, result_200p.pixel_height()); + EXPECT_EQ(2.0f, result_200p.scale()); + EXPECT_EQ(2U, image_skia.image_reps().size()); + + // Get the representation again and make sure it doesn't + // generate new image skia rep. + image_skia.GetRepresentation(1.0f); + image_skia.GetRepresentation(2.0f); + EXPECT_EQ(2U, image_skia.image_reps().size()); +} + +TEST_F(ImageSkiaTest, FixedUnscaledSource) { + ImageSkiaRep image(Size(100, 200), 0.0f); + ImageSkia image_skia(std::make_unique(image), + Size(100, 200)); + EXPECT_EQ(0U, image_skia.image_reps().size()); + + const ImageSkiaRep& result_100p = image_skia.GetRepresentation(1.0f); + EXPECT_EQ(100, result_100p.pixel_width()); + EXPECT_EQ(200, result_100p.pixel_height()); + EXPECT_TRUE(result_100p.unscaled()); + EXPECT_EQ(1U, image_skia.image_reps().size()); + + // 2.0f data doesn't exist, but unscaled ImageSkiaRep shouldn't be rescaled. + const ImageSkiaRep& result_200p = image_skia.GetRepresentation(2.0f); + + EXPECT_EQ(100, result_200p.pixel_width()); + EXPECT_EQ(200, result_200p.pixel_height()); + EXPECT_TRUE(result_200p.unscaled()); + EXPECT_EQ(1U, image_skia.image_reps().size()); + + // Get the representation again and make sure it doesn't + // generate new image skia rep. + image_skia.GetRepresentation(1.0f); + image_skia.GetRepresentation(2.0f); + EXPECT_EQ(1U, image_skia.image_reps().size()); +} + +TEST_F(ImageSkiaTest, DynamicSource) { + ImageSkia image_skia(std::make_unique(Size(100, 200)), + Size(100, 200)); + EXPECT_EQ(0U, image_skia.image_reps().size()); + const ImageSkiaRep& result_100p = image_skia.GetRepresentation(1.0f); + EXPECT_EQ(100, result_100p.GetWidth()); + EXPECT_EQ(200, result_100p.GetHeight()); + EXPECT_EQ(1.0f, result_100p.scale()); + EXPECT_EQ(1U, image_skia.image_reps().size()); + + const ImageSkiaRep& result_200p = + image_skia.GetRepresentation(2.0f); + EXPECT_EQ(100, result_200p.GetWidth()); + EXPECT_EQ(200, result_200p.GetHeight()); + EXPECT_EQ(200, result_200p.pixel_width()); + EXPECT_EQ(400, result_200p.pixel_height()); + EXPECT_EQ(2.0f, result_200p.scale()); + EXPECT_EQ(2U, image_skia.image_reps().size()); + + // Get the representation again and make sure it doesn't + // generate new image skia rep. + image_skia.GetRepresentation(1.0f); + EXPECT_EQ(2U, image_skia.image_reps().size()); + image_skia.GetRepresentation(2.0f); + EXPECT_EQ(2U, image_skia.image_reps().size()); +} + +// Tests that image_reps returns all of the representations in the +// image when there are multiple representations for a scale factor. +// This currently is the case with ImageLoader::LoadImages. +TEST_F(ImageSkiaTest, ManyRepsPerScaleFactor) { + const int kSmallIcon1x = 16; + const int kSmallIcon2x = 32; + const int kLargeIcon1x = 32; + + ImageSkia image(std::make_unique(), + gfx::Size(kSmallIcon1x, kSmallIcon1x)); + // Simulate a source which loads images on a delay. Upon + // GetImageForScaleFactor, it immediately returns null and starts loading + // image reps slowly. + image.GetRepresentation(1.0f); + image.GetRepresentation(2.0f); + + // After a lengthy amount of simulated time, finally loaded image reps. + image.AddRepresentation(ImageSkiaRep( + gfx::Size(kSmallIcon1x, kSmallIcon1x), 1.0f)); + image.AddRepresentation(ImageSkiaRep( + gfx::Size(kSmallIcon2x, kSmallIcon2x), 2.0f)); + image.AddRepresentation(ImageSkiaRep( + gfx::Size(kLargeIcon1x, kLargeIcon1x), 1.0f)); + + std::vector image_reps = image.image_reps(); + EXPECT_EQ(3u, image_reps.size()); + + int num_1x = 0; + int num_2x = 0; + for (size_t i = 0; i < image_reps.size(); ++i) { + if (image_reps[i].scale() == 1.0f) + num_1x++; + else if (image_reps[i].scale() == 2.0f) + num_2x++; + } + EXPECT_EQ(2, num_1x); + EXPECT_EQ(1, num_2x); +} + +TEST_F(ImageSkiaTest, GetBitmap) { + ImageSkia image_skia(std::make_unique(Size(100, 200)), + Size(100, 200)); + const SkBitmap* bitmap = image_skia.bitmap(); + ASSERT_NE(nullptr, bitmap); + EXPECT_FALSE(bitmap->isNull()); +} + +TEST_F(ImageSkiaTest, GetBitmapFromEmpty) { + // Create an image with 1 representation and remove it so the ImageSkiaStorage + // is left with no representations. + ImageSkia empty_image(ImageSkiaRep(Size(100, 200), 1.0f)); + ImageSkia empty_image_copy(empty_image); + empty_image.RemoveRepresentation(1.0f); + + // Check that ImageSkia::bitmap() still returns a valid SkBitmap pointer for + // the image and all its copies. + const SkBitmap* bitmap = empty_image_copy.bitmap(); + ASSERT_NE(nullptr, bitmap); + EXPECT_TRUE(bitmap->isNull()); + EXPECT_TRUE(bitmap->empty()); +} + +TEST_F(ImageSkiaTest, BackedBySameObjectAs) { + // Null images should all be backed by the same object (NULL). + ImageSkia image; + ImageSkia unrelated; + EXPECT_TRUE(image.BackedBySameObjectAs(unrelated)); + + image.AddRepresentation(gfx::ImageSkiaRep(gfx::Size(10, 10), + 1.0f)); + ImageSkia copy = image; + copy.AddRepresentation(gfx::ImageSkiaRep(gfx::Size(10, 10), + 2.0f)); + unrelated.AddRepresentation(gfx::ImageSkiaRep(gfx::Size(10, 10), + 1.0f)); + EXPECT_TRUE(image.BackedBySameObjectAs(copy)); + EXPECT_FALSE(image.BackedBySameObjectAs(unrelated)); + EXPECT_FALSE(copy.BackedBySameObjectAs(unrelated)); +} + +#if DCHECK_IS_ON() +TEST_F(ImageSkiaTest, EmptyOnThreadTest) { + ImageSkia empty; + test::TestOnThread empty_on_thread(&empty); + empty_on_thread.Start(); + empty_on_thread.Join(); + EXPECT_TRUE(empty_on_thread.can_read()); + EXPECT_TRUE(empty_on_thread.can_modify()); +} + +TEST_F(ImageSkiaTest, StaticOnThreadTest) { + ImageSkia image(ImageSkiaRep(Size(100, 200), 1.0f)); + EXPECT_FALSE(image.IsThreadSafe()); + + test::TestOnThread image_on_thread(&image); + // an image that was never accessed on this thread can be + // read by other thread. + image_on_thread.StartAndJoin(); + EXPECT_TRUE(image_on_thread.can_read()); + EXPECT_TRUE(image_on_thread.can_modify()); + EXPECT_FALSE(image.CanRead()); + EXPECT_FALSE(image.CanModify()); + + image.DetachStorageFromSequence(); + // An image is accessed by this thread, + // so other thread cannot read/modify it. + image.image_reps(); + test::TestOnThread image_on_thread2(&image); + image_on_thread2.StartAndJoin(); + EXPECT_FALSE(image_on_thread2.can_read()); + EXPECT_FALSE(image_on_thread2.can_modify()); + EXPECT_TRUE(image.CanRead()); + EXPECT_TRUE(image.CanModify()); + + image.DetachStorageFromSequence(); + ImageSkia deep_copy(image.DeepCopy()); + EXPECT_FALSE(deep_copy.IsThreadSafe()); + test::TestOnThread deepcopy_on_thread(&deep_copy); + deepcopy_on_thread.StartAndJoin(); + EXPECT_TRUE(deepcopy_on_thread.can_read()); + EXPECT_TRUE(deepcopy_on_thread.can_modify()); + EXPECT_FALSE(deep_copy.CanRead()); + EXPECT_FALSE(deep_copy.CanModify()); + + ImageSkia deep_copy2(image.DeepCopy()); + EXPECT_EQ(1U, deep_copy2.image_reps().size()); + // Access it from current thread so that it can't be + // accessed from another thread. + deep_copy2.image_reps(); + EXPECT_FALSE(deep_copy2.IsThreadSafe()); + test::TestOnThread deepcopy2_on_thread(&deep_copy2); + deepcopy2_on_thread.StartAndJoin(); + EXPECT_FALSE(deepcopy2_on_thread.can_read()); + EXPECT_FALSE(deepcopy2_on_thread.can_modify()); + EXPECT_TRUE(deep_copy2.CanRead()); + EXPECT_TRUE(deep_copy2.CanModify()); + + image.DetachStorageFromSequence(); + image.SetReadOnly(); + // A read-only ImageSkia with no source is thread safe. + EXPECT_TRUE(image.IsThreadSafe()); + test::TestOnThread readonly_on_thread(&image); + readonly_on_thread.StartAndJoin(); + EXPECT_TRUE(readonly_on_thread.can_read()); + EXPECT_FALSE(readonly_on_thread.can_modify()); + EXPECT_TRUE(image.CanRead()); + EXPECT_FALSE(image.CanModify()); + + image.DetachStorageFromSequence(); + image.MakeThreadSafe(); + EXPECT_TRUE(image.IsThreadSafe()); + test::TestOnThread threadsafe_on_thread(&image); + threadsafe_on_thread.StartAndJoin(); + EXPECT_TRUE(threadsafe_on_thread.can_read()); + EXPECT_FALSE(threadsafe_on_thread.can_modify()); + EXPECT_TRUE(image.CanRead()); + EXPECT_FALSE(image.CanModify()); +} + +TEST_F(ImageSkiaTest, SourceOnThreadTest) { + ImageSkia image(std::make_unique(Size(100, 200)), + Size(100, 200)); + EXPECT_FALSE(image.IsThreadSafe()); + + test::TestOnThread image_on_thread(&image); + image_on_thread.StartAndJoin(); + // an image that was never accessed on this thread can be + // read by other thread. + EXPECT_TRUE(image_on_thread.can_read()); + EXPECT_TRUE(image_on_thread.can_modify()); + EXPECT_FALSE(image.CanRead()); + EXPECT_FALSE(image.CanModify()); + + image.DetachStorageFromSequence(); + // An image is accessed by this thread, + // so other thread cannot read/modify it. + image.image_reps(); + test::TestOnThread image_on_thread2(&image); + image_on_thread2.StartAndJoin(); + EXPECT_FALSE(image_on_thread2.can_read()); + EXPECT_FALSE(image_on_thread2.can_modify()); + EXPECT_TRUE(image.CanRead()); + EXPECT_TRUE(image.CanModify()); + + image.DetachStorageFromSequence(); + image.SetReadOnly(); + EXPECT_FALSE(image.IsThreadSafe()); + test::TestOnThread readonly_on_thread(&image); + readonly_on_thread.StartAndJoin(); + EXPECT_TRUE(readonly_on_thread.can_read()); + EXPECT_FALSE(readonly_on_thread.can_modify()); + EXPECT_FALSE(image.CanRead()); + EXPECT_FALSE(image.CanModify()); + + image.DetachStorageFromSequence(); + image.MakeThreadSafe(); + EXPECT_TRUE(image.IsThreadSafe()); + // Check if image reps are generated for supported scale factors. + EXPECT_EQ(ImageSkia::GetSupportedScales().size(), + image.image_reps().size()); + test::TestOnThread threadsafe_on_thread(&image); + threadsafe_on_thread.StartAndJoin(); + EXPECT_TRUE(threadsafe_on_thread.can_read()); + EXPECT_FALSE(threadsafe_on_thread.can_modify()); + EXPECT_TRUE(image.CanRead()); + EXPECT_FALSE(image.CanModify()); +} +#endif // DCHECK_IS_ON() + +TEST_F(ImageSkiaTest, Unscaled) { + SkBitmap bitmap; + + // An ImageSkia created with 1x bitmap is unscaled. + ImageSkia image_skia = ImageSkia::CreateFrom1xBitmap(bitmap); + EXPECT_TRUE(image_skia.GetRepresentation(1.0f).unscaled()); + ImageSkiaRep rep_2x(Size(100, 100), 2.0f); + + // When reps for other scales are added, the unscaled image + // becomes scaled. + image_skia.AddRepresentation(rep_2x); + EXPECT_FALSE(image_skia.GetRepresentation(1.0f).unscaled()); + EXPECT_FALSE(image_skia.GetRepresentation(2.0f).unscaled()); +} + +namespace { + +std::vector GetSortedScaleFactors(const gfx::ImageSkia& image) { + const std::vector& image_reps = image.image_reps(); + std::vector scale_factors; + for (size_t i = 0; i < image_reps.size(); ++i) { + scale_factors.push_back(image_reps[i].scale()); + } + std::sort(scale_factors.begin(), scale_factors.end()); + return scale_factors; +} + +} // namespace + +TEST_F(ImageSkiaTest, ArbitraryScaleFactor) { + // source is owned by |image| + DynamicSource* source = new DynamicSource(Size(100, 200)); + ImageSkia image(base::WrapUnique(source), gfx::Size(100, 200)); + + image.GetRepresentation(1.5f); + EXPECT_EQ(2.0f, source->GetLastRequestedScaleAndReset()); + std::vector image_reps = image.image_reps(); + EXPECT_EQ(2u, image_reps.size()); + + std::vector scale_factors = GetSortedScaleFactors(image); + EXPECT_EQ(1.5f, scale_factors[0]); + EXPECT_EQ(2.0f, scale_factors[1]); + + // Requesting 1.75 scale factor also falls back to 2.0f and rescale. + // However, the image already has the 2.0f data, so it won't fetch again. + image.GetRepresentation(1.75f); + EXPECT_EQ(0.0f, source->GetLastRequestedScaleAndReset()); + image_reps = image.image_reps(); + EXPECT_EQ(3u, image_reps.size()); + + scale_factors = GetSortedScaleFactors(image); + EXPECT_EQ(1.5f, scale_factors[0]); + EXPECT_EQ(1.75f, scale_factors[1]); + EXPECT_EQ(2.0f, scale_factors[2]); + + // Requesting 1.25 scale factor also falls back to 2.0f and rescale. + // However, the image already has the 2.0f data, so it won't fetch again. + image.GetRepresentation(1.25f); + EXPECT_EQ(0.0f, source->GetLastRequestedScaleAndReset()); + image_reps = image.image_reps(); + EXPECT_EQ(4u, image_reps.size()); + scale_factors = GetSortedScaleFactors(image); + EXPECT_EQ(1.25f, scale_factors[0]); + EXPECT_EQ(1.5f, scale_factors[1]); + EXPECT_EQ(1.75f, scale_factors[2]); + EXPECT_EQ(2.0f, scale_factors[3]); + + // 1.20 is falled back to 1.0. + image.GetRepresentation(1.20f); + EXPECT_EQ(1.0f, source->GetLastRequestedScaleAndReset()); + image_reps = image.image_reps(); + EXPECT_EQ(6u, image_reps.size()); + scale_factors = GetSortedScaleFactors(image); + EXPECT_EQ(1.0f, scale_factors[0]); + EXPECT_EQ(1.2f, scale_factors[1]); + EXPECT_EQ(1.25f, scale_factors[2]); + EXPECT_EQ(1.5f, scale_factors[3]); + EXPECT_EQ(1.75f, scale_factors[4]); + EXPECT_EQ(2.0f, scale_factors[5]); + + // Scale factor less than 1.0f will be falled back to 1.0f + image.GetRepresentation(0.75f); + EXPECT_EQ(0.0f, source->GetLastRequestedScaleAndReset()); + image_reps = image.image_reps(); + EXPECT_EQ(7u, image_reps.size()); + + scale_factors = GetSortedScaleFactors(image); + EXPECT_EQ(0.75f, scale_factors[0]); + EXPECT_EQ(1.0f, scale_factors[1]); + EXPECT_EQ(1.2f, scale_factors[2]); + EXPECT_EQ(1.25f, scale_factors[3]); + EXPECT_EQ(1.5f, scale_factors[4]); + EXPECT_EQ(1.75f, scale_factors[5]); + EXPECT_EQ(2.0f, scale_factors[6]); + + // Scale factor greater than 2.0f is falled back to 2.0f because it's not + // supported. + image.GetRepresentation(3.0f); + EXPECT_EQ(0.0f, source->GetLastRequestedScaleAndReset()); + image_reps = image.image_reps(); + EXPECT_EQ(8u, image_reps.size()); +} + +TEST_F(ImageSkiaTest, ArbitraryScaleFactorWithMissingResource) { + ImageSkia image( + std::make_unique(ImageSkiaRep(Size(100, 200), 1.0f)), + Size(100, 200)); + + // Requesting 1.5f -- falls back to 2.0f, but couldn't find. It should + // look up 1.0f and then rescale it. Note that the rescaled ImageSkiaRep will + // have 2.0f scale. + const ImageSkiaRep& rep = image.GetRepresentation(1.5f); + EXPECT_EQ(1.5f, rep.scale()); + EXPECT_EQ(2U, image.image_reps().size()); + EXPECT_EQ(2.0f, image.image_reps()[0].scale()); + EXPECT_EQ(1.5f, image.image_reps()[1].scale()); +} + +TEST_F(ImageSkiaTest, UnscaledImageForArbitraryScaleFactor) { + // 0.0f means unscaled. + ImageSkia image( + std::make_unique(ImageSkiaRep(Size(100, 200), 0.0f)), + Size(100, 200)); + + // Requesting 2.0f, which should return 1.0f unscaled image. + const ImageSkiaRep& rep = image.GetRepresentation(2.0f); + EXPECT_EQ(1.0f, rep.scale()); + EXPECT_EQ("100x200", rep.pixel_size().ToString()); + EXPECT_TRUE(rep.unscaled()); + EXPECT_EQ(1U, image.image_reps().size()); + + // Same for any other scale factors. + const ImageSkiaRep& rep15 = image.GetRepresentation(1.5f); + EXPECT_EQ(1.0f, rep15.scale()); + EXPECT_EQ("100x200", rep15.pixel_size().ToString()); + EXPECT_TRUE(rep15.unscaled()); + EXPECT_EQ(1U, image.image_reps().size()); + + const ImageSkiaRep& rep12 = image.GetRepresentation(1.2f); + EXPECT_EQ(1.0f, rep12.scale()); + EXPECT_EQ("100x200", rep12.pixel_size().ToString()); + EXPECT_TRUE(rep12.unscaled()); + EXPECT_EQ(1U, image.image_reps().size()); +} + +} // namespace gfx diff --git a/image/image_skia_util_ios.h b/image/image_skia_util_ios.h new file mode 100644 index 000000000000..6576ff8da7a4 --- /dev/null +++ b/image/image_skia_util_ios.h @@ -0,0 +1,41 @@ +// Copyright 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_IMAGE_IMAGE_SKIA_UTIL_IOS_H_ +#define UI_GFX_IMAGE_IMAGE_SKIA_UTIL_IOS_H_ + +#include "ui/gfx/gfx_export.h" + +#ifdef __OBJC__ +@class UIImage; +#else +class UIImage; +#endif + +namespace gfx { +class ImageSkia; +class ImageSkiaRep; + +// Converts to ImageSkia from UIImage. +GFX_EXPORT gfx::ImageSkia ImageSkiaFromUIImage(UIImage* image); + +// Converts to an ImageSkiaRep of |scale_factor| from UIImage. +// |scale| is passed explicitly in order to allow this method to be used +// with a |scale| which is not supported by the platform. +GFX_EXPORT gfx::ImageSkiaRep ImageSkiaRepOfScaleFromUIImage( + UIImage* image, + float scale); + +// Converts to UIImage from ImageSkia. The returned UIImage will be at the scale +// of the ImageSkiaRep in |image_skia| which most closely matches the device's +// scale factor (eg Retina iPad -> 2x). Returns an autoreleased UIImage. +GFX_EXPORT UIImage* UIImageFromImageSkia(const gfx::ImageSkia& image_skia); + +// Converts to UIImage from ImageSkiaRep. Returns an autoreleased UIImage. +GFX_EXPORT UIImage* UIImageFromImageSkiaRep( + const gfx::ImageSkiaRep& image_skia_rep); + +} // namespace gfx + +#endif // UI_GFX_IMAGE_IMAGE_SKIA_UTIL_IOS_H_ diff --git a/image/image_skia_util_ios.mm b/image/image_skia_util_ios.mm new file mode 100644 index 000000000000..4ecbfdcda32e --- /dev/null +++ b/image/image_skia_util_ios.mm @@ -0,0 +1,55 @@ +// Copyright 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/image/image_skia_util_ios.h" + +#import + +#include "base/mac/scoped_cftyperef.h" +#include "skia/ext/skia_utils_ios.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/gfx/image/image_skia.h" + +namespace gfx { + +gfx::ImageSkia ImageSkiaFromUIImage(UIImage* image) { + gfx::ImageSkia image_skia; + float max_scale = ImageSkia::GetSupportedScales().back(); + gfx::ImageSkiaRep image_skia_rep = ImageSkiaRepOfScaleFromUIImage( + image, max_scale); + if (!image_skia_rep.is_null()) + image_skia.AddRepresentation(image_skia_rep); + return image_skia; +} + +gfx::ImageSkiaRep ImageSkiaRepOfScaleFromUIImage(UIImage* image, float scale) { + if (!image) + return gfx::ImageSkiaRep(); + + CGSize size = image.size; + CGSize desired_size_for_scale = + CGSizeMake(size.width * scale, size.height * scale); + SkBitmap bitmap(skia::CGImageToSkBitmap(image.CGImage, + desired_size_for_scale, + false)); + return gfx::ImageSkiaRep(bitmap, scale); +} + +UIImage* UIImageFromImageSkia(const gfx::ImageSkia& image_skia) { + return UIImageFromImageSkiaRep( + image_skia.GetRepresentation(ImageSkia::GetSupportedScales().back())); +} + +UIImage* UIImageFromImageSkiaRep(const gfx::ImageSkiaRep& image_skia_rep) { + if (image_skia_rep.is_null()) + return nil; + + float scale = image_skia_rep.scale(); + base::ScopedCFTypeRef color_space( + CGColorSpaceCreateDeviceRGB()); + return skia::SkBitmapToUIImageWithColorSpace(image_skia_rep.GetBitmap(), + scale, color_space); +} + +} // namespace gfx diff --git a/image/image_skia_util_mac.h b/image/image_skia_util_mac.h new file mode 100644 index 000000000000..5d7af201bee1 --- /dev/null +++ b/image/image_skia_util_mac.h @@ -0,0 +1,40 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_IMAGE_IMAGE_SKIA_UTIL_MAC_H_ +#define UI_GFX_IMAGE_IMAGE_SKIA_UTIL_MAC_H_ + +#include + +#include "ui/gfx/gfx_export.h" + +using NSSize = CGSize; + +#ifdef __OBJC__ +@class NSImage; +#else +class NSImage; +#endif + +namespace gfx { +class ImageSkia; + +// Converts to ImageSkia from NSImage. +GFX_EXPORT gfx::ImageSkia ImageSkiaFromNSImage(NSImage* image); + +// Resizes NSImage to |size| DIP and then converts to ImageSkia. +GFX_EXPORT gfx::ImageSkia ImageSkiaFromResizedNSImage(NSImage* image, + NSSize size); + +// Converts to NSImage from ImageSkia. +GFX_EXPORT NSImage* NSImageFromImageSkia(const gfx::ImageSkia& image_skia); + +// Converts to NSImage from given ImageSkia and a color space. +GFX_EXPORT NSImage* NSImageFromImageSkiaWithColorSpace( + const gfx::ImageSkia& image_skia, + CGColorSpaceRef color_space); + +} // namespace gfx + +#endif // UI_GFX_IMAGE_IMAGE_SKIA_UTIL_MAC_H_ diff --git a/image/image_skia_util_mac.mm b/image/image_skia_util_mac.mm new file mode 100644 index 000000000000..35b3845e9773 --- /dev/null +++ b/image/image_skia_util_mac.mm @@ -0,0 +1,116 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/image/image_skia_util_mac.h" + +#import +#include + +#include +#include +#include + +#include "base/mac/mac_util.h" +#include "base/mac/scoped_nsobject.h" +#include "skia/ext/skia_utils_mac.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/gfx/image/image_skia.h" + +namespace { + +// Returns NSImageRep whose pixel size most closely matches |desired_size|. +NSImageRep* GetNSImageRepWithPixelSize(NSImage* image, + NSSize desired_size) { + float smallest_diff = std::numeric_limits::max(); + NSImageRep* closest_match = nil; + for (NSImageRep* image_rep in [image representations]) { + float diff = std::abs(desired_size.width - [image_rep pixelsWide]) + + std::abs(desired_size.height - [image_rep pixelsHigh]); + if (diff < smallest_diff) { + smallest_diff = diff; + closest_match = image_rep; + } + } + return closest_match; +} + +// Returns true if NSImage has no representations +bool IsNSImageEmpty(NSImage* image) { + return ([image representations].count == 0); +} + +} // namespace + +namespace gfx { + +gfx::ImageSkia ImageSkiaFromNSImage(NSImage* image) { + return ImageSkiaFromResizedNSImage(image, [image size]); +} + +gfx::ImageSkia ImageSkiaFromResizedNSImage(NSImage* image, + NSSize desired_size) { + // Resize and convert to ImageSkia simultaneously to save on computation. + // TODO(pkotwicz): Separate resizing NSImage and converting to ImageSkia. + // Convert to ImageSkia by finding the most appropriate NSImageRep for + // each supported scale factor and resizing if necessary. + + if (IsNSImageEmpty(image)) + return gfx::ImageSkia(); + + std::vector supported_scales = ImageSkia::GetSupportedScales(); + + gfx::ImageSkia image_skia; + for (size_t i = 0; i < supported_scales.size(); ++i) { + float scale = supported_scales[i]; + NSSize desired_size_for_scale = NSMakeSize(desired_size.width * scale, + desired_size.height * scale); + NSImageRep* ns_image_rep = GetNSImageRepWithPixelSize(image, + desired_size_for_scale); + + // TODO(dcheng): Should this function take a color space argument? + SkBitmap bitmap(skia::NSImageRepToSkBitmapWithColorSpace(ns_image_rep, + desired_size_for_scale, false, base::mac::GetGenericRGBColorSpace())); + if (bitmap.isNull()) + continue; + + image_skia.AddRepresentation(gfx::ImageSkiaRep(bitmap, scale)); + } + return image_skia; +} + +NSImage* NSImageFromImageSkia(const gfx::ImageSkia& image_skia) { + if (image_skia.isNull()) + return nil; + + base::scoped_nsobject image([[NSImage alloc] init]); + image_skia.EnsureRepsForSupportedScales(); + std::vector image_reps = image_skia.image_reps(); + for (std::vector::const_iterator it = image_reps.begin(); + it != image_reps.end(); ++it) { + [image addRepresentation:skia::SkBitmapToNSBitmapImageRep(it->GetBitmap())]; + } + + [image setSize:NSMakeSize(image_skia.width(), image_skia.height())]; + return [image.release() autorelease]; +} + +NSImage* NSImageFromImageSkiaWithColorSpace(const gfx::ImageSkia& image_skia, + CGColorSpaceRef color_space) { + if (image_skia.isNull()) + return nil; + + base::scoped_nsobject image([[NSImage alloc] init]); + image_skia.EnsureRepsForSupportedScales(); + std::vector image_reps = image_skia.image_reps(); + for (std::vector::const_iterator it = image_reps.begin(); + it != image_reps.end(); ++it) { + [image addRepresentation:skia::SkBitmapToNSBitmapImageRepWithColorSpace( + it->GetBitmap(), color_space)]; + } + + [image setSize:NSMakeSize(image_skia.width(), image_skia.height())]; + return [image.release() autorelease]; +} + +} // namespace gfx diff --git a/image/image_unittest.cc b/image/image_unittest.cc new file mode 100644 index 000000000000..94fea8be97c8 --- /dev/null +++ b/image/image_unittest.cc @@ -0,0 +1,708 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +#include "build/build_config.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "third_party/skia/include/core/SkPaint.h" +#include "ui/gfx/image/image.h" +#include "ui/gfx/image/image_png_rep.h" +#include "ui/gfx/image/image_skia.h" +#include "ui/gfx/image/image_unittest_util.h" + +#if defined(OS_IOS) +#include "base/mac/foundation_util.h" +#include "skia/ext/skia_utils_ios.h" +#elif defined(OS_MAC) +#include "base/mac/foundation_util.h" +#include "skia/ext/skia_utils_mac.h" +#endif + +namespace { + +#if defined(OS_APPLE) +const bool kUsesSkiaNatively = false; +#else +const bool kUsesSkiaNatively = true; +#endif + +class ImageTest : public testing::Test { + public: + ImageTest() { + std::vector scales; + scales.push_back(1.0f); +#if !defined(OS_IOS) + scales.push_back(2.0f); +#endif + gfx::ImageSkia::SetSupportedScales(scales); + } +}; + +namespace gt = gfx::test; + +TEST_F(ImageTest, EmptyImage) { + // Test the default constructor. + gfx::Image image; + EXPECT_EQ(0U, image.RepresentationCount()); + EXPECT_TRUE(image.IsEmpty()); + EXPECT_EQ(0, image.Width()); + EXPECT_EQ(0, image.Height()); +} + +// Test constructing a gfx::Image from an empty PlatformImage. +TEST_F(ImageTest, EmptyImageFromEmptyPlatformImage) { +#if defined(OS_APPLE) + gfx::Image image1(nullptr); + EXPECT_TRUE(image1.IsEmpty()); + EXPECT_EQ(0, image1.Width()); + EXPECT_EQ(0, image1.Height()); + EXPECT_EQ(0U, image1.RepresentationCount()); +#endif + + // gfx::ImageSkia and gfx::ImagePNGRep are available on all platforms. + gfx::ImageSkia image_skia; + EXPECT_TRUE(image_skia.isNull()); + gfx::Image image2(image_skia); + EXPECT_TRUE(image2.IsEmpty()); + EXPECT_EQ(0, image2.Width()); + EXPECT_EQ(0, image2.Height()); + EXPECT_EQ(0U, image2.RepresentationCount()); + + std::vector image_png_reps; + gfx::Image image3(image_png_reps); + EXPECT_TRUE(image3.IsEmpty()); + EXPECT_EQ(0, image3.Width()); + EXPECT_EQ(0, image3.Height()); + EXPECT_EQ(0U, image3.RepresentationCount()); +} + +// The resulting Image should be empty when it is created using obviously +// invalid data. +TEST_F(ImageTest, EmptyImageFromObviouslyInvalidPNGImage) { + std::vector image_png_reps1; + image_png_reps1.push_back(gfx::ImagePNGRep(nullptr, 1.0f)); + gfx::Image image1(image_png_reps1); + EXPECT_TRUE(image1.IsEmpty()); + EXPECT_EQ(0U, image1.RepresentationCount()); + + std::vector image_png_reps2; + image_png_reps2.push_back(gfx::ImagePNGRep( + new base::RefCountedBytes(), 1.0f)); + gfx::Image image2(image_png_reps2); + EXPECT_TRUE(image2.IsEmpty()); + EXPECT_EQ(0U, image2.RepresentationCount()); +} + +// Test the Width, Height and Size of an empty and non-empty image. +TEST_F(ImageTest, ImageSize) { + gfx::Image image; + EXPECT_EQ(0, image.Width()); + EXPECT_EQ(0, image.Height()); + EXPECT_EQ(gfx::Size(0, 0), image.Size()); + + gfx::Image image2(gt::CreateImageSkia(10, 25)); + EXPECT_EQ(10, image2.Width()); + EXPECT_EQ(25, image2.Height()); + EXPECT_EQ(gfx::Size(10, 25), image2.Size()); +} + +TEST_F(ImageTest, SkiaToSkia) { + gfx::Image image(gt::CreateImageSkia(25, 25)); + EXPECT_EQ(25, image.Width()); + EXPECT_EQ(25, image.Height()); + + // Test ToImageSkia(). + const gfx::ImageSkia* image_skia1 = image.ToImageSkia(); + EXPECT_TRUE(image_skia1); + EXPECT_FALSE(image_skia1->isNull()); + EXPECT_EQ(1U, image.RepresentationCount()); + + // Make sure double conversion doesn't happen. + const gfx::ImageSkia* image_skia2 = image.ToImageSkia(); + EXPECT_EQ(1U, image.RepresentationCount()); + + // ToImageSkia() should always return the same gfx::ImageSkia. + EXPECT_EQ(image_skia1, image_skia2); + + // Test ToSkBitmap(). + const SkBitmap* bitmap1 = image.ToSkBitmap(); + const SkBitmap* bitmap2 = image.ToSkBitmap(); + EXPECT_TRUE(bitmap1); + EXPECT_FALSE(bitmap1->isNull()); + EXPECT_EQ(bitmap1, bitmap2); + + EXPECT_EQ(1U, image.RepresentationCount()); + EXPECT_TRUE(image.HasRepresentation(gfx::Image::kImageRepSkia)); + if (!kUsesSkiaNatively) + EXPECT_FALSE(image.HasRepresentation(gt::GetPlatformRepresentationType())); +} + +TEST_F(ImageTest, EmptyImageToPNG) { + gfx::Image image; + scoped_refptr png_bytes = image.As1xPNGBytes(); + EXPECT_TRUE(png_bytes.get()); + EXPECT_FALSE(png_bytes->size()); +} + +// Check that getting the 1x PNG bytes from images which do not have a 1x +// representation returns null. +TEST_F(ImageTest, ImageNo1xToPNG) { + // Image with 2x only. + const int kSize2x = 50; + gfx::ImageSkia image_skia; + image_skia.AddRepresentation(gfx::ImageSkiaRep(gt::CreateBitmap( + kSize2x, kSize2x), 2.0f)); + gfx::Image image1(image_skia); + scoped_refptr png_bytes1 = image1.As1xPNGBytes(); + EXPECT_TRUE(png_bytes1.get()); + EXPECT_FALSE(png_bytes1->size()); + + std::vector image_png_reps; + image_png_reps.push_back(gfx::ImagePNGRep( + gt::CreatePNGBytes(kSize2x), 2.0f)); + gfx::Image image2(image_png_reps); + EXPECT_FALSE(image2.IsEmpty()); + EXPECT_EQ(0, image2.Width()); + EXPECT_EQ(0, image2.Height()); + scoped_refptr png_bytes2 = image2.As1xPNGBytes(); + EXPECT_TRUE(png_bytes2.get()); + EXPECT_FALSE(png_bytes2->size()); +} + +// Check that for an image initialized with multi resolution PNG data, +// As1xPNGBytes() returns the 1x bytes. +TEST_F(ImageTest, CreateExtractPNGBytes) { + const int kSize1x = 25; + const int kSize2x = 50; + + scoped_refptr bytes1x = gt::CreatePNGBytes(kSize1x); + std::vector image_png_reps; + image_png_reps.push_back(gfx::ImagePNGRep(bytes1x, 1.0f)); + image_png_reps.push_back(gfx::ImagePNGRep( + gt::CreatePNGBytes(kSize2x), 2.0f)); + + gfx::Image image(image_png_reps); + EXPECT_FALSE(image.IsEmpty()); + EXPECT_EQ(25, image.Width()); + EXPECT_EQ(25, image.Height()); + + EXPECT_TRUE(std::equal(bytes1x->front(), bytes1x->front() + bytes1x->size(), + image.As1xPNGBytes()->front())); +} + +TEST_F(ImageTest, MultiResolutionImageSkiaToPNG) { + const int kSize1x = 25; + const int kSize2x = 50; + + SkBitmap bitmap_1x = gt::CreateBitmap(kSize1x, kSize1x); + gfx::ImageSkia image_skia; + image_skia.AddRepresentation(gfx::ImageSkiaRep(bitmap_1x, + 1.0f)); + image_skia.AddRepresentation(gfx::ImageSkiaRep(gt::CreateBitmap( + kSize2x, kSize2x), 2.0f)); + gfx::Image image(image_skia); + + EXPECT_TRUE( + gt::ArePNGBytesCloseToBitmap(*image.As1xPNGBytes(), bitmap_1x, + gt::MaxColorSpaceConversionColorShift())); + EXPECT_TRUE(image.HasRepresentation(gfx::Image::kImageRepPNG)); +} + +TEST_F(ImageTest, MultiResolutionPNGToImageSkia) { + const int kSize1x = 25; + const int kSize2x = 50; + + scoped_refptr bytes1x = gt::CreatePNGBytes(kSize1x); + scoped_refptr bytes2x = gt::CreatePNGBytes(kSize2x); + + std::vector image_png_reps; + image_png_reps.push_back(gfx::ImagePNGRep(bytes1x, 1.0f)); + image_png_reps.push_back(gfx::ImagePNGRep(bytes2x, 2.0f)); + gfx::Image image(image_png_reps); + + std::vector scales; + scales.push_back(1.0f); + scales.push_back(2.0f); + gfx::ImageSkia image_skia = image.AsImageSkia(); + EXPECT_TRUE(gt::ArePNGBytesCloseToBitmap( + *bytes1x, image_skia.GetRepresentation(1.0f).GetBitmap(), + gt::MaxColorSpaceConversionColorShift())); + EXPECT_TRUE(gt::ArePNGBytesCloseToBitmap( + *bytes2x, image_skia.GetRepresentation(2.0f).GetBitmap(), + gt::MaxColorSpaceConversionColorShift())); + EXPECT_TRUE(gt::ImageSkiaStructureMatches(image_skia, kSize1x, kSize1x, + scales)); +#if !defined(OS_IOS) + // IOS does not support arbitrary scale factors. + gfx::ImageSkiaRep rep_1_6x = image_skia.GetRepresentation(1.6f); + ASSERT_FALSE(rep_1_6x.is_null()); + ASSERT_EQ(1.6f, rep_1_6x.scale()); + EXPECT_EQ("40x40", rep_1_6x.pixel_size().ToString()); + + gfx::ImageSkiaRep rep_0_8x = image_skia.GetRepresentation(0.8f); + ASSERT_FALSE(rep_0_8x.is_null()); + ASSERT_EQ(0.8f, rep_0_8x.scale()); + EXPECT_EQ("20x20", rep_0_8x.pixel_size().ToString()); +#endif +} + +TEST_F(ImageTest, MultiResolutionPNGToPlatform) { + const int kSize1x = 25; + const int kSize2x = 50; + + scoped_refptr bytes1x = gt::CreatePNGBytes(kSize1x); + scoped_refptr bytes2x = gt::CreatePNGBytes(kSize2x); + std::vector image_png_reps; + image_png_reps.push_back(gfx::ImagePNGRep(bytes1x, 1.0f)); + image_png_reps.push_back(gfx::ImagePNGRep(bytes2x, 2.0f)); + + gfx::Image from_png(image_png_reps); + gfx::Image from_platform(gt::CopyViaPlatformType(from_png)); +#if defined(OS_IOS) + // On iOS the platform type (UIImage) only supports one resolution. + std::vector scales = gfx::ImageSkia::GetSupportedScales(); + EXPECT_EQ(scales.size(), 1U); + if (scales[0] == 1.0f) + EXPECT_TRUE( + gt::ArePNGBytesCloseToBitmap(*bytes1x, from_platform.AsBitmap(), + gt::MaxColorSpaceConversionColorShift())); + else if (scales[0] == 2.0f) + EXPECT_TRUE( + gt::ArePNGBytesCloseToBitmap(*bytes2x, from_platform.AsBitmap(), + gt::MaxColorSpaceConversionColorShift())); + else + ADD_FAILURE() << "Unexpected platform scale factor."; +#else + EXPECT_TRUE( + gt::ArePNGBytesCloseToBitmap(*bytes1x, from_platform.AsBitmap(), + gt::MaxColorSpaceConversionColorShift())); +#endif // defined(OS_IOS) +} + + +TEST_F(ImageTest, PlatformToPNGEncodeAndDecode) { + gfx::Image image(gt::CreatePlatformImage()); + scoped_refptr png_data = image.As1xPNGBytes(); + EXPECT_TRUE(png_data.get()); + EXPECT_TRUE(png_data->size()); + EXPECT_TRUE(image.HasRepresentation(gfx::Image::kImageRepPNG)); + + std::vector image_png_reps; + image_png_reps.push_back(gfx::ImagePNGRep(png_data, 1.0f)); + gfx::Image from_png(image_png_reps); + + EXPECT_TRUE(from_png.HasRepresentation(gfx::Image::kImageRepPNG)); + EXPECT_TRUE(gt::IsPlatformImageValid(gt::ToPlatformType(from_png))); +} + +// The platform types use the platform provided encoding/decoding of PNGs. Make +// sure these work with the Skia Encode/Decode. +TEST_F(ImageTest, PNGEncodeFromSkiaDecodeToPlatform) { + // Force the conversion sequence skia to png to platform_type. + gfx::Image from_bitmap = gfx::Image::CreateFrom1xBitmap( + gt::CreateBitmap(25, 25)); + scoped_refptr png_bytes = + from_bitmap.As1xPNGBytes(); + + std::vector image_png_reps; + image_png_reps.push_back(gfx::ImagePNGRep(png_bytes, 1.0f)); + gfx::Image from_png(image_png_reps); + + gfx::Image from_platform(gt::CopyViaPlatformType(from_png)); + + EXPECT_TRUE(gt::IsPlatformImageValid(gt::ToPlatformType(from_platform))); + EXPECT_TRUE( + gt::ArePNGBytesCloseToBitmap(*png_bytes, from_platform.AsBitmap(), + gt::MaxColorSpaceConversionColorShift())); +} + +TEST_F(ImageTest, PNGEncodeFromPlatformDecodeToSkia) { + // Force the conversion sequence platform_type to png to skia. + gfx::Image from_platform(gt::CreatePlatformImage()); + scoped_refptr png_bytes = + from_platform.As1xPNGBytes(); + std::vector image_png_reps; + image_png_reps.push_back(gfx::ImagePNGRep(png_bytes, 1.0f)); + gfx::Image from_png(image_png_reps); + + EXPECT_TRUE(gt::AreBitmapsClose( + from_platform.AsBitmap(), from_png.AsBitmap(), + gt::MaxColorSpaceConversionColorShift())); +} + +TEST_F(ImageTest, PNGDecodeToSkiaFailure) { + scoped_refptr invalid_bytes( + new base::RefCountedBytes()); + invalid_bytes->data().push_back('0'); + std::vector image_png_reps; + image_png_reps.push_back(gfx::ImagePNGRep( + invalid_bytes, 1.0f)); + gfx::Image image(image_png_reps); + gt::CheckImageIndicatesPNGDecodeFailure(image); +} + +TEST_F(ImageTest, PNGDecodeToPlatformFailure) { + scoped_refptr invalid_bytes( + new base::RefCountedBytes()); + invalid_bytes->data().push_back('0'); + std::vector image_png_reps; + image_png_reps.push_back(gfx::ImagePNGRep( + invalid_bytes, 1.0f)); + gfx::Image from_png(image_png_reps); + gfx::Image from_platform(gt::CopyViaPlatformType(from_png)); + gt::CheckImageIndicatesPNGDecodeFailure(from_platform); +} + +TEST_F(ImageTest, SkiaToPlatform) { + gfx::Image image(gt::CreateImageSkia(25, 25)); + EXPECT_EQ(25, image.Width()); + EXPECT_EQ(25, image.Height()); + const size_t kRepCount = kUsesSkiaNatively ? 1U : 2U; + + EXPECT_TRUE(image.HasRepresentation(gfx::Image::kImageRepSkia)); + if (!kUsesSkiaNatively) + EXPECT_FALSE(image.HasRepresentation(gt::GetPlatformRepresentationType())); + + EXPECT_TRUE(gt::IsPlatformImageValid(gt::ToPlatformType(image))); + EXPECT_EQ(kRepCount, image.RepresentationCount()); + + const SkBitmap* bitmap = image.ToSkBitmap(); + EXPECT_FALSE(bitmap->isNull()); + EXPECT_EQ(kRepCount, image.RepresentationCount()); + + EXPECT_TRUE(image.HasRepresentation(gfx::Image::kImageRepSkia)); + EXPECT_TRUE(image.HasRepresentation(gt::GetPlatformRepresentationType())); + EXPECT_EQ(25, image.Width()); + EXPECT_EQ(25, image.Height()); +} + +TEST_F(ImageTest, PlatformToSkia) { + gfx::Image image(gt::CreatePlatformImage()); + EXPECT_EQ(25, image.Width()); + EXPECT_EQ(25, image.Height()); + const size_t kRepCount = kUsesSkiaNatively ? 1U : 2U; + + EXPECT_TRUE(image.HasRepresentation(gt::GetPlatformRepresentationType())); + if (!kUsesSkiaNatively) + EXPECT_FALSE(image.HasRepresentation(gfx::Image::kImageRepSkia)); + + const SkBitmap* bitmap = image.ToSkBitmap(); + EXPECT_TRUE(bitmap); + EXPECT_FALSE(bitmap->isNull()); + EXPECT_EQ(kRepCount, image.RepresentationCount()); + + EXPECT_TRUE(gt::IsPlatformImageValid(gt::ToPlatformType(image))); + EXPECT_EQ(kRepCount, image.RepresentationCount()); + + EXPECT_TRUE(image.HasRepresentation(gfx::Image::kImageRepSkia)); + EXPECT_EQ(25, image.Width()); + EXPECT_EQ(25, image.Height()); +} + +TEST_F(ImageTest, PlatformToPlatform) { + gfx::Image image(gt::CreatePlatformImage()); + EXPECT_EQ(25, image.Width()); + EXPECT_EQ(25, image.Height()); + EXPECT_TRUE(gt::IsPlatformImageValid(gt::ToPlatformType(image))); + EXPECT_EQ(1U, image.RepresentationCount()); + + // Make sure double conversion doesn't happen. + EXPECT_TRUE(gt::IsPlatformImageValid(gt::ToPlatformType(image))); + EXPECT_EQ(1U, image.RepresentationCount()); + + EXPECT_TRUE(image.HasRepresentation(gt::GetPlatformRepresentationType())); + if (!kUsesSkiaNatively) + EXPECT_FALSE(image.HasRepresentation(gfx::Image::kImageRepSkia)); + EXPECT_EQ(25, image.Width()); + EXPECT_EQ(25, image.Height()); +} + +TEST_F(ImageTest, CheckSkiaColor) { + gfx::Image image(gt::CreatePlatformImage()); + + const SkBitmap* bitmap = image.ToSkBitmap(); + gt::CheckColors(bitmap->getColor(10, 10), SK_ColorGREEN); +} + +TEST_F(ImageTest, SkBitmapConversionPreservesOrientation) { + const int width = 50; + const int height = 50; + SkBitmap bitmap; + bitmap.allocN32Pixels(width, height); + bitmap.eraseARGB(255, 0, 255, 0); + + // Paint the upper half of the image in red (lower half is in green). + SkCanvas canvas(bitmap, SkSurfaceProps{}); + SkPaint red; + red.setColor(SK_ColorRED); + canvas.drawRect(SkRect::MakeWH(width, height / 2), red); + { + SCOPED_TRACE("Checking color of the initial SkBitmap"); + gt::CheckColors(bitmap.getColor(10, 10), SK_ColorRED); + gt::CheckColors(bitmap.getColor(10, 40), SK_ColorGREEN); + } + + // Convert from SkBitmap to a platform representation, then check the upper + // half of the platform image to make sure it is red, not green. + gfx::Image from_skbitmap = gfx::Image::CreateFrom1xBitmap(bitmap); + { + SCOPED_TRACE("Checking color of the platform image"); + gt::CheckColors( + gt::GetPlatformImageColor(gt::ToPlatformType(from_skbitmap), 10, 10), + SK_ColorRED); + gt::CheckColors( + gt::GetPlatformImageColor(gt::ToPlatformType(from_skbitmap), 10, 40), + SK_ColorGREEN); + } + + // Force a conversion back to SkBitmap and check that the upper half is red. + gfx::Image from_platform(gt::CopyViaPlatformType(from_skbitmap)); + const SkBitmap* bitmap2 = from_platform.ToSkBitmap(); + { + SCOPED_TRACE("Checking color after conversion back to SkBitmap"); + gt::CheckColors(bitmap2->getColor(10, 10), SK_ColorRED); + gt::CheckColors(bitmap2->getColor(10, 40), SK_ColorGREEN); + } +} + +TEST_F(ImageTest, SkBitmapConversionPreservesTransparency) { + const int width = 50; + const int height = 50; + SkBitmap bitmap; + bitmap.allocN32Pixels(width, height); + bitmap.eraseARGB(0, 0, 255, 0); + + // Paint the upper half of the image in red (lower half is transparent). + SkCanvas canvas(bitmap, SkSurfaceProps{}); + SkPaint red; + red.setColor(SK_ColorRED); + canvas.drawRect(SkRect::MakeWH(width, height / 2), red); + { + SCOPED_TRACE("Checking color of the initial SkBitmap"); + gt::CheckColors(bitmap.getColor(10, 10), SK_ColorRED); + gt::CheckIsTransparent(bitmap.getColor(10, 40)); + } + + // Convert from SkBitmap to a platform representation, then check the upper + // half of the platform image to make sure it is red, not green. + gfx::Image from_skbitmap = gfx::Image::CreateFrom1xBitmap(bitmap); + { + SCOPED_TRACE("Checking color of the platform image"); + gt::CheckColors( + gt::GetPlatformImageColor(gt::ToPlatformType(from_skbitmap), 10, 10), + SK_ColorRED); + gt::CheckIsTransparent( + gt::GetPlatformImageColor(gt::ToPlatformType(from_skbitmap), 10, 40)); + } + + // Force a conversion back to SkBitmap and check that the upper half is red. + gfx::Image from_platform(gt::CopyViaPlatformType(from_skbitmap)); + const SkBitmap* bitmap2 = from_platform.ToSkBitmap(); + { + SCOPED_TRACE("Checking color after conversion back to SkBitmap"); + gt::CheckColors(bitmap2->getColor(10, 10), SK_ColorRED); + gt::CheckIsTransparent(bitmap.getColor(10, 40)); + } +} + +TEST_F(ImageTest, Copy) { + const size_t kRepCount = kUsesSkiaNatively ? 1U : 2U; + + gfx::Image image1(gt::CreateImageSkia(25, 25)); + EXPECT_EQ(25, image1.Width()); + EXPECT_EQ(25, image1.Height()); + gfx::Image image2(image1); + EXPECT_EQ(25, image2.Width()); + EXPECT_EQ(25, image2.Height()); + + EXPECT_EQ(1U, image1.RepresentationCount()); + EXPECT_EQ(1U, image2.RepresentationCount()); + EXPECT_EQ(image1.ToImageSkia(), image2.ToImageSkia()); + + EXPECT_TRUE(gt::IsPlatformImageValid(gt::ToPlatformType(image2))); + EXPECT_EQ(kRepCount, image2.RepresentationCount()); + EXPECT_EQ(kRepCount, image1.RepresentationCount()); +} + +TEST_F(ImageTest, Assign) { + gfx::Image image1(gt::CreatePlatformImage()); + EXPECT_EQ(25, image1.Width()); + EXPECT_EQ(25, image1.Height()); + // Assignment must be on a separate line to the declaration in order to test + // assignment operator (instead of copy constructor). + gfx::Image image2; + image2 = image1; + EXPECT_EQ(25, image2.Width()); + EXPECT_EQ(25, image2.Height()); + + EXPECT_EQ(1U, image1.RepresentationCount()); + EXPECT_EQ(1U, image2.RepresentationCount()); + EXPECT_EQ(image1.ToSkBitmap(), image2.ToSkBitmap()); +} + +TEST_F(ImageTest, Move) { + const size_t kRepCount = kUsesSkiaNatively ? 1U : 2U; + + gfx::Image image1(gt::CreateImageSkia(25, 25)); + EXPECT_EQ(25, image1.Width()); + EXPECT_EQ(25, image1.Height()); + gfx::Image image2(std::move(image1)); + EXPECT_EQ(25, image2.Width()); + EXPECT_EQ(25, image2.Height()); + + EXPECT_EQ(0U, image1.RepresentationCount()); + EXPECT_EQ(1U, image2.RepresentationCount()); + + EXPECT_TRUE(gt::IsPlatformImageValid(gt::ToPlatformType(image2))); + EXPECT_EQ(0U, image1.RepresentationCount()); + EXPECT_EQ(kRepCount, image2.RepresentationCount()); +} + +TEST_F(ImageTest, MoveAssign) { + gfx::Image image1(gt::CreatePlatformImage()); + EXPECT_EQ(25, image1.Width()); + EXPECT_EQ(25, image1.Height()); + // Assignment must be on a separate line to the declaration in order to test + // move assignment operator (instead of move constructor). + gfx::Image image2; + image2 = std::move(image1); + EXPECT_EQ(25, image2.Width()); + EXPECT_EQ(25, image2.Height()); + + EXPECT_EQ(0U, image1.RepresentationCount()); + EXPECT_EQ(1U, image2.RepresentationCount()); +} + +TEST_F(ImageTest, Copy_PreservesRepresentation) { + const gfx::Size kSize1x(25, 25); + const gfx::Size kSize2x(50, 50); + std::vector image_png_reps; + image_png_reps.push_back( + gfx::ImagePNGRep(gt::CreatePNGBytes(kSize1x.width()), 1.0f)); + image_png_reps.push_back( + gfx::ImagePNGRep(gt::CreatePNGBytes(kSize2x.width()), 2.0f)); + gfx::Image image(image_png_reps); + + gfx::ImageSkia image_skia = image.AsImageSkia(); + EXPECT_EQ(kSize1x, image_skia.size()); + SkISize size = image_skia.GetRepresentation(2.0f).GetBitmap().dimensions(); + EXPECT_EQ(kSize2x, gfx::Size(size.fWidth, size.fHeight)); + + gfx::Image image2(image); + gfx::ImageSkia image_skia2 = image2.AsImageSkia(); + EXPECT_EQ(kSize1x, image_skia2.size()); + size = image_skia2.GetRepresentation(2.0f).GetBitmap().dimensions(); + EXPECT_EQ(kSize2x, gfx::Size(size.fWidth, size.fHeight)); + + EXPECT_TRUE(image_skia.BackedBySameObjectAs(image_skia2)); +} + +TEST_F(ImageTest, Copy_PreventsDuplication) { + const gfx::Size kSize1x(25, 25); + const gfx::Size kSize2x(50, 50); + std::vector image_png_reps; + image_png_reps.push_back( + gfx::ImagePNGRep(gt::CreatePNGBytes(kSize1x.width()), 1.0f)); + image_png_reps.push_back( + gfx::ImagePNGRep(gt::CreatePNGBytes(kSize2x.width()), 2.0f)); + gfx::Image image(image_png_reps); + + gfx::Image image2(image); + + gfx::ImageSkia image_skia = image.AsImageSkia(); + EXPECT_EQ(kSize1x, image_skia.size()); + SkISize size = image_skia.GetRepresentation(2.0f).GetBitmap().dimensions(); + EXPECT_EQ(kSize2x, gfx::Size(size.fWidth, size.fHeight)); + + gfx::ImageSkia image_skia2 = image2.AsImageSkia(); + EXPECT_EQ(kSize1x, image_skia2.size()); + size = image_skia2.GetRepresentation(2.0f).GetBitmap().dimensions(); + EXPECT_EQ(kSize2x, gfx::Size(size.fWidth, size.fHeight)); + + EXPECT_TRUE(image_skia.BackedBySameObjectAs(image_skia2)); +} + +TEST_F(ImageTest, Copy_PreservesBackingStore) { + const gfx::Size kSize1x(25, 25); + + gfx::Image image(gt::CreateImageSkia(kSize1x.width(), kSize1x.height())); + gfx::Image image2 = gfx::Image::CreateFrom1xBitmap(image.AsBitmap()); + + gfx::ImageSkia image_skia = image.AsImageSkia(); + gfx::ImageSkia image_skia2 = image2.AsImageSkia(); + + // Because we haven't copied the image representation (scale info, etc.) the + // new Skia image isn't backed by the same object, but it should still contain + // the same bitmap data. + EXPECT_FALSE(image_skia2.BackedBySameObjectAs(image_skia)); + EXPECT_EQ(image_skia.bitmap()->getPixels(), + image_skia2.bitmap()->getPixels()); +} + +TEST_F(ImageTest, MultiResolutionImageSkia) { + const int kWidth1x = 10; + const int kHeight1x = 12; + const int kWidth2x = 20; + const int kHeight2x = 24; + + gfx::ImageSkia image_skia; + image_skia.AddRepresentation(gfx::ImageSkiaRep( + gt::CreateBitmap(kWidth1x, kHeight1x), + 1.0f)); + image_skia.AddRepresentation(gfx::ImageSkiaRep( + gt::CreateBitmap(kWidth2x, kHeight2x), + 2.0f)); + + std::vector scales; + scales.push_back(1.0f); + scales.push_back(2.0f); + EXPECT_TRUE(gt::ImageSkiaStructureMatches(image_skia, kWidth1x, kHeight1x, + scales)); + + // Check that the image has a single representation. + gfx::Image image(image_skia); + EXPECT_EQ(1u, image.RepresentationCount()); + EXPECT_EQ(kWidth1x, image.Width()); + EXPECT_EQ(kHeight1x, image.Height()); +} + +TEST_F(ImageTest, RemoveFromMultiResolutionImageSkia) { + const int kWidth2x = 20; + const int kHeight2x = 24; + + gfx::ImageSkia image_skia; + + image_skia.AddRepresentation(gfx::ImageSkiaRep( + gt::CreateBitmap(kWidth2x, kHeight2x), 2.0f)); + EXPECT_EQ(1u, image_skia.image_reps().size()); + + image_skia.RemoveRepresentation(1.0f); + EXPECT_EQ(1u, image_skia.image_reps().size()); + + image_skia.RemoveRepresentation(2.0f); + EXPECT_EQ(0u, image_skia.image_reps().size()); +} + +// Tests that gfx::Image does indeed take ownership of the SkBitmap it is +// passed. +TEST_F(ImageTest, OwnershipTest) { + gfx::Image image; + { + SkBitmap bitmap(gt::CreateBitmap(10, 10)); + EXPECT_TRUE(!bitmap.isNull()); + image = gfx::Image(gfx::ImageSkia( + gfx::ImageSkiaRep(bitmap, 1.0f))); + } + EXPECT_TRUE(!image.ToSkBitmap()->isNull()); +} + +// Integration tests with UI toolkit frameworks require linking against the +// Views library and cannot be here (ui_base_unittests doesn't include it). They +// instead live in /chrome/browser/ui/tests/ui_gfx_image_unittest.cc. + +} // namespace diff --git a/image/image_unittest_util.cc b/image/image_unittest_util.cc new file mode 100644 index 000000000000..37f3b833f939 --- /dev/null +++ b/image/image_unittest_util.cc @@ -0,0 +1,293 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Because the unit tests for gfx::Image are spread across multiple +// implementation files, this header contains the reusable components. + +#include "ui/gfx/image/image_unittest_util.h" + +#include + +#include +#include + +#include "build/build_config.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/gfx/codec/png_codec.h" +#include "ui/gfx/image/image_skia.h" + +#if defined(OS_IOS) +#include "base/mac/scoped_cftyperef.h" +#include "skia/ext/skia_utils_ios.h" +#elif defined(OS_MAC) +#include "base/mac/mac_util.h" +#include "skia/ext/skia_utils_mac.h" +#endif + +namespace gfx { +namespace test { + +namespace { + +// The maximum color shift in the red, green, and blue components caused by +// converting a gfx::Image between colorspaces. Color shifts occur when +// converting between NSImage & UIImage to ImageSkia. Determined by trial and +// error. +const int kMaxColorSpaceConversionColorShift = 40; + +bool ColorComponentsClose(SkColor component1, + SkColor component2, + int max_deviation) { + int c1 = static_cast(component1); + int c2 = static_cast(component2); + return std::abs(c1 - c2) <= max_deviation; +} + +bool ColorsClose(SkColor color1, SkColor color2, int max_deviation) { + // Be tolerant of floating point rounding and lossy color space conversions. + return ColorComponentsClose(SkColorGetR(color1), SkColorGetR(color2), + max_deviation) && + ColorComponentsClose(SkColorGetG(color1), SkColorGetG(color2), + max_deviation) && + ColorComponentsClose(SkColorGetB(color1), SkColorGetB(color2), + max_deviation) && + ColorComponentsClose(SkColorGetA(color1), SkColorGetA(color2), + max_deviation); +} + +} // namespace + +std::vector Get1xAnd2xScales() { + std::vector scales; + scales.push_back(1.0f); + scales.push_back(2.0f); + return scales; +} + +const SkBitmap CreateBitmap(int width, int height) { + SkBitmap bitmap; + bitmap.allocN32Pixels(width, height); + bitmap.eraseARGB(255, 0, 255, 0); + return bitmap; +} + +gfx::ImageSkia CreateImageSkia(int width, int height) { + return gfx::ImageSkia::CreateFrom1xBitmap(CreateBitmap(width, height)); +} + +scoped_refptr CreatePNGBytes(int edge_size) { + SkBitmap bitmap = CreateBitmap(edge_size, edge_size); + scoped_refptr bytes(new base::RefCountedBytes()); + PNGCodec::EncodeBGRASkBitmap(bitmap, false, &bytes->data()); + return bytes; +} + +gfx::Image CreateImage() { + return CreateImage(100, 50); +} + +gfx::Image CreateImage(int width, int height) { + return gfx::Image::CreateFrom1xBitmap(CreateBitmap(width, height)); +} + +bool AreImagesEqual(const gfx::Image& img1, const gfx::Image& img2) { + return AreImagesClose(img1, img2, 0); +} + +bool AreImagesClose(const gfx::Image& img1, + const gfx::Image& img2, + int max_deviation) { + img1.AsImageSkia().EnsureRepsForSupportedScales(); + img2.AsImageSkia().EnsureRepsForSupportedScales(); + std::vector img1_reps = img1.AsImageSkia().image_reps(); + gfx::ImageSkia image_skia2 = img2.AsImageSkia(); + if (image_skia2.image_reps().size() != img1_reps.size()) + return false; + + for (size_t i = 0; i < img1_reps.size(); ++i) { + float scale = img1_reps[i].scale(); + const gfx::ImageSkiaRep& image_rep2 = image_skia2.GetRepresentation(scale); + if (image_rep2.scale() != scale || + !AreBitmapsClose(img1_reps[i].GetBitmap(), image_rep2.GetBitmap(), + max_deviation)) { + return false; + } + } + return true; +} + +bool AreBitmapsEqual(const SkBitmap& bmp1, const SkBitmap& bmp2) { + return AreBitmapsClose(bmp1, bmp2, 0); +} + +bool AreBitmapsClose(const SkBitmap& bmp1, + const SkBitmap& bmp2, + int max_deviation) { + if (bmp1.isNull() && bmp2.isNull()) + return true; + + if (bmp1.width() != bmp2.width() || + bmp1.height() != bmp2.height() || + bmp1.colorType() != kN32_SkColorType || + bmp2.colorType() != kN32_SkColorType) { + return false; + } + + if (!bmp1.getPixels() || !bmp2.getPixels()) + return false; + + for (int y = 0; y < bmp1.height(); ++y) { + for (int x = 0; x < bmp1.width(); ++x) { + if (!ColorsClose(bmp1.getColor(x,y), bmp2.getColor(x,y), max_deviation)) + return false; + } + } + + return true; +} + +bool ArePNGBytesCloseToBitmap(base::span bytes, + const SkBitmap& bitmap, + int max_deviation) { + SkBitmap decoded; + if (!PNGCodec::Decode(bytes.data(), bytes.size(), &decoded)) + return bitmap.isNull(); + + return AreBitmapsClose(bitmap, decoded, max_deviation); +} + +int MaxColorSpaceConversionColorShift() { + return kMaxColorSpaceConversionColorShift; +} + +void CheckImageIndicatesPNGDecodeFailure(const gfx::Image& image) { + SkBitmap bitmap = image.AsBitmap(); + EXPECT_FALSE(bitmap.isNull()); + EXPECT_LE(16, bitmap.width()); + EXPECT_LE(16, bitmap.height()); + CheckColors(bitmap.getColor(10, 10), SK_ColorRED); +} + +bool ImageSkiaStructureMatches( + const gfx::ImageSkia& image_skia, + int width, + int height, + const std::vector& scales) { + if (image_skia.isNull() || + image_skia.width() != width || + image_skia.height() != height || + image_skia.image_reps().size() != scales.size()) { + return false; + } + + for (size_t i = 0; i < scales.size(); ++i) { + gfx::ImageSkiaRep image_rep = + image_skia.GetRepresentation(scales[i]); + if (image_rep.is_null() || image_rep.scale() != scales[i]) + return false; + + if (image_rep.pixel_width() != static_cast(width * scales[i]) || + image_rep.pixel_height() != static_cast(height * scales[i])) { + return false; + } + } + return true; +} + +bool IsEmpty(const gfx::Image& image) { + const SkBitmap& bmp = *image.ToSkBitmap(); + return bmp.isNull() || + (bmp.width() == 0 && bmp.height() == 0); +} + +PlatformImage CreatePlatformImage() { + SkBitmap bitmap(CreateBitmap(25, 25)); +#if defined(OS_IOS) + float scale = ImageSkia::GetMaxSupportedScale(); + + if (scale > 1.0) { + // Always create a 25pt x 25pt image. + int size = static_cast(25 * scale); + bitmap = CreateBitmap(size, size); + } + + base::ScopedCFTypeRef color_space( + CGColorSpaceCreateDeviceRGB()); + UIImage* image = + skia::SkBitmapToUIImageWithColorSpace(bitmap, scale, color_space); + return image; +#elif defined(OS_MAC) + NSImage* image = skia::SkBitmapToNSImageWithColorSpace( + bitmap, base::mac::GetGenericRGBColorSpace()); + return image; +#else + return gfx::ImageSkia::CreateFrom1xBitmap(bitmap); +#endif +} + +gfx::Image::RepresentationType GetPlatformRepresentationType() { +#if defined(OS_IOS) + return gfx::Image::kImageRepCocoaTouch; +#elif defined(OS_MAC) + return gfx::Image::kImageRepCocoa; +#else + return gfx::Image::kImageRepSkia; +#endif +} + +PlatformImage ToPlatformType(const gfx::Image& image) { +#if defined(OS_IOS) + return image.ToUIImage(); +#elif defined(OS_MAC) + return image.ToNSImage(); +#else + return image.AsImageSkia(); +#endif +} + +gfx::Image CopyViaPlatformType(const gfx::Image& image) { +#if defined(OS_IOS) + return gfx::Image(image.ToUIImage()); +#elif defined(OS_MAC) + return gfx::Image(image.ToNSImage()); +#else + return gfx::Image(image.AsImageSkia()); +#endif +} + +#if defined(OS_APPLE) +// Defined in image_unittest_util_mac.mm. +#else +SkColor GetPlatformImageColor(PlatformImage image, int x, int y) { + return image.bitmap()->getColor(x, y); +} +#endif + +void CheckColors(SkColor color1, SkColor color2) { + EXPECT_TRUE(ColorsClose(color1, color2, MaxColorSpaceConversionColorShift())); +} + +void CheckIsTransparent(SkColor color) { + EXPECT_LT(SkColorGetA(color) / 255.0, 0.05); +} + +bool IsPlatformImageValid(PlatformImage image) { +#if defined(OS_APPLE) + return image != NULL; +#else + return !image.isNull(); +#endif +} + +bool PlatformImagesEqual(PlatformImage image1, PlatformImage image2) { +#if defined(OS_APPLE) + return image1 == image2; +#else + return image1.BackedBySameObjectAs(image2); +#endif +} + +} // namespace test +} // namespace gfx diff --git a/image/image_unittest_util.h b/image/image_unittest_util.h new file mode 100644 index 000000000000..51e5a52cc51c --- /dev/null +++ b/image/image_unittest_util.h @@ -0,0 +1,112 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Because the unit tests for gfx::Image are spread across multiple +// implementation files, this header contains the reusable components. + +#ifndef UI_GFX_IMAGE_IMAGE_UNITTEST_UTIL_H_ +#define UI_GFX_IMAGE_IMAGE_UNITTEST_UTIL_H_ + +#include + +#include "base/containers/span.h" +#include "build/build_config.h" +#include "third_party/skia/include/core/SkColor.h" +#include "ui/gfx/image/image.h" + +namespace gfx { +namespace test { + +#if defined(OS_IOS) +typedef UIImage* PlatformImage; +#elif defined(OS_MAC) +typedef NSImage* PlatformImage; +#else +typedef gfx::ImageSkia PlatformImage; +#endif + +std::vector Get1xAnd2xScales(); + +// Create a bitmap of |width|x|height|. +const SkBitmap CreateBitmap(int width, int height); + +// Creates an ImageSkia of |width|x|height| DIP with bitmap data for an +// arbitrary scale factor. +gfx::ImageSkia CreateImageSkia(int width, int height); + +// Returns PNG encoded bytes for a bitmap of |edge_size|x|edge_size|. +scoped_refptr CreatePNGBytes(int edge_size); + +// TODO(rohitrao): Remove the no-argument version of CreateImage(). +gfx::Image CreateImage(); +gfx::Image CreateImage(int width, int height); + +// Returns true if the images are equal. Converts the images to ImageSkia to +// compare them. +bool AreImagesEqual(const gfx::Image& image1, const gfx::Image& image2); + +// Returns true if the images are visually similar. |max_deviation| is the +// maximum color shift in each of the red, green, and blue components for the +// images to be considered similar. Converts to ImageSkia to compare the images. +bool AreImagesClose(const gfx::Image& image1, + const gfx::Image& image2, + int max_deviation); + +// Returns true if the bitmaps are equal. +bool AreBitmapsEqual(const SkBitmap& bitmap1, const SkBitmap& bitmap2); + +// Returns true if the bitmaps are visually similar. +bool AreBitmapsClose(const SkBitmap& bitmap1, + const SkBitmap& bitmap2, + int max_deviation); + +// Returns true if the passed in PNG bitmap is visually similar to the passed in +// SkBitmap. +bool ArePNGBytesCloseToBitmap(base::span bytes, + const SkBitmap& bitmap, + int max_deviation); + +// Returns the maximum color shift in the red, green, and blue components caused +// by converting a gfx::Image between colorspaces. Color shifts occur when +// converting between NSImage & UIImage to ImageSkia. +int MaxColorSpaceConversionColorShift(); + +// An image which was not successfully decoded to PNG should be a red bitmap. +// Fails if the bitmap is not red. +void CheckImageIndicatesPNGDecodeFailure(const gfx::Image& image); + +// Returns true if the structure of |image_skia| matches the structure +// described by |width|, |height|, and |scale_factors|. +// The structure matches if: +// - |image_skia| is non null. +// - |image_skia| has ImageSkiaReps of |scale_factors|. +// - Each of the ImageSkiaReps has a pixel size of |image_skia|.size() * +// scale_factor. +bool ImageSkiaStructureMatches( + const gfx::ImageSkia& image_skia, + int width, + int height, + const std::vector& scale_factors); + +bool IsEmpty(const gfx::Image& image); + +PlatformImage CreatePlatformImage(); + +gfx::Image::RepresentationType GetPlatformRepresentationType(); + +PlatformImage ToPlatformType(const gfx::Image& image); +gfx::Image CopyViaPlatformType(const gfx::Image& image); + +SkColor GetPlatformImageColor(PlatformImage image, int x, int y); +void CheckColors(SkColor color1, SkColor color2); +void CheckIsTransparent(SkColor color); + +bool IsPlatformImageValid(PlatformImage image); + +bool PlatformImagesEqual(PlatformImage image1, PlatformImage image2); + +} // namespace test +} // namespace gfx + +#endif // UI_GFX_IMAGE_IMAGE_UNITTEST_UTIL_H_ diff --git a/image/image_unittest_util_ios.mm b/image/image_unittest_util_ios.mm new file mode 100644 index 000000000000..ba55030efc93 --- /dev/null +++ b/image/image_unittest_util_ios.mm @@ -0,0 +1,42 @@ +// Copyright 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import +#import + +#include "base/mac/scoped_cftyperef.h" +#include "skia/ext/skia_utils_ios.h" +#include "ui/gfx/image/image_unittest_util.h" + +namespace gfx { +namespace test { + +// The |x| and |y| coordinates are interpreted as scale-independent in ios. +SkColor GetPlatformImageColor(PlatformImage image, int x, int y) { + // Start by extracting the target pixel into a 1x1 CGImage. + base::ScopedCFTypeRef pixel_image(CGImageCreateWithImageInRect( + image.CGImage, CGRectMake(x * image.scale, y * image.scale, 1, 1))); + + // Draw that pixel into a 1x1 bitmap context. + base::ScopedCFTypeRef color_space( + CGColorSpaceCreateDeviceRGB()); + base::ScopedCFTypeRef bitmap_context(CGBitmapContextCreate( + /*data=*/ NULL, + /*width=*/ 1, + /*height=*/ 1, + /*bitsPerComponent=*/ 8, + /*bytesPerRow=*/ 4, + color_space, + kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host)); + CGContextDrawImage(bitmap_context, CGRectMake(0, 0, 1, 1), pixel_image); + + // The CGBitmapContext has the same memory layout as SkColor, so we can just + // read an SkColor straight out of the context. + SkColor* data = + reinterpret_cast(CGBitmapContextGetData(bitmap_context)); + return *data; +} + +} // namespace test +} // namespace gfx diff --git a/image/image_unittest_util_mac.mm b/image/image_unittest_util_mac.mm new file mode 100644 index 000000000000..99aaf6aa8206 --- /dev/null +++ b/image/image_unittest_util_mac.mm @@ -0,0 +1,25 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import + +#include "skia/ext/skia_utils_mac.h" +#include "ui/gfx/image/image_unittest_util.h" + +namespace gfx { +namespace test { + +SkColor GetPlatformImageColor(PlatformImage image, int x, int y) { + // AppKit's coordinate system is flipped. + y = [image size].height - y; + + [image lockFocus]; + NSColor* color = NSReadPixel(NSMakePoint(x, y)); + [image unlockFocus]; + return skia::NSDeviceColorToSkColor( + [color colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]]); +} + +} // namespace test +} // namespace gfx diff --git a/image/image_util.cc b/image/image_util.cc new file mode 100644 index 000000000000..ce94c77ad599 --- /dev/null +++ b/image/image_util.cc @@ -0,0 +1,160 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/image/image_util.h" + +#include + +#include +#include + +#include "base/cxx17_backports.h" +#include "build/build_config.h" +#include "skia/ext/image_operations.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/gfx/codec/jpeg_codec.h" +#include "ui/gfx/codec/webp_codec.h" +#include "ui/gfx/image/image.h" +#include "ui/gfx/image/image_skia.h" +#include "ui/gfx/image/resize_image_dimensions.h" + +namespace { + +// Returns whether column |x| of |bitmap| has any "visible pixels", where +// "visible" is defined as having an opactiy greater than an arbitrary small +// value. +bool ColumnHasVisiblePixels(const SkBitmap& bitmap, int x) { + const SkAlpha kMinimumVisibleOpacity = 12; + for (int y = 0; y < bitmap.height(); ++y) { + if (SkColorGetA(bitmap.getColor(x, y)) > kMinimumVisibleOpacity) + return true; + } + return false; +} + +} // namespace + +namespace gfx { + +// The iOS implementations of the JPEG functions are in image_util_ios.mm. +#if !defined(OS_IOS) + +Image ImageFrom1xJPEGEncodedData(const unsigned char* input, + size_t input_size) { + std::unique_ptr bitmap(gfx::JPEGCodec::Decode(input, input_size)); + if (bitmap.get()) + return Image::CreateFrom1xBitmap(*bitmap); + + return Image(); +} + +Image ResizedImageForSearchByImage(const Image& image) { + return ResizedImageForSearchByImageSkiaRepresentation(image); +} + +Image ResizedImageForMaxDimensions(const Image& image, + int max_width, + int max_height, + int max_area) { + return ResizedImageForMaxDimensionsSkiaRepresentation(image, max_width, + max_height, max_area); +} + +// The MacOS implementation of this function is in image_utils_mac.mm. +#if !defined(OS_MAC) +bool JPEG1xEncodedDataFromImage(const Image& image, + int quality, + std::vector* dst) { + return JPEG1xEncodedDataFromSkiaRepresentation(image, quality, dst); +} +#endif // !defined(OS_MAC) + +bool JPEG1xEncodedDataFromSkiaRepresentation(const Image& image, + int quality, + std::vector* dst) { + const gfx::ImageSkiaRep& image_skia_rep = + image.AsImageSkia().GetRepresentation(1.0f); + if (image_skia_rep.scale() != 1.0f) + return false; + + const SkBitmap& bitmap = image_skia_rep.GetBitmap(); + if (!bitmap.readyToDraw()) + return false; + + return gfx::JPEGCodec::Encode(bitmap, quality, dst); +} + +bool WebpEncodedDataFromImage(const Image& image, + int quality, + std::vector* dst) { + const SkBitmap bitmap = image.AsBitmap(); + return gfx::WebpCodec::Encode(bitmap, quality, dst); +} + +Image ResizedImageForSearchByImageSkiaRepresentation(const Image& image) { + return ResizedImageForMaxDimensionsSkiaRepresentation( + image, kSearchByImageMaxImageWidth, kSearchByImageMaxImageHeight, + kSearchByImageMaxImageArea); +} + +Image ResizedImageForMaxDimensionsSkiaRepresentation(const Image& image, + int max_width, + int max_height, + int max_area) { + const gfx::ImageSkiaRep& image_skia_rep = + image.AsImageSkia().GetRepresentation(1.0f); + if (image_skia_rep.scale() != 1.0f) + return image; + + const SkBitmap& bitmap = image_skia_rep.GetBitmap(); + if (bitmap.height() * bitmap.width() > max_area && + (bitmap.width() > max_width || bitmap.height() > max_height)) { + double scale = std::min(static_cast(max_width) / bitmap.width(), + static_cast(max_height) / bitmap.height()); + int width = base::clamp(scale * bitmap.width(), 1, max_width); + int height = base::clamp(scale * bitmap.height(), 1, max_height); + SkBitmap new_bitmap = skia::ImageOperations::Resize( + bitmap, skia::ImageOperations::RESIZE_GOOD, width, height); + return Image(ImageSkia(ImageSkiaRep(new_bitmap, 0.0f))); + } + + return image; +} +#endif // !defined(OS_IOS) + +void GetVisibleMargins(const ImageSkia& image, int* left, int* right) { + *left = 0; + *right = 0; + if (!image.HasRepresentation(1.f)) + return; + const SkBitmap& bitmap = image.GetRepresentation(1.f).GetBitmap(); + if (bitmap.drawsNothing() || bitmap.isOpaque()) + return; + + int x = 0; + for (; x < bitmap.width(); ++x) { + if (ColumnHasVisiblePixels(bitmap, x)) { + *left = x; + break; + } + } + + if (x == bitmap.width()) { + // Image is fully transparent. Divide the width in half, giving the leading + // region the extra pixel for odd widths. + *left = (bitmap.width() + 1) / 2; + *right = bitmap.width() - *left; + return; + } + + // Since we already know column *left is non-transparent, we can avoid + // rechecking that column; hence the '>' here. + for (x = bitmap.width() - 1; x > *left; --x) { + if (ColumnHasVisiblePixels(bitmap, x)) + break; + } + *right = bitmap.width() - 1 - x; +} + +} // namespace gfx diff --git a/image/image_util.h b/image/image_util.h new file mode 100644 index 000000000000..aa954a2fa327 --- /dev/null +++ b/image/image_util.h @@ -0,0 +1,79 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_IMAGE_IMAGE_UTIL_H_ +#define UI_GFX_IMAGE_IMAGE_UTIL_H_ + +#include + +#include + +#include "ui/gfx/gfx_export.h" + +namespace gfx { +class Image; +class ImageSkia; +} + +namespace gfx { + +// Creates an image from the given JPEG-encoded input. If there was an error +// creating the image, returns an IsEmpty() Image. +GFX_EXPORT Image ImageFrom1xJPEGEncodedData(const unsigned char* input, + size_t input_size); + +// Fills the |dst| vector with JPEG-encoded bytes of the 1x representation of +// the given image. +// Returns true if the image has a 1x representation and the 1x representation +// was encoded successfully. +// |quality| determines the compression level, 0 == lowest, 100 == highest. +// Returns true if the Image was encoded successfully. +GFX_EXPORT bool JPEG1xEncodedDataFromImage(const Image& image, + int quality, + std::vector* dst); + +bool JPEG1xEncodedDataFromSkiaRepresentation(const Image& image, + int quality, + std::vector* dst); + +// Fills the |dst| vector with WebP-encoded bytes of the the given image. +// Returns true if the image was encoded (lossy) successfully. +// |quality| determines the visual quality, 0 == lowest, 100 == highest. +// Returns true if the Image was encoded successfully. +GFX_EXPORT bool WebpEncodedDataFromImage(const Image& image, + int quality, + std::vector* dst); + +// Computes the width of any nearly-transparent regions at the sides of the +// image and returns them in |left| and |right|. This checks each column of +// pixels from the outsides in, looking for anything with alpha above a +// reasonably small value. For a fully-opaque image, the margins will thus be +// (0, 0); for a fully-transparent image, the margins will be +// (width / 2, width / 2), with |left| getting the extra pixel for odd widths. +GFX_EXPORT void GetVisibleMargins(const ImageSkia& image, + int* left, + int* right); + +// Downsizes the image if its area exceeds kSearchByImageMaxImageArea AND +// (either its width exceeds kSearchByImageMaxImageWidth OR its height exceeds +// kSearchByImageMaxImageHeight) in preparation for searching. +GFX_EXPORT Image ResizedImageForSearchByImage(const Image& image); + +// Downsizes the image if its area exceeds the max_area defined AND (either its +// width exceeds the max_width defined OR its height exceeds the max_height +// defined). +GFX_EXPORT Image ResizedImageForMaxDimensions(const Image& image, + int max_width, + int max_height, + int max_area); + +Image ResizedImageForSearchByImageSkiaRepresentation(const Image& image); +Image ResizedImageForMaxDimensionsSkiaRepresentation(const Image& image, + int max_width, + int max_height, + int max_area); + +} // namespace gfx + +#endif // UI_GFX_IMAGE_IMAGE_UTIL_H_ diff --git a/image/image_util_ios.mm b/image/image_util_ios.mm new file mode 100644 index 000000000000..81ac792c0753 --- /dev/null +++ b/image/image_util_ios.mm @@ -0,0 +1,114 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import + +#include "ui/gfx/image/image.h" +#include "ui/gfx/image/image_util.h" +#include "ui/gfx/image/resize_image_dimensions.h" + + +namespace { +// Copied from GTMUIImage+Resize in //third_party/google_toolbox_for_mac to +// avoid depending on other GTM* classes unnecessarily. +UIImage* ResizeUIImage(UIImage* image, + CGSize target_size, + BOOL preserve_aspect_ratio, + BOOL trim_to_fit) { + CGSize imageSize = [image size]; + if (imageSize.height < 1 || imageSize.width < 1) { + return nil; + } + if (target_size.height < 1 || target_size.width < 1) { + return nil; + } + CGFloat aspectRatio = imageSize.width / imageSize.height; + CGFloat targetAspectRatio = target_size.width / target_size.height; + CGRect projectTo = CGRectZero; + if (preserve_aspect_ratio) { + if (trim_to_fit) { + // Scale and clip image so that the aspect ratio is preserved and the + // target size is filled. + if (targetAspectRatio < aspectRatio) { + // clip the x-axis. + projectTo.size.width = target_size.height * aspectRatio; + projectTo.size.height = target_size.height; + projectTo.origin.x = (target_size.width - projectTo.size.width) / 2; + projectTo.origin.y = 0; + } else { + // clip the y-axis. + projectTo.size.width = target_size.width; + projectTo.size.height = target_size.width / aspectRatio; + projectTo.origin.x = 0; + projectTo.origin.y = (target_size.height - projectTo.size.height) / 2; + } + } else { + // Scale image to ensure it fits inside the specified target_size. + if (targetAspectRatio < aspectRatio) { + // target is less wide than the original. + projectTo.size.width = target_size.width; + projectTo.size.height = projectTo.size.width / aspectRatio; + target_size = projectTo.size; + } else { + // target is wider than the original. + projectTo.size.height = target_size.height; + projectTo.size.width = projectTo.size.height * aspectRatio; + target_size = projectTo.size; + } + } // if (clip) + } else { + // Don't preserve the aspect ratio. + projectTo.size = target_size; + } + + projectTo = CGRectIntegral(projectTo); + // There's no CGSizeIntegral, so we fake our own. + CGRect integralRect = CGRectZero; + integralRect.size = target_size; + target_size = CGRectIntegral(integralRect).size; + + // Resize photo. Use UIImage drawing methods because they respect + // UIImageOrientation as opposed to CGContextDrawImage(). + UIGraphicsBeginImageContext(target_size); + [image drawInRect:projectTo]; + UIImage* resizedPhoto = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return resizedPhoto; +} +} // namespace + +namespace gfx { + +bool JPEG1xEncodedDataFromImage(const Image& image, + int quality, + std::vector* dst) { + NSData* data = UIImageJPEGRepresentation(image.ToUIImage(), quality / 100.0); + + if ([data length] == 0) + return false; + + dst->resize([data length]); + [data getBytes:&dst->at(0) length:[data length]]; + return true; +} + +Image ResizedImageForSearchByImage(const Image& image) { + if (image.IsEmpty()) { + return image; + } + UIImage* ui_image = image.ToUIImage(); + + if (ui_image && + ui_image.size.height * ui_image.size.width > kSearchByImageMaxImageArea && + (ui_image.size.width > kSearchByImageMaxImageWidth || + ui_image.size.height > kSearchByImageMaxImageHeight)) { + CGSize new_image_size = + CGSizeMake(kSearchByImageMaxImageWidth, kSearchByImageMaxImageHeight); + ui_image = ResizeUIImage(ui_image, new_image_size, + /*preserve_aspect_ratio=*/YES, /*trim_to_fit=*/NO); + } + return Image(ui_image); +} + +} // end namespace gfx diff --git a/image/image_util_mac.mm b/image/image_util_mac.mm new file mode 100644 index 000000000000..5be20b0f639e --- /dev/null +++ b/image/image_util_mac.mm @@ -0,0 +1,40 @@ +// Copyright (c) 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/image/image_util.h" + +#import + +#include "base/mac/scoped_nsobject.h" +#include "ui/gfx/image/image.h" + +namespace gfx { + +bool JPEG1xEncodedDataFromImage(const Image& image, + int quality, + std::vector* dst) { + if (!image.HasRepresentation(gfx::Image::kImageRepCocoa)) + return JPEG1xEncodedDataFromSkiaRepresentation(image, quality, dst); + + NSImage* nsImage = image.ToNSImage(); + + CGImageRef cgImage = + [nsImage CGImageForProposedRect:nil context:nil hints:nil]; + base::scoped_nsobject rep( + [[NSBitmapImageRep alloc] initWithCGImage:cgImage]); + + float compressionFactor = quality / 100.0; + NSDictionary* options = @{ NSImageCompressionFactor : @(compressionFactor)}; + NSData* data = + [rep representationUsingType:NSJPEGFileType properties:options]; + + if ([data length] == 0) + return false; + + dst->resize([data length]); + [data getBytes:&dst->at(0) length:[data length]]; + return true; +} + +} // end namespace gfx diff --git a/image/image_util_unittest.cc b/image/image_util_unittest.cc new file mode 100644 index 000000000000..0ef65fdbb81e --- /dev/null +++ b/image/image_util_unittest.cc @@ -0,0 +1,159 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/image/image_util.h" + +#include +#include + +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkRect.h" +#include "ui/gfx/image/image_skia.h" +#include "ui/gfx/image/image_unittest_util.h" +#include "ui/gfx/image/resize_image_dimensions.h" + +TEST(ImageUtilTest, JPEGEncodeAndDecode) { + gfx::Image original = gfx::test::CreateImage(100, 100); + + std::vector encoded; + ASSERT_TRUE(gfx::JPEG1xEncodedDataFromImage(original, 80, &encoded)); + + gfx::Image decoded = + gfx::ImageFrom1xJPEGEncodedData(&encoded.front(), encoded.size()); + + // JPEG is lossy, so simply check that the image decoded successfully. + EXPECT_FALSE(decoded.IsEmpty()); +} + +TEST(ImageUtilTest, GetVisibleMargins) { + int left, right; + + // Fully transparent image. + { + SkBitmap bitmap; + bitmap.allocN32Pixels(16, 14); + bitmap.eraseColor(SK_ColorTRANSPARENT); + gfx::ImageSkia img(gfx::ImageSkia::CreateFrom1xBitmap(bitmap)); + gfx::GetVisibleMargins(img, &left, &right); + EXPECT_EQ(8, left); + EXPECT_EQ(8, right); + } + + // Fully non-transparent image. + { + SkBitmap bitmap; + bitmap.allocN32Pixels(16, 14); + bitmap.eraseColor(SK_ColorYELLOW); + gfx::ImageSkia img(gfx::ImageSkia::CreateFrom1xBitmap(bitmap)); + gfx::GetVisibleMargins(img, &left, &right); + EXPECT_EQ(0, left); + EXPECT_EQ(0, right); + } + + // Image with non-transparent section in center. + { + SkBitmap bitmap; + bitmap.allocN32Pixels(16, 14); + bitmap.eraseColor(SK_ColorTRANSPARENT); + bitmap.eraseArea(SkIRect::MakeLTRB(3, 2, 13, 13), SK_ColorYELLOW); + gfx::ImageSkia img(gfx::ImageSkia::CreateFrom1xBitmap(bitmap)); + gfx::GetVisibleMargins(img, &left, &right); + EXPECT_EQ(3, left); + EXPECT_EQ(3, right); + } + + // Image with non-transparent section skewed to one side. + { + SkBitmap bitmap; + bitmap.allocN32Pixels(16, 14); + bitmap.eraseColor(SK_ColorTRANSPARENT); + bitmap.eraseArea(SkIRect::MakeLTRB(3, 2, 5, 5), SK_ColorYELLOW); + gfx::ImageSkia img(gfx::ImageSkia::CreateFrom1xBitmap(bitmap)); + gfx::GetVisibleMargins(img, &left, &right); + EXPECT_EQ(3, left); + EXPECT_EQ(11, right); + } + + // Image with non-transparent section at leading edge. + { + SkBitmap bitmap; + bitmap.allocN32Pixels(16, 14); + bitmap.eraseColor(SK_ColorTRANSPARENT); + bitmap.eraseArea(SkIRect::MakeLTRB(0, 3, 5, 5), SK_ColorYELLOW); + gfx::ImageSkia img(gfx::ImageSkia::CreateFrom1xBitmap(bitmap)); + gfx::GetVisibleMargins(img, &left, &right); + EXPECT_EQ(0, left); + EXPECT_EQ(11, right); + } + + // Image with non-transparent section at trailing edge. + { + SkBitmap bitmap; + bitmap.allocN32Pixels(16, 14); + bitmap.eraseColor(SK_ColorTRANSPARENT); + bitmap.eraseArea(SkIRect::MakeLTRB(4, 3, 16, 13), SK_ColorYELLOW); + gfx::ImageSkia img(gfx::ImageSkia::CreateFrom1xBitmap(bitmap)); + gfx::GetVisibleMargins(img, &left, &right); + EXPECT_EQ(4, left); + EXPECT_EQ(0, right); + } + + // Image with narrow non-transparent section. + { + SkBitmap bitmap; + bitmap.allocN32Pixels(16, 14); + bitmap.eraseColor(SK_ColorTRANSPARENT); + bitmap.eraseArea(SkIRect::MakeLTRB(8, 3, 9, 5), SK_ColorYELLOW); + gfx::ImageSkia img(gfx::ImageSkia::CreateFrom1xBitmap(bitmap)); + gfx::GetVisibleMargins(img, &left, &right); + EXPECT_EQ(8, left); + EXPECT_EQ(7, right); + } + + // Image with faint pixels. + { + SkBitmap bitmap; + bitmap.allocN32Pixels(16, 14); + bitmap.eraseColor(SkColorSetA(SK_ColorYELLOW, 0x02)); + gfx::ImageSkia img(gfx::ImageSkia::CreateFrom1xBitmap(bitmap)); + gfx::GetVisibleMargins(img, &left, &right); + EXPECT_EQ(8, left); + EXPECT_EQ(8, right); + } + + // Fully transparent image with odd width. + { + SkBitmap bitmap; + bitmap.allocN32Pixels(17, 14); + bitmap.eraseColor(SK_ColorTRANSPARENT); + gfx::ImageSkia img(gfx::ImageSkia::CreateFrom1xBitmap(bitmap)); + gfx::GetVisibleMargins(img, &left, &right); + EXPECT_EQ(9, left); + EXPECT_EQ(8, right); + } +} + +TEST(ImageUtilTest, ResizedImageForSearchByImage) { + // Make sure the image large enough to let ResizedImageForSearchByImage to + // resize the image. + gfx::Image original_image = + gfx::test::CreateImage(gfx::kSearchByImageMaxImageWidth * 2, + gfx::kSearchByImageMaxImageHeight * 2); + + gfx::Image resized_image = gfx::ResizedImageForSearchByImage(original_image); + EXPECT_FALSE(resized_image.IsEmpty()); + EXPECT_EQ(resized_image.Width(), gfx::kSearchByImageMaxImageWidth); + EXPECT_EQ(resized_image.Height(), gfx::kSearchByImageMaxImageHeight); +} + +TEST(ImageUtilTest, ResizedImageForSearchByImageShouldKeepRatio) { + // Make sure the image large enough to let ResizedImageForSearchByImage to + // resize the image. + gfx::Image original_image = gfx::test::CreateImage(600, 600); + + gfx::Image resized_image = gfx::ResizedImageForSearchByImage(original_image); + EXPECT_EQ(resized_image.Width(), 400); + EXPECT_EQ(resized_image.Height(), 400); +} diff --git a/image/mojom/BUILD.gn b/image/mojom/BUILD.gn new file mode 100644 index 000000000000..dd07e3cbce5f --- /dev/null +++ b/image/mojom/BUILD.gn @@ -0,0 +1,67 @@ +# Copyright 2017 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//mojo/public/tools/bindings/mojom.gni") + +mojom("mojom") { + sources = [ "image.mojom" ] + + public_deps = [ "//skia/public/mojom" ] + + cpp_typemaps = [ + { + types = [ + { + mojom = "gfx.mojom.ImageSkia" + cpp = "::gfx::ImageSkia" + nullable_is_same_type = true + }, + { + mojom = "gfx.mojom.ImageSkiaRep" + cpp = "::gfx::ImageSkiaRep" + }, + ] + traits_headers = [ "image_skia_mojom_traits.h" ] + traits_public_deps = [ ":mojom_traits" ] + }, + ] +} + +source_set("mojom_traits") { + sources = [ + "image_skia_mojom_traits.cc", + "image_skia_mojom_traits.h", + ] + + public_deps = [ + ":mojom_shared_cpp_sources", + "//skia/public/mojom", + "//ui/gfx", + ] +} + +# Using a test service because the traits need to pass handles around. Revisit +# this after Deserialize(Serialize()) API works with handles. +mojom("test_interfaces") { + sources = [ "image_traits_test_service.mojom" ] + + public_deps = [ ":mojom" ] +} + +source_set("unit_test") { + testonly = true + + sources = [ "image_traits_unittest.cc" ] + + deps = [ + ":mojom_traits", + ":test_interfaces", + "//base", + "//base/test:test_support", + "//mojo/public/cpp/bindings", + "//mojo/public/cpp/test_support:test_utils", + "//testing/gtest", + "//ui/gfx:test_support", + ] +} diff --git a/image/mojom/DEPS b/image/mojom/DEPS new file mode 100644 index 000000000000..2f6a4440d1d0 --- /dev/null +++ b/image/mojom/DEPS @@ -0,0 +1,5 @@ +include_rules = [ + "+mojo/public", + "+skia/public/mojom", + "+ui/gfx/image", +] diff --git a/image/mojom/OWNERS b/image/mojom/OWNERS new file mode 100644 index 000000000000..ab87d1e6148b --- /dev/null +++ b/image/mojom/OWNERS @@ -0,0 +1,8 @@ +per-file *.mojom=set noparent +per-file *.mojom=file://ipc/SECURITY_OWNERS + +per-file *_mojom_traits*.*=set noparent +per-file *_mojom_traits*.*=file://ipc/SECURITY_OWNERS + +per-file *.typemap=set noparent +per-file *.typemap=file://ipc/SECURITY_OWNERS diff --git a/image/mojom/image.mojom b/image/mojom/image.mojom new file mode 100644 index 000000000000..e87520071863 --- /dev/null +++ b/image/mojom/image.mojom @@ -0,0 +1,27 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module gfx.mojom; + +import "skia/public/mojom/bitmap.mojom"; + +[Stable] +struct ImageSkiaRep { + // Transport of the bitmap in this representation. The type here is + // BitmapWithArbitraryBpp because this structure is marked Stable, however + // only N32-format bitmaps are allowed, similar to the skia.mojom.BitmapN32 + // type. + skia.mojom.BitmapWithArbitraryBpp bitmap; + + // Corresponding scale of the bitmap or 0 if unscaled. + float scale; +}; + +// Mojo transport for an ImageSkia via shared buffer. Note that transporting an +// ImageSkia over mojo will load all of its image representations for supported +// scales. +[Stable] +struct ImageSkia { + array image_reps; +}; diff --git a/image/mojom/image_skia_mojom_traits.cc b/image/mojom/image_skia_mojom_traits.cc new file mode 100644 index 000000000000..cda3a6266984 --- /dev/null +++ b/image/mojom/image_skia_mojom_traits.cc @@ -0,0 +1,84 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/image/mojom/image_skia_mojom_traits.h" + +#include + +#include "base/check_op.h" + +namespace mojo { + +// static +SkBitmap +StructTraits::bitmap( + const gfx::ImageSkiaRep& input) { + SkBitmap bitmap = input.GetBitmap(); + DCHECK(!bitmap.drawsNothing()); + CHECK_EQ(bitmap.colorType(), kN32_SkColorType); + return bitmap; +} + +// static +float StructTraits::scale( + const gfx::ImageSkiaRep& input) { + const float scale = input.unscaled() ? 0.0f : input.scale(); + DCHECK_GE(scale, 0.0f); + return scale; +} + +// static +bool StructTraits::Read( + gfx::mojom::ImageSkiaRepDataView data, + gfx::ImageSkiaRep* out) { + // An acceptable scale must be greater than or equal to 0. + if (data.scale() < 0) + return false; + + SkBitmap bitmap; + if (!data.ReadBitmap(&bitmap)) + return false; + // Null/uninitialized bitmaps are not allowed, and an ImageSkiaRep is never + // empty-sized either. + if (bitmap.drawsNothing()) + return false; + // Similar to BitmapN32, ImageSkiaReps are expected to have an N32 bitmap + // type. + if (bitmap.colorType() != kN32_SkColorType) + return false; + + *out = gfx::ImageSkiaRep(bitmap, data.scale()); + return true; +} + +// static +std::vector +StructTraits::image_reps( + const gfx::ImageSkia& input) { + // Trigger the image to load everything. + input.EnsureRepsForSupportedScales(); + return input.image_reps(); +} + +// static +bool StructTraits::Read( + gfx::mojom::ImageSkiaDataView data, + gfx::ImageSkia* out) { + DCHECK(out->isNull()); + + std::vector image_reps; + if (!data.ReadImageReps(&image_reps)) + return false; + + for (const auto& image_rep : image_reps) + out->AddRepresentation(image_rep); + + if (out->isNull()) + return false; + + out->SetReadOnly(); + return true; +} + +} // namespace mojo diff --git a/image/mojom/image_skia_mojom_traits.h b/image/mojom/image_skia_mojom_traits.h new file mode 100644 index 000000000000..701b563382bd --- /dev/null +++ b/image/mojom/image_skia_mojom_traits.h @@ -0,0 +1,41 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_IMAGE_MOJOM_IMAGE_SKIA_MOJOM_TRAITS_H_ +#define UI_GFX_IMAGE_MOJOM_IMAGE_SKIA_MOJOM_TRAITS_H_ + +#include + +#include + +#include "skia/public/mojom/bitmap_skbitmap_mojom_traits.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/gfx/image/image_skia.h" +#include "ui/gfx/image/image_skia_rep.h" +#include "ui/gfx/image/mojom/image.mojom-shared.h" + +namespace mojo { + +template <> +struct StructTraits { + static SkBitmap bitmap(const gfx::ImageSkiaRep& input); + static float scale(const gfx::ImageSkiaRep& input); + + static bool Read(gfx::mojom::ImageSkiaRepDataView data, + gfx::ImageSkiaRep* out); +}; + +template <> +struct StructTraits { + static std::vector image_reps(const gfx::ImageSkia& input); + + static bool IsNull(const gfx::ImageSkia& input) { return input.isNull(); } + static void SetToNull(gfx::ImageSkia* out) { *out = gfx::ImageSkia(); } + + static bool Read(gfx::mojom::ImageSkiaDataView data, gfx::ImageSkia* out); +}; + +} // namespace mojo + +#endif // UI_GFX_IMAGE_MOJOM_IMAGE_SKIA_MOJOM_TRAITS_H_ diff --git a/image/mojom/image_traits_test_service.mojom b/image/mojom/image_traits_test_service.mojom new file mode 100644 index 000000000000..72ab4b6c0df0 --- /dev/null +++ b/image/mojom/image_traits_test_service.mojom @@ -0,0 +1,16 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module gfx.mojom; + +import "ui/gfx/image/mojom/image.mojom"; + +// A test echo interface for traits. +interface ImageTraitsTestService { + [Sync] + EchoImageSkiaRep(ImageSkiaRep? in) => (ImageSkiaRep? out); + + [Sync] + EchoImageSkia(ImageSkia? in) => (ImageSkia? out); +}; diff --git a/image/mojom/image_traits_unittest.cc b/image/mojom/image_traits_unittest.cc new file mode 100644 index 000000000000..297a92966482 --- /dev/null +++ b/image/mojom/image_traits_unittest.cc @@ -0,0 +1,170 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +#include "base/macros.h" +#include "base/test/task_environment.h" +#include "mojo/public/cpp/bindings/receiver_set.h" +#include "mojo/public/cpp/bindings/remote.h" +#include "mojo/public/cpp/test_support/test_utils.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/image/image_skia_operations.h" +#include "ui/gfx/image/image_skia_source.h" +#include "ui/gfx/image/image_unittest_util.h" +#include "ui/gfx/image/mojom/image.mojom.h" +#include "ui/gfx/image/mojom/image_skia_mojom_traits.h" + +namespace gfx { + +namespace { + +// A test ImageSkiaSource that creates an ImageSkiaRep for any scale. +class TestImageSkiaSource : public ImageSkiaSource { + public: + explicit TestImageSkiaSource(const Size& dip_size) : dip_size_(dip_size) {} + + TestImageSkiaSource(const TestImageSkiaSource&) = delete; + TestImageSkiaSource& operator=(const TestImageSkiaSource&) = delete; + + ~TestImageSkiaSource() override = default; + + // ImageSkiaSource: + ImageSkiaRep GetImageForScale(float scale) override { + return ImageSkiaRep(ScaleToCeiledSize(dip_size_, scale), scale); + } + + private: + const Size dip_size_; +}; + +// A helper to construct a skia.mojom.BitmapN32 without using StructTraits +// to bypass checks on the sending/serialization side. +mojo::StructPtr ConstructImageSkiaRep( + const SkBitmap& bitmap, + float scale) { + auto mojom_rep = mojom::ImageSkiaRep::New(); + mojom_rep->bitmap = bitmap; + mojom_rep->scale = scale; + return mojom_rep; +} + +} // namespace + +TEST(ImageTraitsTest, VerifyMojomConstruction) { + SkBitmap bitmap; + bitmap.allocN32Pixels(1, 1); + mojo::StructPtr input = ConstructImageSkiaRep(bitmap, 1); + ImageSkiaRep output; + EXPECT_TRUE( + mojo::test::SerializeAndDeserialize(input, output)); +} + +TEST(ImageTraitsTest, BadColorTypeImageSkiaRep_Deserialize) { + SkBitmap a8_bitmap; + a8_bitmap.allocPixels(SkImageInfo::MakeA8(1, 1)); + + mojo::StructPtr input = + ConstructImageSkiaRep(a8_bitmap, 1); + ImageSkiaRep output; + EXPECT_FALSE( + mojo::test::SerializeAndDeserialize(input, output)); +} + +TEST(ImageTraitsTest, EmptyImageSkiaRep_Deserialize) { + SkBitmap empty_bitmap; + empty_bitmap.allocN32Pixels(0, 0); + // Empty SkBitmap is not null. + EXPECT_FALSE(empty_bitmap.isNull()); + EXPECT_TRUE(empty_bitmap.drawsNothing()); + + mojo::StructPtr input = + ConstructImageSkiaRep(empty_bitmap, 1); + ImageSkiaRep output; + EXPECT_FALSE( + mojo::test::SerializeAndDeserialize(input, output)); +} + +TEST(ImageTraitsTest, ValidImageSkiaRep) { + ImageSkiaRep image_rep(Size(2, 4), 2.0f); + + ImageSkiaRep output; + ASSERT_TRUE(mojo::test::SerializeAndDeserialize( + image_rep, output)); + + EXPECT_FALSE(output.is_null()); + EXPECT_EQ(image_rep.scale(), output.scale()); + EXPECT_TRUE(test::AreBitmapsEqual(image_rep.GetBitmap(), output.GetBitmap())); +} + +TEST(ImageTraitsTest, UnscaledImageSkiaRep) { + ImageSkiaRep image_rep(Size(2, 4), 0.0f); + ASSERT_TRUE(image_rep.unscaled()); + + ImageSkiaRep output; + ASSERT_TRUE(mojo::test::SerializeAndDeserialize( + image_rep, output)); + EXPECT_TRUE(output.unscaled()); + EXPECT_TRUE(test::AreBitmapsEqual(image_rep.GetBitmap(), output.GetBitmap())); +} + +TEST(ImageTraitsTest, NullImageSkia) { + ImageSkia null_image; + ASSERT_TRUE(null_image.isNull()); + + ImageSkia output(ImageSkiaRep(Size(1, 1), 1.0f)); + ASSERT_FALSE(output.isNull()); + ASSERT_TRUE(mojo::test::SerializeAndDeserialize(null_image, + output)); + EXPECT_TRUE(output.isNull()); +} + +TEST(ImageTraitsTest, ImageSkiaRepsAreCreatedAsNeeded) { + const Size kSize(1, 2); + ImageSkia image(std::make_unique(kSize), kSize); + EXPECT_FALSE(image.isNull()); + EXPECT_TRUE(image.image_reps().empty()); + + ImageSkia output; + EXPECT_TRUE(output.isNull()); + ASSERT_TRUE( + mojo::test::SerializeAndDeserialize(image, output)); + EXPECT_FALSE(image.image_reps().empty()); + EXPECT_FALSE(output.isNull()); +} + +TEST(ImageTraitsTest, ImageSkia) { + const Size kSize(1, 2); + ImageSkia image(std::make_unique(kSize), kSize); + image.GetRepresentation(1.0f); + image.GetRepresentation(2.0f); + + ImageSkia output; + ASSERT_TRUE( + mojo::test::SerializeAndDeserialize(image, output)); + + EXPECT_TRUE(test::AreImagesEqual(Image(output), Image(image))); +} + +TEST(ImageTraitsTest, ImageSkiaWithOperations) { + const Size kSize(32, 32); + ImageSkia image(std::make_unique(kSize), kSize); + + const Size kNewSize(16, 16); + ImageSkia resized = ImageSkiaOperations::CreateResizedImage( + image, skia::ImageOperations::RESIZE_BEST, kNewSize); + resized.GetRepresentation(1.0f); + resized.GetRepresentation(2.0f); + + ImageSkia output; + ASSERT_TRUE( + mojo::test::SerializeAndDeserialize(resized, output)); + + EXPECT_TRUE(test::AreImagesEqual(Image(output), Image(resized))); +} + +} // namespace gfx diff --git a/image/resize_image_dimensions.h b/image/resize_image_dimensions.h new file mode 100644 index 000000000000..65f79259a83e --- /dev/null +++ b/image/resize_image_dimensions.h @@ -0,0 +1,17 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_IMAGE_RESIZE_IMAGE_DIMENSIONS_H_ +#define UI_GFX_IMAGE_RESIZE_IMAGE_DIMENSIONS_H_ + +namespace gfx { + +// Dimensions to use when downsizing an image for search-by-image. +const int kSearchByImageMaxImageArea = 90000; +const int kSearchByImageMaxImageWidth = 600; +const int kSearchByImageMaxImageHeight = 400; + +} // namespace gfx + +#endif // UI_GFX_IMAGE_RESIZE_IMAGE_DIMENSIONS_H_ diff --git a/interpolated_transform.cc b/interpolated_transform.cc new file mode 100644 index 000000000000..6277fa46614e --- /dev/null +++ b/interpolated_transform.cc @@ -0,0 +1,373 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/interpolated_transform.h" + +#include + +#include "base/check.h" +#include "base/numerics/safe_conversions.h" +#include "ui/gfx/animation/tween.h" + +namespace { + +static const double EPSILON = 1e-6; + +bool IsMultipleOfNinetyDegrees(double degrees) { + double remainder = fabs(fmod(degrees, 90.0)); + return remainder < EPSILON || 90.0 - remainder < EPSILON; +} + +// Returns false if |degrees| is not a multiple of ninety degrees or if +// |rotation| is NULL. It does not affect |rotation| in this case. Otherwise +// *rotation is set to be the appropriate sanitized rotation matrix. That is, +// the rotation matrix corresponding to |degrees| which has entries that are all +// either 0, 1 or -1. +bool MassageRotationIfMultipleOfNinetyDegrees(gfx::Transform* rotation, + float degrees) { + if (!IsMultipleOfNinetyDegrees(degrees) || !rotation) + return false; + + gfx::Transform transform; + skia::Matrix44& m = transform.matrix(); + float degrees_by_ninety = degrees / 90.0f; + + int n = base::ClampRound(degrees_by_ninety); + + n %= 4; + if (n < 0) + n += 4; + + // n should now be in the range [0, 3] + if (n == 1) { + m.set3x3( 0, 1, 0, + -1, 0, 0, + 0, 0, 1); + } else if (n == 2) { + m.set3x3(-1, 0, 0, + 0, -1, 0, + 0, 0, 1); + } else if (n == 3) { + m.set3x3( 0, -1, 0, + 1, 0, 0, + 0, 0, 1); + } + + *rotation = transform; + return true; +} + +} // namespace + +namespace ui { + +/////////////////////////////////////////////////////////////////////////////// +// InterpolatedTransform +// + +InterpolatedTransform::InterpolatedTransform() + : start_time_(0.0f), + end_time_(1.0f), + reversed_(false) { +} + +InterpolatedTransform::InterpolatedTransform(float start_time, + float end_time) + : start_time_(start_time), + end_time_(end_time), + reversed_(false) { +} + +InterpolatedTransform::~InterpolatedTransform() {} + +gfx::Transform InterpolatedTransform::Interpolate(float t) const { + if (reversed_) + t = 1.0f - t; + gfx::Transform result = InterpolateButDoNotCompose(t); + if (child_.get()) { + result.ConcatTransform(child_->Interpolate(t)); + } + return result; +} + +void InterpolatedTransform::SetChild( + std::unique_ptr child) { + child_ = std::move(child); +} + +inline float InterpolatedTransform::ValueBetween(float time, + float start_value, + float end_value) const { + // can't handle NaN + DCHECK(time == time && start_time_ == start_time_ && end_time_ == end_time_); + if (time != time || start_time_ != start_time_ || end_time_ != end_time_) + return start_value; + + // Ok if equal -- we'll get a step function. Note: if end_time_ == + // start_time_ == x, then if none of the numbers are NaN, then it + // must be true that time < x or time >= x, so we will return early + // due to one of the following if statements. + DCHECK(end_time_ >= start_time_); + + if (time < start_time_) + return start_value; + + if (time >= end_time_) + return end_value; + + float t = (time - start_time_) / (end_time_ - start_time_); + return static_cast( + gfx::Tween::DoubleValueBetween(t, start_value, end_value)); +} + +/////////////////////////////////////////////////////////////////////////////// +// InterpolatedRotation +// + +InterpolatedRotation::InterpolatedRotation(float start_degrees, + float end_degrees) + : InterpolatedTransform(), + start_degrees_(start_degrees), + end_degrees_(end_degrees) { +} + +InterpolatedRotation::InterpolatedRotation(float start_degrees, + float end_degrees, + float start_time, + float end_time) + : InterpolatedTransform(start_time, end_time), + start_degrees_(start_degrees), + end_degrees_(end_degrees) { +} + +InterpolatedRotation::~InterpolatedRotation() {} + +gfx::Transform InterpolatedRotation::InterpolateButDoNotCompose(float t) const { + gfx::Transform result; + float interpolated_degrees = ValueBetween(t, start_degrees_, end_degrees_); + result.Rotate(interpolated_degrees); + if (t == 0.0f || t == 1.0f) + MassageRotationIfMultipleOfNinetyDegrees(&result, interpolated_degrees); + return result; +} + +/////////////////////////////////////////////////////////////////////////////// +// InterpolatedAxisAngleRotation +// + +InterpolatedAxisAngleRotation::InterpolatedAxisAngleRotation( + const gfx::Vector3dF& axis, + float start_degrees, + float end_degrees) + : InterpolatedTransform(), + axis_(axis), + start_degrees_(start_degrees), + end_degrees_(end_degrees) { +} + +InterpolatedAxisAngleRotation::InterpolatedAxisAngleRotation( + const gfx::Vector3dF& axis, + float start_degrees, + float end_degrees, + float start_time, + float end_time) + : InterpolatedTransform(start_time, end_time), + axis_(axis), + start_degrees_(start_degrees), + end_degrees_(end_degrees) { +} + +InterpolatedAxisAngleRotation::~InterpolatedAxisAngleRotation() {} + +gfx::Transform +InterpolatedAxisAngleRotation::InterpolateButDoNotCompose(float t) const { + gfx::Transform result; + result.RotateAbout(axis_, ValueBetween(t, start_degrees_, end_degrees_)); + return result; +} + +/////////////////////////////////////////////////////////////////////////////// +// InterpolatedScale +// + +InterpolatedScale::InterpolatedScale(float start_scale, float end_scale) + : InterpolatedTransform(), + start_scale_(gfx::Point3F(start_scale, start_scale, start_scale)), + end_scale_(gfx::Point3F(end_scale, end_scale, end_scale)) { +} + +InterpolatedScale::InterpolatedScale(float start_scale, float end_scale, + float start_time, float end_time) + : InterpolatedTransform(start_time, end_time), + start_scale_(gfx::Point3F(start_scale, start_scale, start_scale)), + end_scale_(gfx::Point3F(end_scale, end_scale, end_scale)) { +} + +InterpolatedScale::InterpolatedScale(const gfx::Point3F& start_scale, + const gfx::Point3F& end_scale) + : InterpolatedTransform(), + start_scale_(start_scale), + end_scale_(end_scale) { +} + +InterpolatedScale::InterpolatedScale(const gfx::Point3F& start_scale, + const gfx::Point3F& end_scale, + float start_time, + float end_time) + : InterpolatedTransform(start_time, end_time), + start_scale_(start_scale), + end_scale_(end_scale) { +} + +InterpolatedScale::~InterpolatedScale() {} + +gfx::Transform InterpolatedScale::InterpolateButDoNotCompose(float t) const { + gfx::Transform result; + float scale_x = ValueBetween(t, start_scale_.x(), end_scale_.x()); + float scale_y = ValueBetween(t, start_scale_.y(), end_scale_.y()); + float scale_z = ValueBetween(t, start_scale_.z(), end_scale_.z()); + result.Scale3d(scale_x, scale_y, scale_z); + return result; +} + +/////////////////////////////////////////////////////////////////////////////// +// InterpolatedTranslation +// + +InterpolatedTranslation::InterpolatedTranslation(const gfx::PointF& start_pos, + const gfx::PointF& end_pos) + : InterpolatedTransform(), start_pos_(start_pos), end_pos_(end_pos) {} + +InterpolatedTranslation::InterpolatedTranslation(const gfx::PointF& start_pos, + const gfx::PointF& end_pos, + float start_time, + float end_time) + : InterpolatedTransform(start_time, end_time), + start_pos_(start_pos), + end_pos_(end_pos) {} + +InterpolatedTranslation::InterpolatedTranslation(const gfx::Point3F& start_pos, + const gfx::Point3F& end_pos) + : InterpolatedTransform(), start_pos_(start_pos), end_pos_(end_pos) { +} + +InterpolatedTranslation::InterpolatedTranslation(const gfx::Point3F& start_pos, + const gfx::Point3F& end_pos, + float start_time, + float end_time) + : InterpolatedTransform(start_time, end_time), + start_pos_(start_pos), + end_pos_(end_pos) { +} + +InterpolatedTranslation::~InterpolatedTranslation() {} + +gfx::Transform +InterpolatedTranslation::InterpolateButDoNotCompose(float t) const { + gfx::Transform result; + result.Translate3d(ValueBetween(t, start_pos_.x(), end_pos_.x()), + ValueBetween(t, start_pos_.y(), end_pos_.y()), + ValueBetween(t, start_pos_.z(), end_pos_.z())); + return result; +} + +/////////////////////////////////////////////////////////////////////////////// +// InterpolatedConstantTransform +// + +InterpolatedConstantTransform::InterpolatedConstantTransform( + const gfx::Transform& transform) + : InterpolatedTransform(), + transform_(transform) { +} + +gfx::Transform +InterpolatedConstantTransform::InterpolateButDoNotCompose(float t) const { + return transform_; +} + +InterpolatedConstantTransform::~InterpolatedConstantTransform() {} + +/////////////////////////////////////////////////////////////////////////////// +// InterpolatedTransformAboutPivot +// + +InterpolatedTransformAboutPivot::InterpolatedTransformAboutPivot( + const gfx::Point& pivot, + std::unique_ptr transform) + : InterpolatedTransform() { + Init(pivot, std::move(transform)); +} + +InterpolatedTransformAboutPivot::InterpolatedTransformAboutPivot( + const gfx::Point& pivot, + std::unique_ptr transform, + float start_time, + float end_time) + : InterpolatedTransform() { + Init(pivot, std::move(transform)); +} + +InterpolatedTransformAboutPivot::~InterpolatedTransformAboutPivot() {} + +gfx::Transform +InterpolatedTransformAboutPivot::InterpolateButDoNotCompose(float t) const { + if (transform_.get()) { + return transform_->Interpolate(t); + } + return gfx::Transform(); +} + +void InterpolatedTransformAboutPivot::Init( + const gfx::Point& pivot, + std::unique_ptr xform) { + gfx::Transform to_pivot; + gfx::Transform from_pivot; + to_pivot.Translate(SkIntToScalar(-pivot.x()), SkIntToScalar(-pivot.y())); + from_pivot.Translate(SkIntToScalar(pivot.x()), SkIntToScalar(pivot.y())); + + std::unique_ptr pre_transform = + std::make_unique(to_pivot); + std::unique_ptr post_transform = + std::make_unique(from_pivot); + + xform->SetChild(std::move(post_transform)); + pre_transform->SetChild(std::move(xform)); + transform_ = std::move(pre_transform); +} + +InterpolatedMatrixTransform::InterpolatedMatrixTransform( + const gfx::Transform& start_transform, + const gfx::Transform& end_transform) + : InterpolatedTransform() { + Init(start_transform, end_transform); +} + +InterpolatedMatrixTransform::InterpolatedMatrixTransform( + const gfx::Transform& start_transform, + const gfx::Transform& end_transform, + float start_time, + float end_time) + : InterpolatedTransform() { + Init(start_transform, end_transform); +} + +InterpolatedMatrixTransform::~InterpolatedMatrixTransform() {} + +gfx::Transform +InterpolatedMatrixTransform::InterpolateButDoNotCompose(float t) const { + gfx::DecomposedTransform blended = + gfx::BlendDecomposedTransforms(end_decomp_, start_decomp_, t); + return gfx::ComposeTransform(blended); +} + +void InterpolatedMatrixTransform::Init(const gfx::Transform& start_transform, + const gfx::Transform& end_transform) { + bool success = gfx::DecomposeTransform(&start_decomp_, start_transform); + DCHECK(success); + success = gfx::DecomposeTransform(&end_decomp_, end_transform); + DCHECK(success); +} + +} // namespace ui diff --git a/interpolated_transform.h b/interpolated_transform.h new file mode 100644 index 000000000000..b836e24fb4d7 --- /dev/null +++ b/interpolated_transform.h @@ -0,0 +1,294 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_INTERPOLATED_TRANSFORM_H_ +#define UI_GFX_INTERPOLATED_TRANSFORM_H_ + +#include + +#include "base/macros.h" +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/point3_f.h" +#include "ui/gfx/geometry/transform.h" +#include "ui/gfx/geometry/transform_util.h" +#include "ui/gfx/geometry/vector3d_f.h" +#include "ui/gfx/gfx_export.h" + +namespace ui { + +/////////////////////////////////////////////////////////////////////////////// +// class InterpolatedTransform +// +// Abstract base class for transforms that animate over time. These +// interpolated transforms can be combined to allow for more sophisticated +// animations. For example, you might combine a rotation of 90 degrees between +// times 0 and 1, with a scale from 1 to 0.3 between times 0 and 0.25 and a +// scale from 0.3 to 1 from between times 0.75 and 1. +// +/////////////////////////////////////////////////////////////////////////////// +class GFX_EXPORT InterpolatedTransform { + public: + InterpolatedTransform(); + // The interpolated transform varies only when t in (start_time, end_time). + // If t <= start_time, Interpolate(t) will return the initial transform, and + // if t >= end_time, Interpolate(t) will return the final transform. + InterpolatedTransform(float start_time, float end_time); + + InterpolatedTransform(const InterpolatedTransform&) = delete; + InterpolatedTransform& operator=(const InterpolatedTransform&) = delete; + + virtual ~InterpolatedTransform(); + + // Returns the interpolated transform at time t. Note: not virtual. + gfx::Transform Interpolate(float t) const; + + // The Intepolate ultimately returns the product of our transform at time t + // and our child's transform at time t (if we have one). + // + // This function takes ownership of the passed InterpolatedTransform. + void SetChild(std::unique_ptr child); + + // If the interpolated transform is reversed, Interpolate(t) will return + // Interpolate(1 - t) + void SetReversed(bool reversed) { reversed_ = reversed; } + bool Reversed() const { return reversed_; } + + protected: + // Calculates the interpolated transform without considering our child. + virtual gfx::Transform InterpolateButDoNotCompose(float t) const = 0; + + // If time in (start_time_, end_time_], this function linearly interpolates + // between start_value and end_value. More precisely it returns + // (1 - t) * start_value + t * end_value where + // t = (start_time_ - time) / (end_time_ - start_time_). + // If time < start_time_ it returns start_value, and if time >= end_time_ + // it returns end_value. + float ValueBetween(float time, float start_value, float end_value) const; + + float start_time() const { return start_time_; } + float end_time() const { return end_time_; } + + private: + const float start_time_; + const float end_time_; + + // The child transform. If you consider an interpolated transform as a + // function of t. If, without a child, we are f(t), and our child is + // g(t), then with a child we become f'(t) = f(t) * g(t). Using a child + // transform, we can chain collections of transforms together. + std::unique_ptr child_; + + bool reversed_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// class InterpolatedRotation +// +// Represents an animated rotation. +// +/////////////////////////////////////////////////////////////////////////////// +class GFX_EXPORT InterpolatedRotation : public InterpolatedTransform { + public: + InterpolatedRotation(float start_degrees, float end_degrees); + InterpolatedRotation(float start_degrees, + float end_degrees, + float start_time, + float end_time); + + InterpolatedRotation(const InterpolatedRotation&) = delete; + InterpolatedRotation& operator=(const InterpolatedRotation&) = delete; + + ~InterpolatedRotation() override; + + protected: + gfx::Transform InterpolateButDoNotCompose(float t) const override; + + private: + const float start_degrees_; + const float end_degrees_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// class InterpolatedAxisAngleRotation +// +// Represents an animated rotation. +// +/////////////////////////////////////////////////////////////////////////////// +class GFX_EXPORT InterpolatedAxisAngleRotation : public InterpolatedTransform { + public: + InterpolatedAxisAngleRotation(const gfx::Vector3dF& axis, + float start_degrees, + float end_degrees); + InterpolatedAxisAngleRotation(const gfx::Vector3dF& axis, + float start_degrees, + float end_degrees, + float start_time, + float end_time); + + InterpolatedAxisAngleRotation(const InterpolatedAxisAngleRotation&) = delete; + InterpolatedAxisAngleRotation& operator=( + const InterpolatedAxisAngleRotation&) = delete; + + ~InterpolatedAxisAngleRotation() override; + + protected: + gfx::Transform InterpolateButDoNotCompose(float t) const override; + + private: + gfx::Vector3dF axis_; + const float start_degrees_; + const float end_degrees_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// class InterpolatedScale +// +// Represents an animated scale. +// +/////////////////////////////////////////////////////////////////////////////// +class GFX_EXPORT InterpolatedScale : public InterpolatedTransform { + public: + InterpolatedScale(float start_scale, float end_scale); + InterpolatedScale(float start_scale, float end_scale, + float start_time, float end_time); + InterpolatedScale(const gfx::Point3F& start_scale, + const gfx::Point3F& end_scale); + InterpolatedScale(const gfx::Point3F& start_scale, + const gfx::Point3F& end_scale, + float start_time, + float end_time); + + InterpolatedScale(const InterpolatedScale&) = delete; + InterpolatedScale& operator=(const InterpolatedScale&) = delete; + + ~InterpolatedScale() override; + + protected: + gfx::Transform InterpolateButDoNotCompose(float t) const override; + + private: + const gfx::Point3F start_scale_; + const gfx::Point3F end_scale_; +}; + +class GFX_EXPORT InterpolatedTranslation : public InterpolatedTransform { + public: + InterpolatedTranslation(const gfx::PointF& start_pos, + const gfx::PointF& end_pos); + InterpolatedTranslation(const gfx::PointF& start_pos, + const gfx::PointF& end_pos, + float start_time, + float end_time); + InterpolatedTranslation(const gfx::Point3F& start_pos, + const gfx::Point3F& end_pos); + InterpolatedTranslation(const gfx::Point3F& start_pos, + const gfx::Point3F& end_pos, + float start_time, + float end_time); + + InterpolatedTranslation(const InterpolatedTranslation&) = delete; + InterpolatedTranslation& operator=(const InterpolatedTranslation&) = delete; + + ~InterpolatedTranslation() override; + + protected: + gfx::Transform InterpolateButDoNotCompose(float t) const override; + + private: + const gfx::Point3F start_pos_; + const gfx::Point3F end_pos_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// class InterpolatedConstantTransform +// +// Represents a transform that is constant over time. This is only useful when +// composed with other interpolated transforms. +// +// See InterpolatedTransformAboutPivot for an example of its usage. +// +/////////////////////////////////////////////////////////////////////////////// +class GFX_EXPORT InterpolatedConstantTransform : public InterpolatedTransform { + public: + explicit InterpolatedConstantTransform(const gfx::Transform& transform); + + InterpolatedConstantTransform(const InterpolatedConstantTransform&) = delete; + InterpolatedConstantTransform& operator=( + const InterpolatedConstantTransform&) = delete; + + ~InterpolatedConstantTransform() override; + + protected: + gfx::Transform InterpolateButDoNotCompose(float t) const override; + + private: + const gfx::Transform transform_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// class InterpolatedTransformAboutPivot +// +// Represents an animated transform with a transformed origin. Essentially, +// at each time, t, the interpolated transform is created by composing +// P * T * P^-1 where P is a constant transform to the new origin. +// +/////////////////////////////////////////////////////////////////////////////// +class GFX_EXPORT InterpolatedTransformAboutPivot + : public InterpolatedTransform { + public: + // Takes ownership of the passed transform. + InterpolatedTransformAboutPivot( + const gfx::Point& pivot, + std::unique_ptr transform); + + // Takes ownership of the passed transform. + InterpolatedTransformAboutPivot( + const gfx::Point& pivot, + std::unique_ptr transform, + float start_time, + float end_time); + + InterpolatedTransformAboutPivot(const InterpolatedTransformAboutPivot&) = + delete; + InterpolatedTransformAboutPivot& operator=( + const InterpolatedTransformAboutPivot&) = delete; + + ~InterpolatedTransformAboutPivot() override; + + protected: + gfx::Transform InterpolateButDoNotCompose(float t) const override; + + private: + void Init(const gfx::Point& pivot, + std::unique_ptr transform); + + std::unique_ptr transform_; +}; + +class GFX_EXPORT InterpolatedMatrixTransform : public InterpolatedTransform { + public: + InterpolatedMatrixTransform(const gfx::Transform& start_transform, + const gfx::Transform& end_transform); + + InterpolatedMatrixTransform(const gfx::Transform& start_transform, + const gfx::Transform& end_transform, + float start_time, + float end_time); + + ~InterpolatedMatrixTransform() override; + + protected: + gfx::Transform InterpolateButDoNotCompose(float t) const override; + + private: + void Init(const gfx::Transform& start_transform, + const gfx::Transform& end_transform); + + gfx::DecomposedTransform start_decomp_; + gfx::DecomposedTransform end_decomp_; +}; + +} // namespace ui + +#endif // UI_GFX_INTERPOLATED_TRANSFORM_H_ diff --git a/interpolated_transform_unittest.cc b/interpolated_transform_unittest.cc new file mode 100644 index 000000000000..3de686d54a64 --- /dev/null +++ b/interpolated_transform_unittest.cc @@ -0,0 +1,237 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/interpolated_transform.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/geometry/rect.h" + +namespace { + +void CheckApproximatelyEqual(const gfx::Transform& lhs, + const gfx::Transform& rhs) { + for (int i = 0; i < 4; ++i) { + for (int j = 0; j < 4; ++j) { + EXPECT_FLOAT_EQ(lhs.matrix().get(i, j), rhs.matrix().get(i, j)); + } + } +} + +} // namespace + +TEST(InterpolatedTransformTest, InterpolatedRotation) { + ui::InterpolatedRotation interpolated_rotation(0, 100); + ui::InterpolatedRotation interpolated_rotation_diff_start_end( + 0, 100, 100, 200); + + for (int i = 0; i <= 100; ++i) { + gfx::Transform rotation; + rotation.Rotate(i); + gfx::Transform interpolated = interpolated_rotation.Interpolate(i / 100.0f); + CheckApproximatelyEqual(rotation, interpolated); + interpolated = interpolated_rotation_diff_start_end.Interpolate(i + 100); + CheckApproximatelyEqual(rotation, interpolated); + } +} + +TEST(InterpolatedTransformTest, InterpolatedScale) { + ui::InterpolatedScale interpolated_scale(gfx::Point3F(0, 0, 0), + gfx::Point3F(100, 100, 100)); + ui::InterpolatedScale interpolated_scale_diff_start_end( + gfx::Point3F(0, 0, 0), gfx::Point3F(100, 100, 100), 100, 200); + + for (int i = 0; i <= 100; ++i) { + gfx::Transform scale; + scale.Scale3d(i, i, i); + gfx::Transform interpolated = interpolated_scale.Interpolate(i / 100.0f); + CheckApproximatelyEqual(scale, interpolated); + interpolated = interpolated_scale_diff_start_end.Interpolate(i + 100); + CheckApproximatelyEqual(scale, interpolated); + } +} + +TEST(InterpolatedTransformTest, InterpolatedTranslate) { + ui::InterpolatedTranslation interpolated_xform(gfx::PointF(), + gfx::PointF(100.f, 100.f)); + + ui::InterpolatedTranslation interpolated_xform_diff_start_end( + gfx::PointF(), gfx::PointF(100.f, 100.f), 100, 200); + + for (int i = 0; i <= 100; ++i) { + gfx::Transform xform; + xform.Translate(i, i); + gfx::Transform interpolated = interpolated_xform.Interpolate(i / 100.0f); + CheckApproximatelyEqual(xform, interpolated); + interpolated = interpolated_xform_diff_start_end.Interpolate(i + 100); + CheckApproximatelyEqual(xform, interpolated); + } +} + +TEST(InterpolatedTransformTest, InterpolatedTranslate3d) { + ui::InterpolatedTranslation interpolated_xform(gfx::Point3F(0, 0, 0), + gfx::Point3F(100, 100, 100)); + + ui::InterpolatedTranslation interpolated_xform_diff_start_end( + gfx::Point3F(0, 0, 0), gfx::Point3F(100, 100, 100), 100, 200); + + for (int i = 0; i <= 100; ++i) { + gfx::Transform xform; + xform.Translate3d(i, i, i); + gfx::Transform interpolated = interpolated_xform.Interpolate(i / 100.0f); + CheckApproximatelyEqual(xform, interpolated); + interpolated = interpolated_xform_diff_start_end.Interpolate(i + 100); + CheckApproximatelyEqual(xform, interpolated); + } +} + +TEST(InterpolatedTransformTest, InterpolatedRotationAboutPivot) { + gfx::Point pivot(100, 100); + gfx::Point above_pivot(100, 200); + ui::InterpolatedRotation rot(0, 90); + ui::InterpolatedTransformAboutPivot interpolated_xform( + pivot, std::make_unique(0, 90)); + gfx::Transform result = interpolated_xform.Interpolate(0.0f); + CheckApproximatelyEqual(gfx::Transform(), result); + result = interpolated_xform.Interpolate(1.0f); + gfx::Point expected_result = pivot; + result.TransformPoint(&pivot); + EXPECT_EQ(expected_result, pivot); + expected_result = gfx::Point(0, 100); + result.TransformPoint(&above_pivot); + EXPECT_EQ(expected_result, above_pivot); +} + +TEST(InterpolatedTransformTest, InterpolatedScaleAboutPivot) { + gfx::Point pivot(100, 100); + gfx::Point above_pivot(100, 200); + ui::InterpolatedTransformAboutPivot interpolated_xform( + pivot, std::make_unique(gfx::Point3F(1, 1, 1), + gfx::Point3F(2, 2, 2))); + gfx::Transform result = interpolated_xform.Interpolate(0.0f); + CheckApproximatelyEqual(gfx::Transform(), result); + result = interpolated_xform.Interpolate(1.0f); + gfx::Point expected_result = pivot; + result.TransformPoint(&pivot); + EXPECT_EQ(expected_result, pivot); + expected_result = gfx::Point(100, 300); + result.TransformPoint(&above_pivot); + EXPECT_EQ(expected_result, above_pivot); +} + +ui::InterpolatedTransform* GetScreenRotation(int degrees, bool reversed) { + gfx::Point old_pivot; + gfx::Point new_pivot; + + int width = 1920; + int height = 180; + + switch (degrees) { + case 90: + new_pivot = gfx::Point(width, 0); + break; + case -90: + new_pivot = gfx::Point(0, height); + break; + case 180: + case 360: + new_pivot = old_pivot = gfx::Point(width / 2, height / 2); + break; + } + + std::unique_ptr rotation = + std::make_unique( + old_pivot, std::make_unique( + reversed ? degrees : 0, reversed ? 0 : degrees)); + + std::unique_ptr translation = + std::make_unique( + gfx::PointF(), gfx::PointF(new_pivot.x() - old_pivot.x(), + new_pivot.y() - old_pivot.y())); + + float scale_factor = 0.9f; + std::unique_ptr scale_down = + std::make_unique(1.0f, scale_factor, 0.0f, 0.5f); + + std::unique_ptr scale_up = + std::make_unique(1.0f, 1.0f / scale_factor, 0.5f, + 1.0f); + + std::unique_ptr to_return = + std::make_unique(gfx::Transform()); + + scale_up->SetChild(std::move(scale_down)); + translation->SetChild(std::move(scale_up)); + rotation->SetChild(std::move(translation)); + to_return->SetChild(std::move(rotation)); + to_return->SetReversed(reversed); + + return to_return.release(); +} + +TEST(InterpolatedTransformTest, ScreenRotationEndsCleanly) { + for (int i = 0; i < 2; ++i) { + for (int degrees = -360; degrees <= 360; degrees += 90) { + const bool reversed = i == 1; + std::unique_ptr screen_rotation( + GetScreenRotation(degrees, reversed)); + gfx::Transform interpolated = screen_rotation->Interpolate(1.0f); + skia::Matrix44& m = interpolated.matrix(); + // Upper-left 3x3 matrix should all be 0, 1 or -1. + for (int row = 0; row < 3; ++row) { + for (int col = 0; col < 3; ++col) { + float entry = m.get(row, col); + EXPECT_TRUE(entry == 0 || entry == 1 || entry == -1); + } + } + } + } +} + +ui::InterpolatedTransform* GetMaximize() { + gfx::Rect target_bounds(0, 0, 1920, 1080); + gfx::Rect initial_bounds(30, 1000, 192, 108); + + float scale_x = static_cast( + target_bounds.height()) / initial_bounds.width(); + float scale_y = static_cast( + target_bounds.width()) / initial_bounds.height(); + + std::unique_ptr scale = + std::make_unique( + gfx::Point3F(1, 1, 1), gfx::Point3F(scale_x, scale_y, 1)); + + std::unique_ptr translation = + std::make_unique( + gfx::PointF(), gfx::PointF(target_bounds.x() - initial_bounds.x(), + target_bounds.y() - initial_bounds.y())); + + std::unique_ptr rotation = + std::make_unique(0, 4.0f); + + std::unique_ptr rotation_about_pivot( + std::make_unique( + gfx::Point(initial_bounds.width() * 0.5, + initial_bounds.height() * 0.5), + std::move(rotation))); + + scale->SetChild(std::move(translation)); + rotation_about_pivot->SetChild(std::move(scale)); + + rotation_about_pivot->SetReversed(true); + + return rotation_about_pivot.release(); +} + +TEST(InterpolatedTransformTest, MaximizeEndsCleanly) { + std::unique_ptr maximize(GetMaximize()); + gfx::Transform interpolated = maximize->Interpolate(1.0f); + skia::Matrix44& m = interpolated.matrix(); + // Upper-left 3x3 matrix should all be 0, 1 or -1. + for (int row = 0; row < 3; ++row) { + for (int col = 0; col < 3; ++col) { + float entry = m.get(row, col); + EXPECT_TRUE(entry == 0 || entry == 1 || entry == -1); + } + } +} diff --git a/ios/NSString+CrStringDrawing.h b/ios/NSString+CrStringDrawing.h new file mode 100644 index 000000000000..d7c6c49ecdf3 --- /dev/null +++ b/ios/NSString+CrStringDrawing.h @@ -0,0 +1,65 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_IOS_NSSTRING_CRSTRINGDRAWING_H_ +#define UI_GFX_IOS_NSSTRING_CRSTRINGDRAWING_H_ + +#import + +@interface NSString (CrStringDrawing) + +// Calculates and returns the bounding rect for the receiver drawn using the +// given size and font. +// This method is implemented as a wrapper around +// |boundingRectWithSize:options:attributes:context:| using the following values +// for the parameters: +// - size: the provided |size| +// - options: NSStringDrawingUsesLineFragmentOrigin +// - attributes: a NSDictionary with the provided |font| +// - context: nil. +// +// Note that the rect returned may contain fractional values. +- (CGRect)cr_boundingRectWithSize:(CGSize)size + font:(UIFont*)font; + +// Convenience wrapper to just return the size of |boundingRectWithSize:font:|. +// +// Note that the size returned may contain fractional values. +- (CGSize)cr_boundingSizeWithSize:(CGSize)size + font:(UIFont*)font; + +// Returns the size of the string if it were to be rendered with the specified +// font on a single line. The width and height of the CGSize returned are +// pixel-aligned. +// +// This method is a convenience wrapper around sizeWithAttributes: to avoid +// boilerplate required to put |font| in a dictionary of attributes. Do not pass +// nil into this method. +- (CGSize)cr_pixelAlignedSizeWithFont:(UIFont*)font; + +// Deprecated: Use cr_pixelAlignedSizeWithFont: or sizeWithAttributes: +// Provides a drop-in replacement for sizeWithFont:, which was deprecated in iOS +// 7 in favor of -sizeWithAttributes:. Specifically, this method will return +// CGSizeZero if |font| is nil, and the width and height returned are rounded up +// to integer values. +// TODO(lliabraa): This method was added to ease the transition off of the +// deprecated sizeWithFont: method. New call sites should not be added and +// existing call sites should be audited to determine the correct behavior for +// nil |font| and rounding, then replaced with cr_pixelAlignedSizeWithFont: or +// sizeWithAttributes: (crbug.com/364419). +- (CGSize)cr_sizeWithFont:(UIFont*)font; + +// If |index| is 0, returns an empty string. +// If |index| is >= than self.length, returns self. +// Otherwise, returns string cut to have |index| characters with an +// ellipsis at the end. +- (NSString*)cr_stringByCuttingToIndex:(NSUInteger)index; + +// Returns an elided version of string that fits in |bounds|. +// System font of Label size is used for determining the string drawing size. +- (NSString*)cr_stringByElidingToFitSize:(CGSize)bounds; + +@end + +#endif // UI_GFX_IOS_NSSTRING_CRSTRINGDRAWING_H_ diff --git a/ios/NSString+CrStringDrawing.mm b/ios/NSString+CrStringDrawing.mm new file mode 100644 index 000000000000..f482ea13b72b --- /dev/null +++ b/ios/NSString+CrStringDrawing.mm @@ -0,0 +1,74 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "ui/gfx/ios/NSString+CrStringDrawing.h" + +#include + +#include + +#include "base/check.h" +#include "ui/gfx/ios/uikit_util.h" + +@implementation NSString (CrStringDrawing) + +- (CGRect)cr_boundingRectWithSize:(CGSize)size + font:(UIFont*)font { + NSDictionary* attributes = font ? @{NSFontAttributeName: font} : @{}; + return [self boundingRectWithSize:size + options:NSStringDrawingUsesLineFragmentOrigin + attributes:attributes + context:nil]; +} + +- (CGSize)cr_boundingSizeWithSize:(CGSize)size + font:(UIFont*)font { + return [self cr_boundingRectWithSize:size font:font].size; +} + +- (CGSize)cr_pixelAlignedSizeWithFont:(UIFont*)font { + DCHECK(font) << "|font| can not be nil; it is used as a NSDictionary value"; + NSDictionary* attributes = @{ NSFontAttributeName : font }; + return ui::AlignSizeToUpperPixel([self sizeWithAttributes:attributes]); +} + +- (CGSize)cr_sizeWithFont:(UIFont*)font { + if (!font) + return CGSizeZero; + NSDictionary* attributes = @{ NSFontAttributeName : font }; + CGSize size = [self sizeWithAttributes:attributes]; + return CGSizeMake(ceil(size.width), ceil(size.height)); +} + +- (NSString*)cr_stringByCuttingToIndex:(NSUInteger)index { + if (index == 0) + return @""; + if (index >= [self length]) + return [[self retain] autorelease]; + return [[self substringToIndex:(index - 1)] stringByAppendingString:@"…"]; +} + +- (NSString*)cr_stringByElidingToFitSize:(CGSize)bounds { + CGSize sizeForGuess = CGSizeMake(bounds.width, CGFLOAT_MAX); + // Use binary search on the string's length. + size_t lo = 0; + size_t hi = [self length]; + size_t guess = 0; + for (guess = (lo + hi) / 2; lo < hi; guess = (lo + hi) / 2) { + NSString* tempString = [self cr_stringByCuttingToIndex:guess]; + UIFont* font = [UIFont systemFontOfSize:[UIFont labelFontSize]]; + CGSize sizeGuess = + [tempString cr_boundingSizeWithSize:sizeForGuess font:font]; + if (sizeGuess.height > bounds.height) { + hi = guess - 1; + if (hi < lo) + hi = lo; + } else { + lo = guess + 1; + } + } + return [self cr_stringByCuttingToIndex:lo]; +} + +@end diff --git a/ios/NSString+CrStringDrawing_unittest.mm b/ios/NSString+CrStringDrawing_unittest.mm new file mode 100644 index 000000000000..b33a89576f3b --- /dev/null +++ b/ios/NSString+CrStringDrawing_unittest.mm @@ -0,0 +1,172 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#import "ui/gfx/ios/NSString+CrStringDrawing.h" + +#include "base/mac/scoped_nsobject.h" +#include "base/strings/stringprintf.h" +#include "base/strings/sys_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/gtest_mac.h" +#include "testing/platform_test.h" + +namespace { + +typedef PlatformTest NSStringCrStringDrawing; + +// These tests verify that the category methods return the same values as the +// deprecated methods, so ignore warnings about using deprecated methods. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + +// Verifies that |cr_boundingSizeWithSize| returns the same size as the +// deprecated |sizeWithFont:constrainedToSize| for most values. +// Note that the methods return different values in a few cases (so they are not +// included in the test cases): +// - the constrained size.width is less than a character. +// - the constrained size.height is less than the font height. +// - the string is empty. +TEST_F(NSStringCrStringDrawing, BoundingSizeWithSize) { + NSArray* fonts = @[ + [UIFont systemFontOfSize:16], + [UIFont boldSystemFontOfSize:10], + [UIFont fontWithName:@"Helvetica" size:12.0], + ]; + NSArray* strings = @[ + @"Test", + @"multi word test", + @"你好", + @"★ This is a test string that is very long.", + ]; + NSArray* sizes = @[ + [NSValue valueWithCGSize:CGSizeMake(20, 100)], + [NSValue valueWithCGSize:CGSizeMake(100, 100)], + [NSValue valueWithCGSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)], + ]; + for (UIFont* font in fonts) { + for (NSString* string in strings) { + for (NSValue* sizeValue in sizes) { + CGSize test_size = [sizeValue CGSizeValue]; + std::string test_tag = base::StringPrintf( + "for string '%s' with font %s and size %s", + base::SysNSStringToUTF8(string).c_str(), + base::SysNSStringToUTF8([font description]).c_str(), + base::SysNSStringToUTF8(NSStringFromCGSize(test_size)).c_str()); + + CGSize size_with_font = + [string sizeWithFont:font constrainedToSize:test_size]; + CGSize bounding_size = + [string cr_boundingSizeWithSize:test_size font:font]; + EXPECT_EQ(size_with_font.width, bounding_size.width) << test_tag; + EXPECT_EQ(size_with_font.height, bounding_size.height) << test_tag; + } + } + } +} + +TEST_F(NSStringCrStringDrawing, SizeWithFont) { + NSArray* fonts = @[ + [NSNull null], + [UIFont systemFontOfSize:16], + [UIFont boldSystemFontOfSize:10], + [UIFont fontWithName:@"Helvetica" size:12.0], + ]; + for (UIFont* font in fonts) { + if ([font isEqual:[NSNull null]]) + font = nil; + std::string font_tag = "with font "; + font_tag.append( + base::SysNSStringToUTF8(font ? [font description] : @"nil")); + EXPECT_EQ([@"" sizeWithFont:font].width, + [@"" cr_sizeWithFont:font].width) << font_tag; + EXPECT_EQ([@"" sizeWithFont:font].height, + [@"" cr_sizeWithFont:font].height) << font_tag; + EXPECT_EQ([@"Test" sizeWithFont:font].width, + [@"Test" cr_sizeWithFont:font].width) << font_tag; + EXPECT_EQ([@"Test" sizeWithFont:font].height, + [@"Test" cr_sizeWithFont:font].height) << font_tag; + EXPECT_EQ([@"你好" sizeWithFont:font].width, + [@"你好" cr_sizeWithFont:font].width) << font_tag; + EXPECT_EQ([@"你好" sizeWithFont:font].height, + [@"你好" cr_sizeWithFont:font].height) << font_tag; + NSString* long_string = @"★ This is a test string that is very long."; + EXPECT_EQ([long_string sizeWithFont:font].width, + [long_string cr_sizeWithFont:font].width) << font_tag; + EXPECT_EQ([long_string sizeWithFont:font].height, + [long_string cr_sizeWithFont:font].height) << font_tag; + } +} +#pragma clang diagnostic pop // ignored "-Wdeprecated-declarations" + +TEST_F(NSStringCrStringDrawing, PixelAlignedSizeWithFont) { + NSArray* fonts = @[ + [UIFont systemFontOfSize:16], + [UIFont boldSystemFontOfSize:10], + [UIFont fontWithName:@"Helvetica" size:12.0], + ]; + NSArray* strings = @[ + @"", + @"Test", + @"你好", + @"★ This is a test string that is very long.", + ]; + for (UIFont* font in fonts) { + NSDictionary* attributes = @{ NSFontAttributeName : font }; + + for (NSString* string in strings) { + std::string test_tag = base::StringPrintf("for string '%s' with font %s", + base::SysNSStringToUTF8(string).c_str(), + base::SysNSStringToUTF8([font description]).c_str()); + + CGSize size_with_attributes = [string sizeWithAttributes:attributes]; + CGSize size_with_pixel_aligned = + [string cr_pixelAlignedSizeWithFont:font]; + + // Verify that the pixel_aligned size is always rounded up (i.e. the size + // returned from sizeWithAttributes: is less than or equal to the pixel- + // aligned size). + EXPECT_LE(size_with_attributes.width, + size_with_pixel_aligned.width) << test_tag; + EXPECT_LE(size_with_attributes.height, + size_with_pixel_aligned.height) << test_tag; + + // Verify that the pixel_aligned size is never more than a pixel different + // than the size returned from sizeWithAttributes:. + static CGFloat scale = [[UIScreen mainScreen] scale]; + EXPECT_NEAR(size_with_attributes.width * scale, + size_with_pixel_aligned.width * scale, + 0.9999) << test_tag; + EXPECT_NEAR(size_with_attributes.height * scale, + size_with_pixel_aligned.height * scale, + 0.9999) << test_tag; + + // Verify that the pixel-aligned value is pixel-aligned. + EXPECT_FLOAT_EQ(roundf(size_with_pixel_aligned.width * scale), + size_with_pixel_aligned.width * scale) << test_tag; + EXPECT_FLOAT_EQ(roundf(size_with_pixel_aligned.height * scale), + size_with_pixel_aligned.height * scale) << test_tag; + } + } +} + +TEST_F(NSStringCrStringDrawing, CutString) { + EXPECT_NSEQ(@"foo", [@"foo" cr_stringByCuttingToIndex:4]); + EXPECT_NSEQ(@"bar", [@"bar" cr_stringByCuttingToIndex:3]); + EXPECT_NSEQ(@"f…", [@"foo" cr_stringByCuttingToIndex:2]); + EXPECT_NSEQ(@"…", [@"bar" cr_stringByCuttingToIndex:1]); + EXPECT_NSEQ(@"", [@"foo" cr_stringByCuttingToIndex:0]); +} + +TEST_F(NSStringCrStringDrawing, ElideStringToFitInRect) { + NSString* result = + [@"lorem ipsum dolores" cr_stringByElidingToFitSize:CGSizeZero]; + EXPECT_NSEQ(@"", result); + result = [@"lorem ipsum dolores" + cr_stringByElidingToFitSize:CGSizeMake(1000, 1000)]; + EXPECT_NSEQ(@"lorem ipsum dolores", result); + result = + [@"lorem ipsum dolores" cr_stringByElidingToFitSize:CGSizeMake(30, 50)]; + EXPECT_TRUE([@"lorem ipsum dolores" length] > [result length]); +} + +} // namespace diff --git a/ios/OWNERS b/ios/OWNERS new file mode 100644 index 000000000000..ae82009e4e60 --- /dev/null +++ b/ios/OWNERS @@ -0,0 +1,2 @@ +rohitrao@chromium.org +sdefresne@chromium.org diff --git a/ios/uikit_util.h b/ios/uikit_util.h new file mode 100644 index 000000000000..fbacda3d90c9 --- /dev/null +++ b/ios/uikit_util.h @@ -0,0 +1,26 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_IOS_UIKIT_UTIL_H_ +#define UI_GFX_IOS_UIKIT_UTIL_H_ + +#import + +#include "base/compiler_specific.h" + +// UI Util containing functions that require UIKit. + +namespace ui { + +// Returns the closest pixel-aligned value higher than |value|, taking the scale +// factor into account. At a scale of 1, equivalent to ceil(). +CGFloat AlignValueToUpperPixel(CGFloat value) WARN_UNUSED_RESULT; + +// Returns the size resulting from applying AlignToUpperPixel to both +// components. +CGSize AlignSizeToUpperPixel(CGSize size) WARN_UNUSED_RESULT; + +} // namespace ui + +#endif // UI_GFX_IOS_UIKIT_UTIL_H_ diff --git a/ios/uikit_util.mm b/ios/uikit_util.mm new file mode 100644 index 000000000000..2e0bfdb21468 --- /dev/null +++ b/ios/uikit_util.mm @@ -0,0 +1,23 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/ios/uikit_util.h" + +#import + +#include + +namespace ui { + +CGFloat AlignValueToUpperPixel(CGFloat value) { + CGFloat scale = [[UIScreen mainScreen] scale]; + return std::ceil(value * scale) / scale; +} + +CGSize AlignSizeToUpperPixel(CGSize size) { + return CGSizeMake(AlignValueToUpperPixel(size.width), + AlignValueToUpperPixel(size.height)); +} + +} // namespace ui diff --git a/ios/uikit_util_unittest.mm b/ios/uikit_util_unittest.mm new file mode 100644 index 000000000000..269b4b389d6e --- /dev/null +++ b/ios/uikit_util_unittest.mm @@ -0,0 +1,52 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import +#include + +#include "base/cxx17_backports.h" +#include "testing/platform_test.h" +#import "ui/gfx/ios/uikit_util.h" + +namespace { + +typedef PlatformTest UIKitUtilTest; + +TEST_F(UIKitUtilTest, AlignValueToUpperPixel) { + CGFloat scale = [[UIScreen mainScreen] scale]; + // Pick a few interesting values: already aligned, aligned on retina, and + // some unaligned values that would round differently. Ensure that all are + // "integer" values within <1 of the original value in the scaled space. + CGFloat test_values[] = { 10.0, 55.5, 3.1, 2.9 }; + const CGFloat kMaxAlignDelta = 0.9999; + size_t value_count = base::size(test_values); + for (unsigned int i = 0; i < value_count; ++i) { + CGFloat aligned = ui::AlignValueToUpperPixel(test_values[i]); + EXPECT_FLOAT_EQ(aligned * scale, floor(aligned * scale)); + EXPECT_NEAR(aligned * scale, test_values[i] * scale, kMaxAlignDelta); + } +} + +TEST_F(UIKitUtilTest, AlignSizeToUpperPixel) { + CGFloat scale = [[UIScreen mainScreen] scale]; + // Pick a few interesting values: already aligned, aligned on retina, and + // some unaligned values that would round differently. Ensure that all are + // "integer" values within <1 of the original value in the scaled space. + CGFloat test_values[] = { 10.0, 55.5, 3.1, 2.9 }; + const CGFloat kMaxAlignDelta = 0.9999; + size_t value_count = base::size(test_values); + for (unsigned int i = 0; i < value_count; ++i) { + CGFloat width = test_values[i]; + CGFloat height = test_values[(i + 1) % value_count]; + CGSize alignedSize = ui::AlignSizeToUpperPixel(CGSizeMake(width, height)); + EXPECT_FLOAT_EQ(floor(alignedSize.width * scale), + alignedSize.width * scale); + EXPECT_FLOAT_EQ(floor(alignedSize.height * scale), + alignedSize.height * scale); + EXPECT_NEAR(width * scale, alignedSize.width * scale, kMaxAlignDelta); + EXPECT_NEAR(height * scale, alignedSize.height * scale, kMaxAlignDelta); + } +} + +} // namespace diff --git a/ipc/BUILD.gn b/ipc/BUILD.gn new file mode 100644 index 000000000000..9767b92961f2 --- /dev/null +++ b/ipc/BUILD.gn @@ -0,0 +1,30 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +component("ipc") { + output_name = "gfx_ipc" + + sources = [ + "gfx_ipc_export.h", + "gfx_param_traits.cc", + "gfx_param_traits.h", + "gfx_param_traits_macros.h", + ] + + defines = [ "GFX_IPC_IMPLEMENTATION" ] + + public_deps = [ + "//base", + "//ipc", + "//ui/gfx:memory_buffer", + "//ui/gfx:selection_bound", + "//ui/gfx/ipc/geometry", + "//ui/gfx/range", + ] + + frameworks = [ + "CoreFoundation.framework", + "IOSurface.framework", + ] +} diff --git a/ipc/OWNERS b/ipc/OWNERS new file mode 100644 index 000000000000..146c3c3cd621 --- /dev/null +++ b/ipc/OWNERS @@ -0,0 +1,2 @@ +per-file *_param_traits*.*=set noparent +per-file *_param_traits*.*=file://ipc/SECURITY_OWNERS diff --git a/ipc/buffer_types/BUILD.gn b/ipc/buffer_types/BUILD.gn new file mode 100644 index 000000000000..3cf5eca77f35 --- /dev/null +++ b/ipc/buffer_types/BUILD.gn @@ -0,0 +1,22 @@ +# Copyright 2018 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +component("buffer_types") { + output_name = "gfx_ipc_buffer_types" + + sources = [ + "gfx_ipc_export.h", + "gfx_param_traits.cc", + "gfx_param_traits.h", + "gfx_param_traits_macros.h", + ] + + defines = [ "GFX_IPC_BUFFER_TYPES_IMPLEMENTATION" ] + + public_deps = [ + "//base", + "//ipc", + "//ui/gfx:buffer_types", + ] +} diff --git a/ipc/buffer_types/OWNERS b/ipc/buffer_types/OWNERS new file mode 100644 index 000000000000..146c3c3cd621 --- /dev/null +++ b/ipc/buffer_types/OWNERS @@ -0,0 +1,2 @@ +per-file *_param_traits*.*=set noparent +per-file *_param_traits*.*=file://ipc/SECURITY_OWNERS diff --git a/ipc/buffer_types/gfx_ipc_export.h b/ipc/buffer_types/gfx_ipc_export.h new file mode 100644 index 000000000000..83a48dfe8c56 --- /dev/null +++ b/ipc/buffer_types/gfx_ipc_export.h @@ -0,0 +1,29 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_IPC_BUFFER_TYPES_GFX_IPC_EXPORT_H_ +#define UI_GFX_IPC_BUFFER_TYPES_GFX_IPC_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(GFX_IPC_BUFFER_TYPES_IMPLEMENTATION) +#define GFX_IPC_BUFFER_TYPES_EXPORT __declspec(dllexport) +#else +#define GFX_IPC_BUFFER_TYPES_EXPORT __declspec(dllimport) +#endif // defined(GFX_IPC_BUFFER_TYPES_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(GFX_IPC_BUFFER_TYPES_IMPLEMENTATION) +#define GFX_IPC_BUFFER_TYPES_EXPORT __attribute__((visibility("default"))) +#else +#define GFX_IPC_BUFFER_TYPES_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define GFX_IPC_BUFFER_TYPES_EXPORT +#endif + +#endif // UI_GFX_IPC_BUFFER_TYPES_GFX_IPC_EXPORT_H_ diff --git a/ipc/buffer_types/gfx_param_traits.cc b/ipc/buffer_types/gfx_param_traits.cc new file mode 100644 index 000000000000..507b1e7e7fea --- /dev/null +++ b/ipc/buffer_types/gfx_param_traits.cc @@ -0,0 +1,59 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/ipc/buffer_types/gfx_param_traits.h" + +#include +#include + +#include + +#include "base/strings/stringprintf.h" + +namespace IPC { + +void ParamTraits::Write( + base::Pickle* m, + const gfx::BufferUsageAndFormat& p) { + WriteParam(m, p.usage); + WriteParam(m, p.format); +} + +bool ParamTraits::Read( + const base::Pickle* m, + base::PickleIterator* iter, + gfx::BufferUsageAndFormat* r) { + if (!ReadParam(m, iter, &r->usage) || !ReadParam(m, iter, &r->format)) + return false; + return true; +} + +void ParamTraits::Log( + const gfx::BufferUsageAndFormat& p, + std::string* l) { + l->append(base::StringPrintf("(%d, %d)", p.usage, p.format)); +} + +} // namespace IPC + +// Generate param traits write methods. +#include "ipc/param_traits_write_macros.h" +namespace IPC { +#undef UI_GFX_IPC_GFX_BUFFER_TYPES_PARAM_TRAITS_MACROS_H_ +#include "ui/gfx/ipc/buffer_types/gfx_param_traits_macros.h" +} // namespace IPC + +// Generate param traits read methods. +#include "ipc/param_traits_read_macros.h" +namespace IPC { +#undef UI_GFX_IPC_GFX_BUFFER_TYPES_PARAM_TRAITS_MACROS_H_ +#include "ui/gfx/ipc/buffer_types/gfx_param_traits_macros.h" +} // namespace IPC + +// Generate param traits log methods. +#include "ipc/param_traits_log_macros.h" +namespace IPC { +#undef UI_GFX_IPC_GFX_BUFFER_TYPES_PARAM_TRAITS_MACROS_H_ +#include "ui/gfx/ipc/buffer_types/gfx_param_traits_macros.h" +} // namespace IPC diff --git a/ipc/buffer_types/gfx_param_traits.h b/ipc/buffer_types/gfx_param_traits.h new file mode 100644 index 000000000000..6bef3e5870e9 --- /dev/null +++ b/ipc/buffer_types/gfx_param_traits.h @@ -0,0 +1,29 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_IPC_BUFFER_TYPES_GFX_PARAM_TRAITS_H_ +#define UI_GFX_IPC_BUFFER_TYPES_GFX_PARAM_TRAITS_H_ + +#include + +#include "ipc/ipc_message_utils.h" +#include "ipc/param_traits_macros.h" +#include "ui/gfx/ipc/buffer_types/gfx_ipc_export.h" +#include "ui/gfx/ipc/buffer_types/gfx_param_traits_macros.h" + +namespace IPC { + +template <> +struct GFX_IPC_BUFFER_TYPES_EXPORT ParamTraits { + typedef gfx::BufferUsageAndFormat param_type; + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +} // namespace IPC + +#endif // UI_GFX_IPC_BUFFER_TYPES_GFX_PARAM_TRAITS_H_ diff --git a/ipc/buffer_types/gfx_param_traits_macros.h b/ipc/buffer_types/gfx_param_traits_macros.h new file mode 100644 index 000000000000..502a4f1a8fe2 --- /dev/null +++ b/ipc/buffer_types/gfx_param_traits_macros.h @@ -0,0 +1,27 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Singly or multiply-included shared traits file depending upon circumstances. +// This allows the use of IPC serialization macros in more than one IPC message +// file. +#ifndef UI_GFX_IPC_GFX_BUFFER_TYPES_PARAM_TRAITS_MACROS_H_ +#define UI_GFX_IPC_GFX_BUFFER_TYPES_PARAM_TRAITS_MACROS_H_ + +#include "build/build_config.h" +#include "ipc/ipc_message_macros.h" +#include "ui/gfx/buffer_types.h" + +#undef IPC_MESSAGE_EXPORT +#define IPC_MESSAGE_EXPORT GFX_IPC_BUFFER_TYPES_EXPORT + +IPC_ENUM_TRAITS_MAX_VALUE(gfx::BufferFormat, gfx::BufferFormat::LAST) + +IPC_ENUM_TRAITS_MAX_VALUE(gfx::BufferUsage, gfx::BufferUsage::LAST) + +IPC_ENUM_TRAITS_MAX_VALUE(gfx::BufferPlane, gfx::BufferPlane::LAST) + +#undef IPC_MESSAGE_EXPORT +#define IPC_MESSAGE_EXPORT + +#endif // UI_GFX_IPC_GFX_BUFFER_TYPES_PARAM_TRAITS_MACROS_H_ diff --git a/ipc/color/BUILD.gn b/ipc/color/BUILD.gn new file mode 100644 index 000000000000..a9ab03230131 --- /dev/null +++ b/ipc/color/BUILD.gn @@ -0,0 +1,23 @@ +# Copyright 2016 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +component("color") { + output_name = "gfx_ipc_color" + + sources = [ + "gfx_ipc_color_export.h", + "gfx_param_traits.cc", + "gfx_param_traits.h", + "gfx_param_traits_macros.h", + ] + + defines = [ "GFX_IPC_COLOR_IMPLEMENTATION" ] + + public_deps = [ + "//base", + "//ipc", + "//ui/gfx:color_space", + "//ui/gfx/ipc/buffer_types", + ] +} diff --git a/ipc/color/OWNERS b/ipc/color/OWNERS new file mode 100644 index 000000000000..146c3c3cd621 --- /dev/null +++ b/ipc/color/OWNERS @@ -0,0 +1,2 @@ +per-file *_param_traits*.*=set noparent +per-file *_param_traits*.*=file://ipc/SECURITY_OWNERS diff --git a/ipc/color/gfx_ipc_color_export.h b/ipc/color/gfx_ipc_color_export.h new file mode 100644 index 000000000000..eae168df9244 --- /dev/null +++ b/ipc/color/gfx_ipc_color_export.h @@ -0,0 +1,29 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_IPC_COLOR_GFX_IPC_COLOR_EXPORT_H_ +#define UI_GFX_IPC_COLOR_GFX_IPC_COLOR_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(GFX_IPC_COLOR_IMPLEMENTATION) +#define GFX_IPC_COLOR_EXPORT __declspec(dllexport) +#else +#define GFX_IPC_COLOR_EXPORT __declspec(dllimport) +#endif // defined(GFX_IPC_COLOR_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(GFX_IPC_COLOR_IMPLEMENTATION) +#define GFX_IPC_COLOR_EXPORT __attribute__((visibility("default"))) +#else +#define GFX_IPC_COLOR_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define GFX_IPC_COLOR_EXPORT +#endif + +#endif // UI_GFX_IPC_COLOR_GFX_IPC_COLOR_EXPORT_H_ diff --git a/ipc/color/gfx_param_traits.cc b/ipc/color/gfx_param_traits.cc new file mode 100644 index 000000000000..2e9b2b548c57 --- /dev/null +++ b/ipc/color/gfx_param_traits.cc @@ -0,0 +1,95 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/ipc/color/gfx_param_traits.h" + +#include "ipc/ipc_message_utils.h" +#include "ipc/param_traits_macros.h" +#include "ui/gfx/color_space.h" +#include "ui/gfx/display_color_spaces.h" +#include "ui/gfx/ipc/buffer_types/gfx_ipc_export.h" +#include "ui/gfx/ipc/buffer_types/gfx_param_traits_macros.h" + +namespace IPC { + +void ParamTraits::Write(base::Pickle* m, + const gfx::ColorSpace& p) { + WriteParam(m, p.primaries_); + WriteParam(m, p.transfer_); + WriteParam(m, p.matrix_); + WriteParam(m, p.range_); + WriteParam(m, p.custom_primary_matrix_); + WriteParam(m, p.transfer_params_); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + gfx::ColorSpace* r) { + if (!ReadParam(m, iter, &r->primaries_)) + return false; + if (!ReadParam(m, iter, &r->transfer_)) + return false; + if (!ReadParam(m, iter, &r->matrix_)) + return false; + if (!ReadParam(m, iter, &r->range_)) + return false; + if (!ReadParam(m, iter, &r->custom_primary_matrix_)) + return false; + if (!ReadParam(m, iter, &r->transfer_params_)) + return false; + return true; +} + +void ParamTraits::Log(const gfx::ColorSpace& p, + std::string* l) { + l->append(""); +} + +void ParamTraits::Write( + base::Pickle* m, + const gfx::DisplayColorSpaces& p) { + WriteParam(m, p.color_spaces_); + WriteParam(m, p.buffer_formats_); + WriteParam(m, p.sdr_white_level_); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + gfx::DisplayColorSpaces* r) { + if (!ReadParam(m, iter, &r->color_spaces_)) + return false; + if (!ReadParam(m, iter, &r->buffer_formats_)) + return false; + if (!ReadParam(m, iter, &r->sdr_white_level_)) + return false; + return true; +} + +void ParamTraits::Log(const gfx::DisplayColorSpaces& p, + std::string* l) { + l->append(""); +} + +} // namespace IPC + +// Generate param traits write methods. +#include "ipc/param_traits_write_macros.h" +namespace IPC { +#undef UI_GFX_IPC_COLOR_GFX_PARAM_TRAITS_MACROS_H_ +#include "ui/gfx/ipc/color/gfx_param_traits_macros.h" +} // namespace IPC + +// Generate param traits read methods. +#include "ipc/param_traits_read_macros.h" +namespace IPC { +#undef UI_GFX_IPC_COLOR_GFX_PARAM_TRAITS_MACROS_H_ +#include "ui/gfx/ipc/color/gfx_param_traits_macros.h" +} // namespace IPC + +// Generate param traits log methods. +#include "ipc/param_traits_log_macros.h" +namespace IPC { +#undef UI_GFX_IPC_COLOR_GFX_PARAM_TRAITS_MACROS_H_ +#include "ui/gfx/ipc/color/gfx_param_traits_macros.h" +} // namespace IPC diff --git a/ipc/color/gfx_param_traits.h b/ipc/color/gfx_param_traits.h new file mode 100644 index 000000000000..8969194c3dbb --- /dev/null +++ b/ipc/color/gfx_param_traits.h @@ -0,0 +1,44 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_IPC_COLOR_GFX_PARAM_TRAITS_H_ +#define UI_GFX_IPC_COLOR_GFX_PARAM_TRAITS_H_ + +#include "ipc/ipc_message_utils.h" +#include "ipc/param_traits_macros.h" +#include "ipc/ipc_message_macros.h" +#include "ui/gfx/color_space.h" +#include "ui/gfx/ipc/color/gfx_ipc_color_export.h" +#include "ui/gfx/ipc/color/gfx_param_traits_macros.h" + +namespace gfx { +class ColorSpace; +class DisplayColorSpaces; +} + +namespace IPC { + +template <> +struct GFX_IPC_COLOR_EXPORT ParamTraits { + typedef gfx::ColorSpace param_type; + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct GFX_IPC_COLOR_EXPORT ParamTraits { + typedef gfx::DisplayColorSpaces param_type; + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +} // namespace IPC + +#endif // UI_GFX_IPC_COLOR_GFX_PARAM_TRAITS_H_ diff --git a/ipc/color/gfx_param_traits_macros.h b/ipc/color/gfx_param_traits_macros.h new file mode 100644 index 000000000000..bd05adb91583 --- /dev/null +++ b/ipc/color/gfx_param_traits_macros.h @@ -0,0 +1,44 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_IPC_COLOR_GFX_PARAM_TRAITS_MACROS_H_ +#define UI_GFX_IPC_COLOR_GFX_PARAM_TRAITS_MACROS_H_ + +#include "ipc/ipc_message_utils.h" +#include "ipc/param_traits_macros.h" +#include "ipc/ipc_message_macros.h" +#include "ui/gfx/color_space.h" +#include "ui/gfx/icc_profile.h" +#include "ui/gfx/ipc/color/gfx_ipc_color_export.h" + +#undef IPC_MESSAGE_EXPORT +#define IPC_MESSAGE_EXPORT GFX_IPC_COLOR_EXPORT + +IPC_ENUM_TRAITS_MAX_VALUE(gfx::ColorSpace::PrimaryID, + gfx::ColorSpace::PrimaryID::kMaxValue) +IPC_ENUM_TRAITS_MAX_VALUE(gfx::ColorSpace::TransferID, + gfx::ColorSpace::TransferID::kMaxValue) +IPC_ENUM_TRAITS_MAX_VALUE(gfx::ColorSpace::MatrixID, + gfx::ColorSpace::MatrixID::kMaxValue) +IPC_ENUM_TRAITS_MAX_VALUE(gfx::ColorSpace::RangeID, + gfx::ColorSpace::RangeID::kMaxValue) + +IPC_STRUCT_TRAITS_BEGIN(skcms_Matrix3x3) + IPC_STRUCT_TRAITS_MEMBER(vals) +IPC_STRUCT_TRAITS_END() + +IPC_STRUCT_TRAITS_BEGIN(skcms_TransferFunction) + IPC_STRUCT_TRAITS_MEMBER(a) + IPC_STRUCT_TRAITS_MEMBER(b) + IPC_STRUCT_TRAITS_MEMBER(c) + IPC_STRUCT_TRAITS_MEMBER(d) + IPC_STRUCT_TRAITS_MEMBER(e) + IPC_STRUCT_TRAITS_MEMBER(f) + IPC_STRUCT_TRAITS_MEMBER(g) +IPC_STRUCT_TRAITS_END() + +#undef IPC_MESSAGE_EXPORT +#define IPC_MESSAGE_EXPORT + +#endif // UI_GFX_IPC_COLOR_GFX_PARAM_TRAITS_MACROS_H_ diff --git a/ipc/geometry/BUILD.gn b/ipc/geometry/BUILD.gn new file mode 100644 index 000000000000..b8796b222cc2 --- /dev/null +++ b/ipc/geometry/BUILD.gn @@ -0,0 +1,21 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +component("geometry") { + output_name = "gfx_ipc_geometry" + + sources = [ + "gfx_ipc_geometry_export.h", + "gfx_param_traits.cc", + "gfx_param_traits.h", + ] + + defines = [ "GFX_IPC_GEOMETRY_IMPLEMENTATION" ] + + public_deps = [ + "//base", + "//ipc", + "//ui/gfx/geometry", + ] +} diff --git a/ipc/geometry/OWNERS b/ipc/geometry/OWNERS new file mode 100644 index 000000000000..146c3c3cd621 --- /dev/null +++ b/ipc/geometry/OWNERS @@ -0,0 +1,2 @@ +per-file *_param_traits*.*=set noparent +per-file *_param_traits*.*=file://ipc/SECURITY_OWNERS diff --git a/ipc/geometry/gfx_ipc_geometry_export.h b/ipc/geometry/gfx_ipc_geometry_export.h new file mode 100644 index 000000000000..32a07cd0eb5c --- /dev/null +++ b/ipc/geometry/gfx_ipc_geometry_export.h @@ -0,0 +1,29 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_IPC_GEOMETRY_GFX_IPC_GEOMETRY_EXPORT_H_ +#define UI_GFX_IPC_GEOMETRY_GFX_IPC_GEOMETRY_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(GFX_IPC_GEOMETRY_IMPLEMENTATION) +#define GFX_IPC_GEOMETRY_EXPORT __declspec(dllexport) +#else +#define GFX_IPC_GEOMETRY_EXPORT __declspec(dllimport) +#endif // defined(GFX_IPC_GEOMETRY_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(GFX_IPC_GEOMETRY_IMPLEMENTATION) +#define GFX_IPC_GEOMETRY_EXPORT __attribute__((visibility("default"))) +#else +#define GFX_IPC_GEOMETRY_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define GFX_IPC_GEOMETRY_EXPORT +#endif + +#endif // UI_GFX_IPC_GEOMETRY_GFX_IPC_GEOMETRY_EXPORT_H_ diff --git a/ipc/geometry/gfx_param_traits.cc b/ipc/geometry/gfx_param_traits.cc new file mode 100644 index 000000000000..20bc08820591 --- /dev/null +++ b/ipc/geometry/gfx_param_traits.cc @@ -0,0 +1,217 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/ipc/geometry/gfx_param_traits.h" + +#include +#include + +#include + +#include "base/strings/stringprintf.h" +#include "ui/gfx/geometry/point3_f.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/rect_f.h" +#include "ui/gfx/geometry/vector2d_f.h" + +namespace IPC { + +void ParamTraits::Write(base::Pickle* m, const gfx::Point& p) { + WriteParam(m, p.x()); + WriteParam(m, p.y()); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + gfx::Point* r) { + int x, y; + if (!ReadParam(m, iter, &x) || !ReadParam(m, iter, &y)) + return false; + r->set_x(x); + r->set_y(y); + return true; +} + +void ParamTraits::Log(const gfx::Point& p, std::string* l) { + l->append(base::StringPrintf("(%d, %d)", p.x(), p.y())); +} + +void ParamTraits::Write(base::Pickle* m, const gfx::PointF& p) { + WriteParam(m, p.x()); + WriteParam(m, p.y()); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + gfx::PointF* r) { + float x, y; + if (!ReadParam(m, iter, &x) || !ReadParam(m, iter, &y)) + return false; + r->set_x(x); + r->set_y(y); + return true; +} + +void ParamTraits::Log(const gfx::PointF& p, std::string* l) { + l->append(base::StringPrintf("(%f, %f)", p.x(), p.y())); +} + +void ParamTraits::Write(base::Pickle* m, const gfx::Point3F& p) { + WriteParam(m, p.x()); + WriteParam(m, p.y()); + WriteParam(m, p.z()); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + gfx::Point3F* r) { + float x, y, z; + if (!ReadParam(m, iter, &x) || !ReadParam(m, iter, &y) || + !ReadParam(m, iter, &z)) + return false; + r->set_x(x); + r->set_y(y); + r->set_z(z); + return true; +} + +void ParamTraits::Log(const gfx::Point3F& p, std::string* l) { + l->append(base::StringPrintf("(%f, %f, %f)", p.x(), p.y(), p.z())); +} + +void ParamTraits::Write(base::Pickle* m, const gfx::Size& p) { + DCHECK_GE(p.width(), 0); + DCHECK_GE(p.height(), 0); + int values[2] = {p.width(), p.height()}; + m->WriteBytes(&values, sizeof(int) * 2); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + gfx::Size* r) { + const char* char_values; + if (!iter->ReadBytes(&char_values, sizeof(int) * 2)) + return false; + const int* values = reinterpret_cast(char_values); + if (values[0] < 0 || values[1] < 0) + return false; + r->set_width(values[0]); + r->set_height(values[1]); + return true; +} + +void ParamTraits::Log(const gfx::Size& p, std::string* l) { + l->append(base::StringPrintf("(%d, %d)", p.width(), p.height())); +} + +void ParamTraits::Write(base::Pickle* m, const gfx::SizeF& p) { + float values[2] = {p.width(), p.height()}; + m->WriteBytes(&values, sizeof(float) * 2); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + gfx::SizeF* r) { + const char* char_values; + if (!iter->ReadBytes(&char_values, sizeof(float) * 2)) + return false; + const float* values = reinterpret_cast(char_values); + r->set_width(values[0]); + r->set_height(values[1]); + return true; +} + +void ParamTraits::Log(const gfx::SizeF& p, std::string* l) { + l->append(base::StringPrintf("(%f, %f)", p.width(), p.height())); +} + +void ParamTraits::Write(base::Pickle* m, + const gfx::Vector2d& p) { + int values[2] = {p.x(), p.y()}; + m->WriteBytes(&values, sizeof(int) * 2); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + gfx::Vector2d* r) { + const char* char_values; + if (!iter->ReadBytes(&char_values, sizeof(int) * 2)) + return false; + const int* values = reinterpret_cast(char_values); + r->set_x(values[0]); + r->set_y(values[1]); + return true; +} + +void ParamTraits::Log(const gfx::Vector2d& v, std::string* l) { + l->append(base::StringPrintf("(%d, %d)", v.x(), v.y())); +} + +void ParamTraits::Write(base::Pickle* m, + const gfx::Vector2dF& p) { + float values[2] = {p.x(), p.y()}; + m->WriteBytes(&values, sizeof(float) * 2); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + gfx::Vector2dF* r) { + const char* char_values; + if (!iter->ReadBytes(&char_values, sizeof(float) * 2)) + return false; + const float* values = reinterpret_cast(char_values); + r->set_x(values[0]); + r->set_y(values[1]); + return true; +} + +void ParamTraits::Log(const gfx::Vector2dF& v, std::string* l) { + l->append(base::StringPrintf("(%f, %f)", v.x(), v.y())); +} + +void ParamTraits::Write(base::Pickle* m, const gfx::Rect& p) { + int values[4] = {p.x(), p.y(), p.width(), p.height()}; + m->WriteBytes(&values, sizeof(int) * 4); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + gfx::Rect* r) { + const char* char_values; + if (!iter->ReadBytes(&char_values, sizeof(int) * 4)) + return false; + const int* values = reinterpret_cast(char_values); + if (values[2] < 0 || values[3] < 0) + return false; + r->SetRect(values[0], values[1], values[2], values[3]); + return true; +} + +void ParamTraits::Log(const gfx::Rect& p, std::string* l) { + l->append(base::StringPrintf("(%d, %d, %d, %d)", p.x(), p.y(), p.width(), + p.height())); +} + +void ParamTraits::Write(base::Pickle* m, const gfx::RectF& p) { + float values[4] = {p.x(), p.y(), p.width(), p.height()}; + m->WriteBytes(&values, sizeof(float) * 4); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + gfx::RectF* r) { + const char* char_values; + if (!iter->ReadBytes(&char_values, sizeof(float) * 4)) + return false; + const float* values = reinterpret_cast(char_values); + r->SetRect(values[0], values[1], values[2], values[3]); + return true; +} + +void ParamTraits::Log(const gfx::RectF& p, std::string* l) { + l->append(base::StringPrintf("(%f, %f, %f, %f)", p.x(), p.y(), p.width(), + p.height())); +} + +} // namespace IPC diff --git a/ipc/geometry/gfx_param_traits.h b/ipc/geometry/gfx_param_traits.h new file mode 100644 index 000000000000..3020a097848b --- /dev/null +++ b/ipc/geometry/gfx_param_traits.h @@ -0,0 +1,120 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_IPC_GEOMETRY_GFX_PARAM_TRAITS_H_ +#define UI_GFX_IPC_GEOMETRY_GFX_PARAM_TRAITS_H_ + +#include + +#include "ipc/ipc_message_utils.h" +#include "ipc/param_traits_macros.h" +#include "ui/gfx/ipc/geometry/gfx_ipc_geometry_export.h" + +namespace gfx { +class Point; +class PointF; +class Point3F; +class Rect; +class RectF; +class Size; +class SizeF; +class Vector2d; +class Vector2dF; +} // namespace gfx + +namespace IPC { + +template <> +struct GFX_IPC_GEOMETRY_EXPORT ParamTraits { + typedef gfx::Point param_type; + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct GFX_IPC_GEOMETRY_EXPORT ParamTraits { + typedef gfx::PointF param_type; + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct GFX_IPC_GEOMETRY_EXPORT ParamTraits { + typedef gfx::Point3F param_type; + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct GFX_IPC_GEOMETRY_EXPORT ParamTraits { + typedef gfx::Size param_type; + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct GFX_IPC_GEOMETRY_EXPORT ParamTraits { + typedef gfx::SizeF param_type; + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct GFX_IPC_GEOMETRY_EXPORT ParamTraits { + typedef gfx::Vector2d param_type; + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct GFX_IPC_GEOMETRY_EXPORT ParamTraits { + typedef gfx::Vector2dF param_type; + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct GFX_IPC_GEOMETRY_EXPORT ParamTraits { + typedef gfx::Rect param_type; + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct GFX_IPC_GEOMETRY_EXPORT ParamTraits { + typedef gfx::RectF param_type; + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +} // namespace IPC + +#endif // UI_GFX_IPC_GEOMETRY_GFX_PARAM_TRAITS_H_ diff --git a/ipc/gfx_ipc_export.h b/ipc/gfx_ipc_export.h new file mode 100644 index 000000000000..0362b5b0da2d --- /dev/null +++ b/ipc/gfx_ipc_export.h @@ -0,0 +1,29 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_IPC_GFX_IPC_EXPORT_H_ +#define UI_GFX_IPC_GFX_IPC_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(GFX_IPC_IMPLEMENTATION) +#define GFX_IPC_EXPORT __declspec(dllexport) +#else +#define GFX_IPC_EXPORT __declspec(dllimport) +#endif // defined(GFX_IPC_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(GFX_IPC_IMPLEMENTATION) +#define GFX_IPC_EXPORT __attribute__((visibility("default"))) +#else +#define GFX_IPC_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define GFX_IPC_EXPORT +#endif + +#endif // UI_GFX_IPC_GFX_IPC_EXPORT_H_ diff --git a/ipc/gfx_param_traits.cc b/ipc/gfx_param_traits.cc new file mode 100644 index 000000000000..49e2135524d4 --- /dev/null +++ b/ipc/gfx_param_traits.cc @@ -0,0 +1,176 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/ipc/gfx_param_traits.h" + +#include +#include + +#include + +#include "base/strings/stringprintf.h" +#include "ui/gfx/ipc/geometry/gfx_param_traits.h" +#include "ui/gfx/range/range.h" + +#if defined(OS_APPLE) +#include "ipc/mach_port_mac.h" +#endif + +namespace IPC { + +void ParamTraits::Write(base::Pickle* m, const gfx::Range& r) { + m->WriteUInt32(r.start()); + m->WriteUInt32(r.end()); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + gfx::Range* r) { + uint32_t start, end; + if (!iter->ReadUInt32(&start) || !iter->ReadUInt32(&end)) + return false; + r->set_start(start); + r->set_end(end); + return true; +} + +void ParamTraits::Log(const gfx::Range& r, std::string* l) { + l->append(base::StringPrintf("(%d, %d)", r.start(), r.end())); +} + +#if defined(OS_MAC) +void ParamTraits::Write( + base::Pickle* m, + const param_type p) { + MachPortMac mach_port_mac(p.get()); + ParamTraits::Write(m, mach_port_mac); +} + +bool ParamTraits::Read( + const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + MachPortMac mach_port_mac; + if (!ParamTraits::Read(m, iter, &mach_port_mac)) + return false; + r->reset(mach_port_mac.get_mach_port()); + return true; +} + +void ParamTraits::Log( + const param_type& p, + std::string* l) { + l->append("IOSurface Mach send right: "); + LogParam(p.get(), l); +} + +void ParamTraits::Write(base::Pickle* m, + const param_type p) { + gfx::ScopedRefCountedIOSurfaceMachPort io_surface_mach_port( + IOSurfaceCreateMachPort(p.get())); + MachPortMac mach_port_mac(io_surface_mach_port.get()); + ParamTraits::Write(m, mach_port_mac); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + MachPortMac mach_port_mac; + if (!ParamTraits::Read(m, iter, &mach_port_mac)) + return false; + gfx::ScopedRefCountedIOSurfaceMachPort io_surface_mach_port( + mach_port_mac.get_mach_port()); + if (io_surface_mach_port) + r->reset(IOSurfaceLookupFromMachPort(io_surface_mach_port.get())); + else + r->reset(); + return true; +} + +void ParamTraits::Log(const param_type& p, + std::string* l) { + l->append("IOSurface("); + if (p) { + uint32_t io_surface_id = IOSurfaceGetID(p.get()); + LogParam(io_surface_id, l); + } + l->append(")"); +} +#endif // defined(OS_MAC) + +void ParamTraits::Write(base::Pickle* m, + const param_type& p) { + WriteParam(m, static_cast(p.type())); + WriteParam(m, p.edge_start()); + WriteParam(m, p.edge_end()); + WriteParam(m, p.visible_edge_start()); + WriteParam(m, p.visible_edge_end()); + WriteParam(m, p.visible()); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + gfx::SelectionBound::Type type; + gfx::PointF edge_start; + gfx::PointF edge_end; + gfx::PointF visible_edge_start; + gfx::PointF visible_edge_end; + bool visible = false; + + if (!ReadParam(m, iter, &type) || !ReadParam(m, iter, &edge_start) || + !ReadParam(m, iter, &edge_end) || + !ReadParam(m, iter, &visible_edge_start) || + !ReadParam(m, iter, &visible_edge_end) || !ReadParam(m, iter, &visible)) { + return false; + } + + r->set_type(type); + r->SetEdgeStart(edge_start); + r->SetEdgeEnd(edge_end); + r->SetVisibleEdgeStart(visible_edge_start); + r->SetVisibleEdgeEnd(visible_edge_end); + r->set_visible(visible); + return true; +} + +void ParamTraits::Log(const param_type& p, + std::string* l) { + l->append("gfx::SelectionBound("); + LogParam(static_cast(p.type()), l); + l->append(", "); + LogParam(p.edge_start(), l); + l->append(", "); + LogParam(p.edge_end(), l); + l->append(", "); + LogParam(p.visible_edge_start(), l); + l->append(", "); + LogParam(p.visible_edge_end(), l); + l->append(", "); + LogParam(p.visible(), l); + l->append(")"); +} + +} // namespace IPC + +// Generate param traits write methods. +#include "ipc/param_traits_write_macros.h" +namespace IPC { +#undef UI_GFX_IPC_GFX_PARAM_TRAITS_MACROS_H_ +#include "ui/gfx/ipc/gfx_param_traits_macros.h" +} // namespace IPC + +// Generate param traits read methods. +#include "ipc/param_traits_read_macros.h" +namespace IPC { +#undef UI_GFX_IPC_GFX_PARAM_TRAITS_MACROS_H_ +#include "ui/gfx/ipc/gfx_param_traits_macros.h" +} // namespace IPC + +// Generate param traits log methods. +#include "ipc/param_traits_log_macros.h" +namespace IPC { +#undef UI_GFX_IPC_GFX_PARAM_TRAITS_MACROS_H_ +#include "ui/gfx/ipc/gfx_param_traits_macros.h" +} // namespace IPC diff --git a/ipc/gfx_param_traits.h b/ipc/gfx_param_traits.h new file mode 100644 index 000000000000..e9ae63a2d0cc --- /dev/null +++ b/ipc/gfx_param_traits.h @@ -0,0 +1,75 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_IPC_GFX_PARAM_TRAITS_H_ +#define UI_GFX_IPC_GFX_PARAM_TRAITS_H_ + +#include + +#include "ipc/ipc_message_utils.h" +#include "ipc/param_traits_macros.h" +#include "ui/gfx/buffer_types.h" +#include "ui/gfx/ipc/gfx_ipc_export.h" +#include "ui/gfx/ipc/gfx_param_traits_macros.h" +#include "ui/gfx/selection_bound.h" + +#if defined(OS_MAC) +#include "ui/gfx/mac/io_surface.h" +#endif + +namespace gfx { +class Range; +} + +namespace IPC { + +template <> +struct GFX_IPC_EXPORT ParamTraits { + typedef gfx::Range param_type; + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +#if defined(OS_MAC) +template <> +struct GFX_IPC_EXPORT ParamTraits { + typedef gfx::ScopedRefCountedIOSurfaceMachPort param_type; + static void Write(base::Pickle* m, const param_type p); + // Note: Read() passes ownership of the Mach send right from the IPC message + // to the ScopedRefCountedIOSurfaceMachPort. Therefore, Read() may only be + // called once for a given message, otherwise the singular right will be + // managed and released by two objects. + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct GFX_IPC_EXPORT ParamTraits { + typedef gfx::ScopedIOSurface param_type; + static void Write(base::Pickle* m, const param_type p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; +#endif // defined(OS_MAC) + +template <> +struct GFX_IPC_EXPORT ParamTraits { + typedef gfx::SelectionBound param_type; + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +} // namespace IPC + +#endif // UI_GFX_IPC_GFX_PARAM_TRAITS_H_ diff --git a/ipc/gfx_param_traits_macros.h b/ipc/gfx_param_traits_macros.h new file mode 100644 index 000000000000..d8ce305d91c5 --- /dev/null +++ b/ipc/gfx_param_traits_macros.h @@ -0,0 +1,120 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Singly or multiply-included shared traits file depending upon circumstances. +// This allows the use of IPC serialization macros in more than one IPC message +// file. +#ifndef UI_GFX_IPC_GFX_PARAM_TRAITS_MACROS_H_ +#define UI_GFX_IPC_GFX_PARAM_TRAITS_MACROS_H_ + +#include "build/build_config.h" +#include "ipc/ipc_message_macros.h" +#include "ui/gfx/ca_layer_params.h" +#include "ui/gfx/gpu_fence_handle.h" +#include "ui/gfx/gpu_memory_buffer.h" +#include "ui/gfx/ipc/gfx_ipc_export.h" +#include "ui/gfx/presentation_feedback.h" +#include "ui/gfx/selection_bound.h" +#include "ui/gfx/swap_result.h" + +#if defined(OS_LINUX) || defined(OS_CHROMEOS) +#include "ui/gfx/native_pixmap_handle.h" +#endif + +#undef IPC_MESSAGE_EXPORT +#define IPC_MESSAGE_EXPORT GFX_IPC_EXPORT + +IPC_ENUM_TRAITS_MAX_VALUE(gfx::GpuMemoryBufferType, + gfx::GPU_MEMORY_BUFFER_TYPE_LAST) + +IPC_ENUM_TRAITS_MAX_VALUE(gfx::SwapResult, gfx::SwapResult::SWAP_RESULT_LAST) + +IPC_ENUM_TRAITS_MAX_VALUE(gfx::SelectionBound::Type, gfx::SelectionBound::LAST) + +IPC_STRUCT_TRAITS_BEGIN(gfx::CALayerParams) + IPC_STRUCT_TRAITS_MEMBER(is_empty) +#if defined(OS_MAC) + IPC_STRUCT_TRAITS_MEMBER(ca_context_id) + IPC_STRUCT_TRAITS_MEMBER(io_surface_mach_port) + IPC_STRUCT_TRAITS_MEMBER(pixel_size) + IPC_STRUCT_TRAITS_MEMBER(scale_factor) +#endif +IPC_STRUCT_TRAITS_END() + +IPC_STRUCT_TRAITS_BEGIN(gfx::GpuMemoryBufferHandle) + IPC_STRUCT_TRAITS_MEMBER(id) + IPC_STRUCT_TRAITS_MEMBER(type) + IPC_STRUCT_TRAITS_MEMBER(region) + IPC_STRUCT_TRAITS_MEMBER(offset) + IPC_STRUCT_TRAITS_MEMBER(stride) +#if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_FUCHSIA) + IPC_STRUCT_TRAITS_MEMBER(native_pixmap_handle) +#elif defined(OS_APPLE) + IPC_STRUCT_TRAITS_MEMBER(io_surface) +#elif defined(OS_WIN) + IPC_STRUCT_TRAITS_MEMBER(dxgi_handle) +#elif defined(OS_ANDROID) + IPC_STRUCT_TRAITS_MEMBER(android_hardware_buffer) +#endif +IPC_STRUCT_TRAITS_END() + +IPC_STRUCT_TRAITS_BEGIN(gfx::GpuMemoryBufferId) + IPC_STRUCT_TRAITS_MEMBER(id) +IPC_STRUCT_TRAITS_END() + +#if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_FUCHSIA) +IPC_STRUCT_TRAITS_BEGIN(gfx::NativePixmapPlane) + IPC_STRUCT_TRAITS_MEMBER(stride) + IPC_STRUCT_TRAITS_MEMBER(offset) + IPC_STRUCT_TRAITS_MEMBER(size) +#if defined(OS_LINUX) || defined(OS_CHROMEOS) + IPC_STRUCT_TRAITS_MEMBER(fd) +#elif defined(OS_FUCHSIA) + IPC_STRUCT_TRAITS_MEMBER(vmo) +#endif +IPC_STRUCT_TRAITS_END() + +IPC_STRUCT_TRAITS_BEGIN(gfx::NativePixmapHandle) + IPC_STRUCT_TRAITS_MEMBER(planes) +#if defined(OS_LINUX) || defined(OS_CHROMEOS) + IPC_STRUCT_TRAITS_MEMBER(modifier) +#endif +#if defined(OS_FUCHSIA) + IPC_STRUCT_TRAITS_MEMBER(buffer_collection_id) + IPC_STRUCT_TRAITS_MEMBER(buffer_index) + IPC_STRUCT_TRAITS_MEMBER(ram_coherency) +#endif +IPC_STRUCT_TRAITS_END() +#endif + +IPC_STRUCT_TRAITS_BEGIN(gfx::SwapTimings) + IPC_STRUCT_TRAITS_MEMBER(swap_start) + IPC_STRUCT_TRAITS_MEMBER(swap_end) +IPC_STRUCT_TRAITS_END() + +IPC_STRUCT_TRAITS_BEGIN(gfx::SwapResponse) + IPC_STRUCT_TRAITS_MEMBER(swap_id) + IPC_STRUCT_TRAITS_MEMBER(result) + IPC_STRUCT_TRAITS_MEMBER(timings) +IPC_STRUCT_TRAITS_END() + +IPC_STRUCT_TRAITS_BEGIN(gfx::PresentationFeedback) + IPC_STRUCT_TRAITS_MEMBER(timestamp) + IPC_STRUCT_TRAITS_MEMBER(interval) + IPC_STRUCT_TRAITS_MEMBER(flags) +IPC_STRUCT_TRAITS_END() + +IPC_STRUCT_TRAITS_BEGIN(gfx::GpuFenceHandle) +#if defined(OS_POSIX) + IPC_STRUCT_TRAITS_MEMBER(owned_fd) +#endif +#if defined(OS_WIN) + IPC_STRUCT_TRAITS_MEMBER(owned_handle) +#endif +IPC_STRUCT_TRAITS_END() + +#undef IPC_MESSAGE_EXPORT +#define IPC_MESSAGE_EXPORT + +#endif // UI_GFX_IPC_GFX_PARAM_TRAITS_MACROS_H_ diff --git a/ipc/skia/BUILD.gn b/ipc/skia/BUILD.gn new file mode 100644 index 000000000000..fcb670e02252 --- /dev/null +++ b/ipc/skia/BUILD.gn @@ -0,0 +1,27 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +component("skia") { + output_name = "gfx_ipc_skia" + + sources = [ + "gfx_skia_ipc_export.h", + "gfx_skia_param_traits.cc", + "gfx_skia_param_traits.h", + ] + + defines = [ "GFX_SKIA_IPC_IMPLEMENTATION" ] + + deps = [ + "//base", + "//skia", + "//ui/gfx", + "//ui/gfx/geometry", + ] + + public_deps = [ + "//ipc", + "//ui/gfx/ipc", + ] +} diff --git a/ipc/skia/OWNERS b/ipc/skia/OWNERS new file mode 100644 index 000000000000..146c3c3cd621 --- /dev/null +++ b/ipc/skia/OWNERS @@ -0,0 +1,2 @@ +per-file *_param_traits*.*=set noparent +per-file *_param_traits*.*=file://ipc/SECURITY_OWNERS diff --git a/ipc/skia/gfx_skia_ipc_export.h b/ipc/skia/gfx_skia_ipc_export.h new file mode 100644 index 000000000000..8ea331d43ad1 --- /dev/null +++ b/ipc/skia/gfx_skia_ipc_export.h @@ -0,0 +1,29 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_IPC_SKIA_GFX_SKIA_IPC_EXPORT_H_ +#define UI_GFX_IPC_SKIA_GFX_SKIA_IPC_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(GFX_SKIA_IPC_IMPLEMENTATION) +#define GFX_SKIA_IPC_EXPORT __declspec(dllexport) +#else +#define GFX_SKIA_IPC_EXPORT __declspec(dllimport) +#endif // defined(GFX_SKIA_IPC_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(GFX_SKIA_IPC_IMPLEMENTATION) +#define GFX_SKIA_IPC_EXPORT __attribute__((visibility("default"))) +#else +#define GFX_SKIA_IPC_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define GFX_SKIA_IPC_EXPORT +#endif + +#endif // UI_GFX_IPC_SKIA_GFX_SKIA_IPC_EXPORT_H_ diff --git a/ipc/skia/gfx_skia_param_traits.cc b/ipc/skia/gfx_skia_param_traits.cc new file mode 100644 index 000000000000..9da195596d57 --- /dev/null +++ b/ipc/skia/gfx_skia_param_traits.cc @@ -0,0 +1,134 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/ipc/skia/gfx_skia_param_traits.h" + +#include + +#include "base/pickle.h" +#include "ipc/ipc_message_utils.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkImageInfo.h" +#include "ui/gfx/geometry/transform.h" + +// Generate param traits write methods. +#include "ipc/param_traits_write_macros.h" +namespace IPC { +#undef UI_GFX_IPC_SKIA_GFX_SKIA_PARAM_TRAITS_MACROS_H_ +#include "ui/gfx/ipc/skia/gfx_skia_param_traits_macros.h" +} // namespace IPC + +// Generate param traits read methods. +#include "ipc/param_traits_read_macros.h" +namespace IPC { +#undef UI_GFX_IPC_SKIA_GFX_SKIA_PARAM_TRAITS_MACROS_H_ +#include "ui/gfx/ipc/skia/gfx_skia_param_traits_macros.h" +} // namespace IPC + +// Generate param traits log methods. +#include "ipc/param_traits_log_macros.h" +namespace IPC { +#undef UI_GFX_IPC_SKIA_GFX_SKIA_PARAM_TRAITS_MACROS_H_ +#include "ui/gfx/ipc/skia/gfx_skia_param_traits_macros.h" +} // namespace IPC + +namespace IPC { + +void ParamTraits::Write(base::Pickle* m, const SkImageInfo& p) { + WriteParam(m, p.colorType()); + WriteParam(m, p.alphaType()); + WriteParam(m, p.width()); + WriteParam(m, p.height()); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + SkImageInfo* r) { + SkColorType color_type; + SkAlphaType alpha_type; + uint32_t width; + uint32_t height; + if (!ReadParam(m, iter, &color_type) || !ReadParam(m, iter, &alpha_type) || + !ReadParam(m, iter, &width) || !ReadParam(m, iter, &height)) { + return false; + } + + *r = SkImageInfo::Make(width, height, color_type, alpha_type); + return true; +} + +void ParamTraits::Log(const SkImageInfo& p, std::string* l) { + l->append(""); +} + +void ParamTraits::Write(base::Pickle* m, const SkBitmap& p) { + WriteParam(m, p.info()); + size_t pixel_size = p.computeByteSize(); + m->WriteData(reinterpret_cast(p.getPixels()), + static_cast(pixel_size)); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + SkBitmap* r) { + SkImageInfo image_info; + if (!ReadParam(m, iter, &image_info)) + return false; + + const char* bitmap_data; + int bitmap_data_size = 0; + if (!iter->ReadData(&bitmap_data, &bitmap_data_size)) + return false; + // ReadData() only returns true if bitmap_data_size >= 0. + + if (!r->tryAllocPixels(image_info)) + return false; + + if (static_cast(bitmap_data_size) != r->computeByteSize()) + return false; + memcpy(r->getPixels(), bitmap_data, bitmap_data_size); + return true; +} + +void ParamTraits::Log(const SkBitmap& p, std::string* l) { + l->append(""); + LogParam(p.info(), l); +} + +void ParamTraits::Write(base::Pickle* m, const param_type& p) { + SkScalar column_major_data[16]; + p.matrix().asColMajorf(column_major_data); + // We do this in a single write for performance reasons. + m->WriteBytes(&column_major_data, sizeof(SkScalar) * 16); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + const char* column_major_data; + if (!iter->ReadBytes(&column_major_data, sizeof(SkScalar) * 16)) + return false; + r->matrix().setColMajor(reinterpret_cast(column_major_data)); + return true; +} + +void ParamTraits::Log( + const param_type& p, std::string* l) { +#ifdef SK_SCALAR_IS_FLOAT + float row_major_data[16]; + p.matrix().asRowMajorf(row_major_data); +#else + double row_major_data[16]; + p.matrix().asRowMajord(row_major_data); +#endif + l->append("("); + for (int i = 0; i < 16; ++i) { + if (i > 0) + l->append(", "); + LogParam(row_major_data[i], l); + } + l->append(") "); +} + +} // namespace IPC diff --git a/ipc/skia/gfx_skia_param_traits.h b/ipc/skia/gfx_skia_param_traits.h new file mode 100644 index 000000000000..3267c7d3adc9 --- /dev/null +++ b/ipc/skia/gfx_skia_param_traits.h @@ -0,0 +1,61 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_IPC_SKIA_GFX_SKIA_PARAM_TRAITS_H_ +#define UI_GFX_IPC_SKIA_GFX_SKIA_PARAM_TRAITS_H_ + +#include + +#include "ipc/ipc_message_utils.h" +#include "ipc/ipc_param_traits.h" +#include "ui/gfx/ipc/skia/gfx_skia_ipc_export.h" +#include "ui/gfx/ipc/skia/gfx_skia_param_traits_macros.h" + +class SkBitmap; +struct SkImageInfo; + +namespace base { +class Pickle; +class PickleIterator; +} + +namespace gfx { +class Transform; +} + +namespace IPC { + +template <> +struct GFX_SKIA_IPC_EXPORT ParamTraits { + using param_type = SkImageInfo; + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct GFX_SKIA_IPC_EXPORT ParamTraits { + using param_type = SkBitmap; + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct GFX_SKIA_IPC_EXPORT ParamTraits { + using param_type = gfx::Transform; + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +} // namespace IPC + +#endif // UI_GFX_IPC_SKIA_GFX_SKIA_PARAM_TRAITS_H_ diff --git a/ipc/skia/gfx_skia_param_traits_macros.h b/ipc/skia/gfx_skia_param_traits_macros.h new file mode 100644 index 000000000000..ada9951da5b7 --- /dev/null +++ b/ipc/skia/gfx_skia_param_traits_macros.h @@ -0,0 +1,22 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_IPC_SKIA_GFX_SKIA_PARAM_TRAITS_MACROS_H_ +#define UI_GFX_IPC_SKIA_GFX_SKIA_PARAM_TRAITS_MACROS_H_ + +#include + +#include "ipc/ipc_message_macros.h" +#include "third_party/skia/include/core/SkImageInfo.h" + +#undef IPC_MESSAGE_EXPORT +#define IPC_MESSAGE_EXPORT GFX_SKIA_IPC_EXPORT + +IPC_ENUM_TRAITS_MAX_VALUE(SkColorType, kLastEnum_SkColorType) +IPC_ENUM_TRAITS_MAX_VALUE(SkAlphaType, kLastEnum_SkAlphaType) + +#undef IPC_MESSAGE_EXPORT +#define IPC_MESSAGE_EXPORT + +#endif // UI_GFX_IPC_SKIA_GFX_SKIA_PARAM_TRAITS_MACROS_H_ diff --git a/linux/BUILD.gn b/linux/BUILD.gn new file mode 100644 index 000000000000..3d02d93c3751 --- /dev/null +++ b/linux/BUILD.gn @@ -0,0 +1,84 @@ +# Copyright 2018 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//build/config/ozone.gni") +import("//build/config/ui.gni") + +assert(use_x11 || ozone_platform_drm || ozone_platform_wayland || + ozone_platform_x11) + +source_set("drm") { + sources = [ + "drm_util_linux.cc", + "drm_util_linux.h", + ] + + deps = [ + "//base:base", + "//build/config/linux/libdrm", + "//ui/gfx:buffer_types", + ] +} + +source_set("gbm") { + sources = [ + "gbm_buffer.h", + "gbm_defines.h", + "gbm_device.h", + "gbm_util.cc", + "gbm_util.h", + "gbm_wrapper.cc", + "gbm_wrapper.h", + "scoped_gbm_device.cc", + "scoped_gbm_device.h", + ] + + deps = [ + ":drm", + "//base:base", + "//build/config/linux/libdrm", + "//skia", + "//third_party/minigbm", + "//ui/gfx:buffer_types", + "//ui/gfx:memory_buffer", + "//ui/gfx/geometry:geometry", + ] +} + +if (use_x11 || ozone_platform_x11) { + component("gpu_memory_buffer_support_x11") { + sources = [ + "gpu_memory_buffer_support_x11.cc", + "gpu_memory_buffer_support_x11.h", + ] + deps = [ + ":drm", + ":gbm", + "//base", + "//skia", + "//ui/gfx:buffer_types", + "//ui/gfx:memory_buffer", + "//ui/gfx/x", + ] + defines = [ "IS_GBM_SUPPORT_X11_IMPL" ] + } +} + +source_set("test_support") { + testonly = true + + sources = [ + "test/mock_gbm_device.cc", + "test/mock_gbm_device.h", + ] + + deps = [ + ":drm", + ":gbm", + "//base", + "//build/config/linux/libdrm", + "//skia", + "//testing/gtest", + ] +} diff --git a/linux/OWNERS b/linux/OWNERS new file mode 100644 index 000000000000..c46d06f45899 --- /dev/null +++ b/linux/OWNERS @@ -0,0 +1,6 @@ +dcastagna@chromium.org +dnicoara@chromium.org +dongseong.hwang@chromium.org +msisov@igalia.com +rjkroege@chromium.org +spang@chromium.org diff --git a/linux/client_native_pixmap_dmabuf.cc b/linux/client_native_pixmap_dmabuf.cc new file mode 100644 index 000000000000..1befc6b00cdf --- /dev/null +++ b/linux/client_native_pixmap_dmabuf.cc @@ -0,0 +1,283 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/linux/client_native_pixmap_dmabuf.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include "base/command_line.h" +#include "base/logging.h" +#include "base/memory/ptr_util.h" +#include "base/numerics/safe_conversions.h" +#include "base/posix/eintr_wrapper.h" +#include "base/process/memory.h" +#include "base/process/process_metrics.h" +#include "base/trace_event/trace_event.h" +#include "build/build_config.h" +#include "build/chromecast_buildflags.h" +#include "build/chromeos_buildflags.h" +#include "ui/gfx/buffer_format_util.h" +#include "ui/gfx/switches.h" + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) +#include +#else +#include + +struct dma_buf_sync { + __u64 flags; +}; + +#define DMA_BUF_SYNC_READ (1 << 0) +#define DMA_BUF_SYNC_WRITE (2 << 0) +#define DMA_BUF_SYNC_RW (DMA_BUF_SYNC_READ | DMA_BUF_SYNC_WRITE) +#define DMA_BUF_SYNC_START (0 << 2) +#define DMA_BUF_SYNC_END (1 << 2) + +#define DMA_BUF_BASE 'b' +#define DMA_BUF_IOCTL_SYNC _IOW(DMA_BUF_BASE, 0, struct dma_buf_sync) +#endif + +namespace gfx { + +namespace { + +void PrimeSyncStart(int dmabuf_fd) { + struct dma_buf_sync sync_start = {0}; + + sync_start.flags = DMA_BUF_SYNC_START | DMA_BUF_SYNC_RW; + int rv = HANDLE_EINTR(ioctl(dmabuf_fd, DMA_BUF_IOCTL_SYNC, &sync_start)); + PLOG_IF(ERROR, rv) << "Failed DMA_BUF_SYNC_START"; +} + +void PrimeSyncEnd(int dmabuf_fd) { + struct dma_buf_sync sync_end = {0}; + + sync_end.flags = DMA_BUF_SYNC_END | DMA_BUF_SYNC_RW; + int rv = HANDLE_EINTR(ioctl(dmabuf_fd, DMA_BUF_IOCTL_SYNC, &sync_end)); + PLOG_IF(ERROR, rv) << "Failed DMA_BUF_SYNC_END"; +} + +bool AllowCpuMappableBuffers() { + static bool result = base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kEnableNativeGpuMemoryBuffers); + return result; +} + +} // namespace + +ClientNativePixmapDmaBuf::PlaneInfo::PlaneInfo() {} + +ClientNativePixmapDmaBuf::PlaneInfo::PlaneInfo(PlaneInfo&& info) + : data(info.data), offset(info.offset), size(info.size) { + // Set nullptr to info.data in order not to call munmap in |info| dtor. + info.data = nullptr; +} + +ClientNativePixmapDmaBuf::PlaneInfo::~PlaneInfo() { + if (data) { + int ret = munmap(data, offset + size); + DCHECK(!ret); + } +} + +// static +bool ClientNativePixmapDmaBuf::IsConfigurationSupported( + gfx::BufferFormat format, + gfx::BufferUsage usage) { + switch (usage) { + case gfx::BufferUsage::GPU_READ: + return format == gfx::BufferFormat::BGR_565 || + format == gfx::BufferFormat::RGBA_8888 || + format == gfx::BufferFormat::RGBX_8888 || + format == gfx::BufferFormat::BGRA_8888 || + format == gfx::BufferFormat::BGRX_8888 || + format == gfx::BufferFormat::YVU_420; + case gfx::BufferUsage::SCANOUT: + return format == gfx::BufferFormat::BGRX_8888 || + format == gfx::BufferFormat::RGBX_8888 || + format == gfx::BufferFormat::RGBA_8888 || + format == gfx::BufferFormat::BGRA_8888 || + format == gfx::BufferFormat::RGBA_1010102 || + format == gfx::BufferFormat::BGRA_1010102; + case gfx::BufferUsage::SCANOUT_FRONT_RENDERING: + case gfx::BufferUsage::SCANOUT_CPU_READ_WRITE: + // TODO(crbug.com/954233): RG_88 is enabled only with + // --enable-native-gpu-memory-buffers . Otherwise it breaks some telemetry + // tests. Fix that issue and enable it again. + if (format == gfx::BufferFormat::RG_88 && !AllowCpuMappableBuffers()) + return false; + + if (format == gfx::BufferFormat::YUV_420_BIPLANAR) + return true; + + return +#if defined(ARCH_CPU_X86_FAMILY) + // The minigbm backends and Mesa drivers commonly used on x86 systems + // support the following formats. + format == gfx::BufferFormat::R_8 || + format == gfx::BufferFormat::RG_88 || + format == gfx::BufferFormat::YUV_420_BIPLANAR || + format == gfx::BufferFormat::RGBA_1010102 || + format == gfx::BufferFormat::BGRA_1010102 || +#endif + + format == gfx::BufferFormat::BGRX_8888 || + format == gfx::BufferFormat::BGRA_8888 || + format == gfx::BufferFormat::RGBX_8888 || + format == gfx::BufferFormat::RGBA_8888; + case gfx::BufferUsage::SCANOUT_VDA_WRITE: // fallthrough + case gfx::BufferUsage::PROTECTED_SCANOUT_VDA_WRITE: + return false; + + case gfx::BufferUsage::GPU_READ_CPU_READ_WRITE: + if (!AllowCpuMappableBuffers()) + return false; + + if (format == gfx::BufferFormat::YUV_420_BIPLANAR) + return true; + + return +#if defined(ARCH_CPU_X86_FAMILY) + // The minigbm backends and Mesa drivers commonly used on x86 systems + // support the following formats. + format == gfx::BufferFormat::R_8 || + format == gfx::BufferFormat::RG_88 || + format == gfx::BufferFormat::YUV_420_BIPLANAR || + format == gfx::BufferFormat::P010 || +#endif + format == gfx::BufferFormat::BGRA_8888; + case gfx::BufferUsage::SCANOUT_CAMERA_READ_WRITE: + // Each platform only supports one camera buffer type. We list the + // supported buffer formats on all platforms here. When allocating a + // camera buffer the caller is responsible for making sure a buffer is + // successfully allocated. For example, allocating YUV420_BIPLANAR + // for SCANOUT_CAMERA_READ_WRITE may only work on Intel boards. + return format == gfx::BufferFormat::YUV_420_BIPLANAR; + case gfx::BufferUsage::CAMERA_AND_CPU_READ_WRITE: + // R_8 is used as the underlying pixel format for BLOB buffers. + return format == gfx::BufferFormat::R_8; + case gfx::BufferUsage::SCANOUT_VEA_CPU_READ: + case gfx::BufferUsage::VEA_READ_CAMERA_AND_CPU_READ_WRITE: + return format == gfx::BufferFormat::YVU_420 || + format == gfx::BufferFormat::YUV_420_BIPLANAR; + } + NOTREACHED(); + return false; +} + +// static +std::unique_ptr +ClientNativePixmapDmaBuf::ImportFromDmabuf(gfx::NativePixmapHandle handle, + const gfx::Size& size, + gfx::BufferFormat format) { + std::array plane_info; + + size_t expected_planes = gfx::NumberOfPlanesForLinearBufferFormat(format); + if (expected_planes == 0 || handle.planes.size() != expected_planes) { + return nullptr; + } + + for (size_t i = 0; i < handle.planes.size(); ++i) { + // Verify that the plane buffer has appropriate size. + const size_t plane_stride = + base::strict_cast(handle.planes[i].stride); + size_t min_stride = 0; + size_t subsample_factor = SubsamplingFactorForBufferFormat(format, i); + base::CheckedNumeric plane_height = + (base::CheckedNumeric(size.height()) + subsample_factor - 1) / + subsample_factor; + if (!gfx::RowSizeForBufferFormatChecked(size.width(), format, i, + &min_stride) || + plane_stride < min_stride) { + return nullptr; + } + base::CheckedNumeric min_size = + base::CheckedNumeric(plane_stride) * plane_height; + if (!min_size.IsValid() || handle.planes[i].size < min_size.ValueOrDie()) + return nullptr; + + // The stride must be a valid integer in order to be consistent with the + // GpuMemoryBuffer::stride() API. Also, refer to http://crbug.com/1093644#c1 + // for some comments on this check and others in this method. + if (!base::IsValueInRangeForNumericType(plane_stride)) + return nullptr; + + const size_t map_size = base::checked_cast(handle.planes[i].size); + plane_info[i].offset = handle.planes[i].offset; + plane_info[i].size = map_size; + + void* data = mmap(nullptr, map_size + handle.planes[i].offset, + (PROT_READ | PROT_WRITE), MAP_SHARED, + handle.planes[i].fd.get(), 0); + + if (data == MAP_FAILED) { + logging::SystemErrorCode mmap_error = logging::GetLastSystemErrorCode(); + if (mmap_error == ENOMEM) + base::TerminateBecauseOutOfMemory(map_size + + handle.planes[i].offset); + LOG(ERROR) << "Failed to mmap dmabuf: " + << logging::SystemErrorCodeToString(mmap_error); + return nullptr; + } + plane_info[i].data = data; + } + + return base::WrapUnique(new ClientNativePixmapDmaBuf(std::move(handle), size, + std::move(plane_info))); +} + +ClientNativePixmapDmaBuf::ClientNativePixmapDmaBuf( + gfx::NativePixmapHandle handle, + const gfx::Size& size, + std::array plane_info) + : pixmap_handle_(std::move(handle)), + size_(size), + plane_info_(std::move(plane_info)) { + TRACE_EVENT0("drm", "ClientNativePixmapDmaBuf"); +} + +ClientNativePixmapDmaBuf::~ClientNativePixmapDmaBuf() { + TRACE_EVENT0("drm", "~ClientNativePixmapDmaBuf"); +} + +bool ClientNativePixmapDmaBuf::Map() { + TRACE_EVENT0("drm", "DmaBuf:Map"); + for (size_t i = 0; i < pixmap_handle_.planes.size(); ++i) + PrimeSyncStart(pixmap_handle_.planes[i].fd.get()); + return true; +} + +void ClientNativePixmapDmaBuf::Unmap() { + TRACE_EVENT0("drm", "DmaBuf:Unmap"); + for (size_t i = 0; i < pixmap_handle_.planes.size(); ++i) + PrimeSyncEnd(pixmap_handle_.planes[i].fd.get()); +} + +size_t ClientNativePixmapDmaBuf::GetNumberOfPlanes() const { + return pixmap_handle_.planes.size(); +} + +void* ClientNativePixmapDmaBuf::GetMemoryAddress(size_t plane) const { + DCHECK_LT(plane, pixmap_handle_.planes.size()); + return static_cast(plane_info_[plane].data) + + plane_info_[plane].offset; +} + +int ClientNativePixmapDmaBuf::GetStride(size_t plane) const { + DCHECK_LT(plane, pixmap_handle_.planes.size()); + return base::checked_cast(pixmap_handle_.planes[plane].stride); +} + +NativePixmapHandle ClientNativePixmapDmaBuf::CloneHandleForIPC() const { + return gfx::CloneHandleForIPC(pixmap_handle_); +} +} // namespace gfx diff --git a/linux/client_native_pixmap_dmabuf.h b/linux/client_native_pixmap_dmabuf.h new file mode 100644 index 000000000000..7c8c80ece296 --- /dev/null +++ b/linux/client_native_pixmap_dmabuf.h @@ -0,0 +1,70 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_LINUX_CLIENT_NATIVE_PIXMAP_DMABUF_H_ +#define UI_GFX_LINUX_CLIENT_NATIVE_PIXMAP_DMABUF_H_ + +#include + +#include +#include + +#include "base/files/scoped_file.h" +#include "base/macros.h" +#include "ui/gfx/buffer_types.h" +#include "ui/gfx/client_native_pixmap.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/gfx_export.h" +#include "ui/gfx/native_pixmap_handle.h" + +namespace gfx { + +class ClientNativePixmapDmaBuf : public gfx::ClientNativePixmap { + public: + static GFX_EXPORT bool IsConfigurationSupported(gfx::BufferFormat format, + gfx::BufferUsage usage); + + static std::unique_ptr ImportFromDmabuf( + gfx::NativePixmapHandle handle, + const gfx::Size& size, + gfx::BufferFormat format); + + ClientNativePixmapDmaBuf(const ClientNativePixmapDmaBuf&) = delete; + ClientNativePixmapDmaBuf& operator=(const ClientNativePixmapDmaBuf&) = delete; + + ~ClientNativePixmapDmaBuf() override; + + // Overridden from ClientNativePixmap. + bool Map() override; + void Unmap() override; + + size_t GetNumberOfPlanes() const override; + void* GetMemoryAddress(size_t plane) const override; + int GetStride(size_t plane) const override; + NativePixmapHandle CloneHandleForIPC() const override; + + private: + static constexpr size_t kMaxPlanes = 4; + + struct PlaneInfo { + PlaneInfo(); + PlaneInfo(PlaneInfo&& plane_info); + ~PlaneInfo(); + + void* data = nullptr; + size_t offset = 0; + size_t size = 0; + }; + ClientNativePixmapDmaBuf(gfx::NativePixmapHandle handle, + const gfx::Size& size, + std::array plane_info); + + const gfx::NativePixmapHandle pixmap_handle_; + const gfx::Size size_; + const std::array plane_info_; +}; + +} // namespace gfx + +#endif // UI_GFX_LINUX_CLIENT_NATIVE_PIXMAP_DMABUF_H_ diff --git a/linux/client_native_pixmap_factory_dmabuf.cc b/linux/client_native_pixmap_factory_dmabuf.cc new file mode 100644 index 000000000000..9fe3f7d32e73 --- /dev/null +++ b/linux/client_native_pixmap_factory_dmabuf.cc @@ -0,0 +1,102 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/linux/client_native_pixmap_factory_dmabuf.h" + +#include + +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/trace_event/trace_event.h" +#include "build/build_config.h" +#include "ui/gfx/native_pixmap_handle.h" + +// Although, it's compiled for all linux platforms, it does not mean dmabuf +// will work there. Check the comment below in the +// ClientNativePixmapFactoryDmabuf for more details. +#include "ui/gfx/linux/client_native_pixmap_dmabuf.h" + +namespace gfx { + +namespace { + +class ClientNativePixmapOpaque : public ClientNativePixmap { + public: + explicit ClientNativePixmapOpaque(NativePixmapHandle pixmap_handle) + : pixmap_handle_(std::move(pixmap_handle)) {} + ~ClientNativePixmapOpaque() override = default; + + bool Map() override { + NOTREACHED(); + return false; + } + void Unmap() override { NOTREACHED(); } + size_t GetNumberOfPlanes() const override { + return pixmap_handle_.planes.size(); + } + void* GetMemoryAddress(size_t plane) const override { + NOTREACHED(); + return nullptr; + } + int GetStride(size_t plane) const override { + CHECK_LT(plane, pixmap_handle_.planes.size()); + // Even though a ClientNativePixmapOpaque should not be mapped, we may still + // need to query the stride of each plane. See + // VideoFrame::WrapExternalGpuMemoryBuffer() for such a use case. + return base::checked_cast(pixmap_handle_.planes[plane].stride); + } + NativePixmapHandle CloneHandleForIPC() const override { + return gfx::CloneHandleForIPC(pixmap_handle_); + } + + private: + NativePixmapHandle pixmap_handle_; +}; + +} // namespace + +class ClientNativePixmapFactoryDmabuf : public ClientNativePixmapFactory { + public: + explicit ClientNativePixmapFactoryDmabuf() {} + + ClientNativePixmapFactoryDmabuf(const ClientNativePixmapFactoryDmabuf&) = + delete; + ClientNativePixmapFactoryDmabuf& operator=( + const ClientNativePixmapFactoryDmabuf&) = delete; + + ~ClientNativePixmapFactoryDmabuf() override {} + + std::unique_ptr ImportFromHandle( + gfx::NativePixmapHandle handle, + const gfx::Size& size, + gfx::BufferFormat format, + gfx::BufferUsage usage) override { + DCHECK(!handle.planes.empty()); + switch (usage) { + case gfx::BufferUsage::SCANOUT_CPU_READ_WRITE: + case gfx::BufferUsage::GPU_READ_CPU_READ_WRITE: + case gfx::BufferUsage::SCANOUT_CAMERA_READ_WRITE: + case gfx::BufferUsage::CAMERA_AND_CPU_READ_WRITE: + case gfx::BufferUsage::SCANOUT_VEA_CPU_READ: + case gfx::BufferUsage::VEA_READ_CAMERA_AND_CPU_READ_WRITE: + case gfx::BufferUsage::SCANOUT_FRONT_RENDERING: + return ClientNativePixmapDmaBuf::ImportFromDmabuf(std::move(handle), + size, format); + case gfx::BufferUsage::GPU_READ: + case gfx::BufferUsage::SCANOUT: + case gfx::BufferUsage::SCANOUT_VDA_WRITE: + case gfx::BufferUsage::PROTECTED_SCANOUT_VDA_WRITE: + return base::WrapUnique( + new ClientNativePixmapOpaque(std::move(handle))); + } + NOTREACHED(); + return nullptr; + } +}; + +ClientNativePixmapFactory* CreateClientNativePixmapFactoryDmabuf() { + return new ClientNativePixmapFactoryDmabuf(); +} + +} // namespace gfx diff --git a/linux/client_native_pixmap_factory_dmabuf.h b/linux/client_native_pixmap_factory_dmabuf.h new file mode 100644 index 000000000000..7f802a64a01f --- /dev/null +++ b/linux/client_native_pixmap_factory_dmabuf.h @@ -0,0 +1,17 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_LINUX_CLIENT_NATIVE_PIXMAP_FACTORY_DMABUF_H_ +#define UI_GFX_LINUX_CLIENT_NATIVE_PIXMAP_FACTORY_DMABUF_H_ + +#include "ui/gfx/client_native_pixmap_factory.h" +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +GFX_EXPORT ClientNativePixmapFactory* CreateClientNativePixmapFactoryDmabuf(); + +} // namespace gfx + +#endif // UI_GFX_LINUX_CLIENT_NATIVE_PIXMAP_FACTORY_DMABUF_H_ diff --git a/linux/drm_util_linux.cc b/linux/drm_util_linux.cc new file mode 100644 index 000000000000..416fc2303799 --- /dev/null +++ b/linux/drm_util_linux.cc @@ -0,0 +1,101 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/linux/drm_util_linux.h" + +#include + +#include "base/notreached.h" + +namespace ui { + +int GetFourCCFormatFromBufferFormat(gfx::BufferFormat format) { + switch (format) { + case gfx::BufferFormat::R_8: + return DRM_FORMAT_R8; + case gfx::BufferFormat::R_16: + return DRM_FORMAT_R16; + case gfx::BufferFormat::RG_88: + return DRM_FORMAT_GR88; + case gfx::BufferFormat::BGR_565: + return DRM_FORMAT_RGB565; + case gfx::BufferFormat::RGBA_4444: + return DRM_FORMAT_INVALID; + case gfx::BufferFormat::RGBA_8888: + return DRM_FORMAT_ABGR8888; + case gfx::BufferFormat::RGBX_8888: + return DRM_FORMAT_XBGR8888; + case gfx::BufferFormat::BGRA_8888: + return DRM_FORMAT_ARGB8888; + case gfx::BufferFormat::BGRX_8888: + return DRM_FORMAT_XRGB8888; + case gfx::BufferFormat::BGRA_1010102: + return DRM_FORMAT_ARGB2101010; + case gfx::BufferFormat::RGBA_1010102: + return DRM_FORMAT_ABGR2101010; + case gfx::BufferFormat::RGBA_F16: + return DRM_FORMAT_INVALID; + case gfx::BufferFormat::YVU_420: + return DRM_FORMAT_YVU420; + case gfx::BufferFormat::YUV_420_BIPLANAR: + return DRM_FORMAT_NV12; + case gfx::BufferFormat::P010: + return DRM_FORMAT_P010; + } + return DRM_FORMAT_INVALID; +} + +gfx::BufferFormat GetBufferFormatFromFourCCFormat(int format) { + switch (format) { + case DRM_FORMAT_R8: + return gfx::BufferFormat::R_8; + case DRM_FORMAT_GR88: + return gfx::BufferFormat::RG_88; + case DRM_FORMAT_ABGR8888: + return gfx::BufferFormat::RGBA_8888; + case DRM_FORMAT_XBGR8888: + return gfx::BufferFormat::RGBX_8888; + case DRM_FORMAT_ARGB8888: + return gfx::BufferFormat::BGRA_8888; + case DRM_FORMAT_XRGB8888: + return gfx::BufferFormat::BGRX_8888; + case DRM_FORMAT_ARGB2101010: + return gfx::BufferFormat::BGRA_1010102; + case DRM_FORMAT_ABGR2101010: + return gfx::BufferFormat::RGBA_1010102; + case DRM_FORMAT_RGB565: + return gfx::BufferFormat::BGR_565; + case DRM_FORMAT_NV12: + return gfx::BufferFormat::YUV_420_BIPLANAR; + case DRM_FORMAT_YVU420: + return gfx::BufferFormat::YVU_420; + case DRM_FORMAT_P010: + return gfx::BufferFormat::P010; + default: + NOTREACHED(); + return gfx::BufferFormat::BGRA_8888; + } +} + +bool IsValidBufferFormat(uint32_t current_format) { + switch (current_format) { + case DRM_FORMAT_R8: + case DRM_FORMAT_GR88: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ARGB2101010: + case DRM_FORMAT_ABGR2101010: + case DRM_FORMAT_RGB565: + case DRM_FORMAT_NV12: + case DRM_FORMAT_YVU420: + case DRM_FORMAT_P010: + return true; + default: + return false; + } +} + +} // namespace ui diff --git a/linux/drm_util_linux.h b/linux/drm_util_linux.h new file mode 100644 index 000000000000..25aa65f417fa --- /dev/null +++ b/linux/drm_util_linux.h @@ -0,0 +1,22 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_LINUX_DRM_UTIL_LINUX_H_ +#define UI_GFX_LINUX_DRM_UTIL_LINUX_H_ + +#include + +#include "ui/gfx/buffer_types.h" + +namespace ui { + +int GetFourCCFormatFromBufferFormat(gfx::BufferFormat format); +gfx::BufferFormat GetBufferFormatFromFourCCFormat(int format); + +// Returns true if the fourcc format is known. +bool IsValidBufferFormat(uint32_t current_format); + +} // namespace ui + +#endif // UI_GFX_LINUX_DRM_UTIL_LINUX_H__ diff --git a/linux/fontconfig_util.cc b/linux/fontconfig_util.cc new file mode 100644 index 000000000000..6f877d8fd756 --- /dev/null +++ b/linux/fontconfig_util.cc @@ -0,0 +1,226 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/linux/fontconfig_util.h" + +#include + +#include "base/no_destructor.h" +#include "ui/gfx/font_render_params.h" + +namespace gfx { + +namespace { + +// A singleton class to wrap a global font-config configuration. The +// configuration reference counter is incremented to avoid the deletion of the +// structure while being used. This class is single-threaded and should only be +// used on the UI-Thread. +class GFX_EXPORT GlobalFontConfig { + public: + GlobalFontConfig() { + // Without this call, the FontConfig library gets implicitly initialized + // on the first call to FontConfig. Since it's not safe to initialize it + // concurrently from multiple threads, we explicitly initialize it here + // to prevent races when there are multiple renderer's querying the library: + // http://crbug.com/404311 + // Note that future calls to FcInit() are safe no-ops per the FontConfig + // interface. + FcInit(); + + // Increment the reference counter to avoid the config to be deleted while + // being used (see http://crbug.com/1004254). + fc_config_ = FcConfigGetCurrent(); + FcConfigReference(fc_config_); + + // Set rescan interval to 0 to disable re-scan. Re-scanning in the + // background is a source of thread safety issues. + // See in http://crbug.com/1004254. + FcBool result = FcConfigSetRescanInterval(fc_config_, 0); + DCHECK_EQ(result, FcTrue); + } + + GlobalFontConfig(const GlobalFontConfig&) = delete; + GlobalFontConfig& operator=(const GlobalFontConfig&) = delete; + + ~GlobalFontConfig() { FcConfigDestroy(fc_config_); } + + // Retrieve the native font-config FcConfig pointer. + FcConfig* Get() const { + DCHECK_EQ(fc_config_, FcConfigGetCurrent()); + return fc_config_; + } + + // Override the font-config configuration. + void OverrideForTesting(FcConfig* config) { + FcConfigSetCurrent(config); + fc_config_ = config; + } + + // Retrieve the global font-config configuration. + static GlobalFontConfig* GetInstance() { + static base::NoDestructor fontconfig; + return fontconfig.get(); + } + + private: + FcConfig* fc_config_ = nullptr; +}; + +// Converts Fontconfig FC_HINT_STYLE to FontRenderParams::Hinting. +FontRenderParams::Hinting ConvertFontconfigHintStyle(int hint_style) { + switch (hint_style) { + case FC_HINT_SLIGHT: + return FontRenderParams::HINTING_SLIGHT; + case FC_HINT_MEDIUM: + return FontRenderParams::HINTING_MEDIUM; + case FC_HINT_FULL: + return FontRenderParams::HINTING_FULL; + default: + return FontRenderParams::HINTING_NONE; + } +} + +// Converts Fontconfig FC_RGBA to FontRenderParams::SubpixelRendering. +FontRenderParams::SubpixelRendering ConvertFontconfigRgba(int rgba) { + switch (rgba) { + case FC_RGBA_RGB: + return FontRenderParams::SUBPIXEL_RENDERING_RGB; + case FC_RGBA_BGR: + return FontRenderParams::SUBPIXEL_RENDERING_BGR; + case FC_RGBA_VRGB: + return FontRenderParams::SUBPIXEL_RENDERING_VRGB; + case FC_RGBA_VBGR: + return FontRenderParams::SUBPIXEL_RENDERING_VBGR; + default: + return FontRenderParams::SUBPIXEL_RENDERING_NONE; + } +} + +// Extracts a string property from a font-config pattern (e.g. FcPattern). +std::string GetFontConfigPropertyAsString(FcPattern* pattern, + const char* property) { + FcChar8* text = nullptr; + if (FcPatternGetString(pattern, property, 0, &text) != FcResultMatch || + text == nullptr) { + return std::string(); + } + return std::string(reinterpret_cast(text)); +} + +// Extracts an integer property from a font-config pattern (e.g. FcPattern). +int GetFontConfigPropertyAsInt(FcPattern* pattern, + const char* property, + int default_value) { + int value = -1; + if (FcPatternGetInteger(pattern, property, 0, &value) != FcResultMatch) + return default_value; + return value; +} + +// Extracts an boolean property from a font-config pattern (e.g. FcPattern). +bool GetFontConfigPropertyAsBool(FcPattern* pattern, const char* property) { + FcBool value = FcFalse; + if (FcPatternGetBool(pattern, property, 0, &value) != FcResultMatch) + return false; + return value != FcFalse; +} + +} // namespace + +FcConfig* GetGlobalFontConfig() { + return GlobalFontConfig::GetInstance()->Get(); +} + +void OverrideGlobalFontConfigForTesting(FcConfig* config) { + return GlobalFontConfig::GetInstance()->OverrideForTesting(config); +} + +std::string GetFontName(FcPattern* pattern) { + return GetFontConfigPropertyAsString(pattern, FC_FAMILY); +} + +std::string GetFilename(FcPattern* pattern) { + return GetFontConfigPropertyAsString(pattern, FC_FILE); +} + +base::FilePath GetFontPath(FcPattern* pattern) { + std::string filename = GetFilename(pattern); + + // Obtains the system root directory in 'config' if available. All files + // (including file properties in patterns) obtained from this 'config' are + // relative to this system root directory. + const char* sysroot = + reinterpret_cast(FcConfigGetSysRoot(nullptr)); + if (!sysroot) + return base::FilePath(filename); + + // Paths may be specified with a heading slash (e.g. + // /test_fonts/DejaVuSans.ttf). + if (!filename.empty() && base::FilePath::IsSeparator(filename[0])) + filename = filename.substr(1); + + if (filename.empty()) + return base::FilePath(); + + return base::FilePath(sysroot).Append(filename); +} + +int GetFontTtcIndex(FcPattern* pattern) { + return GetFontConfigPropertyAsInt(pattern, FC_INDEX, 0); +} + +bool IsFontBold(FcPattern* pattern) { + int weight = GetFontConfigPropertyAsInt(pattern, FC_WEIGHT, FC_WEIGHT_NORMAL); + return weight >= FC_WEIGHT_BOLD; +} + +bool IsFontItalic(FcPattern* pattern) { + int slant = GetFontConfigPropertyAsInt(pattern, FC_SLANT, FC_SLANT_ROMAN); + return slant != FC_SLANT_ROMAN; +} + +bool IsFontScalable(FcPattern* pattern) { + return GetFontConfigPropertyAsBool(pattern, FC_SCALABLE); +} + +std::string GetFontFormat(FcPattern* pattern) { + return GetFontConfigPropertyAsString(pattern, FC_FONTFORMAT); +} + +void GetFontRenderParamsFromFcPattern(FcPattern* pattern, + FontRenderParams* param_out) { + FcBool fc_antialias = 0; + if (FcPatternGetBool(pattern, FC_ANTIALIAS, 0, &fc_antialias) == + FcResultMatch) { + param_out->antialiasing = fc_antialias; + } + + FcBool fc_autohint = 0; + if (FcPatternGetBool(pattern, FC_AUTOHINT, 0, &fc_autohint) == + FcResultMatch) { + param_out->autohinter = fc_autohint; + } + + FcBool fc_bitmap = 0; + if (FcPatternGetBool(pattern, FC_EMBEDDED_BITMAP, 0, &fc_bitmap) == + FcResultMatch) { + param_out->use_bitmaps = fc_bitmap; + } + + FcBool fc_hinting = 0; + if (FcPatternGetBool(pattern, FC_HINTING, 0, &fc_hinting) == FcResultMatch) { + int fc_hint_style = FC_HINT_NONE; + if (fc_hinting) { + FcPatternGetInteger(pattern, FC_HINT_STYLE, 0, &fc_hint_style); + } + param_out->hinting = ConvertFontconfigHintStyle(fc_hint_style); + } + + int fc_rgba = FC_RGBA_NONE; + if (FcPatternGetInteger(pattern, FC_RGBA, 0, &fc_rgba) == FcResultMatch) + param_out->subpixel_rendering = ConvertFontconfigRgba(fc_rgba); +} + +} // namespace gfx diff --git a/linux/fontconfig_util.h b/linux/fontconfig_util.h new file mode 100644 index 000000000000..c8cb2ae56dc1 --- /dev/null +++ b/linux/fontconfig_util.h @@ -0,0 +1,45 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_LINUX_FONTCONFIG_UTIL_H_ +#define UI_GFX_LINUX_FONTCONFIG_UTIL_H_ + +#include + +#include "base/files/file_path.h" +#include "ui/gfx/font_render_params.h" +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +struct FcPatternDeleter { + void operator()(FcPattern* ptr) const { FcPatternDestroy(ptr); } +}; +using ScopedFcPattern = std::unique_ptr; + +// Retrieve the global font config. Must be called on the main thread. +GFX_EXPORT FcConfig* GetGlobalFontConfig(); +GFX_EXPORT void OverrideGlobalFontConfigForTesting(FcConfig* config); + +// FcPattern accessor wrappers. +GFX_EXPORT std::string GetFontName(FcPattern* pattern); +GFX_EXPORT std::string GetFilename(FcPattern* pattern); +GFX_EXPORT int GetFontTtcIndex(FcPattern* pattern); +GFX_EXPORT bool IsFontBold(FcPattern* pattern); +GFX_EXPORT bool IsFontItalic(FcPattern* pattern); +GFX_EXPORT bool IsFontScalable(FcPattern* pattern); +GFX_EXPORT std::string GetFontFormat(FcPattern* pattern); + +// Return the path of the font. Relative to the sysroot config specified in the +// font config (see: FcConfigGetSysRoot(...)). +GFX_EXPORT base::FilePath GetFontPath(FcPattern* pattern); + +// Returns the appropriate parameters for rendering the font represented by the +// font config pattern. +GFX_EXPORT void GetFontRenderParamsFromFcPattern(FcPattern* pattern, + FontRenderParams* param_out); + +} // namespace gfx + +#endif // UI_GFX_LINUX_FONTCONFIG_UTIL_H_ \ No newline at end of file diff --git a/linux/fontconfig_util_unittest.cc b/linux/fontconfig_util_unittest.cc new file mode 100644 index 000000000000..5c4d7d1f9267 --- /dev/null +++ b/linux/fontconfig_util_unittest.cc @@ -0,0 +1,140 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/linux/fontconfig_util.h" + +#include + +#include "testing/gtest/include/gtest/gtest.h" + +namespace gfx { + +TEST(FontConfigUtilTest, FcPatternAccessors) { + ScopedFcPattern pattern(FcPatternCreate()); + + const char kFamilyName[] = "sans"; + FcPatternAddString(pattern.get(), FC_FAMILY, + reinterpret_cast(kFamilyName)); + const char kFileName[] = "/usr/share/fonts/arial.ttf"; + FcPatternAddString(pattern.get(), FC_FILE, + reinterpret_cast(kFileName)); + const int kIndex = 42; + FcPatternAddInteger(pattern.get(), FC_INDEX, kIndex); + FcPatternAddInteger(pattern.get(), FC_WEIGHT, FC_WEIGHT_BOLD); + FcPatternAddInteger(pattern.get(), FC_SLANT, FC_SLANT_ROMAN); + FcPatternAddBool(pattern.get(), FC_SCALABLE, FcTrue); + const char kFontFormat[] = "TrueType"; + FcPatternAddString(pattern.get(), FC_FONTFORMAT, + reinterpret_cast(kFontFormat)); + + EXPECT_EQ(kFamilyName, GetFontName(pattern.get())); + EXPECT_EQ(kFileName, GetFilename(pattern.get())); + EXPECT_EQ(kIndex, GetFontTtcIndex(pattern.get())); + EXPECT_TRUE(IsFontBold(pattern.get())); + EXPECT_FALSE(IsFontItalic(pattern.get())); + EXPECT_TRUE(IsFontScalable(pattern.get())); + EXPECT_EQ(kFontFormat, GetFontFormat(pattern.get())); +} + +TEST(FontConfigUtilTest, GetFontPathWithSysRoot) { + ScopedFcPattern pattern(FcPatternCreate()); + + // Save the old sysroot, if specified. + std::string old_sysroot; + const FcChar8* old_sysroot_ptr = FcConfigGetSysRoot(nullptr); + if (old_sysroot_ptr) + old_sysroot = reinterpret_cast(old_sysroot_ptr); + + // Override the sysroot. + base::FilePath sysroot("/var/opt/fonts"); + FcConfigSetSysRoot(nullptr, reinterpret_cast( + sysroot.AsUTF8Unsafe().c_str())); + + // Validate that path are relative to sysroot. + const char kFileName[] = "fonts/arial.ttf"; + FcPatternAddString(pattern.get(), FC_FILE, + reinterpret_cast(kFileName)); + const char kExpectedFileName[] = "/var/opt/fonts/fonts/arial.ttf"; + EXPECT_EQ(base::FilePath(kExpectedFileName), GetFontPath(pattern.get())); + + // Restore the old sysroot, if specified. + if (old_sysroot_ptr) { + FcConfigSetSysRoot(nullptr, + reinterpret_cast(old_sysroot.c_str())); + } +} + +TEST(FontConfigUtilTest, GetFontPathWithoutSysRoot) { + ScopedFcPattern pattern(FcPatternCreate()); + + // Save the old sysroot, if specified. + std::string old_sysroot; + const FcChar8* old_sysroot_ptr = FcConfigGetSysRoot(nullptr); + if (old_sysroot_ptr) + old_sysroot = reinterpret_cast(old_sysroot_ptr); + + // Override (remove) the sysroot. + FcConfigSetSysRoot(nullptr, nullptr); + + // Check that the filename is not changed without a sysroot present. + const char kFileName[] = "/var/opt/font/fonts/arial.ttf"; + FcPatternAddString(pattern.get(), FC_FILE, + reinterpret_cast(kFileName)); + EXPECT_EQ(base::FilePath(kFileName), GetFontPath(pattern.get())); + + // Restore the old sysroot, if specified. + if (old_sysroot_ptr) { + FcConfigSetSysRoot(nullptr, + reinterpret_cast(old_sysroot.c_str())); + } +} + +TEST(FontConfigUtilTest, GetFontRenderParamsFromFcPatternWithEmptyPattern) { + ScopedFcPattern pattern(FcPatternCreate()); + + FontRenderParams params; + GetFontRenderParamsFromFcPattern(pattern.get(), ¶ms); + + FontRenderParams empty_params; + EXPECT_EQ(params, empty_params); +} + +TEST(FontConfigUtilTest, GetFontRenderParamsFromFcPatternWithFalseValues) { + ScopedFcPattern pattern(FcPatternCreate()); + FcPatternAddBool(pattern.get(), FC_ANTIALIAS, FcFalse); + FcPatternAddBool(pattern.get(), FC_AUTOHINT, FcFalse); + FcPatternAddBool(pattern.get(), FC_EMBEDDED_BITMAP, FcFalse); + + FontRenderParams params; + GetFontRenderParamsFromFcPattern(pattern.get(), ¶ms); + + FontRenderParams expected_params; + expected_params.antialiasing = false; + expected_params.autohinter = false; + expected_params.use_bitmaps = false; + EXPECT_EQ(params, expected_params); +} + +TEST(FontConfigUtilTest, GetFontRenderParamsFromFcPatternWithValues) { + ScopedFcPattern pattern(FcPatternCreate()); + FcPatternAddBool(pattern.get(), FC_ANTIALIAS, FcTrue); + FcPatternAddBool(pattern.get(), FC_AUTOHINT, FcTrue); + FcPatternAddInteger(pattern.get(), FC_HINT_STYLE, FC_HINT_MEDIUM); + FcPatternAddBool(pattern.get(), FC_EMBEDDED_BITMAP, FcTrue); + FcPatternAddInteger(pattern.get(), FC_RGBA, FC_RGBA_RGB); + + FontRenderParams params; + GetFontRenderParamsFromFcPattern(pattern.get(), ¶ms); + + FontRenderParams expected_params; + expected_params.antialiasing = true; + expected_params.autohinter = true; + expected_params.hinting = FontRenderParams::HINTING_MEDIUM; + expected_params.use_bitmaps = true; + expected_params.subpixel_rendering = FontRenderParams::SUBPIXEL_RENDERING_RGB; + + EXPECT_EQ(params, expected_params); +} + +} // namespace gfx diff --git a/linux/gbm_buffer.h b/linux/gbm_buffer.h new file mode 100644 index 000000000000..a9805d9cbfe5 --- /dev/null +++ b/linux/gbm_buffer.h @@ -0,0 +1,44 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_LINUX_GBM_BUFFER_H_ +#define UI_GFX_LINUX_GBM_BUFFER_H_ + +#include + +#include "third_party/skia/include/core/SkRefCnt.h" +#include "ui/gfx/buffer_types.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/native_pixmap_handle.h" + +class SkSurface; + +namespace ui { + +class GbmBuffer { + public: + virtual ~GbmBuffer() {} + + virtual uint32_t GetFormat() const = 0; + virtual uint64_t GetFormatModifier() const = 0; + virtual uint32_t GetFlags() const = 0; + // TODO(reveman): This should not be needed once crbug.com/597932 is + // fixed, as the size would be queried directly from the underlying bo. + virtual gfx::Size GetSize() const = 0; + virtual gfx::BufferFormat GetBufferFormat() const = 0; + virtual bool AreFdsValid() const = 0; + virtual size_t GetNumPlanes() const = 0; + virtual int GetPlaneFd(size_t plane) const = 0; + virtual uint32_t GetPlaneHandle(size_t plane) const = 0; + virtual uint32_t GetPlaneStride(size_t plane) const = 0; + virtual size_t GetPlaneOffset(size_t plane) const = 0; + virtual size_t GetPlaneSize(size_t plane) const = 0; + virtual uint32_t GetHandle() const = 0; + virtual gfx::NativePixmapHandle ExportHandle() const = 0; + virtual sk_sp GetSurface() = 0; +}; + +} // namespace ui + +#endif // UI_GFX_LINUX_GBM_BUFFER_H_ diff --git a/linux/gbm_defines.h b/linux/gbm_defines.h new file mode 100644 index 000000000000..b663173cb6f5 --- /dev/null +++ b/linux/gbm_defines.h @@ -0,0 +1,27 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_LINUX_GBM_DEFINES_H_ +#define UI_GFX_LINUX_GBM_DEFINES_H_ + +#include + +// Minigbm has some defines that are used by ozone/gbm. However, when we build +// Ozone for Linux and use system libgbm, these defines are not present and +// compilation fails. Thus, to fix the issue, mask out these defines and let the +// compilation go through. The values for these are copied from the minigbm's +// gbm.h file. +#if !defined(MINIGBM) +#define GBM_MAX_PLANES 4 + +#define GBM_BO_USE_TEXTURING 0 +#define GBM_BO_USE_CAMERA_WRITE 0 +#define GBM_BO_USE_HW_VIDEO_DECODER 0 +#define GBM_BO_USE_HW_VIDEO_ENCODER 0 +#define GBM_BO_USE_PROTECTED 0 +#define GBM_BO_USE_SW_READ_OFTEN 0 +#define GBM_BO_USE_FRONT_RENDERING 0 +#endif + +#endif // UI_GFX_LINUX_GBM_DEFINES_H_ diff --git a/linux/gbm_device.h b/linux/gbm_device.h new file mode 100644 index 000000000000..50f7fe56b0b5 --- /dev/null +++ b/linux/gbm_device.h @@ -0,0 +1,40 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_LINUX_GBM_DEVICE_H_ +#define UI_GFX_LINUX_GBM_DEVICE_H_ + +#include +#include + +#include "base/files/file.h" +#include "base/macros.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/native_pixmap_handle.h" + +namespace ui { + +class GbmBuffer; + +class GbmDevice { + public: + virtual ~GbmDevice() {} + + virtual std::unique_ptr CreateBuffer(uint32_t format, + const gfx::Size& size, + uint32_t flags) = 0; + virtual std::unique_ptr CreateBufferWithModifiers( + uint32_t format, + const gfx::Size& size, + uint32_t flags, + const std::vector& modifiers) = 0; + virtual std::unique_ptr CreateBufferFromHandle( + uint32_t format, + const gfx::Size& size, + gfx::NativePixmapHandle handle) = 0; +}; + +} // namespace ui + +#endif // UI_GFX_LINUX_GBM_DEVICE_H_ diff --git a/linux/gbm_util.cc b/linux/gbm_util.cc new file mode 100644 index 000000000000..b9adad963cf2 --- /dev/null +++ b/linux/gbm_util.cc @@ -0,0 +1,46 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/linux/gbm_util.h" + +#include "base/notreached.h" +#include "ui/gfx/linux/gbm_defines.h" + +namespace ui { + +uint32_t BufferUsageToGbmFlags(gfx::BufferUsage usage) { + switch (usage) { + case gfx::BufferUsage::GPU_READ: + return GBM_BO_USE_TEXTURING; + case gfx::BufferUsage::SCANOUT: + return GBM_BO_USE_RENDERING | GBM_BO_USE_SCANOUT | GBM_BO_USE_TEXTURING; + case gfx::BufferUsage::SCANOUT_CAMERA_READ_WRITE: + return GBM_BO_USE_LINEAR | GBM_BO_USE_CAMERA_WRITE | GBM_BO_USE_SCANOUT | + GBM_BO_USE_TEXTURING; + case gfx::BufferUsage::CAMERA_AND_CPU_READ_WRITE: + return GBM_BO_USE_LINEAR | GBM_BO_USE_CAMERA_WRITE; + case gfx::BufferUsage::SCANOUT_CPU_READ_WRITE: + return GBM_BO_USE_LINEAR | GBM_BO_USE_SCANOUT | GBM_BO_USE_TEXTURING; + case gfx::BufferUsage::SCANOUT_VDA_WRITE: + return GBM_BO_USE_SCANOUT | GBM_BO_USE_TEXTURING | + GBM_BO_USE_HW_VIDEO_DECODER; + case gfx::BufferUsage::PROTECTED_SCANOUT_VDA_WRITE: + return GBM_BO_USE_SCANOUT | GBM_BO_USE_PROTECTED | + GBM_BO_USE_HW_VIDEO_DECODER; + case gfx::BufferUsage::GPU_READ_CPU_READ_WRITE: + return GBM_BO_USE_LINEAR | GBM_BO_USE_TEXTURING; + case gfx::BufferUsage::SCANOUT_VEA_CPU_READ: + return GBM_BO_USE_LINEAR | GBM_BO_USE_SCANOUT | GBM_BO_USE_TEXTURING | + GBM_BO_USE_HW_VIDEO_ENCODER; + case gfx::BufferUsage::VEA_READ_CAMERA_AND_CPU_READ_WRITE: + return GBM_BO_USE_LINEAR | GBM_BO_USE_CAMERA_WRITE | + GBM_BO_USE_TEXTURING | GBM_BO_USE_HW_VIDEO_ENCODER | + GBM_BO_USE_SW_READ_OFTEN; + case gfx::BufferUsage::SCANOUT_FRONT_RENDERING: + return GBM_BO_USE_SCANOUT | GBM_BO_USE_TEXTURING | + GBM_BO_USE_FRONT_RENDERING; + } +} + +} // namespace ui diff --git a/linux/gbm_util.h b/linux/gbm_util.h new file mode 100644 index 000000000000..7b45b4a078a3 --- /dev/null +++ b/linux/gbm_util.h @@ -0,0 +1,21 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_LINUX_GBM_UTIL_H_ +#define UI_GFX_LINUX_GBM_UTIL_H_ + +#include + +#include "ui/gfx/buffer_types.h" + +namespace ui { + +// Get GBM buffer object usage flags for a corresponding gfx::BufferUsage. +// Depending on the platform, certain usage flags may not be available (eg. +// GBM_BO_USE_HW_VIDEO_ENCODER on desktop linux). +uint32_t BufferUsageToGbmFlags(gfx::BufferUsage usage); + +} // namespace ui + +#endif // UI_GFX_LINUX_GBM_UTIL_H_ diff --git a/linux/gbm_wrapper.cc b/linux/gbm_wrapper.cc new file mode 100644 index 000000000000..04417ffce636 --- /dev/null +++ b/linux/gbm_wrapper.cc @@ -0,0 +1,399 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/linux/gbm_wrapper.h" + +#include +#include +#include + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "skia/ext/legacy_display_globals.h" +#include "third_party/skia/include/core/SkSurface.h" +#include "ui/gfx/buffer_format_util.h" +#include "ui/gfx/linux/drm_util_linux.h" +#include "ui/gfx/linux/gbm_buffer.h" +#include "ui/gfx/linux/gbm_device.h" + +#if !defined(MINIGBM) +#include +#include +#include + +#include "base/strings/stringize_macros.h" +#endif + +namespace gbm_wrapper { + +namespace { + +// Function availability can be tested by checking if the address of gbm_* is +// not nullptr. +#define WEAK_GBM_FN(x) extern "C" __attribute__((weak)) decltype(x) x + +// TODO(https://crbug.com/784010): Remove these once support for Ubuntu Trusty +// is dropped. +WEAK_GBM_FN(gbm_bo_map); +WEAK_GBM_FN(gbm_bo_unmap); + +// TODO(https://crbug.com/784010): Remove these once support for Ubuntu Trusty +// and Debian Stretch are dropped. +WEAK_GBM_FN(gbm_bo_create_with_modifiers); +WEAK_GBM_FN(gbm_bo_get_handle_for_plane); +WEAK_GBM_FN(gbm_bo_get_modifier); +WEAK_GBM_FN(gbm_bo_get_offset); +WEAK_GBM_FN(gbm_bo_get_plane_count); +WEAK_GBM_FN(gbm_bo_get_stride_for_plane); + +bool HaveGbmMap() { + return gbm_bo_map && gbm_bo_unmap; +} + +bool HaveGbmModifiers() { + return gbm_bo_create_with_modifiers && gbm_bo_get_modifier; +} + +bool HaveGbmMultiplane() { + return gbm_bo_get_handle_for_plane && gbm_bo_get_offset && + gbm_bo_get_plane_count && gbm_bo_get_stride_for_plane; +} + +uint32_t GetHandleForPlane(struct gbm_bo* bo, int plane) { + CHECK(HaveGbmMultiplane() || plane == 0); + return HaveGbmMultiplane() ? gbm_bo_get_handle_for_plane(bo, plane).u32 + : gbm_bo_get_handle(bo).u32; +} + +uint32_t GetStrideForPlane(struct gbm_bo* bo, int plane) { + CHECK(HaveGbmMultiplane() || plane == 0); + return HaveGbmMultiplane() ? gbm_bo_get_stride_for_plane(bo, plane) + : gbm_bo_get_stride(bo); +} + +uint32_t GetOffsetForPlane(struct gbm_bo* bo, int plane) { + CHECK(HaveGbmMultiplane() || plane == 0); + return HaveGbmMultiplane() ? gbm_bo_get_offset(bo, plane) : 0; +} + +int GetPlaneCount(struct gbm_bo* bo) { + return HaveGbmMultiplane() ? gbm_bo_get_plane_count(bo) : 1; +} + +int GetPlaneFdForBo(gbm_bo* bo, size_t plane) { +#if defined(MINIGBM) + return gbm_bo_get_plane_fd(bo, plane); +#else + const int plane_count = GetPlaneCount(bo); + DCHECK(plane_count > 0 && plane < static_cast(plane_count)); + + // System linux gbm (or Mesa gbm) does not provide fds per plane basis. Thus, + // get plane handle and use drm ioctl to get a prime fd out of it avoid having + // two different branches for minigbm and Mesa gbm here. + gbm_device* gbm_dev = gbm_bo_get_device(bo); + int dev_fd = gbm_device_get_fd(gbm_dev); + DCHECK_GE(dev_fd, 0); + + uint32_t plane_handle = GetHandleForPlane(bo, plane); + + int fd = -1; + int ret; + // Use DRM_RDWR to allow the fd to be mappable in another process. + ret = drmPrimeHandleToFD(dev_fd, plane_handle, DRM_CLOEXEC | DRM_RDWR, &fd); + + // Older DRM implementations blocked DRM_RDWR, but gave a read/write mapping + // anyways + if (ret) + ret = drmPrimeHandleToFD(dev_fd, plane_handle, DRM_CLOEXEC, &fd); + + return ret ? ret : fd; +#endif +} + +size_t GetSizeOfPlane(gbm_bo* bo, + uint32_t format, + const gfx::Size& size, + size_t plane) { +#if defined(MINIGBM) + return gbm_bo_get_plane_size(bo, plane); +#else + DCHECK(!size.IsEmpty()); + + // Get row size of the plane, stride and subsampled height to finally get the + // size of a plane in bytes. + const gfx::BufferFormat buffer_format = + ui::GetBufferFormatFromFourCCFormat(format); + const base::CheckedNumeric stride_for_plane = + GetStrideForPlane(bo, plane); + const base::CheckedNumeric subsampled_height = + size.height() / + gfx::SubsamplingFactorForBufferFormat(buffer_format, plane); + + // Apply subsampling factor to get size in bytes. + const base::CheckedNumeric checked_plane_size = + subsampled_height * stride_for_plane; + + return checked_plane_size.ValueOrDie(); +#endif +} + +} // namespace + +class Buffer final : public ui::GbmBuffer { + public: + Buffer(struct gbm_bo* bo, + uint32_t format, + uint32_t flags, + uint64_t modifier, + const gfx::Size& size, + gfx::NativePixmapHandle handle) + : bo_(bo), + format_(format), + format_modifier_(modifier), + flags_(flags), + size_(size), + handle_(std::move(handle)) {} + + Buffer(const Buffer&) = delete; + Buffer& operator=(const Buffer&) = delete; + + ~Buffer() override { + DCHECK(!mmap_data_); + gbm_bo_destroy(bo_); + } + + uint32_t GetFormat() const override { return format_; } + uint64_t GetFormatModifier() const override { return format_modifier_; } + uint32_t GetFlags() const override { return flags_; } + // TODO(reveman): This should not be needed once crbug.com/597932 is fixed, + // as the size would be queried directly from the underlying bo. + gfx::Size GetSize() const override { return size_; } + gfx::BufferFormat GetBufferFormat() const override { + return ui::GetBufferFormatFromFourCCFormat(format_); + } + bool AreFdsValid() const override { + if (handle_.planes.empty()) + return false; + + for (const auto& plane : handle_.planes) { + if (!plane.fd.is_valid()) + return false; + } + return true; + } + size_t GetNumPlanes() const override { return handle_.planes.size(); } + int GetPlaneFd(size_t plane) const override { + DCHECK_LT(plane, handle_.planes.size()); + return handle_.planes[plane].fd.get(); + } + uint32_t GetPlaneStride(size_t plane) const override { + DCHECK_LT(plane, handle_.planes.size()); + return handle_.planes[plane].stride; + } + size_t GetPlaneOffset(size_t plane) const override { + DCHECK_LT(plane, handle_.planes.size()); + return handle_.planes[plane].offset; + } + size_t GetPlaneSize(size_t plane) const override { + DCHECK_LT(plane, handle_.planes.size()); + return static_cast(handle_.planes[plane].size); + } + uint32_t GetPlaneHandle(size_t plane) const override { + DCHECK_LT(plane, handle_.planes.size()); + return GetHandleForPlane(bo_, plane); + } + uint32_t GetHandle() const override { return gbm_bo_get_handle(bo_).u32; } + gfx::NativePixmapHandle ExportHandle() const override { + return CloneHandleForIPC(handle_); + } + + sk_sp GetSurface() override { + CHECK(HaveGbmMap()); + DCHECK(!mmap_data_); + uint32_t stride; + void* addr; + addr = +#if defined(MINIGBM) + gbm_bo_map2(bo_, 0, 0, gbm_bo_get_width(bo_), gbm_bo_get_height(bo_), + GBM_BO_TRANSFER_READ_WRITE, &stride, &mmap_data_, 0); +#else + gbm_bo_map(bo_, 0, 0, gbm_bo_get_width(bo_), gbm_bo_get_height(bo_), + GBM_BO_TRANSFER_READ_WRITE, &stride, &mmap_data_); +#endif + + if (!addr) + return nullptr; + SkImageInfo info = + SkImageInfo::MakeN32Premul(size_.width(), size_.height()); + SkSurfaceProps props = skia::LegacyDisplayGlobals::GetSkSurfaceProps(); + return SkSurface::MakeRasterDirectReleaseProc( + info, addr, stride, &Buffer::UnmapGbmBo, this, &props); + } + + private: + static void UnmapGbmBo(void* pixels, void* context) { + CHECK(HaveGbmMap()); + Buffer* buffer = static_cast(context); + gbm_bo_unmap(buffer->bo_, buffer->mmap_data_); + buffer->mmap_data_ = nullptr; + } + + gbm_bo* const bo_; + void* mmap_data_ = nullptr; + + const uint32_t format_; + const uint64_t format_modifier_; + const uint32_t flags_; + + const gfx::Size size_; + + const gfx::NativePixmapHandle handle_; +}; + +std::unique_ptr CreateBufferForBO(struct gbm_bo* bo, + uint32_t format, + const gfx::Size& size, + uint32_t flags) { + DCHECK(bo); + gfx::NativePixmapHandle handle; + + const uint64_t modifier = HaveGbmModifiers() ? gbm_bo_get_modifier(bo) : 0; + const int plane_count = GetPlaneCount(bo); + // The Mesa's gbm implementation explicitly checks whether plane count <= and + // returns 1 if the condition is true. Nevertheless, use a DCHECK here to make + // sure the condition is not broken there. + DCHECK_GT(plane_count, 0); + // Ensure there are no differences in integer signs by casting any possible + // values to size_t. + for (size_t i = 0; i < static_cast(plane_count); ++i) { + // The fd returned by gbm_bo_get_fd is not ref-counted and need to be + // kept open for the lifetime of the buffer. + base::ScopedFD fd(GetPlaneFdForBo(bo, i)); + + if (!fd.is_valid()) { + PLOG(ERROR) << "Failed to export buffer to dma_buf"; + gbm_bo_destroy(bo); + return nullptr; + } + + handle.planes.emplace_back( + GetStrideForPlane(bo, i), GetOffsetForPlane(bo, i), + GetSizeOfPlane(bo, format, size, i), std::move(fd)); + } + + handle.modifier = modifier; + return std::make_unique(bo, format, flags, modifier, size, + std::move(handle)); +} + +class Device final : public ui::GbmDevice { + public: + Device(gbm_device* device) : device_(device) {} + + Device(const Device&) = delete; + Device& operator=(const Device&) = delete; + + ~Device() override { gbm_device_destroy(device_); } + + std::unique_ptr CreateBuffer(uint32_t format, + const gfx::Size& size, + uint32_t flags) override { + struct gbm_bo* bo = + gbm_bo_create(device_, size.width(), size.height(), format, flags); + if (!bo) { +#if DCHECK_IS_ON() + const char fourcc_as_string[5] = { + static_cast(format), static_cast(format >> 8), + static_cast(format >> 16), static_cast(format >> 24), 0}; + + DVLOG(2) << "Failed to create GBM BO, " << fourcc_as_string << ", " + << size.ToString() << ", flags: 0x" << std::hex << flags + << "; gbm_device_is_format_supported() = " + << gbm_device_is_format_supported(device_, format, flags); +#endif + return nullptr; + } + + return CreateBufferForBO(bo, format, size, flags); + } + + std::unique_ptr CreateBufferWithModifiers( + uint32_t format, + const gfx::Size& size, + uint32_t flags, + const std::vector& modifiers) override { + if (modifiers.empty()) + return CreateBuffer(format, size, flags); + CHECK(HaveGbmModifiers()); + struct gbm_bo* bo = gbm_bo_create_with_modifiers( + device_, size.width(), size.height(), format, modifiers.data(), + modifiers.size()); + if (!bo) + return nullptr; + + return CreateBufferForBO(bo, format, size, flags); + } + + std::unique_ptr CreateBufferFromHandle( + uint32_t format, + const gfx::Size& size, + gfx::NativePixmapHandle handle) override { + DCHECK_EQ(handle.planes[0].offset, 0u); + + // Try to use scanout if supported. + int gbm_flags = GBM_BO_USE_SCANOUT; +#if defined(MINIGBM) + gbm_flags |= GBM_BO_USE_TEXTURING; +#endif + if (!gbm_device_is_format_supported(device_, format, gbm_flags)) + gbm_flags &= ~GBM_BO_USE_SCANOUT; + + struct gbm_bo* bo = nullptr; + if (!gbm_device_is_format_supported(device_, format, gbm_flags)) { + LOG(ERROR) << "gbm format not supported: " << format; + return nullptr; + } + + struct gbm_import_fd_modifier_data fd_data; + fd_data.width = size.width(); + fd_data.height = size.height(); + fd_data.format = format; + fd_data.num_fds = handle.planes.size(); + fd_data.modifier = handle.modifier; + + DCHECK_LE(handle.planes.size(), 3u); + for (size_t i = 0; i < handle.planes.size(); ++i) { + fd_data.fds[i] = handle.planes[i < handle.planes.size() ? i : 0].fd.get(); + fd_data.strides[i] = handle.planes[i].stride; + fd_data.offsets[i] = handle.planes[i].offset; + } + + // The fd passed to gbm_bo_import is not ref-counted and need to be + // kept open for the lifetime of the buffer. + bo = gbm_bo_import(device_, GBM_BO_IMPORT_FD_MODIFIER, &fd_data, gbm_flags); + if (!bo) { + LOG(ERROR) << "nullptr returned from gbm_bo_import"; + return nullptr; + } + + return std::make_unique(bo, format, gbm_flags, handle.modifier, + size, std::move(handle)); + } + + private: + gbm_device* const device_; +}; + +} // namespace gbm_wrapper + +namespace ui { + +std::unique_ptr CreateGbmDevice(int fd) { + gbm_device* device = gbm_create_device(fd); + if (!device) + return nullptr; + return std::make_unique(device); +} + +} // namespace ui diff --git a/linux/gbm_wrapper.h b/linux/gbm_wrapper.h new file mode 100644 index 000000000000..b06f44ecb41b --- /dev/null +++ b/linux/gbm_wrapper.h @@ -0,0 +1,18 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_LINUX_GBM_WRAPPER_H_ +#define UI_GFX_LINUX_GBM_WRAPPER_H_ + +#include + +#include "ui/gfx/linux/gbm_device.h" + +namespace ui { + +std::unique_ptr CreateGbmDevice(int fd); + +} // namespace ui + +#endif // UI_GFX_LINUX_GBM_WRAPPER_H_ diff --git a/linux/gpu_memory_buffer_support_x11.cc b/linux/gpu_memory_buffer_support_x11.cc new file mode 100644 index 000000000000..5b89bfdaad60 --- /dev/null +++ b/linux/gpu_memory_buffer_support_x11.cc @@ -0,0 +1,138 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/linux/gpu_memory_buffer_support_x11.h" + +#include +#include + +#include + +#include "base/containers/contains.h" +#include "base/debug/crash_logging.h" +#include "base/posix/eintr_wrapper.h" +#include "ui/gfx/buffer_format_util.h" +#include "ui/gfx/buffer_types.h" +#include "ui/gfx/buffer_usage_util.h" +#include "ui/gfx/linux/drm_util_linux.h" +#include "ui/gfx/linux/gbm_buffer.h" +#include "ui/gfx/linux/gbm_device.h" +#include "ui/gfx/linux/gbm_util.h" +#include "ui/gfx/linux/gbm_wrapper.h" +#include "ui/gfx/x/connection.h" +#include "ui/gfx/x/dri3.h" +#include "ui/gfx/x/future.h" + +namespace ui { + +namespace { + +// Obtain an authenticated DRM fd from X11 and create a GbmDevice with it. +std::unique_ptr CreateX11GbmDevice() { + auto* connection = x11::Connection::Get(); + // |connection| may be nullptr in headless mode. + if (!connection) + return nullptr; + + auto& dri3 = connection->dri3(); + if (!dri3.present()) + return nullptr; + + // Let the X11 server know the DRI3 client version. This is required to use + // the DRI3 extension. We don't care about the returned server version because + // we only use features from the original DRI3 interface. + dri3.QueryVersion({x11::Dri3::major_version, x11::Dri3::minor_version}); + + // Obtain an authenticated DRM fd. + auto reply = dri3.Open({connection->default_root(), 0}).Sync(); + if (!reply) + return nullptr; + + base::ScopedFD fd(HANDLE_EINTR(dup(reply->device_fd.get()))); + if (!fd.is_valid()) + return nullptr; + if (HANDLE_EINTR(fcntl(fd.get(), F_SETFD, FD_CLOEXEC)) == -1) + return nullptr; + + return ui::CreateGbmDevice(fd.release()); +} + +std::vector CreateSupportedConfigList( + ui::GbmDevice* device) { + if (!device) + return {}; + + std::vector configs; + for (gfx::BufferUsage usage : { + gfx::BufferUsage::GPU_READ, + gfx::BufferUsage::SCANOUT, + gfx::BufferUsage::SCANOUT_CPU_READ_WRITE, + gfx::BufferUsage::GPU_READ_CPU_READ_WRITE, + }) { + for (gfx::BufferFormat format : { + gfx::BufferFormat::R_8, + gfx::BufferFormat::RG_88, + gfx::BufferFormat::RGBA_8888, + gfx::BufferFormat::RGBX_8888, + gfx::BufferFormat::BGRA_8888, + gfx::BufferFormat::BGRX_8888, + gfx::BufferFormat::BGRA_1010102, + + // On some Intel setups calling gbm_bo_create() with this format + // results in a crash caused by an integer-divide-by-zero. + // TODO(thomasanderson): Enable this format. + // gfx::BufferFormat::RGBA_1010102, + gfx::BufferFormat::BGR_565, + gfx::BufferFormat::YUV_420_BIPLANAR, + gfx::BufferFormat::YVU_420, + gfx::BufferFormat::P010, + }) { + // At least on mesa/amdgpu, gbm_device_is_format_supported() lies. Test + // format support by creating a buffer directly. Use a 2x2 buffer so that + // YUV420 formats get properly tested. + if (device->CreateBuffer(GetFourCCFormatFromBufferFormat(format), + gfx::Size(2, 2), BufferUsageToGbmFlags(usage))) { + configs.push_back(gfx::BufferUsageAndFormat(usage, format)); + } + } + } + return configs; +} + +} // namespace + +// static +GpuMemoryBufferSupportX11* GpuMemoryBufferSupportX11::GetInstance() { + static base::NoDestructor instance; + return instance.get(); +} + +GpuMemoryBufferSupportX11::GpuMemoryBufferSupportX11() + : device_(CreateX11GbmDevice()), + supported_configs_(CreateSupportedConfigList(device_.get())) {} + +GpuMemoryBufferSupportX11::~GpuMemoryBufferSupportX11() = default; + +std::unique_ptr GpuMemoryBufferSupportX11::CreateBuffer( + gfx::BufferFormat format, + const gfx::Size& size, + gfx::BufferUsage usage) { + DCHECK(device_); + DCHECK(base::Contains(supported_configs_, + gfx::BufferUsageAndFormat(usage, format))); + + static base::debug::CrashKeyString* crash_key_string = + base::debug::AllocateCrashKeyString("buffer_usage_and_format", + base::debug::CrashKeySize::Size64); + std::string buffer_usage_and_format = gfx::BufferFormatToString(format) + + std::string(",") + + gfx::BufferUsageToString(usage); + base::debug::ScopedCrashKeyString scoped_crash_key( + crash_key_string, buffer_usage_and_format.c_str()); + + return device_->CreateBuffer(GetFourCCFormatFromBufferFormat(format), size, + BufferUsageToGbmFlags(usage)); +} + +} // namespace ui diff --git a/linux/gpu_memory_buffer_support_x11.h b/linux/gpu_memory_buffer_support_x11.h new file mode 100644 index 000000000000..600354a90076 --- /dev/null +++ b/linux/gpu_memory_buffer_support_x11.h @@ -0,0 +1,55 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_LINUX_GPU_MEMORY_BUFFER_SUPPORT_X11_H_ +#define UI_GFX_LINUX_GPU_MEMORY_BUFFER_SUPPORT_X11_H_ + +#include +#include + +#include "base/component_export.h" +#include "base/no_destructor.h" +#include "ui/gfx/buffer_types.h" + +namespace gfx { +class Size; +} + +namespace ui { + +class GbmBuffer; +class GbmDevice; + +// Obtains and holds a GbmDevice for creating GbmBuffers. Maintains a list of +// supported buffer configurations. +class COMPONENT_EXPORT(GBM_SUPPORT_X11) GpuMemoryBufferSupportX11 { + public: + static GpuMemoryBufferSupportX11* GetInstance(); + + std::unique_ptr CreateBuffer(gfx::BufferFormat format, + const gfx::Size& size, + gfx::BufferUsage usage); + + ~GpuMemoryBufferSupportX11(); + + GpuMemoryBufferSupportX11(const GpuMemoryBufferSupportX11&) = delete; + GpuMemoryBufferSupportX11& operator=(const GpuMemoryBufferSupportX11&) = + delete; + + const std::vector& supported_configs() const { + return supported_configs_; + } + + private: + friend class base::NoDestructor; + + GpuMemoryBufferSupportX11(); + + const std::unique_ptr device_; + const std::vector supported_configs_; +}; + +} // namespace ui + +#endif // UI_GFX_LINUX_GPU_MEMORY_BUFFER_SUPPORT_X11_H_ diff --git a/linux/native_pixmap_dmabuf.cc b/linux/native_pixmap_dmabuf.cc new file mode 100644 index 000000000000..2cddfdde26fd --- /dev/null +++ b/linux/native_pixmap_dmabuf.cc @@ -0,0 +1,83 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/linux/native_pixmap_dmabuf.h" + +#include + +#include "base/posix/eintr_wrapper.h" + +namespace gfx { + +NativePixmapDmaBuf::NativePixmapDmaBuf(const gfx::Size& size, + gfx::BufferFormat format, + gfx::NativePixmapHandle handle) + : size_(size), format_(format), handle_(std::move(handle)) {} + +NativePixmapDmaBuf::~NativePixmapDmaBuf() {} + +bool NativePixmapDmaBuf::AreDmaBufFdsValid() const { + if (handle_.planes.empty()) + return false; + + for (const auto& plane : handle_.planes) { + if (!plane.fd.is_valid()) + return false; + } + return true; +} + +int NativePixmapDmaBuf::GetDmaBufFd(size_t plane) const { + DCHECK_LT(plane, handle_.planes.size()); + return handle_.planes[plane].fd.get(); +} + +uint32_t NativePixmapDmaBuf::GetDmaBufPitch(size_t plane) const { + DCHECK_LT(plane, handle_.planes.size()); + return handle_.planes[plane].stride; +} + +size_t NativePixmapDmaBuf::GetDmaBufOffset(size_t plane) const { + DCHECK_LT(plane, handle_.planes.size()); + return static_cast(handle_.planes[plane].offset); +} + +size_t NativePixmapDmaBuf::GetDmaBufPlaneSize(size_t plane) const { + DCHECK_LT(plane, handle_.planes.size()); + return static_cast(handle_.planes[plane].size); +} + +uint64_t NativePixmapDmaBuf::GetBufferFormatModifier() const { + return handle_.modifier; +} + +gfx::BufferFormat NativePixmapDmaBuf::GetBufferFormat() const { + return format_; +} + +size_t NativePixmapDmaBuf::GetNumberOfPlanes() const { + return handle_.planes.size(); +} + +gfx::Size NativePixmapDmaBuf::GetBufferSize() const { + return size_; +} + +uint32_t NativePixmapDmaBuf::GetUniqueId() const { + return 0; +} + +bool NativePixmapDmaBuf::ScheduleOverlayPlane( + gfx::AcceleratedWidget widget, + const gfx::OverlayPlaneData& overlay_plane_data, + std::vector acquire_fences, + std::vector release_fences) { + return false; +} + +gfx::NativePixmapHandle NativePixmapDmaBuf::ExportHandle() { + return gfx::CloneHandleForIPC(handle_); +} + +} // namespace gfx diff --git a/linux/native_pixmap_dmabuf.h b/linux/native_pixmap_dmabuf.h new file mode 100644 index 000000000000..78edc1474de9 --- /dev/null +++ b/linux/native_pixmap_dmabuf.h @@ -0,0 +1,61 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_LINUX_NATIVE_PIXMAP_DMABUF_H_ +#define UI_GFX_LINUX_NATIVE_PIXMAP_DMABUF_H_ + +#include + +#include + +#include "base/files/scoped_file.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "ui/gfx/client_native_pixmap.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/native_pixmap.h" + +namespace gfx { + +// This class converts a gfx::NativePixmapHandle to a gfx::NativePixmap. +// It is useful because gl::GLImageNativePixmap::Initialize only takes +// a gfx::NativePixmap as input. +class GFX_EXPORT NativePixmapDmaBuf : public gfx::NativePixmap { + public: + NativePixmapDmaBuf(const gfx::Size& size, + gfx::BufferFormat format, + gfx::NativePixmapHandle handle); + + NativePixmapDmaBuf(const NativePixmapDmaBuf&) = delete; + NativePixmapDmaBuf& operator=(const NativePixmapDmaBuf&) = delete; + + // NativePixmap: + bool AreDmaBufFdsValid() const override; + int GetDmaBufFd(size_t plane) const override; + uint32_t GetDmaBufPitch(size_t plane) const override; + size_t GetDmaBufOffset(size_t plane) const override; + size_t GetDmaBufPlaneSize(size_t plane) const override; + uint64_t GetBufferFormatModifier() const override; + gfx::BufferFormat GetBufferFormat() const override; + size_t GetNumberOfPlanes() const override; + gfx::Size GetBufferSize() const override; + uint32_t GetUniqueId() const override; + bool ScheduleOverlayPlane(gfx::AcceleratedWidget widget, + const gfx::OverlayPlaneData& overlay_plane_data, + std::vector acquire_fences, + std::vector release_fences) override; + gfx::NativePixmapHandle ExportHandle() override; + + protected: + ~NativePixmapDmaBuf() override; + + private: + gfx::Size size_; + gfx::BufferFormat format_; + gfx::NativePixmapHandle handle_; +}; + +} // namespace gfx + +#endif // UI_GFX_LINUX_NATIVE_PIXMAP_DMABUF_H_ diff --git a/linux/native_pixmap_dmabuf_unittest.cc b/linux/native_pixmap_dmabuf_unittest.cc new file mode 100644 index 000000000000..1d7ccfc7af76 --- /dev/null +++ b/linux/native_pixmap_dmabuf_unittest.cc @@ -0,0 +1,76 @@ +// Copyright (c) 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/linux/native_pixmap_dmabuf.h" + +#include +#include +#include + +#include + +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/buffer_format_util.h" + +namespace gfx { + +class NativePixmapDmaBufTest + : public ::testing::TestWithParam { + protected: + gfx::NativePixmapHandle CreateMockNativePixmapHandle( + gfx::Size image_size, + const gfx::BufferFormat format) { + gfx::NativePixmapHandle handle; + handle.modifier = 1; + const int num_planes = gfx::NumberOfPlanesForLinearBufferFormat(format); + for (int i = 0; i < num_planes; ++i) { + // These values are arbitrarily chosen to be different from each other. + const int stride = (i + 1) * image_size.width(); + const int offset = i * image_size.width() * image_size.height(); + const uint64_t size = stride * image_size.height(); + base::ScopedFD fd(open("/dev/zero", O_RDONLY)); + EXPECT_TRUE(fd.is_valid()); + + handle.planes.emplace_back(stride, offset, size, std::move(fd)); + } + + return handle; + } +}; + +INSTANTIATE_TEST_SUITE_P(ConvertTest, + NativePixmapDmaBufTest, + ::testing::Values(gfx::BufferFormat::RGBX_8888, + gfx::BufferFormat::YVU_420)); + +// Verifies NativePixmapDmaBuf conversion from and to NativePixmapHandle. +TEST_P(NativePixmapDmaBufTest, Convert) { + const gfx::BufferFormat format = GetParam(); + const gfx::Size image_size(128, 64); + + gfx::NativePixmapHandle handle = + CreateMockNativePixmapHandle(image_size, format); + + gfx::NativePixmapHandle handle_clone = CloneHandleForIPC(handle); + + // NativePixmapHandle to NativePixmapDmabuf + scoped_refptr native_pixmap_dmabuf( + new gfx::NativePixmapDmaBuf(image_size, format, std::move(handle))); + EXPECT_TRUE(native_pixmap_dmabuf->AreDmaBufFdsValid()); + EXPECT_EQ(native_pixmap_dmabuf->GetBufferFormatModifier(), + handle_clone.modifier); + // NativePixmap to NativePixmapHandle. + const size_t num_planes = gfx::NumberOfPlanesForLinearBufferFormat( + native_pixmap_dmabuf->GetBufferFormat()); + for (size_t i = 0; i < num_planes; ++i) { + EXPECT_EQ(native_pixmap_dmabuf->GetDmaBufPitch(i), + handle_clone.planes[i].stride); + EXPECT_EQ(native_pixmap_dmabuf->GetDmaBufOffset(i), + handle_clone.planes[i].offset); + EXPECT_EQ(native_pixmap_dmabuf->GetDmaBufPlaneSize(i), + static_cast(handle_clone.planes[i].size)); + } +} + +} // namespace gfx diff --git a/linux/scoped_gbm_device.cc b/linux/scoped_gbm_device.cc new file mode 100644 index 000000000000..951a91ba1147 --- /dev/null +++ b/linux/scoped_gbm_device.cc @@ -0,0 +1,14 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/linux/scoped_gbm_device.h" + +namespace ui { + +void GbmDeviceDeleter::operator()(gbm_device* device) { + if (device) + gbm_device_destroy(device); +} + +} // namespace ui diff --git a/linux/scoped_gbm_device.h b/linux/scoped_gbm_device.h new file mode 100644 index 000000000000..0abed1574d36 --- /dev/null +++ b/linux/scoped_gbm_device.h @@ -0,0 +1,22 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_LINUX_SCOPED_GBM_DEVICE_H_ +#define UI_GFX_LINUX_SCOPED_GBM_DEVICE_H_ + +#include + +#include + +namespace ui { + +struct GbmDeviceDeleter { + void operator()(gbm_device* device); +}; + +using ScopedGbmDevice = std::unique_ptr; + +} // namespace ui + +#endif // UI_GFX_LINUX_SCOPED_GBM_DEVICE_H_ diff --git a/linux/test/mock_gbm_device.cc b/linux/test/mock_gbm_device.cc new file mode 100644 index 000000000000..9ea3659a25ab --- /dev/null +++ b/linux/test/mock_gbm_device.cc @@ -0,0 +1,188 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/linux/test/mock_gbm_device.h" + +#include +#include +#include + +#include "base/check_op.h" +#include "base/containers/contains.h" +#include "base/files/file_util.h" +#include "base/logging.h" +#include "base/notreached.h" +#include "base/numerics/safe_math.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkSurface.h" +#include "ui/gfx/linux/drm_util_linux.h" +#include "ui/gfx/linux/gbm_buffer.h" + +namespace ui { +namespace { + +base::ScopedFD MakeFD() { + base::FilePath temp_path; + if (!base::CreateTemporaryFile(&temp_path)) + return {}; + auto file = + base::File(temp_path, base::File::FLAG_READ | base::File::FLAG_WRITE | + base::File::FLAG_CREATE_ALWAYS); + return base::ScopedFD(file.TakePlatformFile()); +} + +class MockGbmBuffer final : public ui::GbmBuffer { + public: + MockGbmBuffer(uint32_t format, + uint32_t flags, + uint64_t modifier, + const gfx::Size& size, + std::vector planes, + std::vector handles) + : format_(format), + format_modifier_(modifier), + flags_(flags), + size_(size), + planes_(std::move(planes)), + handles_(std::move(handles)) {} + + MockGbmBuffer(const MockGbmBuffer&) = delete; + MockGbmBuffer& operator=(const MockGbmBuffer&) = delete; + + ~MockGbmBuffer() override = default; + + uint32_t GetFormat() const override { return format_; } + uint64_t GetFormatModifier() const override { return format_modifier_; } + uint32_t GetFlags() const override { return flags_; } + gfx::Size GetSize() const override { return size_; } + gfx::BufferFormat GetBufferFormat() const override { + return ui::GetBufferFormatFromFourCCFormat(format_); + } + bool AreFdsValid() const override { + if (planes_.empty()) + return false; + + for (const auto& plane : planes_) { + if (!plane.fd.is_valid()) + return false; + } + return true; + } + size_t GetNumPlanes() const override { return planes_.size(); } + int GetPlaneFd(size_t plane) const override { + return planes_[plane].fd.get(); + } + uint32_t GetPlaneStride(size_t plane) const override { + DCHECK_LT(plane, planes_.size()); + return planes_[plane].stride; + } + size_t GetPlaneOffset(size_t plane) const override { + DCHECK_LT(plane, planes_.size()); + return planes_[plane].offset; + } + size_t GetPlaneSize(size_t plane) const override { + DCHECK_LT(plane, planes_.size()); + return static_cast(planes_[plane].size); + } + uint32_t GetPlaneHandle(size_t plane) const override { + DCHECK_LT(plane, planes_.size()); + return handles_[plane]; + } + uint32_t GetHandle() const override { return GetPlaneHandle(0); } + gfx::NativePixmapHandle ExportHandle() const override { + NOTIMPLEMENTED(); + return gfx::NativePixmapHandle(); + } + + sk_sp GetSurface() override { return nullptr; } + + private: + uint32_t format_ = 0; + uint64_t format_modifier_ = 0; + uint32_t flags_ = 0; + gfx::Size size_; + std::vector planes_; + std::vector handles_; +}; + +} // namespace + +MockGbmDevice::MockGbmDevice() = default; + +MockGbmDevice::~MockGbmDevice() = default; + +void MockGbmDevice::set_allocation_failure(bool should_fail_allocations) { + should_fail_allocations_ = should_fail_allocations; +} + +std::vector MockGbmDevice::GetSupportedModifiers() const { + return supported_modifiers_; +} + +std::unique_ptr MockGbmDevice::CreateBuffer(uint32_t format, + const gfx::Size& size, + uint32_t flags) { + if (should_fail_allocations_) + return nullptr; + + return CreateBufferWithModifiers(format, size, flags, {}); +} + +std::unique_ptr MockGbmDevice::CreateBufferWithModifiers( + uint32_t format, + const gfx::Size& size, + uint32_t flags, + const std::vector& modifiers) { + if (should_fail_allocations_) + return nullptr; + + uint32_t bytes_per_pixel; + switch (format) { + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_ARGB2101010: + case DRM_FORMAT_ABGR2101010: + bytes_per_pixel = 4; + break; + case DRM_FORMAT_NV12: + bytes_per_pixel = 2; + break; + default: + NOTREACHED() << "Unsupported format: " << format; + return nullptr; + } + + uint64_t format_modifier = + modifiers.empty() ? DRM_FORMAT_MOD_NONE : modifiers.back(); + + if (!base::Contains(supported_modifiers_, format_modifier)) { + PLOG(ERROR) << "Unsupported format modifier: " << std::hex + << format_modifier; + return nullptr; + } + + uint32_t width = base::checked_cast(size.width()); + uint32_t height = base::checked_cast(size.height()); + uint32_t plane_stride = base::CheckMul(bytes_per_pixel, width).ValueOrDie(); + uint32_t plane_size = base::CheckMul(plane_stride, height).ValueOrDie(); + uint32_t plane_offset = 0; + + std::vector planes; + planes.emplace_back(plane_stride, plane_offset, plane_size, MakeFD()); + std::vector handles; + handles.push_back(next_handle_++); + + return std::make_unique(format, flags, format_modifier, size, + std::move(planes), std::move(handles)); +} + +std::unique_ptr MockGbmDevice::CreateBufferFromHandle( + uint32_t format, + const gfx::Size& size, + gfx::NativePixmapHandle handle) { + NOTREACHED(); + return nullptr; +} + +} // namespace ui diff --git a/linux/test/mock_gbm_device.h b/linux/test/mock_gbm_device.h new file mode 100644 index 000000000000..16c9d2dc4faa --- /dev/null +++ b/linux/test/mock_gbm_device.h @@ -0,0 +1,53 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_LINUX_TEST_MOCK_GBM_DEVICE_H_ +#define UI_GFX_LINUX_TEST_MOCK_GBM_DEVICE_H_ + +#include +#include +#include "ui/gfx/linux/gbm_device.h" + +namespace ui { + +// The real DrmDevice makes actual DRM calls which we can't use in unit tests. +class MockGbmDevice : public GbmDevice { + public: + MockGbmDevice(); + + MockGbmDevice(const MockGbmDevice&) = delete; + MockGbmDevice& operator=(const MockGbmDevice&) = delete; + + ~MockGbmDevice() override; + + void set_allocation_failure(bool should_fail_allocations); + std::vector GetSupportedModifiers() const; + + // GbmDevice: + std::unique_ptr CreateBuffer(uint32_t format, + const gfx::Size& size, + uint32_t flags) override; + std::unique_ptr CreateBufferWithModifiers( + uint32_t format, + const gfx::Size& size, + uint32_t flags, + const std::vector& modifiers) override; + std::unique_ptr CreateBufferFromHandle( + uint32_t format, + const gfx::Size& size, + gfx::NativePixmapHandle handle) override; + + private: + uint32_t next_handle_ = 0; + bool should_fail_allocations_ = false; + + // List of modifiers that MockGbm validates when used. + const std::vector supported_modifiers_ = { + DRM_FORMAT_MOD_LINEAR, I915_FORMAT_MOD_X_TILED, I915_FORMAT_MOD_Y_TILED, + I915_FORMAT_MOD_Yf_TILED_CCS}; +}; + +} // namespace ui + +#endif // UI_GFX_LINUX_TEST_MOCK_GBM_DEVICE_H_ diff --git a/mac/coordinate_conversion.h b/mac/coordinate_conversion.h new file mode 100644 index 000000000000..3b75485357b7 --- /dev/null +++ b/mac/coordinate_conversion.h @@ -0,0 +1,35 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_MAC_COORDINATE_CONVERSION_H_ +#define UI_GFX_MAC_COORDINATE_CONVERSION_H_ + +#import + +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +class Point; +class Rect; + +// Convert a gfx::Rect specified with the origin at the top left of the primary +// display into AppKit secreen coordinates (origin at the bottom left). +GFX_EXPORT NSRect ScreenRectToNSRect(const Rect& rect); + +// Convert an AppKit NSRect with origin in the bottom left of the primary +// display into a gfx::Rect with origin at the top left of the primary display. +GFX_EXPORT Rect ScreenRectFromNSRect(const NSRect& point); + +// Convert a gfx::Point specified with the origin at the top left of the primary +// display into AppKit screen coordinates (origin at the bottom left). +GFX_EXPORT NSPoint ScreenPointToNSPoint(const Point& point); + +// Convert an AppKit NSPoint with origin in the bottom left of the primary +// display into a gfx::Point with origin at the top left of the primary display. +GFX_EXPORT Point ScreenPointFromNSPoint(const NSPoint& point); + +} // namespace gfx + +#endif // UI_GFX_MAC_COORDINATE_CONVERSION_H_ diff --git a/mac/coordinate_conversion.mm b/mac/coordinate_conversion.mm new file mode 100644 index 000000000000..b31ce9e1451b --- /dev/null +++ b/mac/coordinate_conversion.mm @@ -0,0 +1,45 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "ui/gfx/mac/coordinate_conversion.h" + +#import + +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/rect.h" + +namespace gfx { + +namespace { + +// The height of the primary display, which OSX defines as the monitor with the +// menubar. This is always at index 0. +CGFloat PrimaryDisplayHeight() { + return NSMaxY([[[NSScreen screens] firstObject] frame]); +} + +} // namespace + +NSRect ScreenRectToNSRect(const Rect& rect) { + return NSMakeRect(rect.x(), + PrimaryDisplayHeight() - rect.y() - rect.height(), + rect.width(), + rect.height()); +} + +Rect ScreenRectFromNSRect(const NSRect& rect) { + return Rect(rect.origin.x, + PrimaryDisplayHeight() - rect.origin.y - rect.size.height, + rect.size.width, rect.size.height); +} + +NSPoint ScreenPointToNSPoint(const Point& point) { + return NSMakePoint(point.x(), PrimaryDisplayHeight() - point.y()); +} + +Point ScreenPointFromNSPoint(const NSPoint& point) { + return Point(point.x, PrimaryDisplayHeight() - point.y); +} + +} // namespace gfx diff --git a/mac/coordinate_conversion_unittest.mm b/mac/coordinate_conversion_unittest.mm new file mode 100644 index 000000000000..23f078a6b8a2 --- /dev/null +++ b/mac/coordinate_conversion_unittest.mm @@ -0,0 +1,139 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "ui/gfx/mac/coordinate_conversion.h" + +#import + +#include + +#import "base/mac/scoped_objc_class_swizzler.h" +#include "base/macros.h" +#import "testing/gtest_mac.h" +#import "testing/platform_test.h" +#include "ui/gfx/geometry/rect.h" + +const int kTestWidth = 320; +const int kTestHeight = 200; + +// Class to donate an implementation of -[NSScreen frame] that provides a known +// value for robust tests. +@interface MacCoordinateConversionTestScreenDonor : NSObject +- (NSRect)frame; +@end + +@implementation MacCoordinateConversionTestScreenDonor +- (NSRect)frame { + return NSMakeRect(0, 0, kTestWidth, kTestHeight); +} +@end + +namespace gfx { +namespace { + +class MacCoordinateConversionTest : public PlatformTest { + public: + MacCoordinateConversionTest() {} + + MacCoordinateConversionTest(const MacCoordinateConversionTest&) = delete; + MacCoordinateConversionTest& operator=(const MacCoordinateConversionTest&) = + delete; + + // Overridden from testing::Test: + void SetUp() override; + void TearDown() override; + + private: + std::unique_ptr swizzle_frame_; +}; + +void MacCoordinateConversionTest::SetUp() { + // Before swizzling, do a sanity check that the primary screen's origin is + // (0, 0). This should always be true. + NSRect primary_screen_frame = [[[NSScreen screens] firstObject] frame]; + EXPECT_EQ(0, primary_screen_frame.origin.x); + EXPECT_EQ(0, primary_screen_frame.origin.y); + + swizzle_frame_ = std::make_unique( + [NSScreen class], [MacCoordinateConversionTestScreenDonor class], + @selector(frame)); + + primary_screen_frame = [[[NSScreen screens] firstObject] frame]; + EXPECT_EQ(kTestWidth, primary_screen_frame.size.width); + EXPECT_EQ(kTestHeight, primary_screen_frame.size.height); +} + +void MacCoordinateConversionTest::TearDown() { + swizzle_frame_.reset(); +} + +} // namespace + +// Tests for coordinate conversion on Mac. Start with the following setup: +// AppKit ....... gfx +// 199 0 +// 189 10 Window of height 40 fills in pixel +// 179 --------- 20 at index 20 +// 169 | | 30 through +// ... : : .. to +// 150 | | 49 pixel +// 140 --------- 59 at index 59 +// 130 69 (inclusive). +// .. .. +// 0 199 +TEST_F(MacCoordinateConversionTest, ScreenRectToFromNSRect) { + // Window on the primary screen. + Rect gfx_rect = Rect(10, 20, 30, 40); + NSRect ns_rect = ScreenRectToNSRect(gfx_rect); + EXPECT_NSEQ(NSMakeRect(10, 140, 30, 40), ns_rect); + EXPECT_EQ(gfx_rect, ScreenRectFromNSRect(ns_rect)); + + // Window in a screen to the left of the primary screen. + gfx_rect = Rect(-40, 20, 30, 40); + ns_rect = ScreenRectToNSRect(gfx_rect); + EXPECT_NSEQ(NSMakeRect(-40, 140, 30, 40), ns_rect); + EXPECT_EQ(gfx_rect, ScreenRectFromNSRect(ns_rect)); + + // Window in a screen below the primary screen. + gfx_rect = Rect(10, 220, 30, 40); + ns_rect = ScreenRectToNSRect(gfx_rect); + EXPECT_NSEQ(NSMakeRect(10, -60, 30, 40), ns_rect); + EXPECT_EQ(gfx_rect, ScreenRectFromNSRect(ns_rect)); + + // Window in a screen below and to the left primary screen. + gfx_rect = Rect(-40, 220, 30, 40); + ns_rect = ScreenRectToNSRect(gfx_rect); + EXPECT_NSEQ(NSMakeRect(-40, -60, 30, 40), ns_rect); + EXPECT_EQ(gfx_rect, ScreenRectFromNSRect(ns_rect)); +} + +// Test point conversions using the same setup as ScreenRectToFromNSRect, but +// using only the origin. +TEST_F(MacCoordinateConversionTest, ScreenPointToFromNSPoint) { + // Point on the primary screen. + Point gfx_point = Point(10, 20); + NSPoint ns_point = ScreenPointToNSPoint(gfx_point); + EXPECT_NSEQ(NSMakePoint(10, 180), ns_point); + EXPECT_EQ(gfx_point, ScreenPointFromNSPoint(ns_point)); + + // Point in a screen to the left of the primary screen. + gfx_point = Point(-40, 20); + ns_point = ScreenPointToNSPoint(gfx_point); + EXPECT_NSEQ(NSMakePoint(-40, 180), ns_point); + EXPECT_EQ(gfx_point, ScreenPointFromNSPoint(ns_point)); + + // Point in a screen below the primary screen. + gfx_point = Point(10, 220); + ns_point = ScreenPointToNSPoint(gfx_point); + EXPECT_NSEQ(NSMakePoint(10, -20), ns_point); + EXPECT_EQ(gfx_point, ScreenPointFromNSPoint(ns_point)); + + // Point in a screen below and to the left primary screen. + gfx_point = Point(-40, 220); + ns_point = ScreenPointToNSPoint(gfx_point); + EXPECT_NSEQ(NSMakePoint(-40, -20), ns_point); + EXPECT_EQ(gfx_point, ScreenPointFromNSPoint(ns_point)); +} + +} // namespace gfx diff --git a/mac/display_icc_profiles.cc b/mac/display_icc_profiles.cc new file mode 100644 index 000000000000..119b652bb878 --- /dev/null +++ b/mac/display_icc_profiles.cc @@ -0,0 +1,93 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/mac/display_icc_profiles.h" + +#include "base/notreached.h" +#include "ui/gfx/icc_profile.h" + +namespace gfx { + +DisplayICCProfiles* DisplayICCProfiles::GetInstance() { + static base::NoDestructor profiles; + return profiles.get(); +} + +base::ScopedCFTypeRef DisplayICCProfiles::GetDataForColorSpace( + const ColorSpace& color_space) { + UpdateIfNeeded(); + base::ScopedCFTypeRef result; + auto found = map_.find(color_space); + if (found != map_.end()) + result = found->second; + return result; +} + +DisplayICCProfiles::DisplayICCProfiles() { + CGDisplayRegisterReconfigurationCallback( + DisplayICCProfiles::DisplayReconfigurationCallBack, this); +} + +DisplayICCProfiles::~DisplayICCProfiles() { + NOTREACHED(); +} + +void DisplayICCProfiles::UpdateIfNeeded() { + if (!needs_update_) + return; + needs_update_ = false; + map_.clear(); + + // Always add Apple's sRGB profile. + base::ScopedCFTypeRef srgb_icc(CGColorSpaceCopyICCProfile( + CGColorSpaceCreateWithName(kCGColorSpaceSRGB))); + map_[ColorSpace::CreateSRGB()] = srgb_icc; + + // Add the profiles for all active displays. + uint32_t display_count = 0; + CGError error = kCGErrorSuccess; + error = CGGetActiveDisplayList(0, nullptr, &display_count); + if (error != kCGErrorSuccess) + return; + if (!display_count) + return; + + std::vector displays(display_count); + error = + CGGetActiveDisplayList(displays.size(), displays.data(), &display_count); + if (error != kCGErrorSuccess) + return; + + for (uint32_t i = 0; i < display_count; ++i) { + base::ScopedCFTypeRef cg_color_space( + CGDisplayCopyColorSpace(displays[i])); + if (!cg_color_space) + continue; + base::ScopedCFTypeRef icc_data( + CGColorSpaceCopyICCProfile(cg_color_space)); + if (!icc_data) + continue; + ICCProfile icc_profile = ICCProfile::FromData(CFDataGetBytePtr(icc_data), + CFDataGetLength(icc_data)); + ColorSpace color_space = icc_profile.GetColorSpace(); + // If the ICC profile isn't accurately parametrically approximated, then + // don't store its data (we will assign the best parametric fit to + // IOSurfaces, and rely on the system compositor to do conversion to the + // display profile). + if (color_space.IsValid() && icc_profile.IsColorSpaceAccurate()) + map_[color_space] = icc_data; + } +} + +// static +void DisplayICCProfiles::DisplayReconfigurationCallBack( + CGDirectDisplayID display, + CGDisplayChangeSummaryFlags flags, + void* user_info) { + DisplayICCProfiles* profiles = + reinterpret_cast(user_info); + profiles->needs_update_ = true; +} + +} // namespace gfx diff --git a/mac/display_icc_profiles.h b/mac/display_icc_profiles.h new file mode 100644 index 000000000000..565bb0ca3193 --- /dev/null +++ b/mac/display_icc_profiles.h @@ -0,0 +1,61 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_MAC_DISPLAY_ICC_PROFILES_H_ +#define UI_GFX_MAC_DISPLAY_ICC_PROFILES_H_ + +#include + +#include "base/containers/flat_map.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/macros.h" +#include "base/no_destructor.h" +#include "ui/gfx/color_space.h" +#include "ui/gfx/color_space_export.h" + +namespace gfx { + +// A map from ColorSpace objects to the display ICC profile data from which the +// ColorSpace was derived. +// - The color space for an IOSurface, when composited by CoreAnimation, is +// specified via ICC profile metadata. +// - The power cost of compositing an IOSurface that has the same color space +// as the display it is being composited to is substantially less (~0.5 W for +// fullscreen updates at 60fps) than the cost of compositing an IOSurface +// that has a different color space than the display is being composited to. +// - This power savings is realized only if the ICC profile metadata on the +// IOSurface matches, byte-for-byte, the profile of the CGDirectDisplayID it +// is being displayed on. +// - This structure maintains a map from ColorSpace objects to ICC profile data +// for all displays in the system (and auto-updates as displays change). +class COLOR_SPACE_EXPORT DisplayICCProfiles { + public: + static DisplayICCProfiles* GetInstance(); + + DisplayICCProfiles(const DisplayICCProfiles&) = delete; + DisplayICCProfiles& operator=(const DisplayICCProfiles&) = delete; + + // This will return null if |color_space| does not correspond to a display. + base::ScopedCFTypeRef GetDataForColorSpace( + const ColorSpace& color_space); + + private: + friend class base::NoDestructor; + + static void DisplayReconfigurationCallBack(CGDirectDisplayID display, + CGDisplayChangeSummaryFlags flags, + void* user_info); + + DisplayICCProfiles(); + ~DisplayICCProfiles(); + + void UpdateIfNeeded(); + + base::flat_map> map_; + bool needs_update_ = true; +}; + +} // namespace gfx + +#endif // UI_GFX_MAC_DISPLAY_ICC_PROFILES_H_ diff --git a/mac/io_surface.cc b/mac/io_surface.cc new file mode 100644 index 000000000000..7a199bc598eb --- /dev/null +++ b/mac/io_surface.cc @@ -0,0 +1,347 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/mac/io_surface.h" + +#include +#include +#include +#include + +#include "base/bits.h" +#include "base/command_line.h" +#include "base/cxx17_backports.h" +#include "base/feature_list.h" +#include "base/logging.h" +#include "base/mac/mac_util.h" +#include "base/mac/mach_logging.h" +#include "base/metrics/histogram_macros.h" +#include "base/trace_event/trace_event.h" +#include "ui/gfx/buffer_format_util.h" +#include "ui/gfx/color_space.h" +#include "ui/gfx/icc_profile.h" + +namespace gfx { + +namespace { + +const base::Feature kIOSurfaceUseNamedSRGBForREC709{ + "IOSurfaceUseNamedSRGBForREC709", base::FEATURE_ENABLED_BY_DEFAULT}; + +void AddIntegerValue(CFMutableDictionaryRef dictionary, + const CFStringRef key, + int32_t value) { + base::ScopedCFTypeRef number( + CFNumberCreate(NULL, kCFNumberSInt32Type, &value)); + CFDictionaryAddValue(dictionary, key, number.get()); +} + +int32_t BytesPerElement(gfx::BufferFormat format, int plane) { + switch (format) { + case gfx::BufferFormat::R_8: + DCHECK_EQ(plane, 0); + return 1; + case gfx::BufferFormat::BGRA_8888: + case gfx::BufferFormat::BGRX_8888: + case gfx::BufferFormat::RGBA_8888: + case gfx::BufferFormat::BGRA_1010102: + DCHECK_EQ(plane, 0); + return 4; + case gfx::BufferFormat::RGBA_F16: + DCHECK_EQ(plane, 0); + return 8; + case gfx::BufferFormat::YUV_420_BIPLANAR: { + constexpr int32_t bytes_per_element[] = {1, 2}; + DCHECK_LT(static_cast(plane), base::size(bytes_per_element)); + return bytes_per_element[plane]; + } + case gfx::BufferFormat::P010: { + constexpr int32_t bytes_per_element[] = {2, 4}; + DCHECK_LT(static_cast(plane), base::size(bytes_per_element)); + return bytes_per_element[plane]; + } + case gfx::BufferFormat::R_16: + case gfx::BufferFormat::RG_88: + case gfx::BufferFormat::BGR_565: + case gfx::BufferFormat::RGBA_4444: + case gfx::BufferFormat::RGBX_8888: + case gfx::BufferFormat::RGBA_1010102: + case gfx::BufferFormat::YVU_420: + NOTREACHED(); + return 0; + } + + NOTREACHED(); + return 0; +} + +} // namespace + +uint32_t BufferFormatToIOSurfacePixelFormat(gfx::BufferFormat format) { + switch (format) { + case gfx::BufferFormat::R_8: + return 'L008'; + case gfx::BufferFormat::BGRA_1010102: + return 'l10r'; // little-endian ARGB2101010 full-range ARGB + case gfx::BufferFormat::BGRA_8888: + case gfx::BufferFormat::BGRX_8888: + case gfx::BufferFormat::RGBA_8888: + return 'BGRA'; + case gfx::BufferFormat::RGBA_F16: + return 'RGhA'; + case gfx::BufferFormat::YUV_420_BIPLANAR: + return '420v'; + case gfx::BufferFormat::P010: + return 'x420'; + case gfx::BufferFormat::R_16: + case gfx::BufferFormat::RG_88: + case gfx::BufferFormat::BGR_565: + case gfx::BufferFormat::RGBA_4444: + case gfx::BufferFormat::RGBX_8888: + case gfx::BufferFormat::RGBA_1010102: + // Technically RGBA_1010102 should be accepted as 'R10k', but then it won't + // be supported by CGLTexImageIOSurface2D(), so it's best to reject it here. + case gfx::BufferFormat::YVU_420: + return 0; + } + + NOTREACHED(); + return 0; +} + +namespace internal { + +// static +mach_port_t IOSurfaceMachPortTraits::Retain(mach_port_t port) { + kern_return_t kr = + mach_port_mod_refs(mach_task_self(), port, MACH_PORT_RIGHT_SEND, 1); + MACH_LOG_IF(ERROR, kr != KERN_SUCCESS, kr) + << "IOSurfaceMachPortTraits::Retain mach_port_mod_refs"; + return port; +} + +// static +void IOSurfaceMachPortTraits::Release(mach_port_t port) { + kern_return_t kr = + mach_port_mod_refs(mach_task_self(), port, MACH_PORT_RIGHT_SEND, -1); + MACH_LOG_IF(ERROR, kr != KERN_SUCCESS, kr) + << "IOSurfaceMachPortTraits::Release mach_port_mod_refs"; +} + +// Common method used by IOSurfaceSetColorSpace and IOSurfaceCanSetColorSpace. +bool IOSurfaceSetColorSpace(IOSurfaceRef io_surface, + const ColorSpace& color_space) { + // Allow but ignore invalid color spaces. + if (!color_space.IsValid()) + return true; + + // Prefer using named spaces. + CFStringRef color_space_name = nullptr; + if (__builtin_available(macos 10.12, *)) { + if (color_space == ColorSpace::CreateSRGB() || + (base::FeatureList::IsEnabled(kIOSurfaceUseNamedSRGBForREC709) && + color_space == ColorSpace::CreateREC709())) { + color_space_name = kCGColorSpaceSRGB; + } else if (color_space == ColorSpace::CreateDisplayP3D65()) { + color_space_name = kCGColorSpaceDisplayP3; + } else if (color_space == ColorSpace::CreateExtendedSRGB()) { + color_space_name = kCGColorSpaceExtendedSRGB; + } else if (color_space == ColorSpace::CreateSCRGBLinear()) { + color_space_name = kCGColorSpaceExtendedLinearSRGB; + } + } + // The symbols kCGColorSpaceITUR_2020_PQ_EOTF and kCGColorSpaceITUR_2020_HLG + // have been deprecated. Claim that we were able to set the color space, + // because the path that will render these color spaces will use the + // HDRCopier, which will manually convert them to a non-deprecated format. + // https://crbug.com/1108627: Bug wherein these symbols are deprecated and + // also not available in some SDK versions. + // https://crbug.com/1101041: Introduces the HDR copier. + // https://crbug.com/1061723: Discussion of issues related to HLG. + if (__builtin_available(macos 10.15, *)) { + if (color_space == ColorSpace(ColorSpace::PrimaryID::BT2020, + ColorSpace::TransferID::SMPTEST2084, + ColorSpace::MatrixID::BT2020_NCL, + ColorSpace::RangeID::LIMITED)) { + if (__builtin_available(macos 11.0, *)) { + color_space_name = kCGColorSpaceITUR_2100_PQ; + } else { + return true; + } + } else if (color_space == ColorSpace(ColorSpace::PrimaryID::BT2020, + ColorSpace::TransferID::ARIB_STD_B67, + ColorSpace::MatrixID::BT2020_NCL, + ColorSpace::RangeID::LIMITED)) { + if (__builtin_available(macos 11.0, *)) { + color_space_name = kCGColorSpaceITUR_2100_HLG; + } else { + return true; + } + } + } + if (color_space_name) { + if (io_surface) { + IOSurfaceSetValue(io_surface, CFSTR("IOSurfaceColorSpace"), + color_space_name); + } + return true; + } + + gfx::ColorSpace as_rgb = color_space.GetAsRGB(); + gfx::ColorSpace as_full_range_rgb = color_space.GetAsFullRangeRGB(); + + // IOSurfaces do not support full-range YUV video. Fortunately, the hardware + // decoders never produce full-range video. + // https://crbug.com/882627 + if (color_space != as_rgb && as_rgb == as_full_range_rgb) + return false; + + // Generate an ICCProfile from the parametric color space. + ICCProfile icc_profile = ICCProfile::FromColorSpace(as_full_range_rgb); + if (!icc_profile.IsValid()) + return false; + + // Package it as a CFDataRef and send it to the IOSurface. + std::vector icc_profile_data = icc_profile.GetData(); + base::ScopedCFTypeRef cf_data_icc_profile(CFDataCreate( + nullptr, reinterpret_cast(icc_profile_data.data()), + icc_profile_data.size())); + + IOSurfaceSetValue(io_surface, CFSTR("IOSurfaceColorSpace"), + cf_data_icc_profile); + return true; +} + +} // namespace internal + +IOSurfaceRef CreateIOSurface(const gfx::Size& size, + gfx::BufferFormat format, + bool should_clear) { + TRACE_EVENT0("ui", "CreateIOSurface"); + base::TimeTicks start_time = base::TimeTicks::Now(); + + base::ScopedCFTypeRef properties( + CFDictionaryCreateMutable(kCFAllocatorDefault, 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + AddIntegerValue(properties, kIOSurfaceWidth, size.width()); + AddIntegerValue(properties, kIOSurfaceHeight, size.height()); + AddIntegerValue(properties, kIOSurfacePixelFormat, + BufferFormatToIOSurfacePixelFormat(format)); + + // Don't specify plane information unless there are indeed multiple planes + // because DisplayLink drivers do not support this. + // http://crbug.com/527556 + size_t num_planes = gfx::NumberOfPlanesForLinearBufferFormat(format); + if (num_planes > 1) { + base::ScopedCFTypeRef planes(CFArrayCreateMutable( + kCFAllocatorDefault, num_planes, &kCFTypeArrayCallBacks)); + size_t total_bytes_alloc = 0; + for (size_t plane = 0; plane < num_planes; ++plane) { + const size_t factor = + gfx::SubsamplingFactorForBufferFormat(format, plane); + const size_t plane_width = (size.width() + factor - 1) / factor; + const size_t plane_height = (size.height() + factor - 1) / factor; + const size_t plane_bytes_per_element = BytesPerElement(format, plane); + const size_t plane_bytes_per_row = IOSurfaceAlignProperty( + kIOSurfacePlaneBytesPerRow, + base::bits::AlignUp(plane_width, 2) * plane_bytes_per_element); + const size_t plane_bytes_alloc = IOSurfaceAlignProperty( + kIOSurfacePlaneSize, + base::bits::AlignUp(plane_height, 2) * plane_bytes_per_row); + const size_t plane_offset = + IOSurfaceAlignProperty(kIOSurfacePlaneOffset, total_bytes_alloc); + + base::ScopedCFTypeRef plane_info( + CFDictionaryCreateMutable(kCFAllocatorDefault, 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + AddIntegerValue(plane_info, kIOSurfacePlaneWidth, plane_width); + AddIntegerValue(plane_info, kIOSurfacePlaneHeight, plane_height); + AddIntegerValue(plane_info, kIOSurfacePlaneBytesPerElement, + plane_bytes_per_element); + AddIntegerValue(plane_info, kIOSurfacePlaneBytesPerRow, + plane_bytes_per_row); + AddIntegerValue(plane_info, kIOSurfacePlaneSize, plane_bytes_alloc); + AddIntegerValue(plane_info, kIOSurfacePlaneOffset, plane_offset); + CFArrayAppendValue(planes, plane_info); + total_bytes_alloc = plane_offset + plane_bytes_alloc; + } + CFDictionaryAddValue(properties, kIOSurfacePlaneInfo, planes); + + total_bytes_alloc = + IOSurfaceAlignProperty(kIOSurfaceAllocSize, total_bytes_alloc); + AddIntegerValue(properties, kIOSurfaceAllocSize, total_bytes_alloc); + } else { + const size_t bytes_per_element = BytesPerElement(format, 0); + const size_t bytes_per_row = IOSurfaceAlignProperty( + kIOSurfaceBytesPerRow, + base::bits::AlignUp(size.width(), 2) * bytes_per_element); + const size_t bytes_alloc = IOSurfaceAlignProperty( + kIOSurfaceAllocSize, + base::bits::AlignUp(size.height(), 2) * bytes_per_row); + AddIntegerValue(properties, kIOSurfaceBytesPerElement, bytes_per_element); + AddIntegerValue(properties, kIOSurfaceBytesPerRow, bytes_per_row); + AddIntegerValue(properties, kIOSurfaceAllocSize, bytes_alloc); + } + + IOSurfaceRef surface = IOSurfaceCreate(properties); + if (!surface) { + LOG(ERROR) << "Failed to allocate IOSurface of size " << size.ToString() + << "."; + return nullptr; + } + + if (should_clear) { + // Zero-initialize the IOSurface. Calling IOSurfaceLock/IOSurfaceUnlock + // appears to be sufficient. https://crbug.com/584760#c17 + IOReturn r = IOSurfaceLock(surface, 0, nullptr); + DCHECK_EQ(kIOReturnSuccess, r); + r = IOSurfaceUnlock(surface, 0, nullptr); + DCHECK_EQ(kIOReturnSuccess, r); + } + + // Ensure that all IOSurfaces start as sRGB. + if (__builtin_available(macos 10.12, *)) { + IOSurfaceSetValue(surface, CFSTR("IOSurfaceColorSpace"), kCGColorSpaceSRGB); + } else { + CGColorSpaceRef color_space = base::mac::GetSRGBColorSpace(); + base::ScopedCFTypeRef color_space_icc( + CGColorSpaceCopyICCProfile(color_space)); + IOSurfaceSetValue(surface, CFSTR("IOSurfaceColorSpace"), color_space_icc); + } + + UMA_HISTOGRAM_TIMES("GPU.IOSurface.CreateTime", + base::TimeTicks::Now() - start_time); + return surface; +} + +bool IOSurfaceCanSetColorSpace(const ColorSpace& color_space) { + return internal::IOSurfaceSetColorSpace(nullptr, color_space); +} + +void IOSurfaceSetColorSpace(IOSurfaceRef io_surface, + const ColorSpace& color_space) { + if (!internal::IOSurfaceSetColorSpace(io_surface, color_space)) { + DLOG(ERROR) << "Failed to set color space for IOSurface: " + << color_space.ToString(); + } +} + +GFX_EXPORT base::ScopedCFTypeRef IOSurfaceMachPortToIOSurface( + ScopedRefCountedIOSurfaceMachPort io_surface_mach_port) { + base::ScopedCFTypeRef io_surface; + if (!io_surface_mach_port) { + DLOG(ERROR) << "Invalid mach port."; + return io_surface; + } + io_surface.reset(IOSurfaceLookupFromMachPort(io_surface_mach_port)); + if (!io_surface) { + DLOG(ERROR) << "Unable to lookup IOSurface."; + return io_surface; + } + return io_surface; +} + +} // namespace gfx diff --git a/mac/io_surface.h b/mac/io_surface.h new file mode 100644 index 000000000000..88c6a5dba21f --- /dev/null +++ b/mac/io_surface.h @@ -0,0 +1,88 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_MAC_IO_SURFACE_H_ +#define UI_GFX_MAC_IO_SURFACE_H_ + +#include +#include + +#include "base/mac/scoped_cftyperef.h" +#include "ui/gfx/buffer_types.h" +#include "ui/gfx/color_space.h" +#include "ui/gfx/generic_shared_memory_id.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +namespace internal { + +struct IOSurfaceMachPortTraits { + GFX_EXPORT static mach_port_t InvalidValue() { return MACH_PORT_NULL; } + GFX_EXPORT static mach_port_t Retain(mach_port_t); + GFX_EXPORT static void Release(mach_port_t); +}; + +struct ScopedInUseIOSurfaceTraits { + static IOSurfaceRef InvalidValue() { return nullptr; } + static IOSurfaceRef Retain(IOSurfaceRef io_surface) { + CFRetain(io_surface); + IOSurfaceIncrementUseCount(io_surface); + return io_surface; + } + static void Release(IOSurfaceRef io_surface) { + IOSurfaceDecrementUseCount(io_surface); + CFRelease(io_surface); + } +}; + +} // namespace internal + +using IOSurfaceId = GenericSharedMemoryId; + +// Helper function to create an IOSurface with a specified size and format. +// The surface is zero-initialized if |should_clear| is true. This is not +// necessary for anonymous surfaces that are not exported to renderers and used +// as render targets only. +GFX_EXPORT IOSurfaceRef CreateIOSurface(const Size& size, + BufferFormat format, + bool should_clear = true); + +// A scoper for handling Mach port names that are send rights for IOSurfaces. +// This scoper is both copyable and assignable, which will increase the kernel +// reference count of the right. On destruction, the reference count is +// decremented. +using ScopedRefCountedIOSurfaceMachPort = + base::ScopedTypeRef; + +// A scoper for holding a reference to an IOSurface and also incrementing its +// in-use counter while the scoper exists. +using ScopedInUseIOSurface = + base::ScopedTypeRef; + +// A scoper for holding a reference to an IOSurface. +using ScopedIOSurface = base::ScopedCFTypeRef; + +// Return true if there exists a value for IOSurfaceColorSpace or +// IOSurfaceICCProfile that will make CoreAnimation render using |color_space|. +GFX_EXPORT bool IOSurfaceCanSetColorSpace(const gfx::ColorSpace& color_space); + +// Set color space for given IOSurface. IOSurfaceCanSetColorSpace must return +// true for |color_space| otherwise this does nothing. +GFX_EXPORT void IOSurfaceSetColorSpace(IOSurfaceRef io_surface, + const gfx::ColorSpace& color_space); + +// Return the expected four character code pixel format for an IOSurface with +// the specified gfx::BufferFormat. +GFX_EXPORT uint32_t +BufferFormatToIOSurfacePixelFormat(gfx::BufferFormat format); + +// Return an IOSurface consuming |io_surface_mach_port|. +GFX_EXPORT base::ScopedCFTypeRef IOSurfaceMachPortToIOSurface( + ScopedRefCountedIOSurfaceMachPort io_surface_mach_port); + +} // namespace gfx + +#endif // UI_GFX_MAC_IO_SURFACE_H_ diff --git a/mac/io_surface_hdr_metadata.cc b/mac/io_surface_hdr_metadata.cc new file mode 100644 index 000000000000..f42502465502 --- /dev/null +++ b/mac/io_surface_hdr_metadata.cc @@ -0,0 +1,42 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/mac/io_surface_hdr_metadata.h" + +#include "base/mac/foundation_util.h" +#include "base/mac/scoped_cftyperef.h" +#include "ui/gfx/mojom/hdr_metadata.mojom.h" + +namespace gfx { + +namespace { + +// The key under which HDR metadata is attached to an IOSurface. +const CFStringRef kCrIOSurfaceHDRMetadataKey = + CFSTR("CrIOSurfaceHDRMetadataKey"); + +} // namespace + +void IOSurfaceSetHDRMetadata(IOSurfaceRef io_surface, + gfx::HDRMetadata hdr_metadata) { + std::vector std_data = + gfx::mojom::HDRMetadata::Serialize(&hdr_metadata); + base::ScopedCFTypeRef cf_data( + CFDataCreate(nullptr, std_data.data(), std_data.size())); + IOSurfaceSetValue(io_surface, kCrIOSurfaceHDRMetadataKey, cf_data); +} + +bool IOSurfaceGetHDRMetadata(IOSurfaceRef io_surface, + gfx::HDRMetadata& hdr_metadata) { + base::ScopedCFTypeRef cf_untyped( + IOSurfaceCopyValue(io_surface, kCrIOSurfaceHDRMetadataKey)); + CFDataRef cf_data = base::mac::CFCast(cf_untyped); + if (!cf_data) + return false; + const UInt8* raw_data = CFDataGetBytePtr(cf_data); + std::vector std_data(raw_data, raw_data + CFDataGetLength(cf_data)); + return gfx::mojom::HDRMetadata::Deserialize(std_data, &hdr_metadata); +} + +} // namespace gfx diff --git a/mac/io_surface_hdr_metadata.h b/mac/io_surface_hdr_metadata.h new file mode 100644 index 000000000000..1bee4842c19f --- /dev/null +++ b/mac/io_surface_hdr_metadata.h @@ -0,0 +1,31 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_MAC_IO_SURFACE_HDR_METADATA_H_ +#define UI_GFX_MAC_IO_SURFACE_HDR_METADATA_H_ + +#include + +#include "base/component_export.h" + +namespace gfx { + +struct HDRMetadata; + +// Attach |hdr_metadata| to |io_surface|. After this is called, any other +// process that has opened |io_surface| will be able to read |hdr_metadata| +// using the function IOSurfaceGetHDRMetadata. +void COMPONENT_EXPORT(GFX_IO_SURFACE_HDR_METADATA) + IOSurfaceSetHDRMetadata(IOSurfaceRef io_surface, + gfx::HDRMetadata hdr_metadata); + +// Retrieve in |hdr_metadata| the value that was attached to |io_surface|. This +// will return false on failure. +bool COMPONENT_EXPORT(GFX_IO_SURFACE_HDR_METADATA) + IOSurfaceGetHDRMetadata(IOSurfaceRef io_surface, + gfx::HDRMetadata& hdr_metadata); + +} // namespace gfx + +#endif // UI_GFX_MAC_IO_SURFACE_HDR_METADATA_H_ diff --git a/mac/io_surface_unittest.cc b/mac/io_surface_unittest.cc new file mode 100644 index 000000000000..31eef23d7553 --- /dev/null +++ b/mac/io_surface_unittest.cc @@ -0,0 +1,48 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/mac/io_surface.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/hdr_metadata.h" +#include "ui/gfx/mac/io_surface_hdr_metadata.h" + +namespace gfx { + +namespace { + +// Check that empty NSBezierPath is returned for empty SkPath. +TEST(IOSurface, HDRMetadata) { + gfx::HDRMetadata in; + in.color_volume_metadata.primary_r = PointF(1.0, 2.0); + in.color_volume_metadata.primary_g = PointF(4.0, 5.0); + in.color_volume_metadata.primary_b = PointF(7.0, 8.0); + in.color_volume_metadata.white_point = PointF(10.0, 11.0); + in.color_volume_metadata.luminance_max = 13; + in.color_volume_metadata.luminance_min = 14; + in.max_content_light_level = 15; + in.max_frame_average_light_level = 16; + + base::ScopedCFTypeRef io_surface( + CreateIOSurface(gfx::Size(100, 100), gfx::BufferFormat::BGRA_8888)); + + gfx::HDRMetadata out; + EXPECT_FALSE(IOSurfaceGetHDRMetadata(io_surface, out)); + IOSurfaceSetHDRMetadata(io_surface, in); + EXPECT_TRUE(IOSurfaceGetHDRMetadata(io_surface, out)); + EXPECT_EQ(in, out); +} + +TEST(IOSurface, OddSizeMultiPlanar) { + base::ScopedCFTypeRef io_surface( + CreateIOSurface(gfx::Size(101, 99), gfx::BufferFormat::YUV_420_BIPLANAR)); + DCHECK(io_surface); + // Plane sizes are rounded up. + // https://crbug.com/1226056 + EXPECT_EQ(IOSurfaceGetWidthOfPlane(io_surface, 1), 51u); + EXPECT_EQ(IOSurfaceGetHeightOfPlane(io_surface, 1), 50u); +} + +} // namespace + +} // namespace gfx diff --git a/mac/nswindow_frame_controls.h b/mac/nswindow_frame_controls.h new file mode 100644 index 000000000000..d58eecae3b9a --- /dev/null +++ b/mac/nswindow_frame_controls.h @@ -0,0 +1,35 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_MAC_NSWINDOW_FRAME_CONTROLS_H_ +#define UI_GFX_MAC_NSWINDOW_FRAME_CONTROLS_H_ + +#include "ui/gfx/gfx_export.h" + +@class NSWindow; + +namespace gfx { + +class Size; + +// Set whether the window can be fullscreened. +GFX_EXPORT void SetNSWindowCanFullscreen(NSWindow* window, + bool allow_fullscreen); + +// Sets whether the window appears on all workspaces. +GFX_EXPORT void SetNSWindowVisibleOnAllWorkspaces(NSWindow* window, + bool always_visible); + +// Sets the min/max size of the window as well as showing/hiding resize, +// maximize, and fullscreen controls. +// Sizes refer to the content size (inner bounds). +GFX_EXPORT void ApplyNSWindowSizeConstraints(NSWindow* window, + const gfx::Size& min_size, + const gfx::Size& max_size, + bool can_resize, + bool can_fullscreen); + +} // namespace gfx + +#endif // UI_GFX_MAC_NSWINDOW_FRAME_CONTROLS_H_ diff --git a/mac/nswindow_frame_controls.mm b/mac/nswindow_frame_controls.mm new file mode 100644 index 000000000000..3802078d24b5 --- /dev/null +++ b/mac/nswindow_frame_controls.mm @@ -0,0 +1,71 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "ui/gfx/mac/nswindow_frame_controls.h" + +#import + +#include "ui/gfx/geometry/size.h" + +namespace { + +// The value used to represent an unbounded width or height. +const int kUnboundedSize = 0; + +void SetResizableStyleMask(NSWindow* window, bool resizable) { + NSUInteger style_mask = [window styleMask]; + if (resizable) + style_mask |= NSResizableWindowMask; + else + style_mask &= ~NSResizableWindowMask; + [window setStyleMask:style_mask]; +} + +} // namespace + +namespace gfx { + +void SetNSWindowCanFullscreen(NSWindow* window, bool allow_fullscreen) { + NSWindowCollectionBehavior behavior = [window collectionBehavior]; + if (behavior & NSWindowCollectionBehaviorFullScreenAuxiliary) + return; + if (allow_fullscreen) + behavior |= NSWindowCollectionBehaviorFullScreenPrimary; + else + behavior &= ~NSWindowCollectionBehaviorFullScreenPrimary; + [window setCollectionBehavior:behavior]; +} + +void SetNSWindowVisibleOnAllWorkspaces(NSWindow* window, bool always_visible) { + NSWindowCollectionBehavior behavior = [window collectionBehavior]; + if (always_visible) + behavior |= NSWindowCollectionBehaviorCanJoinAllSpaces; + else + behavior &= ~NSWindowCollectionBehaviorCanJoinAllSpaces; + [window setCollectionBehavior:behavior]; +} + +void ApplyNSWindowSizeConstraints(NSWindow* window, + const gfx::Size& min_size, + const gfx::Size& max_size, + bool can_resize, + bool can_fullscreen) { + [window setContentMinSize:NSMakeSize(min_size.width(), min_size.height())]; + + CGFloat max_width = + max_size.width() == kUnboundedSize ? CGFLOAT_MAX : max_size.width(); + CGFloat max_height = + max_size.height() == kUnboundedSize ? CGFLOAT_MAX : max_size.height(); + [window setContentMaxSize:NSMakeSize(max_width, max_height)]; + + SetResizableStyleMask(window, can_resize); + [window setShowsResizeIndicator:can_resize]; + + // Set the window to participate in Lion Fullscreen mode. + SetNSWindowCanFullscreen(window, can_fullscreen); + + [[window standardWindowButton:NSWindowZoomButton] setEnabled:can_fullscreen]; +} + +} // namespace gfx diff --git a/mac/scoped_cocoa_disable_screen_updates.h b/mac/scoped_cocoa_disable_screen_updates.h new file mode 100644 index 000000000000..1ab2e289a377 --- /dev/null +++ b/mac/scoped_cocoa_disable_screen_updates.h @@ -0,0 +1,32 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_MAC_SCOPED_COCOA_DISABLE_SCREEN_UPDATES_H_ +#define UI_GFX_MAC_SCOPED_COCOA_DISABLE_SCREEN_UPDATES_H_ + +#include "base/macros.h" +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +// A stack-based class to disable Cocoa screen updates. When instantiated, it +// disables screen updates and enables them when destroyed. Update disabling +// can be nested, and there is a time-maximum (about 1 second) after which +// Cocoa will automatically re-enable updating. This class doesn't attempt to +// overrule that. +class GFX_EXPORT ScopedCocoaDisableScreenUpdates { + public: + ScopedCocoaDisableScreenUpdates(); + + ScopedCocoaDisableScreenUpdates(const ScopedCocoaDisableScreenUpdates&) = + delete; + ScopedCocoaDisableScreenUpdates& operator=( + const ScopedCocoaDisableScreenUpdates&) = delete; + + ~ScopedCocoaDisableScreenUpdates(); +}; + +} // namespace gfx + +#endif // UI_GFX_MAC_SCOPED_COCOA_DISABLE_SCREEN_UPDATES_H_ diff --git a/mac/scoped_cocoa_disable_screen_updates.mm b/mac/scoped_cocoa_disable_screen_updates.mm new file mode 100644 index 000000000000..b31792b689b4 --- /dev/null +++ b/mac/scoped_cocoa_disable_screen_updates.mm @@ -0,0 +1,19 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/mac/scoped_cocoa_disable_screen_updates.h" + +#import + +namespace gfx { + +ScopedCocoaDisableScreenUpdates::ScopedCocoaDisableScreenUpdates() { + [NSAnimationContext beginGrouping]; +} + +ScopedCocoaDisableScreenUpdates::~ScopedCocoaDisableScreenUpdates() { + [NSAnimationContext endGrouping]; +} + +} // namespace gfx diff --git a/mojom/BUILD.gn b/mojom/BUILD.gn new file mode 100644 index 000000000000..7c30bf8c7c46 --- /dev/null +++ b/mojom/BUILD.gn @@ -0,0 +1,410 @@ +# Copyright 2016 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//build/config/ozone.gni") +import("//mojo/public/tools/bindings/mojom.gni") + +mojom("mojom") { + generate_java = true + sources = [ + "accelerated_widget.mojom", + "buffer_types.mojom", + "ca_layer_params.mojom", + "color_space.mojom", + "delegated_ink_metadata.mojom", + "delegated_ink_point.mojom", + "delegated_ink_point_renderer.mojom", + "display_color_spaces.mojom", + "font_render_params.mojom", + "gpu_extra_info.mojom", + "gpu_fence_handle.mojom", + "hdr_metadata.mojom", + "hdr_static_metadata.mojom", + "mask_filter_info.mojom", + "overlay_priority_hint.mojom", + "overlay_transform.mojom", + "presentation_feedback.mojom", + "rrect_f.mojom", + "selection_bound.mojom", + "swap_result.mojom", + "swap_timings.mojom", + "transform.mojom", + ] + + public_deps = [ + ":native_handle_types", + "//mojo/public/mojom/base", + "//skia/public/mojom", + "//ui/gfx/geometry/mojom", + ] + + enabled_features = [] + if (use_x11 || ozone_platform_x11) { + enabled_features += [ "enable_x11_params" ] + } + + shared_cpp_typemaps = [ + { + types = [ + { + mojom = "gfx.mojom.BufferFormat" + cpp = "::gfx::BufferFormat" + }, + { + mojom = "gfx.mojom.BufferUsage" + cpp = "::gfx::BufferUsage" + }, + { + mojom = "gfx.mojom.BufferUsageAndFormat" + cpp = "::gfx::BufferUsageAndFormat" + }, + { + mojom = "gfx.mojom.BufferPlane" + cpp = "::gfx::BufferPlane" + }, + { + mojom = "gfx.mojom.GpuMemoryBufferHandle" + cpp = "::gfx::GpuMemoryBufferHandle" + move_only = true + nullable_is_same_type = true + }, + { + mojom = "gfx.mojom.GpuMemoryBufferId" + cpp = "::gfx::GpuMemoryBufferId" + copyable_pass_by_value = true + }, + { + mojom = "gfx.mojom.GpuMemoryBufferType" + cpp = "::gfx::GpuMemoryBufferType" + }, + ] + + traits_headers = [ "buffer_types_mojom_traits.h" ] + traits_public_deps = [ ":shared_mojom_traits" ] + }, + { + types = [ + { + mojom = "gfx.mojom.ColorSpace" + cpp = "::gfx::ColorSpace" + }, + ] + traits_headers = [ "color_space_mojom_traits.h" ] + traits_public_deps = [ ":shared_mojom_traits" ] + }, + { + types = [ + { + mojom = "gfx.mojom.DisplayColorSpaces" + cpp = "::gfx::DisplayColorSpaces" + }, + ] + traits_headers = [ "display_color_spaces_mojom_traits.h" ] + traits_public_deps = [ ":shared_mojom_traits" ] + }, + { + types = [ + { + mojom = "gfx.mojom.GpuExtraInfo" + cpp = "::gfx::GpuExtraInfo" + }, + { + mojom = "gfx.mojom.ANGLEFeature" + cpp = "::gfx::ANGLEFeature" + }, + ] + traits_headers = [ "gpu_extra_info_mojom_traits.h" ] + traits_public_deps = [ ":shared_mojom_traits" ] + }, + { + types = [ + { + mojom = "gfx.mojom.GpuFenceHandle" + cpp = "::gfx::GpuFenceHandle" + move_only = true + nullable_is_same_type = true + }, + ] + traits_headers = [ "gpu_fence_handle_mojom_traits.h" ] + traits_public_deps = [ ":shared_mojom_traits" ] + }, + { + types = [ + { + mojom = "gfx.mojom.HDRStaticMetadata" + cpp = "::gfx::HDRStaticMetadata" + }, + ] + traits_headers = [ "hdr_static_metadata_mojom_traits.h" ] + traits_public_deps = [ ":shared_mojom_traits" ] + }, + { + types = [ + { + mojom = "gfx.mojom.PresentationFeedback" + cpp = "::gfx::PresentationFeedback" + }, + ] + traits_headers = [ "presentation_feedback_mojom_traits.h" ] + traits_public_deps = [ "//ui/gfx" ] + }, + { + types = [ + { + mojom = "gfx.mojom.SelectionBound" + cpp = "::gfx::SelectionBound" + }, + ] + traits_headers = [ "selection_bound_mojom_traits.h" ] + traits_public_deps = [ "//ui/gfx/geometry/mojom:mojom_traits" ] + }, + { + types = [ + { + mojom = "gfx.mojom.SwapTimings" + cpp = "::gfx::SwapTimings" + }, + ] + traits_headers = [ "swap_timings_mojom_traits.h" ] + traits_public_deps = [ "//ui/gfx" ] + }, + { + types = [ + { + mojom = "gfx.mojom.Transform" + cpp = "::gfx::Transform" + }, + ] + traits_headers = [ "transform_mojom_traits.h" ] + traits_public_deps = [ "//ui/gfx" ] + }, + ] + + cpp_typemaps = [ + { + types = [ + { + mojom = "gfx.mojom.AcceleratedWidget" + cpp = "::gfx::AcceleratedWidget" + copyable_pass_by_value = true + }, + ] + traits_headers = [ "accelerated_widget_mojom_traits.h" ] + traits_public_deps = [ "//ui/gfx" ] + }, + { + types = [ + { + mojom = "gfx.mojom.CALayerParams" + cpp = "::gfx::CALayerParams" + }, + ] + traits_sources = [ "ca_layer_params_mojom_traits.cc" ] + traits_headers = [ "ca_layer_params_mojom_traits.h" ] + traits_public_deps = [ "//ui/gfx" ] + }, + { + types = [ + { + mojom = "gfx.mojom.DelegatedInkMetadata" + cpp = "::std::unique_ptr<::gfx::DelegatedInkMetadata>" + move_only = true + nullable_is_same_type = true + }, + ] + traits_sources = [ "delegated_ink_metadata_mojom_traits.cc" ] + traits_headers = [ "delegated_ink_metadata_mojom_traits.h" ] + traits_public_deps = [ "//ui/gfx" ] + }, + { + types = [ + { + mojom = "gfx.mojom.DelegatedInkPoint" + cpp = "::gfx::DelegatedInkPoint" + }, + ] + traits_sources = [ "delegated_ink_point_mojom_traits.cc" ] + traits_headers = [ "delegated_ink_point_mojom_traits.h" ] + traits_public_deps = [ "//ui/gfx" ] + }, + { + types = [ + { + mojom = "gfx.mojom.ColorVolumeMetadata" + cpp = "::gfx::ColorVolumeMetadata" + }, + { + mojom = "gfx.mojom.HDRMetadata" + cpp = "::gfx::HDRMetadata" + }, + ] + traits_headers = [ "hdr_metadata_mojom_traits.h" ] + traits_sources = [ "hdr_metadata_mojom_traits.cc" ] + traits_public_deps = [ "//ui/gfx" ] + }, + { + types = [ + { + mojom = "gfx.mojom.Hinting" + cpp = "::gfx::FontRenderParams::Hinting" + }, + { + mojom = "gfx.mojom.SubpixelRendering" + cpp = "::gfx::FontRenderParams::SubpixelRendering" + }, + ] + traits_headers = [ "font_render_params_mojom_traits.h" ] + traits_public_deps = [ "//ui/gfx" ] + }, + { + types = [ + { + mojom = "gfx.mojom.OverlayTransform" + cpp = "::gfx::OverlayTransform" + }, + ] + traits_headers = [ "overlay_transform_mojom_traits.h" ] + traits_public_deps = [ "//ui/gfx" ] + }, + { + types = [ + { + mojom = "gfx.mojom.OverlayPriorityHint" + cpp = "::gfx::OverlayPriorityHint" + }, + ] + traits_headers = [ "overlay_priority_hint_mojom_traits.h" ] + traits_public_deps = [ "//ui/gfx" ] + }, + { + types = [ + { + mojom = "gfx.mojom.RRectF" + cpp = "::gfx::RRectF" + }, + ] + traits_headers = [ "rrect_f_mojom_traits.h" ] + traits_public_deps = [ "//ui/gfx/geometry/mojom:mojom_traits" ] + }, + { + types = [ + { + mojom = "gfx.mojom.SwapResult" + cpp = "::gfx::SwapResult" + }, + ] + traits_headers = [ "swap_result_mojom_traits.h" ] + traits_public_deps = [ "//ui/gfx" ] + }, + { + types = [ + { + mojom = "gfx.mojom.MaskFilterInfo" + cpp = "::gfx::MaskFilterInfo" + }, + ] + traits_sources = [ "mask_filter_info_mojom_traits.cc" ] + traits_headers = [ "mask_filter_info_mojom_traits.h" ] + traits_public_deps = [ "//ui/gfx" ] + }, + ] + + cpp_typemaps += shared_cpp_typemaps + blink_cpp_typemaps = shared_cpp_typemaps + blink_cpp_typemaps += [ + { + types = [ + { + mojom = "gfx.mojom.DelegatedInkMetadata" + cpp = "::std::unique_ptr<::gfx::DelegatedInkMetadata>" + move_only = true + nullable_is_same_type = true + }, + ] + traits_headers = [ "delegated_ink_metadata_mojom_traits.h" ] + traits_public_deps = [ "//ui/gfx" ] + }, + ] +} + +mojom("native_handle_types") { + sources = [ "native_handle_types.mojom" ] + if (is_linux || is_chromeos || use_ozone) { + enabled_features = [ "supports_native_pixmap" ] + } + public_deps = [ "//mojo/public/mojom/base" ] + generate_java = true + + shared_cpp_typemap = { + types = [ + { + mojom = "gfx.mojom.NativePixmapHandle" + cpp = "::gfx::NativePixmapHandle" + move_only = true + }, + { + mojom = "gfx.mojom.NativePixmapPlane" + cpp = "::gfx::NativePixmapPlane" + move_only = true + }, + ] + traits_headers = [ "native_handle_types_mojom_traits.h" ] + traits_public_deps = [ ":native_handle_types_mojom_traits" ] + } + cpp_typemaps = [ shared_cpp_typemap ] + blink_cpp_typemaps = [ shared_cpp_typemap ] +} + +mojom("test_interfaces") { + sources = [ "traits_test_service.mojom" ] + + public_deps = [ ":mojom" ] +} + +component("native_handle_types_mojom_traits") { + output_name = "gfx_native_types_shared_mojom_traits" + defines = [ "IS_GFX_NATIVE_HANDLE_TYPES_SHARED_MOJOM_TRAITS_IMPL" ] + sources = [ + "native_handle_types_mojom_traits.cc", + "native_handle_types_mojom_traits.h", + ] + + public_deps = [ + ":native_handle_types_shared", + "//base", + "//mojo/public/mojom/base", + "//ui/gfx", + ] +} + +component("shared_mojom_traits") { + output_name = "gfx_shared_mojom_traits" + defines = [ "IS_GFX_SHARED_MOJOM_TRAITS_IMPL" ] + sources = [ + "buffer_types_mojom_traits.cc", + "buffer_types_mojom_traits.h", + "color_space_mojom_traits.cc", + "color_space_mojom_traits.h", + "display_color_spaces_mojom_traits.cc", + "display_color_spaces_mojom_traits.h", + "gpu_extra_info_mojom_traits.cc", + "gpu_extra_info_mojom_traits.h", + "gpu_fence_handle_mojom_traits.cc", + "gpu_fence_handle_mojom_traits.h", + "hdr_static_metadata_mojom_traits.cc", + "hdr_static_metadata_mojom_traits.h", + ] + public_deps = [ + ":mojom_shared", + ":native_handle_types", + "//ui/gfx", + ] + if (use_ozone) { + public_deps += [ "//ui/ozone:buildflags" ] + } + frameworks = [ + "CoreFoundation.framework", + "IOSurface.framework", + ] +} diff --git a/mojom/DEPS b/mojom/DEPS new file mode 100644 index 000000000000..507f9cdaad04 --- /dev/null +++ b/mojom/DEPS @@ -0,0 +1,9 @@ +include_rules = [ + "+mojo/public", +] + +specific_include_rules = { + "delegated_ink_metadata_mojom_traits\.h" : [ + "+skia/public/mojom/skcolor_mojom_traits.h", + ], +} \ No newline at end of file diff --git a/mojom/OWNERS b/mojom/OWNERS new file mode 100644 index 000000000000..8c29d1b5d409 --- /dev/null +++ b/mojom/OWNERS @@ -0,0 +1,9 @@ +set noparent +file://ipc/SECURITY_OWNERS + +per-file *.mojom=set noparent +per-file *.mojom=file://ipc/SECURITY_OWNERS +per-file *_mojom_traits*.*=set noparent +per-file *_mojom_traits*.*=file://ipc/SECURITY_OWNERS +per-file *.typemap=set noparent +per-file *.typemap=file://ipc/SECURITY_OWNERS diff --git a/mojom/accelerated_widget.mojom b/mojom/accelerated_widget.mojom new file mode 100644 index 000000000000..8647d67b11c5 --- /dev/null +++ b/mojom/accelerated_widget.mojom @@ -0,0 +1,10 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module gfx.mojom; + +// See ui/gfx/native_widget_types.h. +struct AcceleratedWidget { + uint64 widget; +}; diff --git a/mojom/accelerated_widget_mojom_traits.h b/mojom/accelerated_widget_mojom_traits.h new file mode 100644 index 000000000000..668459088ebd --- /dev/null +++ b/mojom/accelerated_widget_mojom_traits.h @@ -0,0 +1,45 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_MOJOM_ACCELERATED_WIDGET_MOJOM_TRAITS_H_ +#define UI_GFX_MOJOM_ACCELERATED_WIDGET_MOJOM_TRAITS_H_ + +#include "build/build_config.h" +#include "ui/gfx/mojom/accelerated_widget.mojom.h" +#include "ui/gfx/native_widget_types.h" + +namespace mojo { + +template <> +struct StructTraits { + static uint64_t widget(gfx::AcceleratedWidget widget) { +#if defined(OS_WIN) || defined(OS_ANDROID) || defined(OS_IOS) + return reinterpret_cast(widget); +#elif defined(USE_OZONE) || defined(USE_X11) || defined(OS_MAC) + return static_cast(widget); +#else + NOTREACHED(); + return 0; +#endif + } + + static bool Read(gfx::mojom::AcceleratedWidgetDataView data, + gfx::AcceleratedWidget* out) { +#if defined(OS_WIN) || defined(OS_ANDROID) || defined(OS_IOS) + *out = reinterpret_cast(data.widget()); + return true; +#elif defined(USE_OZONE) || defined(USE_X11) || defined(OS_MAC) + *out = static_cast(data.widget()); + return true; +#else + NOTREACHED(); + return false; +#endif + } +}; + +} // namespace mojo + +#endif // UI_GFX_MOJOM_ACCELERATED_WIDGET_MOJOM_TRAITS_H_ diff --git a/mojom/buffer_types.mojom b/mojom/buffer_types.mojom new file mode 100644 index 000000000000..1072f738f597 --- /dev/null +++ b/mojom/buffer_types.mojom @@ -0,0 +1,68 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module gfx.mojom; + +import "ui/gfx/mojom/native_handle_types.mojom"; + +// gfx::BufferFormat +enum BufferFormat { + R_8, + R_16, + RG_88, + BGR_565, + RGBA_4444, + RGBX_8888, + RGBA_8888, + BGRX_8888, + BGRA_1010102, + RGBA_1010102, + BGRA_8888, + RGBA_F16, + YVU_420, + YUV_420_BIPLANAR, + P010, +}; + +// gfx::BufferUsage +enum BufferUsage { + GPU_READ, + SCANOUT, + SCANOUT_CAMERA_READ_WRITE, + CAMERA_AND_CPU_READ_WRITE, + SCANOUT_CPU_READ_WRITE, + SCANOUT_VDA_WRITE, + PROTECTED_SCANOUT_VDA_WRITE, + GPU_READ_CPU_READ_WRITE, + SCANOUT_VEA_CPU_READ, + VEA_READ_CAMERA_AND_CPU_READ_WRITE, + SCANOUT_FRONT_RENDERING, +}; + +struct BufferUsageAndFormat { + BufferUsage usage; + BufferFormat format; +}; + +enum BufferPlane { + DEFAULT, + Y, + UV, + U, + V, +}; + +// gfx::GpuMemoryBufferId +[Stable] +struct GpuMemoryBufferId { + int32 id; +}; + +// gfx::GpuMemoryBufferHandle +struct GpuMemoryBufferHandle { + GpuMemoryBufferId id; + uint32 offset; + uint32 stride; + GpuMemoryBufferPlatformHandle? platform_handle; +}; diff --git a/mojom/buffer_types_mojom_traits.cc b/mojom/buffer_types_mojom_traits.cc new file mode 100644 index 000000000000..9af344380cbf --- /dev/null +++ b/mojom/buffer_types_mojom_traits.cc @@ -0,0 +1,169 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/mojom/buffer_types_mojom_traits.h" + +#include "build/build_config.h" + +#if defined(OS_ANDROID) +#include "base/android/scoped_hardware_buffer_handle.h" +#include "mojo/public/cpp/system/message_pipe.h" +#include "mojo/public/cpp/system/scope_to_message_pipe.h" +#endif + +namespace mojo { + +// static +bool StructTraits:: + Read(gfx::mojom::BufferUsageAndFormatDataView data, + gfx::BufferUsageAndFormat* out) { + return data.ReadUsage(&out->usage) && data.ReadFormat(&out->format); +} + +gfx::mojom::GpuMemoryBufferPlatformHandlePtr StructTraits< + gfx::mojom::GpuMemoryBufferHandleDataView, + gfx::GpuMemoryBufferHandle>::platform_handle(gfx::GpuMemoryBufferHandle& + handle) { + switch (handle.type) { + case gfx::EMPTY_BUFFER: + break; + case gfx::SHARED_MEMORY_BUFFER: + return gfx::mojom::GpuMemoryBufferPlatformHandle::NewSharedMemoryHandle( + std::move(handle.region)); + case gfx::NATIVE_PIXMAP: +#if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(USE_OZONE) + return gfx::mojom::GpuMemoryBufferPlatformHandle::NewNativePixmapHandle( + std::move(handle.native_pixmap_handle)); +#else + break; +#endif + case gfx::IO_SURFACE_BUFFER: { +#if defined(OS_MAC) + gfx::ScopedRefCountedIOSurfaceMachPort io_surface_mach_port( + IOSurfaceCreateMachPort(handle.io_surface.get())); + return gfx::mojom::GpuMemoryBufferPlatformHandle::NewMachPort( + mojo::PlatformHandle( + base::mac::RetainMachSendRight(io_surface_mach_port.get()))); +#else + break; +#endif + } + case gfx::DXGI_SHARED_HANDLE: +#if defined(OS_WIN) + DCHECK(handle.dxgi_handle.IsValid()); + return gfx::mojom::GpuMemoryBufferPlatformHandle::NewDxgiHandle( + gfx::mojom::DxgiHandle::New( + mojo::PlatformHandle(std::move(handle.dxgi_handle)), + std::move(handle.region))); +#else + break; +#endif + case gfx::ANDROID_HARDWARE_BUFFER: { +#if defined(OS_ANDROID) + // We must keep a ref to the AHardwareBuffer alive until the receiver has + // acquired its own reference. We do this by sending a message pipe handle + // along with the buffer. When the receiver deserializes (or even if they + // die without ever reading the message) their end of the pipe will be + // closed. We will eventually detect this and release the AHB reference. + mojo::MessagePipe tracking_pipe; + auto wrapped_handle = gfx::mojom::AHardwareBufferHandle::New( + mojo::PlatformHandle( + handle.android_hardware_buffer.SerializeAsFileDescriptor()), + std::move(tracking_pipe.handle0)); + + // Pass ownership of the input handle to our tracking pipe to keep the AHB + // alive until it's deserialized. + mojo::ScopeToMessagePipe(std::move(handle.android_hardware_buffer), + std::move(tracking_pipe.handle1)); + return gfx::mojom::GpuMemoryBufferPlatformHandle:: + NewAndroidHardwareBufferHandle(std::move(wrapped_handle)); +#else + break; +#endif + } + } + + return nullptr; +} + +bool StructTraits:: + Read(gfx::mojom::GpuMemoryBufferHandleDataView data, + gfx::GpuMemoryBufferHandle* out) { + if (!data.ReadId(&out->id)) + return false; + + out->offset = data.offset(); + out->stride = data.stride(); + + gfx::mojom::GpuMemoryBufferPlatformHandlePtr platform_handle; + if (!data.ReadPlatformHandle(&platform_handle)) { + return false; + } + + if (!platform_handle) { + out->type = gfx::EMPTY_BUFFER; + return true; + } + + switch (platform_handle->which()) { + case gfx::mojom::GpuMemoryBufferPlatformHandleDataView::Tag:: + SHARED_MEMORY_HANDLE: + out->type = gfx::SHARED_MEMORY_BUFFER; + out->region = std::move(platform_handle->get_shared_memory_handle()); + return true; +#if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(USE_OZONE) + case gfx::mojom::GpuMemoryBufferPlatformHandleDataView::Tag:: + NATIVE_PIXMAP_HANDLE: + out->type = gfx::NATIVE_PIXMAP; + out->native_pixmap_handle = + std::move(platform_handle->get_native_pixmap_handle()); + return true; +#elif defined(OS_MAC) + case gfx::mojom::GpuMemoryBufferPlatformHandleDataView::Tag::MACH_PORT: { + out->type = gfx::IO_SURFACE_BUFFER; + if (!platform_handle->get_mach_port().is_mach_send()) + return false; + gfx::ScopedRefCountedIOSurfaceMachPort io_surface_mach_port( + platform_handle->get_mach_port().ReleaseMachSendRight()); + if (io_surface_mach_port) { + out->io_surface.reset( + IOSurfaceLookupFromMachPort(io_surface_mach_port.get())); + } else { + out->io_surface.reset(); + } + return true; + } +#elif defined(OS_WIN) + case gfx::mojom::GpuMemoryBufferPlatformHandleDataView::Tag::DXGI_HANDLE: { + out->type = gfx::DXGI_SHARED_HANDLE; + auto dxgi_handle = std::move(platform_handle->get_dxgi_handle()); + out->dxgi_handle = dxgi_handle->buffer_handle.TakeHandle(); + out->region = std::move(dxgi_handle->shared_memory_handle); + return true; + } +#elif defined(OS_ANDROID) + case gfx::mojom::GpuMemoryBufferPlatformHandleDataView::Tag:: + ANDROID_HARDWARE_BUFFER_HANDLE: { + out->type = gfx::ANDROID_HARDWARE_BUFFER; + gfx::mojom::AHardwareBufferHandlePtr buffer_handle = + std::move(platform_handle->get_android_hardware_buffer_handle()); + if (!buffer_handle) + return false; + + base::ScopedFD scoped_fd = buffer_handle->buffer_handle.TakeFD(); + if (!scoped_fd.is_valid()) + return false; + out->android_hardware_buffer = base::android::ScopedHardwareBufferHandle:: + DeserializeFromFileDescriptor(std::move(scoped_fd)); + return out->android_hardware_buffer.is_valid(); + } +#endif + } + + return false; +} + +} // namespace mojo diff --git a/mojom/buffer_types_mojom_traits.h b/mojom/buffer_types_mojom_traits.h new file mode 100644 index 000000000000..5ed27e0f5f13 --- /dev/null +++ b/mojom/buffer_types_mojom_traits.h @@ -0,0 +1,282 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_MOJOM_BUFFER_TYPES_MOJOM_TRAITS_H_ +#define UI_GFX_MOJOM_BUFFER_TYPES_MOJOM_TRAITS_H_ + + +#include "base/component_export.h" +#include "build/build_config.h" +#include "mojo/public/cpp/bindings/enum_traits.h" +#include "mojo/public/cpp/bindings/struct_traits.h" +#include "ui/gfx/buffer_types.h" +#include "ui/gfx/gpu_memory_buffer.h" +#include "ui/gfx/mojom/buffer_types.mojom-shared.h" +#include "ui/gfx/mojom/native_handle_types.mojom.h" + +namespace mojo { + +template <> +struct COMPONENT_EXPORT(GFX_SHARED_MOJOM_TRAITS) + EnumTraits { + static gfx::mojom::BufferFormat ToMojom(gfx::BufferFormat format) { + switch (format) { + case gfx::BufferFormat::R_8: + return gfx::mojom::BufferFormat::R_8; + case gfx::BufferFormat::R_16: + return gfx::mojom::BufferFormat::R_16; + case gfx::BufferFormat::RG_88: + return gfx::mojom::BufferFormat::RG_88; + case gfx::BufferFormat::BGR_565: + return gfx::mojom::BufferFormat::BGR_565; + case gfx::BufferFormat::RGBA_4444: + return gfx::mojom::BufferFormat::RGBA_4444; + case gfx::BufferFormat::RGBX_8888: + return gfx::mojom::BufferFormat::RGBX_8888; + case gfx::BufferFormat::RGBA_8888: + return gfx::mojom::BufferFormat::RGBA_8888; + case gfx::BufferFormat::BGRX_8888: + return gfx::mojom::BufferFormat::BGRX_8888; + case gfx::BufferFormat::BGRA_1010102: + return gfx::mojom::BufferFormat::BGRA_1010102; + case gfx::BufferFormat::RGBA_1010102: + return gfx::mojom::BufferFormat::RGBA_1010102; + case gfx::BufferFormat::BGRA_8888: + return gfx::mojom::BufferFormat::BGRA_8888; + case gfx::BufferFormat::RGBA_F16: + return gfx::mojom::BufferFormat::RGBA_F16; + case gfx::BufferFormat::YVU_420: + return gfx::mojom::BufferFormat::YVU_420; + case gfx::BufferFormat::YUV_420_BIPLANAR: + return gfx::mojom::BufferFormat::YUV_420_BIPLANAR; + case gfx::BufferFormat::P010: + return gfx::mojom::BufferFormat::P010; + } + NOTREACHED(); + return gfx::mojom::BufferFormat::kMinValue; + } + + static bool FromMojom(gfx::mojom::BufferFormat input, + gfx::BufferFormat* out) { + switch (input) { + case gfx::mojom::BufferFormat::R_8: + *out = gfx::BufferFormat::R_8; + return true; + case gfx::mojom::BufferFormat::R_16: + *out = gfx::BufferFormat::R_16; + return true; + case gfx::mojom::BufferFormat::RG_88: + *out = gfx::BufferFormat::RG_88; + return true; + case gfx::mojom::BufferFormat::BGR_565: + *out = gfx::BufferFormat::BGR_565; + return true; + case gfx::mojom::BufferFormat::RGBA_4444: + *out = gfx::BufferFormat::RGBA_4444; + return true; + case gfx::mojom::BufferFormat::RGBX_8888: + *out = gfx::BufferFormat::RGBX_8888; + return true; + case gfx::mojom::BufferFormat::BGRA_1010102: + *out = gfx::BufferFormat::BGRA_1010102; + return true; + case gfx::mojom::BufferFormat::RGBA_1010102: + *out = gfx::BufferFormat::RGBA_1010102; + return true; + case gfx::mojom::BufferFormat::RGBA_8888: + *out = gfx::BufferFormat::RGBA_8888; + return true; + case gfx::mojom::BufferFormat::BGRX_8888: + *out = gfx::BufferFormat::BGRX_8888; + return true; + case gfx::mojom::BufferFormat::BGRA_8888: + *out = gfx::BufferFormat::BGRA_8888; + return true; + case gfx::mojom::BufferFormat::RGBA_F16: + *out = gfx::BufferFormat::RGBA_F16; + return true; + case gfx::mojom::BufferFormat::YVU_420: + *out = gfx::BufferFormat::YVU_420; + return true; + case gfx::mojom::BufferFormat::YUV_420_BIPLANAR: + *out = gfx::BufferFormat::YUV_420_BIPLANAR; + return true; + case gfx::mojom::BufferFormat::P010: + *out = gfx::BufferFormat::P010; + return true; + } + NOTREACHED(); + return false; + } +}; + +template <> +struct COMPONENT_EXPORT(GFX_SHARED_MOJOM_TRAITS) + EnumTraits { + static gfx::mojom::BufferUsage ToMojom(gfx::BufferUsage usage) { + switch (usage) { + case gfx::BufferUsage::GPU_READ: + return gfx::mojom::BufferUsage::GPU_READ; + case gfx::BufferUsage::SCANOUT: + return gfx::mojom::BufferUsage::SCANOUT; + case gfx::BufferUsage::SCANOUT_CAMERA_READ_WRITE: + return gfx::mojom::BufferUsage::SCANOUT_CAMERA_READ_WRITE; + case gfx::BufferUsage::CAMERA_AND_CPU_READ_WRITE: + return gfx::mojom::BufferUsage::CAMERA_AND_CPU_READ_WRITE; + case gfx::BufferUsage::SCANOUT_CPU_READ_WRITE: + return gfx::mojom::BufferUsage::SCANOUT_CPU_READ_WRITE; + case gfx::BufferUsage::SCANOUT_VDA_WRITE: + return gfx::mojom::BufferUsage::SCANOUT_VDA_WRITE; + case gfx::BufferUsage::PROTECTED_SCANOUT_VDA_WRITE: + return gfx::mojom::BufferUsage::PROTECTED_SCANOUT_VDA_WRITE; + case gfx::BufferUsage::GPU_READ_CPU_READ_WRITE: + return gfx::mojom::BufferUsage::GPU_READ_CPU_READ_WRITE; + case gfx::BufferUsage::SCANOUT_VEA_CPU_READ: + return gfx::mojom::BufferUsage::SCANOUT_VEA_CPU_READ; + case gfx::BufferUsage::VEA_READ_CAMERA_AND_CPU_READ_WRITE: + return gfx::mojom::BufferUsage::VEA_READ_CAMERA_AND_CPU_READ_WRITE; + case gfx::BufferUsage::SCANOUT_FRONT_RENDERING: + return gfx::mojom::BufferUsage::SCANOUT_FRONT_RENDERING; + } + NOTREACHED(); + return gfx::mojom::BufferUsage::kMinValue; + } + + static bool FromMojom(gfx::mojom::BufferUsage input, gfx::BufferUsage* out) { + switch (input) { + case gfx::mojom::BufferUsage::GPU_READ: + *out = gfx::BufferUsage::GPU_READ; + return true; + case gfx::mojom::BufferUsage::SCANOUT: + *out = gfx::BufferUsage::SCANOUT; + return true; + case gfx::mojom::BufferUsage::SCANOUT_CAMERA_READ_WRITE: + *out = gfx::BufferUsage::SCANOUT_CAMERA_READ_WRITE; + return true; + case gfx::mojom::BufferUsage::CAMERA_AND_CPU_READ_WRITE: + *out = gfx::BufferUsage::CAMERA_AND_CPU_READ_WRITE; + return true; + case gfx::mojom::BufferUsage::SCANOUT_CPU_READ_WRITE: + *out = gfx::BufferUsage::SCANOUT_CPU_READ_WRITE; + return true; + case gfx::mojom::BufferUsage::SCANOUT_VDA_WRITE: + *out = gfx::BufferUsage::SCANOUT_VDA_WRITE; + return true; + case gfx::mojom::BufferUsage::PROTECTED_SCANOUT_VDA_WRITE: + *out = gfx::BufferUsage::PROTECTED_SCANOUT_VDA_WRITE; + return true; + case gfx::mojom::BufferUsage::GPU_READ_CPU_READ_WRITE: + *out = gfx::BufferUsage::GPU_READ_CPU_READ_WRITE; + return true; + case gfx::mojom::BufferUsage::SCANOUT_VEA_CPU_READ: + *out = gfx::BufferUsage::SCANOUT_VEA_CPU_READ; + return true; + case gfx::mojom::BufferUsage::VEA_READ_CAMERA_AND_CPU_READ_WRITE: + *out = gfx::BufferUsage::VEA_READ_CAMERA_AND_CPU_READ_WRITE; + return true; + case gfx::mojom::BufferUsage::SCANOUT_FRONT_RENDERING: + *out = gfx::BufferUsage::SCANOUT_FRONT_RENDERING; + return true; + } + NOTREACHED(); + return false; + } +}; + +template <> +struct COMPONENT_EXPORT(GFX_SHARED_MOJOM_TRAITS) + StructTraits { + static gfx::BufferUsage usage(const gfx::BufferUsageAndFormat& input) { + return input.usage; + } + + static gfx::BufferFormat format(const gfx::BufferUsageAndFormat& input) { + return input.format; + } + + static bool Read(gfx::mojom::BufferUsageAndFormatDataView data, + gfx::BufferUsageAndFormat* out); +}; + +template <> +struct COMPONENT_EXPORT(GFX_SHARED_MOJOM_TRAITS) + StructTraits { + static int32_t id(const gfx::GpuMemoryBufferId& buffer_id) { + return buffer_id.id; + } + static bool Read(gfx::mojom::GpuMemoryBufferIdDataView data, + gfx::GpuMemoryBufferId* out) { + out->id = data.id(); + return true; + } +}; + +template <> +struct COMPONENT_EXPORT(GFX_SHARED_MOJOM_TRAITS) + StructTraits { + static gfx::GpuMemoryBufferId id(const gfx::GpuMemoryBufferHandle& handle) { + return handle.id; + } + static uint32_t offset(const gfx::GpuMemoryBufferHandle& handle) { + return handle.offset; + } + static uint32_t stride(const gfx::GpuMemoryBufferHandle& handle) { + return handle.stride; + } + static mojo::StructPtr + platform_handle(gfx::GpuMemoryBufferHandle& handle); + + static bool Read(gfx::mojom::GpuMemoryBufferHandleDataView data, + gfx::GpuMemoryBufferHandle* handle); +}; + +template <> +struct COMPONENT_EXPORT(GFX_SHARED_MOJOM_TRAITS) + EnumTraits { + static gfx::mojom::BufferPlane ToMojom(gfx::BufferPlane format) { + switch (format) { + case gfx::BufferPlane::DEFAULT: + return gfx::mojom::BufferPlane::DEFAULT; + case gfx::BufferPlane::Y: + return gfx::mojom::BufferPlane::Y; + case gfx::BufferPlane::UV: + return gfx::mojom::BufferPlane::UV; + case gfx::BufferPlane::U: + return gfx::mojom::BufferPlane::U; + case gfx::BufferPlane::V: + return gfx::mojom::BufferPlane::V; + } + NOTREACHED(); + return gfx::mojom::BufferPlane::kMinValue; + } + + static bool FromMojom(gfx::mojom::BufferPlane input, gfx::BufferPlane* out) { + switch (input) { + case gfx::mojom::BufferPlane::DEFAULT: + *out = gfx::BufferPlane::DEFAULT; + return true; + case gfx::mojom::BufferPlane::Y: + *out = gfx::BufferPlane::Y; + return true; + case gfx::mojom::BufferPlane::UV: + *out = gfx::BufferPlane::UV; + return true; + case gfx::mojom::BufferPlane::U: + *out = gfx::BufferPlane::U; + return true; + case gfx::mojom::BufferPlane::V: + *out = gfx::BufferPlane::V; + return true; + } + NOTREACHED(); + return false; + } +}; + +} // namespace mojo + +#endif // UI_GFX_MOJOM_BUFFER_TYPES_MOJOM_TRAITS_H_ diff --git a/mojom/ca_layer_params.mojom b/mojom/ca_layer_params.mojom new file mode 100644 index 000000000000..de00e766ba17 --- /dev/null +++ b/mojom/ca_layer_params.mojom @@ -0,0 +1,22 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module gfx.mojom; + +import "ui/gfx/geometry/mojom/geometry.mojom"; + +union CALayerContent { + uint32 ca_context_id; + handle io_surface_mach_port; +}; + +// gfx::CALayerParams +struct CALayerParams { + // TODO(676224): Use preprocessor to restrict platform-specific members to + // desired platform. + bool is_empty; + CALayerContent content; + gfx.mojom.Size pixel_size; + float scale_factor; +}; diff --git a/mojom/ca_layer_params_mojom_traits.cc b/mojom/ca_layer_params_mojom_traits.cc new file mode 100644 index 000000000000..c1f21d7f4348 --- /dev/null +++ b/mojom/ca_layer_params_mojom_traits.cc @@ -0,0 +1,59 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/mojom/ca_layer_params_mojom_traits.h" + +#include "build/build_config.h" +#include "mojo/public/cpp/system/platform_handle.h" +#include "ui/gfx/geometry/mojom/geometry_mojom_traits.h" + +namespace mojo { + +gfx::mojom::CALayerContentPtr +StructTraits::content( + const gfx::CALayerParams& ca_layer_params) { +#if defined(OS_MAC) + if (ca_layer_params.io_surface_mach_port) { + DCHECK(!ca_layer_params.ca_context_id); + return gfx::mojom::CALayerContent::NewIoSurfaceMachPort( + mojo::PlatformHandle(base::mac::RetainMachSendRight( + ca_layer_params.io_surface_mach_port.get()))); + } +#endif + return gfx::mojom::CALayerContent::NewCaContextId( + ca_layer_params.ca_context_id); +} + +bool StructTraits::Read( + gfx::mojom::CALayerParamsDataView data, + gfx::CALayerParams* out) { + out->is_empty = data.is_empty(); + + gfx::mojom::CALayerContentDataView content_data; + data.GetContentDataView(&content_data); + switch (content_data.tag()) { + case gfx::mojom::CALayerContentDataView::Tag::CA_CONTEXT_ID: + out->ca_context_id = content_data.ca_context_id(); + break; + case gfx::mojom::CALayerContentDataView::Tag::IO_SURFACE_MACH_PORT: +#if defined(OS_MAC) + mojo::PlatformHandle platform_handle = + content_data.TakeIoSurfaceMachPort(); + if (!platform_handle.is_mach_send()) + return false; + out->io_surface_mach_port.reset(platform_handle.ReleaseMachSendRight()); + break; +#else + return false; +#endif + } + + if (!data.ReadPixelSize(&out->pixel_size)) + return false; + + out->scale_factor = data.scale_factor(); + return true; +} + +} // namespace mojo diff --git a/mojom/ca_layer_params_mojom_traits.h b/mojom/ca_layer_params_mojom_traits.h new file mode 100644 index 000000000000..4cac766eae31 --- /dev/null +++ b/mojom/ca_layer_params_mojom_traits.h @@ -0,0 +1,36 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_MOJOM_CA_LAYER_PARAMS_MOJOM_TRAITS_H_ +#define UI_GFX_MOJOM_CA_LAYER_PARAMS_MOJOM_TRAITS_H_ + +#include "ui/gfx/ca_layer_params.h" +#include "ui/gfx/mojom/ca_layer_params.mojom.h" + +namespace mojo { + +template <> +struct StructTraits { + static uint32_t is_empty(const gfx::CALayerParams& ca_layer_params) { + return ca_layer_params.is_empty; + } + + static gfx::Size pixel_size(const gfx::CALayerParams& ca_layer_params) { + return ca_layer_params.pixel_size; + } + + static float scale_factor(const gfx::CALayerParams& ca_layer_params) { + return ca_layer_params.scale_factor; + } + + static gfx::mojom::CALayerContentPtr content( + const gfx::CALayerParams& ca_layer_params); + + static bool Read(gfx::mojom::CALayerParamsDataView data, + gfx::CALayerParams* out); +}; + +} // namespace mojo + +#endif // UI_GFX_MOJOM_CA_LAYER_PARAMS_MOJOM_TRAITS_H_ diff --git a/mojom/color_space.mojom b/mojom/color_space.mojom new file mode 100644 index 000000000000..1955dea6f361 --- /dev/null +++ b/mojom/color_space.mojom @@ -0,0 +1,86 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module gfx.mojom; + +// Mojo equivalent of ui::gfx::ColorSpace. See ui/gfx/color_space.h for +// additional documentation. + +enum ColorSpacePrimaryID { + INVALID, + BT709, + BT470M, + BT470BG, + SMPTE170M, + SMPTE240M, + FILM, + BT2020, + SMPTEST428_1, + SMPTEST431_2, + SMPTEST432_1, + XYZ_D50, + ADOBE_RGB, + APPLE_GENERIC_RGB, + WIDE_GAMUT_COLOR_SPIN, + CUSTOM +}; + +enum ColorSpaceTransferID { + INVALID, + BT709, + BT709_APPLE, + GAMMA18, + GAMMA22, + GAMMA24, + GAMMA28, + SMPTE170M, + SMPTE240M, + LINEAR, + LOG, + LOG_SQRT, + IEC61966_2_4, + BT1361_ECG, + IEC61966_2_1, + BT2020_10, + BT2020_12, + SMPTEST2084, + SMPTEST428_1, + ARIB_STD_B67, + IEC61966_2_1_HDR, + LINEAR_HDR, + CUSTOM, + CUSTOM_HDR, + PIECEWISE_HDR +}; + +enum ColorSpaceMatrixID { + INVALID, + RGB, + BT709, + FCC, + BT470BG, + SMPTE170M, + SMPTE240M, + YCOCG, + BT2020_NCL, + BT2020_CL, + YDZDX, + GBR +}; + +enum ColorSpaceRangeID { + INVALID, + LIMITED, + FULL, + DERIVED +}; + +struct ColorSpace { + ColorSpacePrimaryID primaries; + ColorSpaceTransferID transfer; + ColorSpaceMatrixID matrix; + ColorSpaceRangeID range; + array custom_primary_matrix; + array transfer_params; +}; diff --git a/mojom/color_space_mojom_traits.cc b/mojom/color_space_mojom_traits.cc new file mode 100644 index 000000000000..6aa83b1817c0 --- /dev/null +++ b/mojom/color_space_mojom_traits.cc @@ -0,0 +1,35 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/mojom/color_space_mojom_traits.h" + + +namespace mojo { + +// static +bool StructTraits::Read( + gfx::mojom::ColorSpaceDataView input, + gfx::ColorSpace* out) { + if (!input.ReadPrimaries(&out->primaries_)) + return false; + if (!input.ReadTransfer(&out->transfer_)) + return false; + if (!input.ReadMatrix(&out->matrix_)) + return false; + if (!input.ReadRange(&out->range_)) + return false; + { + base::span matrix(out->custom_primary_matrix_); + if (!input.ReadCustomPrimaryMatrix(&matrix)) + return false; + } + { + base::span matrix(out->transfer_params_); + if (!input.ReadTransferParams(&matrix)) + return false; + } + return true; +} + +} // namespace mojo diff --git a/mojom/color_space_mojom_traits.h b/mojom/color_space_mojom_traits.h new file mode 100644 index 000000000000..28943a0cf9ec --- /dev/null +++ b/mojom/color_space_mojom_traits.h @@ -0,0 +1,409 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_MOJOM_COLOR_SPACE_MOJOM_TRAITS_H_ +#define UI_GFX_MOJOM_COLOR_SPACE_MOJOM_TRAITS_H_ + +#include "base/component_export.h" +#include "base/containers/span.h" +#include "ui/gfx/color_space.h" +#include "ui/gfx/mojom/color_space.mojom-shared.h" + +namespace mojo { + +template <> +struct EnumTraits { + static gfx::mojom::ColorSpacePrimaryID ToMojom( + gfx::ColorSpace::PrimaryID input) { + switch (input) { + case gfx::ColorSpace::PrimaryID::INVALID: + return gfx::mojom::ColorSpacePrimaryID::INVALID; + case gfx::ColorSpace::PrimaryID::BT709: + return gfx::mojom::ColorSpacePrimaryID::BT709; + case gfx::ColorSpace::PrimaryID::BT470M: + return gfx::mojom::ColorSpacePrimaryID::BT470M; + case gfx::ColorSpace::PrimaryID::BT470BG: + return gfx::mojom::ColorSpacePrimaryID::BT470BG; + case gfx::ColorSpace::PrimaryID::SMPTE170M: + return gfx::mojom::ColorSpacePrimaryID::SMPTE170M; + case gfx::ColorSpace::PrimaryID::SMPTE240M: + return gfx::mojom::ColorSpacePrimaryID::SMPTE240M; + case gfx::ColorSpace::PrimaryID::FILM: + return gfx::mojom::ColorSpacePrimaryID::FILM; + case gfx::ColorSpace::PrimaryID::BT2020: + return gfx::mojom::ColorSpacePrimaryID::BT2020; + case gfx::ColorSpace::PrimaryID::SMPTEST428_1: + return gfx::mojom::ColorSpacePrimaryID::SMPTEST428_1; + case gfx::ColorSpace::PrimaryID::SMPTEST431_2: + return gfx::mojom::ColorSpacePrimaryID::SMPTEST431_2; + case gfx::ColorSpace::PrimaryID::SMPTEST432_1: + return gfx::mojom::ColorSpacePrimaryID::SMPTEST432_1; + case gfx::ColorSpace::PrimaryID::XYZ_D50: + return gfx::mojom::ColorSpacePrimaryID::XYZ_D50; + case gfx::ColorSpace::PrimaryID::ADOBE_RGB: + return gfx::mojom::ColorSpacePrimaryID::ADOBE_RGB; + case gfx::ColorSpace::PrimaryID::APPLE_GENERIC_RGB: + return gfx::mojom::ColorSpacePrimaryID::APPLE_GENERIC_RGB; + case gfx::ColorSpace::PrimaryID::WIDE_GAMUT_COLOR_SPIN: + return gfx::mojom::ColorSpacePrimaryID::WIDE_GAMUT_COLOR_SPIN; + case gfx::ColorSpace::PrimaryID::CUSTOM: + return gfx::mojom::ColorSpacePrimaryID::CUSTOM; + } + NOTREACHED(); + return gfx::mojom::ColorSpacePrimaryID::INVALID; + } + + static bool FromMojom(gfx::mojom::ColorSpacePrimaryID input, + gfx::ColorSpace::PrimaryID* out) { + switch (input) { + case gfx::mojom::ColorSpacePrimaryID::INVALID: + *out = gfx::ColorSpace::PrimaryID::INVALID; + return true; + case gfx::mojom::ColorSpacePrimaryID::BT709: + *out = gfx::ColorSpace::PrimaryID::BT709; + return true; + case gfx::mojom::ColorSpacePrimaryID::BT470M: + *out = gfx::ColorSpace::PrimaryID::BT470M; + return true; + case gfx::mojom::ColorSpacePrimaryID::BT470BG: + *out = gfx::ColorSpace::PrimaryID::BT470BG; + return true; + case gfx::mojom::ColorSpacePrimaryID::SMPTE170M: + *out = gfx::ColorSpace::PrimaryID::SMPTE170M; + return true; + case gfx::mojom::ColorSpacePrimaryID::SMPTE240M: + *out = gfx::ColorSpace::PrimaryID::SMPTE240M; + return true; + case gfx::mojom::ColorSpacePrimaryID::FILM: + *out = gfx::ColorSpace::PrimaryID::FILM; + return true; + case gfx::mojom::ColorSpacePrimaryID::BT2020: + *out = gfx::ColorSpace::PrimaryID::BT2020; + return true; + case gfx::mojom::ColorSpacePrimaryID::SMPTEST428_1: + *out = gfx::ColorSpace::PrimaryID::SMPTEST428_1; + return true; + case gfx::mojom::ColorSpacePrimaryID::SMPTEST431_2: + *out = gfx::ColorSpace::PrimaryID::SMPTEST431_2; + return true; + case gfx::mojom::ColorSpacePrimaryID::SMPTEST432_1: + *out = gfx::ColorSpace::PrimaryID::SMPTEST432_1; + return true; + case gfx::mojom::ColorSpacePrimaryID::XYZ_D50: + *out = gfx::ColorSpace::PrimaryID::XYZ_D50; + return true; + case gfx::mojom::ColorSpacePrimaryID::ADOBE_RGB: + *out = gfx::ColorSpace::PrimaryID::ADOBE_RGB; + return true; + case gfx::mojom::ColorSpacePrimaryID::APPLE_GENERIC_RGB: + *out = gfx::ColorSpace::PrimaryID::APPLE_GENERIC_RGB; + return true; + case gfx::mojom::ColorSpacePrimaryID::WIDE_GAMUT_COLOR_SPIN: + *out = gfx::ColorSpace::PrimaryID::WIDE_GAMUT_COLOR_SPIN; + return true; + case gfx::mojom::ColorSpacePrimaryID::CUSTOM: + *out = gfx::ColorSpace::PrimaryID::CUSTOM; + return true; + } + NOTREACHED(); + return false; + } +}; + +template <> +struct EnumTraits { + static gfx::mojom::ColorSpaceTransferID ToMojom( + gfx::ColorSpace::TransferID input) { + switch (input) { + case gfx::ColorSpace::TransferID::INVALID: + return gfx::mojom::ColorSpaceTransferID::INVALID; + case gfx::ColorSpace::TransferID::BT709: + return gfx::mojom::ColorSpaceTransferID::BT709; + case gfx::ColorSpace::TransferID::BT709_APPLE: + return gfx::mojom::ColorSpaceTransferID::BT709_APPLE; + case gfx::ColorSpace::TransferID::GAMMA18: + return gfx::mojom::ColorSpaceTransferID::GAMMA18; + case gfx::ColorSpace::TransferID::GAMMA22: + return gfx::mojom::ColorSpaceTransferID::GAMMA22; + case gfx::ColorSpace::TransferID::GAMMA24: + return gfx::mojom::ColorSpaceTransferID::GAMMA24; + case gfx::ColorSpace::TransferID::GAMMA28: + return gfx::mojom::ColorSpaceTransferID::GAMMA28; + case gfx::ColorSpace::TransferID::SMPTE170M: + return gfx::mojom::ColorSpaceTransferID::SMPTE170M; + case gfx::ColorSpace::TransferID::SMPTE240M: + return gfx::mojom::ColorSpaceTransferID::SMPTE240M; + case gfx::ColorSpace::TransferID::LINEAR: + return gfx::mojom::ColorSpaceTransferID::LINEAR; + case gfx::ColorSpace::TransferID::LOG: + return gfx::mojom::ColorSpaceTransferID::LOG; + case gfx::ColorSpace::TransferID::LOG_SQRT: + return gfx::mojom::ColorSpaceTransferID::LOG_SQRT; + case gfx::ColorSpace::TransferID::IEC61966_2_4: + return gfx::mojom::ColorSpaceTransferID::IEC61966_2_4; + case gfx::ColorSpace::TransferID::BT1361_ECG: + return gfx::mojom::ColorSpaceTransferID::BT1361_ECG; + case gfx::ColorSpace::TransferID::IEC61966_2_1: + return gfx::mojom::ColorSpaceTransferID::IEC61966_2_1; + case gfx::ColorSpace::TransferID::BT2020_10: + return gfx::mojom::ColorSpaceTransferID::BT2020_10; + case gfx::ColorSpace::TransferID::BT2020_12: + return gfx::mojom::ColorSpaceTransferID::BT2020_12; + case gfx::ColorSpace::TransferID::SMPTEST2084: + return gfx::mojom::ColorSpaceTransferID::SMPTEST2084; + case gfx::ColorSpace::TransferID::SMPTEST428_1: + return gfx::mojom::ColorSpaceTransferID::SMPTEST428_1; + case gfx::ColorSpace::TransferID::ARIB_STD_B67: + return gfx::mojom::ColorSpaceTransferID::ARIB_STD_B67; + case gfx::ColorSpace::TransferID::IEC61966_2_1_HDR: + return gfx::mojom::ColorSpaceTransferID::IEC61966_2_1_HDR; + case gfx::ColorSpace::TransferID::LINEAR_HDR: + return gfx::mojom::ColorSpaceTransferID::LINEAR_HDR; + case gfx::ColorSpace::TransferID::CUSTOM: + return gfx::mojom::ColorSpaceTransferID::CUSTOM; + case gfx::ColorSpace::TransferID::CUSTOM_HDR: + return gfx::mojom::ColorSpaceTransferID::CUSTOM_HDR; + case gfx::ColorSpace::TransferID::PIECEWISE_HDR: + return gfx::mojom::ColorSpaceTransferID::PIECEWISE_HDR; + } + NOTREACHED(); + return gfx::mojom::ColorSpaceTransferID::INVALID; + } + + static bool FromMojom(gfx::mojom::ColorSpaceTransferID input, + gfx::ColorSpace::TransferID* out) { + switch (input) { + case gfx::mojom::ColorSpaceTransferID::INVALID: + *out = gfx::ColorSpace::TransferID::INVALID; + return true; + case gfx::mojom::ColorSpaceTransferID::BT709: + *out = gfx::ColorSpace::TransferID::BT709; + return true; + case gfx::mojom::ColorSpaceTransferID::BT709_APPLE: + *out = gfx::ColorSpace::TransferID::BT709_APPLE; + return true; + case gfx::mojom::ColorSpaceTransferID::GAMMA18: + *out = gfx::ColorSpace::TransferID::GAMMA18; + return true; + case gfx::mojom::ColorSpaceTransferID::GAMMA22: + *out = gfx::ColorSpace::TransferID::GAMMA22; + return true; + case gfx::mojom::ColorSpaceTransferID::GAMMA24: + *out = gfx::ColorSpace::TransferID::GAMMA24; + return true; + case gfx::mojom::ColorSpaceTransferID::GAMMA28: + *out = gfx::ColorSpace::TransferID::GAMMA28; + return true; + case gfx::mojom::ColorSpaceTransferID::SMPTE170M: + *out = gfx::ColorSpace::TransferID::SMPTE170M; + return true; + case gfx::mojom::ColorSpaceTransferID::SMPTE240M: + *out = gfx::ColorSpace::TransferID::SMPTE240M; + return true; + case gfx::mojom::ColorSpaceTransferID::LINEAR: + *out = gfx::ColorSpace::TransferID::LINEAR; + return true; + case gfx::mojom::ColorSpaceTransferID::LOG: + *out = gfx::ColorSpace::TransferID::LOG; + return true; + case gfx::mojom::ColorSpaceTransferID::LOG_SQRT: + *out = gfx::ColorSpace::TransferID::LOG_SQRT; + return true; + case gfx::mojom::ColorSpaceTransferID::IEC61966_2_4: + *out = gfx::ColorSpace::TransferID::IEC61966_2_4; + return true; + case gfx::mojom::ColorSpaceTransferID::BT1361_ECG: + *out = gfx::ColorSpace::TransferID::BT1361_ECG; + return true; + case gfx::mojom::ColorSpaceTransferID::IEC61966_2_1: + *out = gfx::ColorSpace::TransferID::IEC61966_2_1; + return true; + case gfx::mojom::ColorSpaceTransferID::BT2020_10: + *out = gfx::ColorSpace::TransferID::BT2020_10; + return true; + case gfx::mojom::ColorSpaceTransferID::BT2020_12: + *out = gfx::ColorSpace::TransferID::BT2020_12; + return true; + case gfx::mojom::ColorSpaceTransferID::SMPTEST2084: + *out = gfx::ColorSpace::TransferID::SMPTEST2084; + return true; + case gfx::mojom::ColorSpaceTransferID::SMPTEST428_1: + *out = gfx::ColorSpace::TransferID::SMPTEST428_1; + return true; + case gfx::mojom::ColorSpaceTransferID::ARIB_STD_B67: + *out = gfx::ColorSpace::TransferID::ARIB_STD_B67; + return true; + case gfx::mojom::ColorSpaceTransferID::IEC61966_2_1_HDR: + *out = gfx::ColorSpace::TransferID::IEC61966_2_1_HDR; + return true; + case gfx::mojom::ColorSpaceTransferID::LINEAR_HDR: + *out = gfx::ColorSpace::TransferID::LINEAR_HDR; + return true; + case gfx::mojom::ColorSpaceTransferID::CUSTOM: + *out = gfx::ColorSpace::TransferID::CUSTOM; + return true; + case gfx::mojom::ColorSpaceTransferID::CUSTOM_HDR: + *out = gfx::ColorSpace::TransferID::CUSTOM_HDR; + return true; + case gfx::mojom::ColorSpaceTransferID::PIECEWISE_HDR: + *out = gfx::ColorSpace::TransferID::PIECEWISE_HDR; + return true; + } + NOTREACHED(); + return false; + } +}; + +template <> +struct EnumTraits { + static gfx::mojom::ColorSpaceMatrixID ToMojom( + gfx::ColorSpace::MatrixID input) { + switch (input) { + case gfx::ColorSpace::MatrixID::INVALID: + return gfx::mojom::ColorSpaceMatrixID::INVALID; + case gfx::ColorSpace::MatrixID::RGB: + return gfx::mojom::ColorSpaceMatrixID::RGB; + case gfx::ColorSpace::MatrixID::BT709: + return gfx::mojom::ColorSpaceMatrixID::BT709; + case gfx::ColorSpace::MatrixID::FCC: + return gfx::mojom::ColorSpaceMatrixID::FCC; + case gfx::ColorSpace::MatrixID::BT470BG: + return gfx::mojom::ColorSpaceMatrixID::BT470BG; + case gfx::ColorSpace::MatrixID::SMPTE170M: + return gfx::mojom::ColorSpaceMatrixID::SMPTE170M; + case gfx::ColorSpace::MatrixID::SMPTE240M: + return gfx::mojom::ColorSpaceMatrixID::SMPTE240M; + case gfx::ColorSpace::MatrixID::YCOCG: + return gfx::mojom::ColorSpaceMatrixID::YCOCG; + case gfx::ColorSpace::MatrixID::BT2020_NCL: + return gfx::mojom::ColorSpaceMatrixID::BT2020_NCL; + case gfx::ColorSpace::MatrixID::BT2020_CL: + return gfx::mojom::ColorSpaceMatrixID::BT2020_CL; + case gfx::ColorSpace::MatrixID::YDZDX: + return gfx::mojom::ColorSpaceMatrixID::YDZDX; + case gfx::ColorSpace::MatrixID::GBR: + return gfx::mojom::ColorSpaceMatrixID::GBR; + } + NOTREACHED(); + return gfx::mojom::ColorSpaceMatrixID::INVALID; + } + + static bool FromMojom(gfx::mojom::ColorSpaceMatrixID input, + gfx::ColorSpace::MatrixID* out) { + switch (input) { + case gfx::mojom::ColorSpaceMatrixID::INVALID: + *out = gfx::ColorSpace::MatrixID::INVALID; + return true; + case gfx::mojom::ColorSpaceMatrixID::RGB: + *out = gfx::ColorSpace::MatrixID::RGB; + return true; + case gfx::mojom::ColorSpaceMatrixID::BT709: + *out = gfx::ColorSpace::MatrixID::BT709; + return true; + case gfx::mojom::ColorSpaceMatrixID::FCC: + *out = gfx::ColorSpace::MatrixID::FCC; + return true; + case gfx::mojom::ColorSpaceMatrixID::BT470BG: + *out = gfx::ColorSpace::MatrixID::BT470BG; + return true; + case gfx::mojom::ColorSpaceMatrixID::SMPTE170M: + *out = gfx::ColorSpace::MatrixID::SMPTE170M; + return true; + case gfx::mojom::ColorSpaceMatrixID::SMPTE240M: + *out = gfx::ColorSpace::MatrixID::SMPTE240M; + return true; + case gfx::mojom::ColorSpaceMatrixID::YCOCG: + *out = gfx::ColorSpace::MatrixID::YCOCG; + return true; + case gfx::mojom::ColorSpaceMatrixID::BT2020_NCL: + *out = gfx::ColorSpace::MatrixID::BT2020_NCL; + return true; + case gfx::mojom::ColorSpaceMatrixID::BT2020_CL: + *out = gfx::ColorSpace::MatrixID::BT2020_CL; + return true; + case gfx::mojom::ColorSpaceMatrixID::YDZDX: + *out = gfx::ColorSpace::MatrixID::YDZDX; + return true; + case gfx::mojom::ColorSpaceMatrixID::GBR: + *out = gfx::ColorSpace::MatrixID::GBR; + return true; + } + NOTREACHED(); + return false; + } +}; + +template <> +struct EnumTraits { + static gfx::mojom::ColorSpaceRangeID ToMojom(gfx::ColorSpace::RangeID input) { + switch (input) { + case gfx::ColorSpace::RangeID::INVALID: + return gfx::mojom::ColorSpaceRangeID::INVALID; + case gfx::ColorSpace::RangeID::LIMITED: + return gfx::mojom::ColorSpaceRangeID::LIMITED; + case gfx::ColorSpace::RangeID::FULL: + return gfx::mojom::ColorSpaceRangeID::FULL; + case gfx::ColorSpace::RangeID::DERIVED: + return gfx::mojom::ColorSpaceRangeID::DERIVED; + } + NOTREACHED(); + return gfx::mojom::ColorSpaceRangeID::INVALID; + } + + static bool FromMojom(gfx::mojom::ColorSpaceRangeID input, + gfx::ColorSpace::RangeID* out) { + switch (input) { + case gfx::mojom::ColorSpaceRangeID::INVALID: + *out = gfx::ColorSpace::RangeID::INVALID; + return true; + case gfx::mojom::ColorSpaceRangeID::LIMITED: + *out = gfx::ColorSpace::RangeID::LIMITED; + return true; + case gfx::mojom::ColorSpaceRangeID::FULL: + *out = gfx::ColorSpace::RangeID::FULL; + return true; + case gfx::mojom::ColorSpaceRangeID::DERIVED: + *out = gfx::ColorSpace::RangeID::DERIVED; + return true; + } + NOTREACHED(); + return false; + } +}; + +template <> +struct COMPONENT_EXPORT(GFX_SHARED_MOJOM_TRAITS) + StructTraits { + static gfx::ColorSpace::PrimaryID primaries(const gfx::ColorSpace& input) { + return input.primaries_; + } + + static gfx::ColorSpace::TransferID transfer(const gfx::ColorSpace& input) { + return input.transfer_; + } + + static gfx::ColorSpace::MatrixID matrix(const gfx::ColorSpace& input) { + return input.matrix_; + } + + static gfx::ColorSpace::RangeID range(const gfx::ColorSpace& input) { + return input.range_; + } + + static base::span custom_primary_matrix( + const gfx::ColorSpace& input) { + return input.custom_primary_matrix_; + } + + static base::span transfer_params(const gfx::ColorSpace& input) { + return input.transfer_params_; + } + + static bool Read(gfx::mojom::ColorSpaceDataView data, gfx::ColorSpace* out); +}; + +} // namespace mojo + +#endif // UI_GFX_MOJOM_COLOR_SPACE_MOJOM_TRAITS_H_ diff --git a/mojom/delegated_ink_metadata.mojom b/mojom/delegated_ink_metadata.mojom new file mode 100644 index 000000000000..c3b5438888e8 --- /dev/null +++ b/mojom/delegated_ink_metadata.mojom @@ -0,0 +1,20 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module gfx.mojom; + +import "mojo/public/mojom/base/time.mojom"; +import "skia/public/mojom/skcolor.mojom"; +import "ui/gfx/geometry/mojom/geometry.mojom"; + +// See ui/gfx/delegated_ink_metadata.h. +struct DelegatedInkMetadata { + PointF point; + double diameter; + skia.mojom.SkColor color; + mojo_base.mojom.TimeTicks timestamp; + RectF presentation_area; + mojo_base.mojom.TimeTicks frame_time; + bool is_hovering; +}; diff --git a/mojom/delegated_ink_metadata_mojom_traits.cc b/mojom/delegated_ink_metadata_mojom_traits.cc new file mode 100644 index 000000000000..f3f10a896ab6 --- /dev/null +++ b/mojom/delegated_ink_metadata_mojom_traits.cc @@ -0,0 +1,35 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/mojom/delegated_ink_metadata_mojom_traits.h" + +namespace mojo { + +// static +bool StructTraits>:: + Read(gfx::mojom::DelegatedInkMetadataDataView data, + std::unique_ptr* out) { + // Diameter isn't expected to ever be below 0, so stop here if it is in order + // to avoid unexpected calculations in viz. + if (data.diameter() < 0) + return false; + + gfx::PointF point; + base::TimeTicks timestamp; + gfx::RectF presentation_area; + SkColor color; + base::TimeTicks frame_time; + if (!data.ReadPoint(&point) || !data.ReadTimestamp(×tamp) || + !data.ReadPresentationArea(&presentation_area) || + !data.ReadColor(&color) || !data.ReadFrameTime(&frame_time)) { + return false; + } + *out = std::make_unique( + point, data.diameter(), color, timestamp, presentation_area, frame_time, + data.is_hovering()); + return true; +} + +} // namespace mojo diff --git a/mojom/delegated_ink_metadata_mojom_traits.h b/mojom/delegated_ink_metadata_mojom_traits.h new file mode 100644 index 000000000000..b9c875d1f5a4 --- /dev/null +++ b/mojom/delegated_ink_metadata_mojom_traits.h @@ -0,0 +1,70 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_MOJOM_DELEGATED_INK_METADATA_MOJOM_TRAITS_H_ +#define UI_GFX_MOJOM_DELEGATED_INK_METADATA_MOJOM_TRAITS_H_ + +#include + +#include "mojo/public/cpp/base/time_mojom_traits.h" +#include "skia/public/mojom/skcolor_mojom_traits.h" +#include "ui/gfx/delegated_ink_metadata.h" +#include "ui/gfx/mojom/delegated_ink_metadata.mojom-shared.h" +#include "ui/gfx/mojom/rrect_f_mojom_traits.h" + +namespace mojo { + +template <> +struct StructTraits> { + static bool IsNull(const std::unique_ptr& input) { + return !input; + } + + static void SetToNull(std::unique_ptr* input) { + input->reset(); + } + + static const gfx::PointF& point( + const std::unique_ptr& input) { + return input->point(); + } + + static double diameter( + const std::unique_ptr& input) { + return input->diameter(); + } + + static SkColor color( + const std::unique_ptr& input) { + return input->color(); + } + + static base::TimeTicks timestamp( + const std::unique_ptr& input) { + return input->timestamp(); + } + + static const gfx::RectF& presentation_area( + const std::unique_ptr& input) { + return input->presentation_area(); + } + + static base::TimeTicks frame_time( + const std::unique_ptr& input) { + return input->frame_time(); + } + + static bool is_hovering( + const std::unique_ptr& input) { + return input->is_hovering(); + } + + static bool Read(gfx::mojom::DelegatedInkMetadataDataView data, + std::unique_ptr* out); +}; + +} // namespace mojo + +#endif // UI_GFX_MOJOM_DELEGATED_INK_METADATA_MOJOM_TRAITS_H_ diff --git a/mojom/delegated_ink_point.mojom b/mojom/delegated_ink_point.mojom new file mode 100644 index 000000000000..8ed6561c1c7e --- /dev/null +++ b/mojom/delegated_ink_point.mojom @@ -0,0 +1,15 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module gfx.mojom; + +import "mojo/public/mojom/base/time.mojom"; +import "ui/gfx/geometry/mojom/geometry.mojom"; + +// See ui/gfx/delegated_ink_point.h. +struct DelegatedInkPoint { + PointF point; + mojo_base.mojom.TimeTicks timestamp; + int32 pointer_id; +}; diff --git a/mojom/delegated_ink_point_mojom_traits.cc b/mojom/delegated_ink_point_mojom_traits.cc new file mode 100644 index 000000000000..97fd785876bd --- /dev/null +++ b/mojom/delegated_ink_point_mojom_traits.cc @@ -0,0 +1,18 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/mojom/delegated_ink_point_mojom_traits.h" + +namespace mojo { + +// static +bool StructTraits< + gfx::mojom::DelegatedInkPointDataView, + gfx::DelegatedInkPoint>::Read(gfx::mojom::DelegatedInkPointDataView data, + gfx::DelegatedInkPoint* out) { + out->pointer_id_ = data.pointer_id(); + return data.ReadPoint(&out->point_) && data.ReadTimestamp(&out->timestamp_); +} + +} // namespace mojo diff --git a/mojom/delegated_ink_point_mojom_traits.h b/mojom/delegated_ink_point_mojom_traits.h new file mode 100644 index 000000000000..b188b302631c --- /dev/null +++ b/mojom/delegated_ink_point_mojom_traits.h @@ -0,0 +1,36 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_MOJOM_DELEGATED_INK_POINT_MOJOM_TRAITS_H_ +#define UI_GFX_MOJOM_DELEGATED_INK_POINT_MOJOM_TRAITS_H_ + +#include "mojo/public/cpp/base/time_mojom_traits.h" +#include "ui/gfx/delegated_ink_point.h" +#include "ui/gfx/geometry/mojom/geometry_mojom_traits.h" +#include "ui/gfx/mojom/delegated_ink_point.mojom-shared.h" + +namespace mojo { + +template <> +struct StructTraits { + static const gfx::PointF& point(const gfx::DelegatedInkPoint& input) { + return input.point(); + } + + static base::TimeTicks timestamp(const gfx::DelegatedInkPoint& input) { + return input.timestamp(); + } + + static int32_t pointer_id(const gfx::DelegatedInkPoint& input) { + return input.pointer_id(); + } + + static bool Read(gfx::mojom::DelegatedInkPointDataView data, + gfx::DelegatedInkPoint* out); +}; + +} // namespace mojo + +#endif // UI_GFX_MOJOM_DELEGATED_INK_POINT_MOJOM_TRAITS_H_ diff --git a/mojom/delegated_ink_point_renderer.mojom b/mojom/delegated_ink_point_renderer.mojom new file mode 100644 index 000000000000..223f343afcda --- /dev/null +++ b/mojom/delegated_ink_point_renderer.mojom @@ -0,0 +1,25 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module gfx.mojom; + +import "ui/gfx/mojom/delegated_ink_point.mojom"; + +// This interface is used to connect the browser process to viz to support +// delegated ink trails. A delegated ink point will be produced in the +// browser process and sent to viz to be held until DrawAndSwap occurs, at +// which point any delegated ink points that arrived may be used to draw the +// ink trail. When the browser detects the end of the trail, it will call +// ResetPrediction() so that viz does not predict any points further than what +// the user is expecting. +interface DelegatedInkPointRenderer { + // Used to send the DelegatedInkPoint that was created in the browser process + // to viz in order to be drawn as part of the delegated ink trail. + StoreDelegatedInkPoint(DelegatedInkPoint point); + + // Used to reset prediction and prediction metrics that have been generated + // by previously received points. Used by the browser process when a delegated + // ink trail should end. + ResetPrediction(); +}; \ No newline at end of file diff --git a/mojom/display_color_spaces.mojom b/mojom/display_color_spaces.mojom new file mode 100644 index 000000000000..900e43e10a8d --- /dev/null +++ b/mojom/display_color_spaces.mojom @@ -0,0 +1,31 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module gfx.mojom; + +import "ui/gfx/mojom/buffer_types.mojom"; +import "ui/gfx/mojom/color_space.mojom"; +import "ui/gfx/mojom/hdr_static_metadata.mojom"; + +// See the typemapped enum gfx::ContentColorUsage. +enum ContentColorUsage { + kSRGB, + kWideColorGamut, + kHDR, +}; + + +// See the typemapped class gfx::DisplayColorSpaces. +struct DisplayColorSpaces { + // The arrays of length 6 correspond to the 6 configurations in the + // cross-product of (SRGB, WCG, HDR) x (opaque, transparent). The order + // corresponds to the order in gfx::DisplayColorSpaces. + array color_spaces; + array buffer_formats; + + // The SDR white level is used to composite SDR and HDR content on Windows. + float sdr_white_level; + + gfx.mojom.HDRStaticMetadata? hdr_static_metadata; +}; diff --git a/mojom/display_color_spaces_mojom_traits.cc b/mojom/display_color_spaces_mojom_traits.cc new file mode 100644 index 000000000000..1077123a599d --- /dev/null +++ b/mojom/display_color_spaces_mojom_traits.cc @@ -0,0 +1,81 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/mojom/display_color_spaces_mojom_traits.h" + +namespace mojo { + +// static +gfx::mojom::ContentColorUsage +EnumTraits::ToMojom( + gfx::ContentColorUsage input) { + switch (input) { + case gfx::ContentColorUsage::kSRGB: + return gfx::mojom::ContentColorUsage::kSRGB; + case gfx::ContentColorUsage::kWideColorGamut: + return gfx::mojom::ContentColorUsage::kWideColorGamut; + case gfx::ContentColorUsage::kHDR: + return gfx::mojom::ContentColorUsage::kHDR; + } + NOTREACHED(); + return gfx::mojom::ContentColorUsage::kSRGB; +} + +// static +bool EnumTraits:: + FromMojom(gfx::mojom::ContentColorUsage input, + gfx::ContentColorUsage* output) { + switch (input) { + case gfx::mojom::ContentColorUsage::kSRGB: + *output = gfx::ContentColorUsage::kSRGB; + return true; + case gfx::mojom::ContentColorUsage::kWideColorGamut: + *output = gfx::ContentColorUsage::kWideColorGamut; + return true; + case gfx::mojom::ContentColorUsage::kHDR: + *output = gfx::ContentColorUsage::kHDR; + return true; + } + NOTREACHED(); + return false; +} + +// static +base::span +StructTraits:: + color_spaces(const gfx::DisplayColorSpaces& input) { + return input.color_spaces_; +} + +// static +base::span +StructTraits:: + buffer_formats(const gfx::DisplayColorSpaces& input) { + return input.buffer_formats_; +} + +// static +bool StructTraits< + gfx::mojom::DisplayColorSpacesDataView, + gfx::DisplayColorSpaces>::Read(gfx::mojom::DisplayColorSpacesDataView input, + gfx::DisplayColorSpaces* out) { + base::span buffer_formats(out->buffer_formats_); + if (!input.ReadBufferFormats(&buffer_formats)) + return false; + + base::span color_spaces(out->color_spaces_); + if (!input.ReadColorSpaces(&color_spaces)) + return false; + + out->SetSDRWhiteLevel(input.sdr_white_level()); + + absl::optional hdr_static_metadata( + out->hdr_static_metadata_); + if (!input.ReadHdrStaticMetadata(&hdr_static_metadata)) + return false; + + return true; +} + +} // namespace mojo diff --git a/mojom/display_color_spaces_mojom_traits.h b/mojom/display_color_spaces_mojom_traits.h new file mode 100644 index 000000000000..8a695470a7c1 --- /dev/null +++ b/mojom/display_color_spaces_mojom_traits.h @@ -0,0 +1,47 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_MOJOM_DISPLAY_COLOR_SPACES_MOJOM_TRAITS_H_ +#define UI_GFX_MOJOM_DISPLAY_COLOR_SPACES_MOJOM_TRAITS_H_ + +#include "base/containers/span.h" +#include "ui/gfx/display_color_spaces.h" +#include "ui/gfx/mojom/buffer_types_mojom_traits.h" +#include "ui/gfx/mojom/color_space_mojom_traits.h" +#include "ui/gfx/mojom/display_color_spaces.mojom-shared.h" +#include "ui/gfx/mojom/hdr_static_metadata_mojom_traits.h" + +namespace mojo { + +template <> +struct COMPONENT_EXPORT(GFX_SHARED_MOJOM_TRAITS) + EnumTraits { + static gfx::mojom::ContentColorUsage ToMojom(gfx::ContentColorUsage input); + static bool FromMojom(gfx::mojom::ContentColorUsage input, + gfx::ContentColorUsage* output); +}; + +template <> +struct COMPONENT_EXPORT(GFX_SHARED_MOJOM_TRAITS) + StructTraits { + static base::span color_spaces( + const gfx::DisplayColorSpaces& input); + static base::span buffer_formats( + const gfx::DisplayColorSpaces& input); + static float sdr_white_level(const gfx::DisplayColorSpaces& input) { + return input.GetSDRWhiteLevel(); + } + static const absl::optional& hdr_static_metadata( + const gfx::DisplayColorSpaces& input) { + return input.hdr_static_metadata(); + } + + static bool Read(gfx::mojom::DisplayColorSpacesDataView data, + gfx::DisplayColorSpaces* out); +}; + +} // namespace mojo + +#endif // UI_GFX_MOJOM_DISPLAY_COLOR_SPACES_MOJOM_TRAITS_H_ diff --git a/mojom/font_render_params.mojom b/mojom/font_render_params.mojom new file mode 100644 index 000000000000..ac75219f6c24 --- /dev/null +++ b/mojom/font_render_params.mojom @@ -0,0 +1,22 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module gfx.mojom; + +// gfx::FontRenderParams::Hinting +enum Hinting { + kNone, + kSlight, + kMedium, + kFull, +}; + +// gfx::FontRenderParams::SubpixelRendering +enum SubpixelRendering { + kNone, + kRGB, + kBGR, + kVRGB, + kVBGR, +}; diff --git a/mojom/font_render_params_mojom_traits.h b/mojom/font_render_params_mojom_traits.h new file mode 100644 index 000000000000..7f2c3a35b84f --- /dev/null +++ b/mojom/font_render_params_mojom_traits.h @@ -0,0 +1,98 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_MOJOM_FONT_RENDER_PARAMS_MOJOM_TRAITS_H_ +#define UI_GFX_MOJOM_FONT_RENDER_PARAMS_MOJOM_TRAITS_H_ + +#include "ui/gfx/font_render_params.h" +#include "ui/gfx/mojom/font_render_params.mojom.h" + +namespace mojo { + +template <> +struct EnumTraits { + static gfx::mojom::SubpixelRendering ToMojom( + gfx::FontRenderParams::SubpixelRendering input) { + switch (input) { + case gfx::FontRenderParams::SUBPIXEL_RENDERING_NONE: + return gfx::mojom::SubpixelRendering::kNone; + case gfx::FontRenderParams::SUBPIXEL_RENDERING_RGB: + return gfx::mojom::SubpixelRendering::kRGB; + case gfx::FontRenderParams::SUBPIXEL_RENDERING_BGR: + return gfx::mojom::SubpixelRendering::kBGR; + case gfx::FontRenderParams::SUBPIXEL_RENDERING_VRGB: + return gfx::mojom::SubpixelRendering::kVRGB; + case gfx::FontRenderParams::SUBPIXEL_RENDERING_VBGR: + return gfx::mojom::SubpixelRendering::kVBGR; + } + NOTREACHED(); + return gfx::mojom::SubpixelRendering::kNone; + } + + static bool FromMojom(gfx::mojom::SubpixelRendering input, + gfx::FontRenderParams::SubpixelRendering* out) { + switch (input) { + case gfx::mojom::SubpixelRendering::kNone: + *out = gfx::FontRenderParams::SUBPIXEL_RENDERING_NONE; + return true; + case gfx::mojom::SubpixelRendering::kRGB: + *out = gfx::FontRenderParams::SUBPIXEL_RENDERING_RGB; + return true; + case gfx::mojom::SubpixelRendering::kBGR: + *out = gfx::FontRenderParams::SUBPIXEL_RENDERING_BGR; + return true; + case gfx::mojom::SubpixelRendering::kVRGB: + *out = gfx::FontRenderParams::SUBPIXEL_RENDERING_VRGB; + return true; + case gfx::mojom::SubpixelRendering::kVBGR: + *out = gfx::FontRenderParams::SUBPIXEL_RENDERING_VBGR; + return true; + } + *out = gfx::FontRenderParams::SUBPIXEL_RENDERING_NONE; + return false; + } +}; + +template <> +struct EnumTraits { + static gfx::mojom::Hinting ToMojom(gfx::FontRenderParams::Hinting input) { + switch (input) { + case gfx::FontRenderParams::HINTING_NONE: + return gfx::mojom::Hinting::kNone; + case gfx::FontRenderParams::HINTING_SLIGHT: + return gfx::mojom::Hinting::kSlight; + case gfx::FontRenderParams::HINTING_MEDIUM: + return gfx::mojom::Hinting::kMedium; + case gfx::FontRenderParams::HINTING_FULL: + return gfx::mojom::Hinting::kFull; + } + NOTREACHED(); + return gfx::mojom::Hinting::kNone; + } + + static bool FromMojom(gfx::mojom::Hinting input, + gfx::FontRenderParams::Hinting* out) { + switch (input) { + case gfx::mojom::Hinting::kNone: + *out = gfx::FontRenderParams::HINTING_NONE; + return true; + case gfx::mojom::Hinting::kSlight: + *out = gfx::FontRenderParams::HINTING_SLIGHT; + return true; + case gfx::mojom::Hinting::kMedium: + *out = gfx::FontRenderParams::HINTING_MEDIUM; + return true; + case gfx::mojom::Hinting::kFull: + *out = gfx::FontRenderParams::HINTING_FULL; + return true; + } + NOTREACHED(); + return false; + } +}; + +} // namespace mojo + +#endif // UI_GFX_MOJOM_FONT_RENDER_PARAMS_MOJOM_TRAITS_H_ diff --git a/mojom/gpu_extra_info.mojom b/mojom/gpu_extra_info.mojom new file mode 100644 index 000000000000..1b7b023fa907 --- /dev/null +++ b/mojom/gpu_extra_info.mojom @@ -0,0 +1,27 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// ui/gfx/gpu_extra_info.h +module gfx.mojom; + +import "ui/gfx/mojom/buffer_types.mojom"; + +// gfx::ANGLEFeature +struct ANGLEFeature { + string name; + string category; + string description; + string bug; + string status; + string condition; +}; + +// gfx:GpuExtraInfo +struct GpuExtraInfo { + // List of features queried from ANGLE + array angle_features; + + [EnableIf=enable_x11_params] + array gpu_memory_buffer_support_x11; +}; diff --git a/mojom/gpu_extra_info_mojom_traits.cc b/mojom/gpu_extra_info_mojom_traits.cc new file mode 100644 index 000000000000..c40726b360b8 --- /dev/null +++ b/mojom/gpu_extra_info_mojom_traits.cc @@ -0,0 +1,34 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/mojom/gpu_extra_info_mojom_traits.h" + +#include "build/build_config.h" +#include "ui/gfx/mojom/buffer_types_mojom_traits.h" + +namespace mojo { + +// static +bool StructTraits::Read( + gfx::mojom::ANGLEFeatureDataView data, + gfx::ANGLEFeature* out) { + return data.ReadName(&out->name) && data.ReadCategory(&out->category) && + data.ReadDescription(&out->description) && data.ReadBug(&out->bug) && + data.ReadStatus(&out->status) && data.ReadCondition(&out->condition); +} + +// static +bool StructTraits::Read( + gfx::mojom::GpuExtraInfoDataView data, + gfx::GpuExtraInfo* out) { + if (!data.ReadAngleFeatures(&out->angle_features)) + return false; +#if defined(USE_OZONE_PLATFORM_X11) || defined(USE_X11) + if (!data.ReadGpuMemoryBufferSupportX11(&out->gpu_memory_buffer_support_x11)) + return false; +#endif + return true; +} + +} // namespace mojo diff --git a/mojom/gpu_extra_info_mojom_traits.h b/mojom/gpu_extra_info_mojom_traits.h new file mode 100644 index 000000000000..dd6b118c8b0e --- /dev/null +++ b/mojom/gpu_extra_info_mojom_traits.h @@ -0,0 +1,74 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_MOJOM_GPU_EXTRA_INFO_MOJOM_TRAITS_H_ +#define UI_GFX_MOJOM_GPU_EXTRA_INFO_MOJOM_TRAITS_H_ + +#include "base/component_export.h" +#include "ui/gfx/gpu_extra_info.h" +#include "ui/gfx/mojom/buffer_types_mojom_traits.h" +#include "ui/gfx/mojom/gpu_extra_info.mojom-shared.h" + +#if defined(USE_OZONE) +#include "ui/ozone/buildflags.h" +#if BUILDFLAG(OZONE_PLATFORM_X11) +#define USE_OZONE_PLATFORM_X11 +#endif +#endif + +namespace mojo { + +template <> +struct COMPONENT_EXPORT(GFX_SHARED_MOJOM_TRAITS) + StructTraits { + static bool Read(gfx::mojom::ANGLEFeatureDataView data, + gfx::ANGLEFeature* out); + + static const std::string& name(const gfx::ANGLEFeature& input) { + return input.name; + } + + static const std::string& category(const gfx::ANGLEFeature& input) { + return input.category; + } + + static const std::string& description(const gfx::ANGLEFeature& input) { + return input.description; + } + + static const std::string& bug(const gfx::ANGLEFeature& input) { + return input.bug; + } + + static const std::string& status(const gfx::ANGLEFeature& input) { + return input.status; + } + + static const std::string& condition(const gfx::ANGLEFeature& input) { + return input.condition; + } +}; + +template <> +struct COMPONENT_EXPORT(GFX_SHARED_MOJOM_TRAITS) + StructTraits { + static bool Read(gfx::mojom::GpuExtraInfoDataView data, + gfx::GpuExtraInfo* out); + + static const std::vector& angle_features( + const gfx::GpuExtraInfo& input) { + return input.angle_features; + } + +#if defined(USE_OZONE_PLATFORM_X11) || defined(USE_X11) + static const std::vector& + gpu_memory_buffer_support_x11(const gfx::GpuExtraInfo& input) { + return input.gpu_memory_buffer_support_x11; + } +#endif +}; + +} // namespace mojo + +#endif // UI_GFX_MOJOM_GPU_EXTRA_INFO_MOJOM_TRAITS_H_ diff --git a/mojom/gpu_fence_handle.mojom b/mojom/gpu_fence_handle.mojom new file mode 100644 index 000000000000..18c881484411 --- /dev/null +++ b/mojom/gpu_fence_handle.mojom @@ -0,0 +1,14 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module gfx.mojom; + +// See ui/gfx/ipc/gpu_fence_handle.h +struct GpuFenceHandle { + [EnableIf=is_posix] + handle native_fd; + + [EnableIf=is_win] + handle native_handle; +}; diff --git a/mojom/gpu_fence_handle_mojom_traits.cc b/mojom/gpu_fence_handle_mojom_traits.cc new file mode 100644 index 000000000000..02289ca55f9a --- /dev/null +++ b/mojom/gpu_fence_handle_mojom_traits.cc @@ -0,0 +1,54 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/mojom/gpu_fence_handle_mojom_traits.h" + +#include "build/build_config.h" +#include "mojo/public/cpp/system/platform_handle.h" + +namespace mojo { + +#if defined(OS_POSIX) +mojo::PlatformHandle +StructTraits::native_fd(gfx::GpuFenceHandle& handle) { + return mojo::PlatformHandle(std::move(handle.owned_fd)); +} +#elif defined(OS_WIN) +mojo::PlatformHandle +StructTraits::native_handle(gfx::GpuFenceHandle& handle) { + return mojo::PlatformHandle(std::move(handle.owned_handle)); +} +#endif + +bool StructTraits:: + Read(gfx::mojom::GpuFenceHandleDataView data, gfx::GpuFenceHandle* out) { +#if defined(OS_POSIX) + out->owned_fd = data.TakeNativeFd().TakeFD(); + return true; +#elif defined(OS_WIN) + out->owned_handle = data.TakeNativeHandle().TakeHandle(); + return true; +#else + return false; +#endif +} + +void StructTraits::SetToNull(gfx::GpuFenceHandle* handle) { +#if defined(OS_POSIX) + handle->owned_fd.reset(); +#elif defined(OS_WIN) + handle->owned_handle.Close(); +#endif +} + +bool StructTraits::IsNull(const gfx::GpuFenceHandle& + handle) { + return handle.is_null(); +} + +} // namespace mojo diff --git a/mojom/gpu_fence_handle_mojom_traits.h b/mojom/gpu_fence_handle_mojom_traits.h new file mode 100644 index 000000000000..74c04d42148e --- /dev/null +++ b/mojom/gpu_fence_handle_mojom_traits.h @@ -0,0 +1,31 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_MOJOM_GPU_FENCE_HANDLE_MOJOM_TRAITS_H_ +#define UI_GFX_MOJOM_GPU_FENCE_HANDLE_MOJOM_TRAITS_H_ + +#include "base/component_export.h" +#include "build/build_config.h" +#include "ui/gfx/gpu_fence_handle.h" +#include "ui/gfx/mojom/gpu_fence_handle.mojom-shared.h" + +namespace mojo { + +template <> +struct COMPONENT_EXPORT(GFX_SHARED_MOJOM_TRAITS) + StructTraits { +#if defined(OS_POSIX) + static mojo::PlatformHandle native_fd(gfx::GpuFenceHandle& handle); +#elif defined(OS_WIN) + static mojo::PlatformHandle native_handle(gfx::GpuFenceHandle& handle); +#endif + static bool Read(gfx::mojom::GpuFenceHandleDataView data, + gfx::GpuFenceHandle* handle); + static void SetToNull(gfx::GpuFenceHandle* handle); + static bool IsNull(const gfx::GpuFenceHandle& handle); +}; + +} // namespace mojo + +#endif // UI_GFX_MOJOM_GPU_FENCE_HANDLE_MOJOM_TRAITS_H_ diff --git a/mojom/hdr_metadata.mojom b/mojom/hdr_metadata.mojom new file mode 100644 index 000000000000..e4c8f48bb00b --- /dev/null +++ b/mojom/hdr_metadata.mojom @@ -0,0 +1,24 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module gfx.mojom; + +import "ui/gfx/geometry/mojom/geometry.mojom"; + +// This defines a mojo transport format for gfx::HDRMetadata. +// See ui/gl/hdr_metadata.h for description. +struct ColorVolumeMetadata { + gfx.mojom.PointF primary_r; + gfx.mojom.PointF primary_g; + gfx.mojom.PointF primary_b; + gfx.mojom.PointF white_point; + float luminance_max; + float luminance_min; + }; + +struct HDRMetadata { + ColorVolumeMetadata color_volume_metadata; + uint32 max_content_light_level; + uint32 max_frame_average_light_level; + }; diff --git a/mojom/hdr_metadata_mojom_traits.cc b/mojom/hdr_metadata_mojom_traits.cc new file mode 100644 index 000000000000..462123065664 --- /dev/null +++ b/mojom/hdr_metadata_mojom_traits.cc @@ -0,0 +1,35 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/mojom/hdr_metadata_mojom_traits.h" + +namespace mojo { + +bool StructTraits:: + Read(gfx::mojom::ColorVolumeMetadataDataView data, + gfx::ColorVolumeMetadata* output) { + output->luminance_max = data.luminance_max(); + output->luminance_min = data.luminance_min(); + if (!data.ReadPrimaryR(&output->primary_r)) + return false; + if (!data.ReadPrimaryG(&output->primary_g)) + return false; + if (!data.ReadPrimaryB(&output->primary_b)) + return false; + if (!data.ReadWhitePoint(&output->white_point)) + return false; + return true; +} + +bool StructTraits::Read( + gfx::mojom::HDRMetadataDataView data, + gfx::HDRMetadata* output) { + output->max_content_light_level = data.max_content_light_level(); + output->max_frame_average_light_level = data.max_frame_average_light_level(); + if (!data.ReadColorVolumeMetadata(&output->color_volume_metadata)) + return false; + return true; +} +} // namespace mojo diff --git a/mojom/hdr_metadata_mojom_traits.h b/mojom/hdr_metadata_mojom_traits.h new file mode 100644 index 000000000000..5c477db168a3 --- /dev/null +++ b/mojom/hdr_metadata_mojom_traits.h @@ -0,0 +1,58 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_MOJOM_HDR_METADATA_MOJOM_TRAITS_H_ +#define UI_GFX_MOJOM_HDR_METADATA_MOJOM_TRAITS_H_ + +#include "ui/gfx/hdr_metadata.h" +#include "ui/gfx/mojom/hdr_metadata.mojom.h" + +namespace mojo { + +template <> +struct StructTraits { + static const gfx::PointF& primary_r(const gfx::ColorVolumeMetadata& input) { + return input.primary_r; + } + static const gfx::PointF& primary_g(const gfx::ColorVolumeMetadata& input) { + return input.primary_g; + } + static const gfx::PointF& primary_b(const gfx::ColorVolumeMetadata& input) { + return input.primary_b; + } + static const gfx::PointF& white_point(const gfx::ColorVolumeMetadata& input) { + return input.white_point; + } + static float luminance_max(const gfx::ColorVolumeMetadata& input) { + return input.luminance_max; + } + static float luminance_min(const gfx::ColorVolumeMetadata& input) { + return input.luminance_min; + } + + static bool Read(gfx::mojom::ColorVolumeMetadataDataView data, + gfx::ColorVolumeMetadata* output); +}; + +template <> +struct StructTraits { + static unsigned max_content_light_level(const gfx::HDRMetadata& input) { + return input.max_content_light_level; + } + static unsigned max_frame_average_light_level(const gfx::HDRMetadata& input) { + return input.max_frame_average_light_level; + } + static const gfx::ColorVolumeMetadata& color_volume_metadata( + const gfx::HDRMetadata& input) { + return input.color_volume_metadata; + } + + static bool Read(gfx::mojom::HDRMetadataDataView data, + gfx::HDRMetadata* output); +}; + +} // namespace mojo + +#endif // UI_GFX_MOJOM_HDR_METADATA_MOJOM_TRAITS_H_ diff --git a/mojom/hdr_static_metadata.mojom b/mojom/hdr_static_metadata.mojom new file mode 100644 index 000000000000..fc2aad0939bf --- /dev/null +++ b/mojom/hdr_static_metadata.mojom @@ -0,0 +1,12 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module gfx.mojom; + +// This defines a mojo transport format for gfx::HDRStaticMetadata. +struct HDRStaticMetadata { + float max; + float max_avg; + float min; +}; diff --git a/mojom/hdr_static_metadata_mojom_traits.cc b/mojom/hdr_static_metadata_mojom_traits.cc new file mode 100644 index 000000000000..86707e4d1a4f --- /dev/null +++ b/mojom/hdr_static_metadata_mojom_traits.cc @@ -0,0 +1,19 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/mojom/hdr_static_metadata_mojom_traits.h" + +namespace mojo { + +bool StructTraits< + gfx::mojom::HDRStaticMetadataDataView, + gfx::HDRStaticMetadata>::Read(gfx::mojom::HDRStaticMetadataDataView data, + gfx::HDRStaticMetadata* output) { + output->max = data.max(); + output->max_avg = data.max_avg(); + output->min = data.min(); + return true; +} + +} // namespace mojo diff --git a/mojom/hdr_static_metadata_mojom_traits.h b/mojom/hdr_static_metadata_mojom_traits.h new file mode 100644 index 000000000000..411fbbb9b34b --- /dev/null +++ b/mojom/hdr_static_metadata_mojom_traits.h @@ -0,0 +1,30 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_MOJOM_HDR_STATIC_METADATA_MOJOM_TRAITS_H_ +#define UI_GFX_MOJOM_HDR_STATIC_METADATA_MOJOM_TRAITS_H_ + +#include "base/component_export.h" +#include "ui/gfx/hdr_static_metadata.h" +#include "ui/gfx/mojom/hdr_static_metadata.mojom-shared.h" + +namespace mojo { + +template <> +struct COMPONENT_EXPORT(GFX_SHARED_MOJOM_TRAITS) + StructTraits { + static float max(const gfx::HDRStaticMetadata& input) { return input.max; } + static float max_avg(const gfx::HDRStaticMetadata& input) { + return input.max_avg; + } + static float min(const gfx::HDRStaticMetadata& input) { return input.min; } + + static bool Read(gfx::mojom::HDRStaticMetadataDataView data, + gfx::HDRStaticMetadata* output); +}; + +} // namespace mojo + +#endif // UI_GFX_MOJOM_HDR_STATIC_METADATA_MOJOM_TRAITS_H_ diff --git a/mojom/mask_filter_info.mojom b/mojom/mask_filter_info.mojom new file mode 100644 index 000000000000..7e3b9ea0d256 --- /dev/null +++ b/mojom/mask_filter_info.mojom @@ -0,0 +1,12 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module gfx.mojom; + +import "ui/gfx/mojom/rrect_f.mojom"; + +// See ui/gfx/mask_filter_info.h. +struct MaskFilterInfo { + gfx.mojom.RRectF rounded_corner_bounds; +}; diff --git a/mojom/mask_filter_info_mojom_traits.cc b/mojom/mask_filter_info_mojom_traits.cc new file mode 100644 index 000000000000..683762ae549a --- /dev/null +++ b/mojom/mask_filter_info_mojom_traits.cc @@ -0,0 +1,19 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/mojom/mask_filter_info_mojom_traits.h" + +namespace mojo { + +// static +bool StructTraits:: + Read(gfx::mojom::MaskFilterInfoDataView data, gfx::MaskFilterInfo* out) { + gfx::RRectF bounds; + if (!data.ReadRoundedCornerBounds(&bounds)) + return false; + *out = gfx::MaskFilterInfo(bounds); + return true; +} + +} // namespace mojo diff --git a/mojom/mask_filter_info_mojom_traits.h b/mojom/mask_filter_info_mojom_traits.h new file mode 100644 index 000000000000..849742a91ff9 --- /dev/null +++ b/mojom/mask_filter_info_mojom_traits.h @@ -0,0 +1,25 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_MOJOM_MASK_FILTER_INFO_MOJOM_TRAITS_H_ +#define UI_GFX_MOJOM_MASK_FILTER_INFO_MOJOM_TRAITS_H_ + +#include "ui/gfx/geometry/mask_filter_info.h" +#include "ui/gfx/mojom/mask_filter_info.mojom-shared.h" +#include "ui/gfx/mojom/rrect_f_mojom_traits.h" + +namespace mojo { +template <> +struct StructTraits { + static const gfx::RRectF& rounded_corner_bounds( + const gfx::MaskFilterInfo& info) { + return info.rounded_corner_bounds(); + } + + static bool Read(gfx::mojom::MaskFilterInfoDataView data, + gfx::MaskFilterInfo* out); +}; + +} // namespace mojo +#endif // UI_GFX_MOJOM_MASK_FILTER_INFO_MOJOM_TRAITS_H_ diff --git a/mojom/mojom_traits_unittest.cc b/mojom/mojom_traits_unittest.cc new file mode 100644 index 000000000000..c7e36bf9eb4b --- /dev/null +++ b/mojom/mojom_traits_unittest.cc @@ -0,0 +1,300 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/test/task_environment.h" +#include "build/build_config.h" +#include "mojo/public/cpp/bindings/receiver_set.h" +#include "mojo/public/cpp/bindings/remote.h" +#include "mojo/public/cpp/test_support/test_utils.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/geometry/rrect_f.h" +#include "ui/gfx/geometry/transform.h" +#include "ui/gfx/mojom/accelerated_widget_mojom_traits.h" +#include "ui/gfx/mojom/buffer_types_mojom_traits.h" +#include "ui/gfx/mojom/presentation_feedback.mojom.h" +#include "ui/gfx/mojom/presentation_feedback_mojom_traits.h" +#include "ui/gfx/mojom/traits_test_service.mojom.h" +#include "ui/gfx/native_widget_types.h" +#include "ui/gfx/selection_bound.h" + +namespace gfx { + +namespace { + +gfx::AcceleratedWidget CastToAcceleratedWidget(int i) { +#if defined(USE_OZONE) || defined(USE_X11) || defined(OS_APPLE) + return static_cast(i); +#else + return reinterpret_cast(i); +#endif +} + +class StructTraitsTest : public testing::Test, public mojom::TraitsTestService { + public: + StructTraitsTest() {} + + StructTraitsTest(const StructTraitsTest&) = delete; + StructTraitsTest& operator=(const StructTraitsTest&) = delete; + + protected: + mojo::Remote GetTraitsTestRemote() { + mojo::Remote remote; + traits_test_receivers_.Add(this, remote.BindNewPipeAndPassReceiver()); + return remote; + } + + private: + // TraitsTestService: + void EchoSelectionBound(const SelectionBound& s, + EchoSelectionBoundCallback callback) override { + std::move(callback).Run(s); + } + + void EchoTransform(const Transform& t, + EchoTransformCallback callback) override { + std::move(callback).Run(t); + } + + void EchoGpuMemoryBufferHandle( + GpuMemoryBufferHandle handle, + EchoGpuMemoryBufferHandleCallback callback) override { + std::move(callback).Run(std::move(handle)); + } + + void EchoRRectF(const RRectF& r, EchoRRectFCallback callback) override { + std::move(callback).Run(r); + } + + base::test::TaskEnvironment task_environment_; + mojo::ReceiverSet traits_test_receivers_; +}; + +} // namespace + +TEST_F(StructTraitsTest, SelectionBound) { + const gfx::SelectionBound::Type type = gfx::SelectionBound::CENTER; + const gfx::PointF edge_start(1234.5f, 5678.6f); + const gfx::PointF edge_end(910112.5f, 13141516.6f); + const bool visible = true; + gfx::SelectionBound input; + input.set_type(type); + input.SetEdge(edge_start, edge_end); + input.set_visible(visible); + mojo::Remote remote = GetTraitsTestRemote(); + gfx::SelectionBound output; + remote->EchoSelectionBound(input, &output); + EXPECT_EQ(type, output.type()); + EXPECT_EQ(edge_start, output.edge_start()); + EXPECT_EQ(edge_end, output.edge_end()); + EXPECT_EQ(input.edge_start_rounded(), output.edge_start_rounded()); + EXPECT_EQ(input.edge_end_rounded(), output.edge_end_rounded()); + EXPECT_EQ(visible, output.visible()); +} + +TEST_F(StructTraitsTest, Transform) { + const float col1row1 = 1.f; + const float col2row1 = 2.f; + const float col3row1 = 3.f; + const float col4row1 = 4.f; + const float col1row2 = 5.f; + const float col2row2 = 6.f; + const float col3row2 = 7.f; + const float col4row2 = 8.f; + const float col1row3 = 9.f; + const float col2row3 = 10.f; + const float col3row3 = 11.f; + const float col4row3 = 12.f; + const float col1row4 = 13.f; + const float col2row4 = 14.f; + const float col3row4 = 15.f; + const float col4row4 = 16.f; + gfx::Transform input(col1row1, col2row1, col3row1, col4row1, col1row2, + col2row2, col3row2, col4row2, col1row3, col2row3, + col3row3, col4row3, col1row4, col2row4, col3row4, + col4row4); + mojo::Remote remote = GetTraitsTestRemote(); + gfx::Transform output; + remote->EchoTransform(input, &output); + EXPECT_EQ(col1row1, output.matrix().get(0, 0)); + EXPECT_EQ(col2row1, output.matrix().get(0, 1)); + EXPECT_EQ(col3row1, output.matrix().get(0, 2)); + EXPECT_EQ(col4row1, output.matrix().get(0, 3)); + EXPECT_EQ(col1row2, output.matrix().get(1, 0)); + EXPECT_EQ(col2row2, output.matrix().get(1, 1)); + EXPECT_EQ(col3row2, output.matrix().get(1, 2)); + EXPECT_EQ(col4row2, output.matrix().get(1, 3)); + EXPECT_EQ(col1row3, output.matrix().get(2, 0)); + EXPECT_EQ(col2row3, output.matrix().get(2, 1)); + EXPECT_EQ(col3row3, output.matrix().get(2, 2)); + EXPECT_EQ(col4row3, output.matrix().get(2, 3)); + EXPECT_EQ(col1row4, output.matrix().get(3, 0)); + EXPECT_EQ(col2row4, output.matrix().get(3, 1)); + EXPECT_EQ(col3row4, output.matrix().get(3, 2)); + EXPECT_EQ(col4row4, output.matrix().get(3, 3)); +} + +TEST_F(StructTraitsTest, AcceleratedWidget) { + gfx::AcceleratedWidget input(CastToAcceleratedWidget(1001)); + gfx::AcceleratedWidget output; + mojo::test::SerializeAndDeserialize(input, + output); + EXPECT_EQ(input, output); +} + +TEST_F(StructTraitsTest, GpuMemoryBufferHandle) { + const gfx::GpuMemoryBufferId kId(99); + const uint32_t kOffset = 126; + const int32_t kStride = 256; + base::UnsafeSharedMemoryRegion shared_memory_region = + base::UnsafeSharedMemoryRegion::Create(1024); + ASSERT_TRUE(shared_memory_region.IsValid()); + ASSERT_TRUE(shared_memory_region.Map().IsValid()); + + gfx::GpuMemoryBufferHandle handle; + handle.type = gfx::SHARED_MEMORY_BUFFER; + handle.id = kId; + handle.region = shared_memory_region.Duplicate(); + handle.offset = kOffset; + handle.stride = kStride; + + mojo::Remote remote = GetTraitsTestRemote(); + gfx::GpuMemoryBufferHandle output; + remote->EchoGpuMemoryBufferHandle(std::move(handle), &output); + EXPECT_EQ(gfx::SHARED_MEMORY_BUFFER, output.type); + EXPECT_EQ(kId, output.id); + EXPECT_EQ(kOffset, output.offset); + EXPECT_EQ(kStride, output.stride); + + base::UnsafeSharedMemoryRegion output_memory = std::move(output.region); + EXPECT_TRUE(output_memory.Map().IsValid()); + +#if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(USE_OZONE) + gfx::GpuMemoryBufferHandle handle2; + const uint64_t kSize = kOffset + kStride; + handle2.type = gfx::NATIVE_PIXMAP; + handle2.id = kId; + handle2.offset = kOffset; + handle2.stride = kStride; +#if defined(OS_LINUX) || defined(OS_CHROMEOS) + const uint64_t kModifier = 2; + base::ScopedFD buffer_handle; + handle2.native_pixmap_handle.modifier = kModifier; +#elif defined(OS_FUCHSIA) + zx::vmo buffer_handle; + handle2.native_pixmap_handle.buffer_collection_id = + gfx::SysmemBufferCollectionId::Create(); + handle2.native_pixmap_handle.buffer_index = 4; + handle2.native_pixmap_handle.ram_coherency = true; +#endif + handle2.native_pixmap_handle.planes.emplace_back(kOffset, kStride, kSize, + std::move(buffer_handle)); + remote->EchoGpuMemoryBufferHandle(std::move(handle2), &output); + EXPECT_EQ(gfx::NATIVE_PIXMAP, output.type); +#if defined(OS_LINUX) || defined(OS_CHROMEOS) + EXPECT_EQ(kModifier, output.native_pixmap_handle.modifier); +#elif defined(OS_FUCHSIA) + EXPECT_EQ(handle2.native_pixmap_handle.buffer_collection_id, + output.native_pixmap_handle.buffer_collection_id); + EXPECT_EQ(handle2.native_pixmap_handle.buffer_index, + output.native_pixmap_handle.buffer_index); + EXPECT_EQ(handle2.native_pixmap_handle.ram_coherency, + output.native_pixmap_handle.ram_coherency); +#endif + ASSERT_EQ(1u, output.native_pixmap_handle.planes.size()); + EXPECT_EQ(kSize, output.native_pixmap_handle.planes.back().size); +#endif +} + +TEST_F(StructTraitsTest, NullGpuMemoryBufferHandle) { + mojo::Remote remote = GetTraitsTestRemote(); + GpuMemoryBufferHandle output; + remote->EchoGpuMemoryBufferHandle(GpuMemoryBufferHandle(), &output); + EXPECT_TRUE(output.is_null()); +} + +TEST_F(StructTraitsTest, BufferFormat) { + using BufferFormatTraits = + mojo::EnumTraits; + BufferFormat output; + mojo::Remote remote = GetTraitsTestRemote(); + for (int i = 0; i <= static_cast(BufferFormat::LAST); ++i) { + BufferFormat input = static_cast(i); + BufferFormatTraits::FromMojom(BufferFormatTraits::ToMojom(input), &output); + EXPECT_EQ(output, input); + } +} + +TEST_F(StructTraitsTest, BufferUsage) { + using BufferUsageTraits = + mojo::EnumTraits; + BufferUsage output; + mojo::Remote remote = GetTraitsTestRemote(); + for (int i = 0; i <= static_cast(BufferUsage::LAST); ++i) { + BufferUsage input = static_cast(i); + BufferUsageTraits::FromMojom(BufferUsageTraits::ToMojom(input), &output); + EXPECT_EQ(output, input); + } +} + +TEST_F(StructTraitsTest, PresentationFeedback) { + base::TimeTicks timestamp = base::TimeTicks() + base::Seconds(12); + base::TimeDelta interval = base::Milliseconds(23); + uint32_t flags = + PresentationFeedback::kVSync | PresentationFeedback::kZeroCopy; + PresentationFeedback input{timestamp, interval, flags}; + input.available_timestamp = base::TimeTicks() + base::Milliseconds(20); + input.ready_timestamp = base::TimeTicks() + base::Milliseconds(21); + input.latch_timestamp = base::TimeTicks() + base::Milliseconds(22); + PresentationFeedback output; + mojo::test::SerializeAndDeserialize(input, + output); + EXPECT_EQ(timestamp, output.timestamp); + EXPECT_EQ(interval, output.interval); + EXPECT_EQ(flags, output.flags); + EXPECT_EQ(input.available_timestamp, output.available_timestamp); + EXPECT_EQ(input.ready_timestamp, output.ready_timestamp); + EXPECT_EQ(input.latch_timestamp, output.latch_timestamp); +} + +TEST_F(StructTraitsTest, RRectF) { + gfx::RRectF input(40, 50, 60, 70, 1, 2); + input.SetCornerRadii(RRectF::Corner::kUpperRight, 3, 4); + input.SetCornerRadii(RRectF::Corner::kLowerRight, 5, 6); + input.SetCornerRadii(RRectF::Corner::kLowerLeft, 7, 8); + EXPECT_EQ(input.GetType(), RRectF::Type::kComplex); + mojo::Remote remote = GetTraitsTestRemote(); + gfx::RRectF output; + remote->EchoRRectF(input, &output); + EXPECT_EQ(input, output); + input = gfx::RRectF(40, 50, 0, 70, 0); + EXPECT_EQ(input.GetType(), RRectF::Type::kEmpty); + remote->EchoRRectF(input, &output); + EXPECT_EQ(input, output); + input = RRectF(40, 50, 60, 70, 0); + EXPECT_EQ(input.GetType(), RRectF::Type::kRect); + remote->EchoRRectF(input, &output); + EXPECT_EQ(input, output); + input = RRectF(40, 50, 60, 70, 5); + EXPECT_EQ(input.GetType(), RRectF::Type::kSingle); + remote->EchoRRectF(input, &output); + EXPECT_EQ(input, output); + input = RRectF(40, 50, 60, 70, 6, 3); + EXPECT_EQ(input.GetType(), RRectF::Type::kSimple); + remote->EchoRRectF(input, &output); + EXPECT_EQ(input, output); + input = RRectF(40, 50, 60, 70, 30, 35); + EXPECT_EQ(input.GetType(), RRectF::Type::kOval); + remote->EchoRRectF(input, &output); + EXPECT_EQ(input, output); + input.SetCornerRadii(RRectF::Corner::kUpperLeft, 50, 50); + input.SetCornerRadii(RRectF::Corner::kUpperRight, 20, 20); + input.SetCornerRadii(RRectF::Corner::kLowerRight, 0, 0); + input.SetCornerRadii(RRectF::Corner::kLowerLeft, 0, 0); + remote->EchoRRectF(input, &output); + EXPECT_EQ(input, output); +} + +} // namespace gfx diff --git a/mojom/native_handle_types.mojom b/mojom/native_handle_types.mojom new file mode 100644 index 000000000000..e81dd83d3e75 --- /dev/null +++ b/mojom/native_handle_types.mojom @@ -0,0 +1,79 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module gfx.mojom; + +import "mojo/public/mojom/base/shared_memory.mojom"; +import "mojo/public/mojom/base/unguessable_token.mojom"; + +// gfx::NativePixmapPlane +[EnableIf=supports_native_pixmap, Stable] +struct NativePixmapPlane { + uint32 stride; + uint64 offset; + uint64 size; + + // A platform-specific handle the underlying memory object. + handle buffer_handle; +}; + +// gfx::NativePixmapHandle +[EnableIf=supports_native_pixmap] +struct NativePixmapHandle { + array planes; + + [EnableIf=is_linux] + uint64 modifier; + [EnableIf=is_chromeos_ash] + uint64 modifier; + + [EnableIf=is_fuchsia] + mojo_base.mojom.UnguessableToken? buffer_collection_id; + [EnableIf=is_fuchsia] + uint32 buffer_index; + [EnableIf=is_fuchsia] + bool ram_coherency; +}; + +[EnableIf=is_android] +struct AHardwareBufferHandle { + // The actual file descriptor used to wrap the AHardwareBuffer object for + // serialization. + handle buffer_handle; + + // A message pipe handle which tracks the lifetime of this + // AHardwareBufferHandle. The sender may use this to observe the lifetime + // remotely by watching the other end of this pipe. Useful for retaining a + // sender-side AHB ref until the receiver deserializes the AHB and acquires + // its own ref. + handle tracking_pipe; +}; + +[EnableIf=is_win] +struct DxgiHandle { + // The actual buffer windows handle. + handle buffer_handle; + + // Shared memory copy of all the data. Valid only if requested by the + // consumer. It is included here because DXGI GMBs are unmappable except in + // the GPU process. So without it the consumer if a CPU readable frame is + // needed would resort to request a copy in the shared memory via GPU process. + mojo_base.mojom.UnsafeSharedMemoryRegion? shared_memory_handle; +}; + +union GpuMemoryBufferPlatformHandle { + mojo_base.mojom.UnsafeSharedMemoryRegion shared_memory_handle; + + [EnableIf=supports_native_pixmap] + NativePixmapHandle native_pixmap_handle; + + [EnableIf=is_mac] + handle mach_port; + + [EnableIf=is_win] + DxgiHandle dxgi_handle; + + [EnableIf=is_android] + AHardwareBufferHandle android_hardware_buffer_handle; +}; diff --git a/mojom/native_handle_types_mojom_traits.cc b/mojom/native_handle_types_mojom_traits.cc new file mode 100644 index 000000000000..15e5b8b9766b --- /dev/null +++ b/mojom/native_handle_types_mojom_traits.cc @@ -0,0 +1,63 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/mojom/native_handle_types_mojom_traits.h" + +#include "build/build_config.h" + +namespace mojo { + +#if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(USE_OZONE) +mojo::PlatformHandle StructTraits< + gfx::mojom::NativePixmapPlaneDataView, + gfx::NativePixmapPlane>::buffer_handle(gfx::NativePixmapPlane& plane) { +#if defined(OS_LINUX) || defined(OS_CHROMEOS) + return mojo::PlatformHandle(std::move(plane.fd)); +#elif defined(OS_FUCHSIA) + return mojo::PlatformHandle(std::move(plane.vmo)); +#endif // defined(OS_LINUX) || defined(OS_CHROMEOS) +} + +bool StructTraits< + gfx::mojom::NativePixmapPlaneDataView, + gfx::NativePixmapPlane>::Read(gfx::mojom::NativePixmapPlaneDataView data, + gfx::NativePixmapPlane* out) { + out->stride = data.stride(); + out->offset = data.offset(); + out->size = data.size(); + + mojo::PlatformHandle handle = data.TakeBufferHandle(); +#if defined(OS_LINUX) || defined(OS_CHROMEOS) + if (!handle.is_fd()) + return false; + out->fd = handle.TakeFD(); +#elif defined(OS_FUCHSIA) + if (!handle.is_handle()) + return false; + out->vmo = zx::vmo(handle.TakeHandle()); +#endif // defined(OS_LINUX) || defined(OS_CHROMEOS) + + return true; +} + +bool StructTraits< + gfx::mojom::NativePixmapHandleDataView, + gfx::NativePixmapHandle>::Read(gfx::mojom::NativePixmapHandleDataView data, + gfx::NativePixmapHandle* out) { +#if defined(OS_LINUX) || defined(OS_CHROMEOS) + out->modifier = data.modifier(); +#endif + +#if defined(OS_FUCHSIA) + if (!data.ReadBufferCollectionId(&out->buffer_collection_id)) + return false; + out->buffer_index = data.buffer_index(); + out->ram_coherency = data.ram_coherency(); +#endif + + return data.ReadPlanes(&out->planes); +} +#endif // defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(USE_OZONE) + +} // namespace mojo diff --git a/mojom/native_handle_types_mojom_traits.h b/mojom/native_handle_types_mojom_traits.h new file mode 100644 index 000000000000..43ff1495535c --- /dev/null +++ b/mojom/native_handle_types_mojom_traits.h @@ -0,0 +1,80 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_MOJOM_NATIVE_HANDLE_TYPES_MOJOM_TRAITS_H_ +#define UI_GFX_MOJOM_NATIVE_HANDLE_TYPES_MOJOM_TRAITS_H_ + +#include "base/component_export.h" +#include "base/numerics/safe_conversions.h" +#include "build/build_config.h" +#include "mojo/public/cpp/base/unguessable_token_mojom_traits.h" +#include "mojo/public/cpp/bindings/struct_traits.h" +#include "mojo/public/cpp/bindings/union_traits.h" +#include "mojo/public/cpp/system/platform_handle.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/gfx/mojom/native_handle_types.mojom-shared.h" + +#if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(USE_OZONE) +#include "ui/gfx/native_pixmap_handle.h" +#endif + +namespace mojo { + +#if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(USE_OZONE) +template <> +struct COMPONENT_EXPORT(GFX_NATIVE_HANDLE_TYPES_SHARED_MOJOM_TRAITS) + StructTraits { + static uint32_t stride(const gfx::NativePixmapPlane& plane) { + return plane.stride; + } + static int32_t offset(const gfx::NativePixmapPlane& plane) { + return base::saturated_cast(plane.offset); + } + static uint64_t size(const gfx::NativePixmapPlane& plane) { + return plane.size; + } + static mojo::PlatformHandle buffer_handle(gfx::NativePixmapPlane& plane); + static bool Read(gfx::mojom::NativePixmapPlaneDataView data, + gfx::NativePixmapPlane* out); +}; + +template <> +struct COMPONENT_EXPORT(GFX_NATIVE_HANDLE_TYPES_SHARED_MOJOM_TRAITS) + StructTraits { + static std::vector& planes( + gfx::NativePixmapHandle& pixmap_handle) { + return pixmap_handle.planes; + } + +#if defined(OS_LINUX) || defined(OS_CHROMEOS) + static uint64_t modifier(const gfx::NativePixmapHandle& pixmap_handle) { + return pixmap_handle.modifier; + } +#endif + +#if defined(OS_FUCHSIA) + static const absl::optional& buffer_collection_id( + const gfx::NativePixmapHandle& pixmap_handle) { + return pixmap_handle.buffer_collection_id; + } + + static uint32_t buffer_index(gfx::NativePixmapHandle& pixmap_handle) { + return pixmap_handle.buffer_index; + } + + static bool ram_coherency(gfx::NativePixmapHandle& pixmap_handle) { + return pixmap_handle.ram_coherency; + } +#endif // defined(OS_FUCHSIA) + + static bool Read(gfx::mojom::NativePixmapHandleDataView data, + gfx::NativePixmapHandle* out); +}; +#endif // defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(USE_OZONE) + +} // namespace mojo + +#endif // UI_GFX_MOJOM_NATIVE_HANDLE_TYPES_MOJOM_TRAITS_H_ diff --git a/mojom/overlay_priority_hint.mojom b/mojom/overlay_priority_hint.mojom new file mode 100644 index 000000000000..9e976afd9bfe --- /dev/null +++ b/mojom/overlay_priority_hint.mojom @@ -0,0 +1,13 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module gfx.mojom; + +// gfx::OverlayPriorityHint +enum OverlayPriorityHint { + kNone = 0, + kRegular, + kLowLatencyCanvas, + kHardwareProtection, +}; diff --git a/mojom/overlay_priority_hint_mojom_traits.h b/mojom/overlay_priority_hint_mojom_traits.h new file mode 100644 index 000000000000..ea950ec429cf --- /dev/null +++ b/mojom/overlay_priority_hint_mojom_traits.h @@ -0,0 +1,54 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_MOJOM_OVERLAY_PRIORITY_HINT_MOJOM_TRAITS_H_ +#define UI_GFX_MOJOM_OVERLAY_PRIORITY_HINT_MOJOM_TRAITS_H_ + +#include "ui/gfx/mojom/overlay_priority_hint.mojom.h" +#include "ui/gfx/overlay_priority_hint.h" + +namespace mojo { + +template <> +struct EnumTraits { + static gfx::mojom::OverlayPriorityHint ToMojom( + gfx::OverlayPriorityHint hint) { + switch (hint) { + case gfx::OverlayPriorityHint::kNone: + return gfx::mojom::OverlayPriorityHint::kNone; + case gfx::OverlayPriorityHint::kRegular: + return gfx::mojom::OverlayPriorityHint::kRegular; + case gfx::OverlayPriorityHint::kLowLatencyCanvas: + return gfx::mojom::OverlayPriorityHint::kLowLatencyCanvas; + case gfx::OverlayPriorityHint::kHardwareProtection: + return gfx::mojom::OverlayPriorityHint::kHardwareProtection; + } + NOTREACHED(); + return gfx::mojom::OverlayPriorityHint::kNone; + } + + static bool FromMojom(gfx::mojom::OverlayPriorityHint input, + gfx::OverlayPriorityHint* out) { + switch (input) { + case gfx::mojom::OverlayPriorityHint::kNone: + *out = gfx::OverlayPriorityHint::kNone; + return true; + case gfx::mojom::OverlayPriorityHint::kRegular: + *out = gfx::OverlayPriorityHint::kRegular; + return true; + case gfx::mojom::OverlayPriorityHint::kLowLatencyCanvas: + *out = gfx::OverlayPriorityHint::kLowLatencyCanvas; + return true; + case gfx::mojom::OverlayPriorityHint::kHardwareProtection: + *out = gfx::OverlayPriorityHint::kHardwareProtection; + return true; + } + NOTREACHED(); + return false; + } +}; + +} // namespace mojo + +#endif // UI_GFX_MOJOM_OVERLAY_PRIORITY_HINT_MOJOM_TRAITS_H_ diff --git a/mojom/overlay_transform.mojom b/mojom/overlay_transform.mojom new file mode 100644 index 000000000000..af64e3629a1f --- /dev/null +++ b/mojom/overlay_transform.mojom @@ -0,0 +1,17 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module gfx.mojom; + +// gfx::OverlayTransform +enum OverlayTransform { + OVERLAY_TRANSFORM_INVALID, + OVERLAY_TRANSFORM_NONE, + OVERLAY_TRANSFORM_FLIP_HORIZONTAL, + OVERLAY_TRANSFORM_FLIP_VERTICAL, + OVERLAY_TRANSFORM_ROTATE_90, + OVERLAY_TRANSFORM_ROTATE_180, + OVERLAY_TRANSFORM_ROTATE_270, + OVERLAY_TRANSFORM_LAST = OVERLAY_TRANSFORM_ROTATE_270 +}; diff --git a/mojom/overlay_transform_mojom_traits.h b/mojom/overlay_transform_mojom_traits.h new file mode 100644 index 000000000000..b94c5f14e1b0 --- /dev/null +++ b/mojom/overlay_transform_mojom_traits.h @@ -0,0 +1,68 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_MOJOM_OVERLAY_TRANSFORM_MOJOM_TRAITS_H_ +#define UI_GFX_MOJOM_OVERLAY_TRANSFORM_MOJOM_TRAITS_H_ + +#include "ui/gfx/mojom/overlay_transform.mojom.h" +#include "ui/gfx/overlay_transform.h" + +namespace mojo { + +template <> +struct EnumTraits { + static gfx::mojom::OverlayTransform ToMojom(gfx::OverlayTransform format) { + switch (format) { + case gfx::OverlayTransform::OVERLAY_TRANSFORM_INVALID: + return gfx::mojom::OverlayTransform::OVERLAY_TRANSFORM_INVALID; + case gfx::OverlayTransform::OVERLAY_TRANSFORM_NONE: + return gfx::mojom::OverlayTransform::OVERLAY_TRANSFORM_NONE; + case gfx::OverlayTransform::OVERLAY_TRANSFORM_FLIP_HORIZONTAL: + return gfx::mojom::OverlayTransform::OVERLAY_TRANSFORM_FLIP_HORIZONTAL; + case gfx::OverlayTransform::OVERLAY_TRANSFORM_FLIP_VERTICAL: + return gfx::mojom::OverlayTransform::OVERLAY_TRANSFORM_FLIP_VERTICAL; + case gfx::OverlayTransform::OVERLAY_TRANSFORM_ROTATE_90: + return gfx::mojom::OverlayTransform::OVERLAY_TRANSFORM_ROTATE_90; + case gfx::OverlayTransform::OVERLAY_TRANSFORM_ROTATE_180: + return gfx::mojom::OverlayTransform::OVERLAY_TRANSFORM_ROTATE_180; + case gfx::OverlayTransform::OVERLAY_TRANSFORM_ROTATE_270: + return gfx::mojom::OverlayTransform::OVERLAY_TRANSFORM_ROTATE_270; + } + NOTREACHED(); + return gfx::mojom::OverlayTransform::OVERLAY_TRANSFORM_INVALID; + } + + static bool FromMojom(gfx::mojom::OverlayTransform input, + gfx::OverlayTransform* out) { + switch (input) { + case gfx::mojom::OverlayTransform::OVERLAY_TRANSFORM_INVALID: + *out = gfx::OverlayTransform::OVERLAY_TRANSFORM_INVALID; + return true; + case gfx::mojom::OverlayTransform::OVERLAY_TRANSFORM_NONE: + *out = gfx::OverlayTransform::OVERLAY_TRANSFORM_NONE; + return true; + case gfx::mojom::OverlayTransform::OVERLAY_TRANSFORM_FLIP_HORIZONTAL: + *out = gfx::OverlayTransform::OVERLAY_TRANSFORM_FLIP_HORIZONTAL; + return true; + case gfx::mojom::OverlayTransform::OVERLAY_TRANSFORM_FLIP_VERTICAL: + *out = gfx::OverlayTransform::OVERLAY_TRANSFORM_FLIP_VERTICAL; + return true; + case gfx::mojom::OverlayTransform::OVERLAY_TRANSFORM_ROTATE_90: + *out = gfx::OverlayTransform::OVERLAY_TRANSFORM_ROTATE_90; + return true; + case gfx::mojom::OverlayTransform::OVERLAY_TRANSFORM_ROTATE_180: + *out = gfx::OverlayTransform::OVERLAY_TRANSFORM_ROTATE_180; + return true; + case gfx::mojom::OverlayTransform::OVERLAY_TRANSFORM_ROTATE_270: + *out = gfx::OverlayTransform::OVERLAY_TRANSFORM_ROTATE_270; + return true; + } + NOTREACHED(); + return false; + } +}; + +} // namespace mojo + +#endif // UI_GFX_MOJOM_OVERLAY_TRANSFORM_MOJOM_TRAITS_H_ diff --git a/mojom/presentation_feedback.mojom b/mojom/presentation_feedback.mojom new file mode 100644 index 000000000000..2ddc6618224b --- /dev/null +++ b/mojom/presentation_feedback.mojom @@ -0,0 +1,19 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module gfx.mojom; + +import "mojo/public/mojom/base/time.mojom"; + +// gfx::PresentationFeedback +struct PresentationFeedback { + mojo_base.mojom.TimeTicks timestamp; + mojo_base.mojom.TimeDelta interval; + uint32 flags; + + mojo_base.mojom.TimeTicks available_timestamp; + mojo_base.mojom.TimeTicks ready_timestamp; + mojo_base.mojom.TimeTicks latch_timestamp; + mojo_base.mojom.TimeTicks writes_done_timestamp; +}; diff --git a/mojom/presentation_feedback_mojom_traits.h b/mojom/presentation_feedback_mojom_traits.h new file mode 100644 index 000000000000..bee9e788c2fe --- /dev/null +++ b/mojom/presentation_feedback_mojom_traits.h @@ -0,0 +1,64 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_MOJOM_PRESENTATION_FEEDBACK_MOJOM_TRAITS_H_ +#define UI_GFX_MOJOM_PRESENTATION_FEEDBACK_MOJOM_TRAITS_H_ + +#include "base/time/time.h" +#include "mojo/public/cpp/base/time_mojom_traits.h" +#include "ui/gfx/mojom/presentation_feedback.mojom-shared.h" +#include "ui/gfx/presentation_feedback.h" + +namespace mojo { + +template <> +struct StructTraits { + static base::TimeTicks timestamp(const gfx::PresentationFeedback& input) { + return input.timestamp; + } + + static base::TimeDelta interval(const gfx::PresentationFeedback& input) { + return input.interval; + } + + static uint32_t flags(const gfx::PresentationFeedback& input) { + return input.flags; + } + + static base::TimeTicks available_timestamp( + const gfx::PresentationFeedback& input) { + return input.available_timestamp; + } + + static base::TimeTicks ready_timestamp( + const gfx::PresentationFeedback& input) { + return input.ready_timestamp; + } + + static base::TimeTicks latch_timestamp( + const gfx::PresentationFeedback& input) { + return input.latch_timestamp; + } + + static base::TimeTicks writes_done_timestamp( + const gfx::PresentationFeedback& input) { + return input.writes_done_timestamp; + } + + static bool Read(gfx::mojom::PresentationFeedbackDataView data, + gfx::PresentationFeedback* out) { + out->flags = data.flags(); + return data.ReadTimestamp(&out->timestamp) && + data.ReadInterval(&out->interval) && + data.ReadAvailableTimestamp(&out->available_timestamp) && + data.ReadReadyTimestamp(&out->ready_timestamp) && + data.ReadLatchTimestamp(&out->latch_timestamp) && + data.ReadWritesDoneTimestamp(&out->writes_done_timestamp); + } +}; + +} // namespace mojo + +#endif // UI_GFX_MOJOM_PRESENTATION_FEEDBACK_MOJOM_TRAITS_H_ diff --git a/mojom/rrect_f.mojom b/mojom/rrect_f.mojom new file mode 100644 index 000000000000..c10e8f46f4c5 --- /dev/null +++ b/mojom/rrect_f.mojom @@ -0,0 +1,26 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module gfx.mojom; + +import "ui/gfx/geometry/mojom/geometry.mojom"; + +enum RRectFType { + kEmpty, + kRect, + kSingle, + kSimple, + kOval, + kComplex, +}; + +// See ui/gfx/rrect_f.h. +struct RRectF { + RRectFType type; + gfx.mojom.RectF rect; + gfx.mojom.Vector2dF upper_left; + gfx.mojom.Vector2dF upper_right; + gfx.mojom.Vector2dF lower_right; + gfx.mojom.Vector2dF lower_left; +}; diff --git a/mojom/rrect_f_mojom_traits.h b/mojom/rrect_f_mojom_traits.h new file mode 100644 index 000000000000..1e7328570be9 --- /dev/null +++ b/mojom/rrect_f_mojom_traits.h @@ -0,0 +1,121 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_MOJOM_RRECT_F_MOJOM_TRAITS_H_ +#define UI_GFX_MOJOM_RRECT_F_MOJOM_TRAITS_H_ + +#include "ui/gfx/geometry/mojom/geometry_mojom_traits.h" +#include "ui/gfx/geometry/rrect_f.h" +#include "ui/gfx/geometry/rrect_f_builder.h" +#include "ui/gfx/mojom/rrect_f.mojom-shared.h" + +namespace mojo { + +namespace { + +gfx::mojom::RRectFType GfxRRectFTypeToMojo(gfx::RRectF::Type type) { + switch (type) { + case gfx::RRectF::Type::kEmpty: + return gfx::mojom::RRectFType::kEmpty; + case gfx::RRectF::Type::kRect: + return gfx::mojom::RRectFType::kRect; + case gfx::RRectF::Type::kSingle: + return gfx::mojom::RRectFType::kSingle; + case gfx::RRectF::Type::kSimple: + return gfx::mojom::RRectFType::kSimple; + case gfx::RRectF::Type::kOval: + return gfx::mojom::RRectFType::kOval; + case gfx::RRectF::Type::kComplex: + return gfx::mojom::RRectFType::kComplex; + } + NOTREACHED(); + return gfx::mojom::RRectFType::kEmpty; +} + +gfx::RRectF::Type MojoRRectFTypeToGfx(gfx::mojom::RRectFType type) { + switch (type) { + case gfx::mojom::RRectFType::kEmpty: + return gfx::RRectF::Type::kEmpty; + case gfx::mojom::RRectFType::kRect: + return gfx::RRectF::Type::kRect; + case gfx::mojom::RRectFType::kSingle: + return gfx::RRectF::Type::kSingle; + case gfx::mojom::RRectFType::kSimple: + return gfx::RRectF::Type::kSimple; + case gfx::mojom::RRectFType::kOval: + return gfx::RRectF::Type::kOval; + case gfx::mojom::RRectFType::kComplex: + return gfx::RRectF::Type::kComplex; + } + NOTREACHED(); + return gfx::RRectF::Type::kEmpty; +} + +} // namespace + +template <> +struct StructTraits { + static gfx::mojom::RRectFType type(const gfx::RRectF& input) { + return GfxRRectFTypeToMojo(input.GetType()); + } + + static gfx::RectF rect(const gfx::RRectF& input) { return input.rect(); } + + static gfx::Vector2dF upper_left(const gfx::RRectF& input) { + return input.GetCornerRadii(gfx::RRectF::Corner::kUpperLeft); + } + + static gfx::Vector2dF upper_right(const gfx::RRectF& input) { + return input.GetCornerRadii(gfx::RRectF::Corner::kUpperRight); + } + + static gfx::Vector2dF lower_right(const gfx::RRectF& input) { + return input.GetCornerRadii(gfx::RRectF::Corner::kLowerRight); + } + + static gfx::Vector2dF lower_left(const gfx::RRectF& input) { + return input.GetCornerRadii(gfx::RRectF::Corner::kLowerLeft); + } + + static bool Read(gfx::mojom::RRectFDataView data, gfx::RRectF* out) { + gfx::RRectF::Type type(MojoRRectFTypeToGfx(data.type())); + gfx::RectF rect; + if (!data.ReadRect(&rect)) + return false; + if (type <= gfx::RRectF::Type::kRect) { + *out = gfx::RRectFBuilder().set_rect(rect).Build(); + return true; + } + gfx::Vector2dF upper_left; + if (!data.ReadUpperLeft(&upper_left)) + return false; + if (type <= gfx::RRectF::Type::kSimple) { + *out = gfx::RRectFBuilder() + .set_rect(rect) + .set_radius(upper_left.x(), upper_left.y()) + .Build(); + return true; + } + gfx::Vector2dF upper_right; + gfx::Vector2dF lower_right; + gfx::Vector2dF lower_left; + if (!data.ReadUpperRight(&upper_right) || + !data.ReadLowerRight(&lower_right) || + !data.ReadLowerLeft(&lower_left)) { + return false; + } + *out = gfx::RRectFBuilder() + .set_rect(rect) + .set_upper_left(upper_left.x(), upper_left.y()) + .set_upper_right(upper_right.x(), upper_right.y()) + .set_lower_right(lower_right.x(), lower_right.y()) + .set_lower_left(lower_left.x(), lower_left.y()) + .Build(); + return true; + } +}; + +} // namespace mojo + +#endif // UI_GFX_MOJOM_RRECT_F_MOJOM_TRAITS_H_ diff --git a/mojom/selection_bound.mojom b/mojom/selection_bound.mojom new file mode 100644 index 000000000000..55b56c152eb9 --- /dev/null +++ b/mojom/selection_bound.mojom @@ -0,0 +1,26 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module gfx.mojom; + +import "ui/gfx/geometry/mojom/geometry.mojom"; + +enum SelectionBoundType { + LEFT, + RIGHT, + CENTER, + EMPTY, + LAST = EMPTY +}; + +// See ui/gfx/selection_bound.h. +struct SelectionBound { + SelectionBoundType type; + gfx.mojom.PointF edge_start; + gfx.mojom.PointF edge_end; + gfx.mojom.PointF visible_edge_start; + gfx.mojom.PointF visible_edge_end; + bool visible; +}; + diff --git a/mojom/selection_bound_mojom_traits.h b/mojom/selection_bound_mojom_traits.h new file mode 100644 index 000000000000..e001ae5c274f --- /dev/null +++ b/mojom/selection_bound_mojom_traits.h @@ -0,0 +1,96 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_MOJOM_SELECTION_BOUND_MOJOM_TRAITS_H_ +#define UI_GFX_MOJOM_SELECTION_BOUND_MOJOM_TRAITS_H_ + +#include "ui/gfx/geometry/mojom/geometry_mojom_traits.h" +#include "ui/gfx/mojom/selection_bound.mojom-shared.h" +#include "ui/gfx/selection_bound.h" + +namespace mojo { + +namespace { + +gfx::mojom::SelectionBoundType GfxSelectionBoundTypeToMojo( + gfx::SelectionBound::Type type) { + switch (type) { + case gfx::SelectionBound::LEFT: + return gfx::mojom::SelectionBoundType::LEFT; + case gfx::SelectionBound::RIGHT: + return gfx::mojom::SelectionBoundType::RIGHT; + case gfx::SelectionBound::CENTER: + return gfx::mojom::SelectionBoundType::CENTER; + case gfx::SelectionBound::EMPTY: + return gfx::mojom::SelectionBoundType::EMPTY; + } + NOTREACHED(); + return gfx::mojom::SelectionBoundType::EMPTY; +} + +gfx::SelectionBound::Type MojoSelectionBoundTypeToGfx( + gfx::mojom::SelectionBoundType type) { + switch (type) { + case gfx::mojom::SelectionBoundType::LEFT: + return gfx::SelectionBound::LEFT; + case gfx::mojom::SelectionBoundType::RIGHT: + return gfx::SelectionBound::RIGHT; + case gfx::mojom::SelectionBoundType::CENTER: + return gfx::SelectionBound::CENTER; + case gfx::mojom::SelectionBoundType::EMPTY: + return gfx::SelectionBound::EMPTY; + } + NOTREACHED(); + return gfx::SelectionBound::EMPTY; +} + +} + +template <> +struct StructTraits { + static gfx::mojom::SelectionBoundType type(const gfx::SelectionBound& input) { + return GfxSelectionBoundTypeToMojo(input.type()); + } + + static gfx::PointF edge_start(const gfx::SelectionBound& input) { + return input.edge_start(); + } + + static gfx::PointF edge_end(const gfx::SelectionBound& input) { + return input.edge_end(); + } + + static gfx::PointF visible_edge_start(const gfx::SelectionBound& input) { + return input.visible_edge_start(); + } + + static gfx::PointF visible_edge_end(const gfx::SelectionBound& input) { + return input.visible_edge_end(); + } + + static bool visible(const gfx::SelectionBound& input) { + return input.visible(); + } + + static bool Read(gfx::mojom::SelectionBoundDataView data, + gfx::SelectionBound* out) { + gfx::PointF edge_start; + gfx::PointF edge_end; + gfx::PointF visible_edge_start; + gfx::PointF visible_edge_end; + if (!data.ReadEdgeStart(&edge_start) || !data.ReadEdgeEnd(&edge_end) || + !data.ReadVisibleEdgeStart(&visible_edge_start) || + !data.ReadVisibleEdgeEnd(&visible_edge_end)) + return false; + out->SetEdge(edge_start, edge_end); + out->SetVisibleEdge(visible_edge_start, visible_edge_end); + out->set_type(MojoSelectionBoundTypeToGfx(data.type())); + out->set_visible(data.visible()); + return true; + } +}; + +} // namespace mojo + +#endif // UI_GFX_MOJOM_SELECTION_BOUND_MOJOM_TRAITS_H_ diff --git a/mojom/swap_result.mojom b/mojom/swap_result.mojom new file mode 100644 index 000000000000..f206fee972f4 --- /dev/null +++ b/mojom/swap_result.mojom @@ -0,0 +1,17 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module gfx.mojom; + +// SwapResult information which is used to indicate whether buffer swap +// succeeded or not. These values correspond to gfx::SwapResult values in +// ui/gfx/swap_result.h. Currently, it is used by the Ozone/Wayland to identify +// whether a buffer swap requested by the GPU process has been successful on +// the browser process side or not. +enum SwapResult { + ACK, + FAILED, + SKIPPED, + NAK_RECREATE_BUFFERS, +}; diff --git a/mojom/swap_result_mojom_traits.h b/mojom/swap_result_mojom_traits.h new file mode 100644 index 000000000000..245a5a3cd317 --- /dev/null +++ b/mojom/swap_result_mojom_traits.h @@ -0,0 +1,53 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_MOJOM_SWAP_RESULT_MOJOM_TRAITS_H_ +#define UI_GFX_MOJOM_SWAP_RESULT_MOJOM_TRAITS_H_ + +#include "mojo/public/cpp/bindings/enum_traits.h" +#include "ui/gfx/mojom/swap_result.mojom-shared.h" +#include "ui/gfx/swap_result.h" + +namespace mojo { + +template <> +struct EnumTraits { + static gfx::mojom::SwapResult ToMojom(gfx::SwapResult input) { + switch (input) { + case gfx::SwapResult::SWAP_ACK: + return gfx::mojom::SwapResult::ACK; + case gfx::SwapResult::SWAP_FAILED: + return gfx::mojom::SwapResult::FAILED; + case gfx::SwapResult::SWAP_SKIPPED: + return gfx::mojom::SwapResult::SKIPPED; + case gfx::SwapResult::SWAP_NAK_RECREATE_BUFFERS: + return gfx::mojom::SwapResult::NAK_RECREATE_BUFFERS; + } + NOTREACHED(); + return gfx::mojom::SwapResult::FAILED; + } + + static bool FromMojom(gfx::mojom::SwapResult input, gfx::SwapResult* out) { + switch (input) { + case gfx::mojom::SwapResult::ACK: + *out = gfx::SwapResult::SWAP_ACK; + return true; + case gfx::mojom::SwapResult::FAILED: + *out = gfx::SwapResult::SWAP_FAILED; + return true; + case gfx::mojom::SwapResult::SKIPPED: + *out = gfx::SwapResult::SWAP_SKIPPED; + return true; + case gfx::mojom::SwapResult::NAK_RECREATE_BUFFERS: + *out = gfx::SwapResult::SWAP_NAK_RECREATE_BUFFERS; + return true; + } + NOTREACHED(); + return false; + } +}; + +} // namespace mojo + +#endif // UI_GFX_MOJOM_SWAP_RESULT_MOJOM_TRAITS_H_ diff --git a/mojom/swap_timings.mojom b/mojom/swap_timings.mojom new file mode 100644 index 000000000000..af51a0b54db6 --- /dev/null +++ b/mojom/swap_timings.mojom @@ -0,0 +1,13 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module gfx.mojom; + +import "mojo/public/mojom/base/time.mojom"; + +// gfx::SwapTimings +struct SwapTimings { + mojo_base.mojom.TimeTicks swap_start; + mojo_base.mojom.TimeTicks swap_end; +}; diff --git a/mojom/swap_timings_mojom_traits.h b/mojom/swap_timings_mojom_traits.h new file mode 100644 index 000000000000..96ea98931b1e --- /dev/null +++ b/mojom/swap_timings_mojom_traits.h @@ -0,0 +1,34 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_MOJOM_SWAP_TIMINGS_MOJOM_TRAITS_H_ +#define UI_GFX_MOJOM_SWAP_TIMINGS_MOJOM_TRAITS_H_ + +#include "base/time/time.h" +#include "mojo/public/cpp/base/time_mojom_traits.h" +#include "ui/gfx/mojom/swap_timings.mojom-shared.h" +#include "ui/gfx/swap_result.h" + +namespace mojo { + +template <> +struct StructTraits { + static base::TimeTicks swap_start(const gfx::SwapTimings& input) { + return input.swap_start; + } + + static base::TimeTicks swap_end(const gfx::SwapTimings& input) { + return input.swap_end; + } + + static bool Read(gfx::mojom::SwapTimingsDataView data, + gfx::SwapTimings* out) { + return data.ReadSwapStart(&out->swap_start) && + data.ReadSwapEnd(&out->swap_end); + } +}; + +} // namespace mojo + +#endif // UI_GFX_MOJOM_SWAP_TIMINGS_MOJOM_TRAITS_H_ diff --git a/mojom/traits_test_service.mojom b/mojom/traits_test_service.mojom new file mode 100644 index 000000000000..7cdcb7fa4474 --- /dev/null +++ b/mojom/traits_test_service.mojom @@ -0,0 +1,28 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module gfx.mojom; + +import "ui/gfx/mojom/accelerated_widget.mojom"; +import "ui/gfx/mojom/buffer_types.mojom"; +import "ui/gfx/mojom/rrect_f.mojom"; +import "ui/gfx/mojom/selection_bound.mojom"; +import "ui/gfx/mojom/transform.mojom"; + +// All functions on this interface echo their arguments to test StructTraits +// serialization and deserialization. +interface TraitsTestService { + [Sync] + EchoSelectionBound(SelectionBound s) => (SelectionBound pass); + + [Sync] + EchoTransform(Transform t) => (Transform pass); + + [Sync] + EchoGpuMemoryBufferHandle(GpuMemoryBufferHandle g) + => (GpuMemoryBufferHandle pass); + + [Sync] + EchoRRectF(RRectF t) => (RRectF pass); +}; diff --git a/mojom/transform.mojom b/mojom/transform.mojom new file mode 100644 index 000000000000..badc081d5156 --- /dev/null +++ b/mojom/transform.mojom @@ -0,0 +1,14 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module gfx.mojom; + +// See ui/gfx/transform.h. +[Stable] +struct Transform { + // Column major order. + // The identity matrix is considered null and will not be serialized. This + // saves the cost of serialization and deserialization. + array? matrix; +}; diff --git a/mojom/transform_mojom_traits.h b/mojom/transform_mojom_traits.h new file mode 100644 index 000000000000..b8c3887ddbe8 --- /dev/null +++ b/mojom/transform_mojom_traits.h @@ -0,0 +1,48 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_MOJOM_TRANSFORM_MOJOM_TRAITS_H_ +#define UI_GFX_MOJOM_TRANSFORM_MOJOM_TRAITS_H_ + +#include "mojo/public/cpp/bindings/array_traits.h" +#include "ui/gfx/geometry/transform.h" +#include "ui/gfx/mojom/transform.mojom-shared.h" + +namespace mojo { + +template <> +struct ArrayTraits { + using Element = float; + + static bool IsNull(const skia::Matrix44& input) { return input.isIdentity(); } + + static size_t GetSize(const skia::Matrix44& input) { return 16; } + + static float GetAt(const skia::Matrix44& input, size_t index) { + return input.getFloat(static_cast(index % 4), + static_cast(index / 4)); + } +}; + +template <> +struct StructTraits { + static const skia::Matrix44& matrix(const gfx::Transform& transform) { + return transform.matrix(); + } + + static bool Read(gfx::mojom::TransformDataView data, gfx::Transform* out) { + ArrayDataView matrix; + data.GetMatrixDataView(&matrix); + if (matrix.is_null()) { + out->MakeIdentity(); + return true; + } + out->matrix().setColMajorf(matrix.data()); + return true; + } +}; + +} // namespace mojo + +#endif // UI_GFX_MOJOM_TRANSFORM_MOJOM_TRAITS_H_ diff --git a/native_pixmap.h b/native_pixmap.h new file mode 100644 index 000000000000..c90af239a93b --- /dev/null +++ b/native_pixmap.h @@ -0,0 +1,79 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_NATIVE_PIXMAP_H_ +#define UI_GFX_NATIVE_PIXMAP_H_ + +#include "base/bind.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "ui/gfx/buffer_types.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/native_pixmap_handle.h" +#include "ui/gfx/native_widget_types.h" + +namespace gfx { +struct OverlayPlaneData; +class GpuFence; + +// This represents a buffer that can be directly imported via GL for +// rendering, or exported via dma-buf fds. +class NativePixmap : public base::RefCountedThreadSafe { + public: + NativePixmap() {} + + NativePixmap(const NativePixmap&) = delete; + NativePixmap& operator=(const NativePixmap&) = delete; + + virtual bool AreDmaBufFdsValid() const = 0; + virtual int GetDmaBufFd(size_t plane) const = 0; + virtual uint32_t GetDmaBufPitch(size_t plane) const = 0; + virtual size_t GetDmaBufOffset(size_t plane) const = 0; + virtual size_t GetDmaBufPlaneSize(size_t plane) const = 0; + // Return the number of non-interleaved "color" planes. + virtual size_t GetNumberOfPlanes() const = 0; + + // The following methods return format, modifier and size of the buffer, + // respectively. + virtual gfx::BufferFormat GetBufferFormat() const = 0; + virtual uint64_t GetBufferFormatModifier() const = 0; + virtual gfx::Size GetBufferSize() const = 0; + + // Return an id that is guaranteed to be unique and equal for all instances + // of this NativePixmap backed by the same buffer, for the duration of its + // lifetime. If such id cannot be generated, 0 (an invalid id) is returned. + // + // TODO(posciak): crbug.com/771863, remove this once a different mechanism + // for protected shared memory buffers is implemented. + virtual uint32_t GetUniqueId() const = 0; + + // Sets the overlay plane to switch to at the next page flip. + // |widget| specifies the screen to display this overlay plane on. + // |acquire_fences| specifies gpu fences to wait on before the pixmap is ready + // to be displayed. These fence are fired when the gpu has finished writing to + // the pixmap. + // |release_fences| specifies gpu fences that are signalled when the pixmap + // has been displayed and is ready for reuse. + // |overlay_plane_data| specifies overlay data such as opacity, z_order, size, + // etc. + virtual bool ScheduleOverlayPlane( + gfx::AcceleratedWidget widget, + const gfx::OverlayPlaneData& overlay_plane_data, + std::vector acquire_fences, + std::vector release_fences) = 0; + + // Export the buffer for sharing across processes. + // Any file descriptors in the exported handle are owned by the caller. + virtual gfx::NativePixmapHandle ExportHandle() = 0; + + protected: + virtual ~NativePixmap() {} + + private: + friend class base::RefCountedThreadSafe; +}; + +} // namespace gfx + +#endif // UI_GFX_NATIVE_PIXMAP_H_ diff --git a/native_pixmap_handle.cc b/native_pixmap_handle.cc new file mode 100644 index 000000000000..137056bcdcd7 --- /dev/null +++ b/native_pixmap_handle.cc @@ -0,0 +1,113 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/native_pixmap_handle.h" + +#include + +#include "base/logging.h" +#include "build/build_config.h" + +#if defined(OS_LINUX) || defined(OS_CHROMEOS) +#include +#include "base/posix/eintr_wrapper.h" +#endif + +#if defined(OS_FUCHSIA) +#include +#include "base/fuchsia/fuchsia_logging.h" +#endif + +namespace gfx { + +#if defined(OS_LINUX) || defined(OS_CHROMEOS) +static_assert(NativePixmapHandle::kNoModifier == DRM_FORMAT_MOD_INVALID, + "gfx::NativePixmapHandle::kNoModifier should be an alias for" + "DRM_FORMAT_MOD_INVALID"); +#endif + +NativePixmapPlane::NativePixmapPlane() : stride(0), offset(0), size(0) {} + +NativePixmapPlane::NativePixmapPlane(int stride, + int offset, + uint64_t size +#if defined(OS_LINUX) || defined(OS_CHROMEOS) + , + base::ScopedFD fd +#elif defined(OS_FUCHSIA) + , + zx::vmo vmo +#endif + ) + : stride(stride), + offset(offset), + size(size) +#if defined(OS_LINUX) || defined(OS_CHROMEOS) + , + fd(std::move(fd)) +#elif defined(OS_FUCHSIA) + , + vmo(std::move(vmo)) +#endif +{ +} + +NativePixmapPlane::NativePixmapPlane(NativePixmapPlane&& other) = default; + +NativePixmapPlane::~NativePixmapPlane() = default; + +NativePixmapPlane& NativePixmapPlane::operator=(NativePixmapPlane&& other) = + default; + +NativePixmapHandle::NativePixmapHandle() = default; +NativePixmapHandle::NativePixmapHandle(NativePixmapHandle&& other) = default; + +NativePixmapHandle::~NativePixmapHandle() = default; + +NativePixmapHandle& NativePixmapHandle::operator=(NativePixmapHandle&& other) = + default; + +NativePixmapHandle CloneHandleForIPC(const NativePixmapHandle& handle) { + NativePixmapHandle clone; + for (auto& plane : handle.planes) { +#if defined(OS_LINUX) || defined(OS_CHROMEOS) + DCHECK(plane.fd.is_valid()); + base::ScopedFD fd_dup(HANDLE_EINTR(dup(plane.fd.get()))); + if (!fd_dup.is_valid()) { + PLOG(ERROR) << "dup"; + return NativePixmapHandle(); + } + clone.planes.emplace_back(plane.stride, plane.offset, plane.size, + std::move(fd_dup)); +#elif defined(OS_FUCHSIA) + zx::vmo vmo_dup; + // VMO may be set to NULL for pixmaps that cannot be mapped. + if (plane.vmo) { + zx_status_t status = plane.vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo_dup); + if (status != ZX_OK) { + ZX_DLOG(ERROR, status) << "zx_handle_duplicate"; + return NativePixmapHandle(); + } + } + clone.planes.emplace_back(plane.stride, plane.offset, plane.size, + std::move(vmo_dup)); +#else +#error Unsupported OS +#endif + } + +#if defined(OS_LINUX) || defined(OS_CHROMEOS) + clone.modifier = handle.modifier; +#endif + +#if defined(OS_FUCHSIA) + clone.buffer_collection_id = handle.buffer_collection_id; + clone.buffer_index = handle.buffer_index; + clone.ram_coherency = handle.ram_coherency; +#endif + + return clone; +} + +} // namespace gfx diff --git a/native_pixmap_handle.h b/native_pixmap_handle.h new file mode 100644 index 000000000000..13f07f29e842 --- /dev/null +++ b/native_pixmap_handle.h @@ -0,0 +1,111 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_NATIVE_PIXMAP_HANDLE_H_ +#define UI_GFX_NATIVE_PIXMAP_HANDLE_H_ + +#include +#include + +#include + +#include "base/unguessable_token.h" +#include "build/build_config.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/gfx/gfx_export.h" + +#if defined(OS_LINUX) || defined(OS_CHROMEOS) +#include "base/files/scoped_file.h" +#endif + +#if defined(OS_FUCHSIA) +#include +#endif + +namespace gfx { + +// NativePixmapPlane is used to carry the plane related information for GBM +// buffer. More fields can be added if they are plane specific. +struct GFX_EXPORT NativePixmapPlane { + NativePixmapPlane(); + NativePixmapPlane(int stride, + int offset, + uint64_t size +#if defined(OS_LINUX) || defined(OS_CHROMEOS) + , + base::ScopedFD fd +#elif defined(OS_FUCHSIA) + , + zx::vmo vmo +#endif + ); + NativePixmapPlane(NativePixmapPlane&& other); + ~NativePixmapPlane(); + + NativePixmapPlane& operator=(NativePixmapPlane&& other); + + // The strides and offsets in bytes to be used when accessing the buffers via + // a memory mapping. One per plane per entry. + uint32_t stride; + uint64_t offset; + // Size in bytes of the plane. + // This is necessary to map the buffers. + uint64_t size; + +#if defined(OS_LINUX) || defined(OS_CHROMEOS) + // File descriptor for the underlying memory object (usually dmabuf). + base::ScopedFD fd; +#elif defined(OS_FUCHSIA) + zx::vmo vmo; +#endif +}; + +#if defined(OS_FUCHSIA) +// Buffer collection ID is used to identify sysmem buffer collections across +// processes. +using SysmemBufferCollectionId = base::UnguessableToken; +#endif + +struct GFX_EXPORT NativePixmapHandle { + // This is the same value as DRM_FORMAT_MOD_INVALID, which is not a valid + // modifier. We use this to indicate that layout information + // (tiling/compression) if any will be communicated out of band. + static constexpr uint64_t kNoModifier = 0x00ffffffffffffffULL; + + NativePixmapHandle(); + NativePixmapHandle(NativePixmapHandle&& other); + + ~NativePixmapHandle(); + + NativePixmapHandle& operator=(NativePixmapHandle&& other); + + std::vector planes; + +#if defined(OS_LINUX) || defined(OS_CHROMEOS) + // The modifier is retrieved from GBM library and passed to EGL driver. + // Generally it's platform specific, and we don't need to modify it in + // Chromium code. Also one per plane per entry. + uint64_t modifier = kNoModifier; +#endif + +#if defined(OS_FUCHSIA) + absl::optional buffer_collection_id; + uint32_t buffer_index = 0; + + // Set to true for sysmem buffers which are initialized with RAM coherency + // domain. This means that clients that write to the buffers must flush CPU + // cache. + bool ram_coherency = false; +#endif +}; + +// Returns an instance of |handle| which can be sent over IPC. This duplicates +// the file-handles, so that the IPC code take ownership of them, without +// invalidating |handle|. +GFX_EXPORT NativePixmapHandle +CloneHandleForIPC(const NativePixmapHandle& handle); + +} // namespace gfx + +#endif // UI_GFX_NATIVE_PIXMAP_HANDLE_H_ diff --git a/native_widget_types.h b/native_widget_types.h new file mode 100644 index 000000000000..334d61f1f33b --- /dev/null +++ b/native_widget_types.h @@ -0,0 +1,259 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_NATIVE_WIDGET_TYPES_H_ +#define UI_GFX_NATIVE_WIDGET_TYPES_H_ + +#include + +#include "build/build_config.h" +#include "build/chromeos_buildflags.h" +#include "ui/gfx/gfx_export.h" + +#if defined(OS_ANDROID) +#include "base/android/scoped_java_ref.h" +#elif defined(OS_APPLE) +#include +#elif defined(OS_WIN) +#include "base/win/windows_types.h" +#endif + +// This file provides cross platform typedefs for native widget types. +// NativeWindow: this is a handle to a native, top-level window +// NativeView: this is a handle to a native UI element. It may be the +// same type as a NativeWindow on some platforms. +// NativeViewId: Often, in our cross process model, we need to pass around a +// reference to a "window". This reference will, say, be echoed back from a +// renderer to the browser when it wishes to query its size. On Windows we +// use an HWND for this. +// +// As a rule of thumb - if you're in the renderer, you should be dealing +// with NativeViewIds. This should remind you that you shouldn't be doing +// direct operations on platform widgets from the renderer process. +// +// If you're in the browser, you're probably dealing with NativeViews, +// unless you're in the IPC layer, which will be translating between +// NativeViewIds from the renderer and NativeViews. +// +// The name 'View' here meshes with OS X where the UI elements are called +// 'views' and with our Chrome UI code where the elements are also called +// 'views'. + +#if defined(USE_AURA) +namespace aura { +class Window; +} +namespace ui { +class Cursor; +class Event; +namespace mojom { +enum class CursorType; +} +} // namespace ui + +#endif // defined(USE_AURA) + +#if defined(OS_WIN) +typedef struct HFONT__* HFONT; +struct IAccessible; +#elif defined(OS_IOS) +struct CGContext; +#ifdef __OBJC__ +@class UIEvent; +@class UIFont; +@class UIImage; +@class UIView; +@class UIWindow; +@class UITextField; +#else +class UIEvent; +class UIFont; +class UIImage; +class UIView; +class UIWindow; +class UITextField; +#endif // __OBJC__ +#elif defined(OS_MAC) +struct CGContext; +#ifdef __OBJC__ +@class NSCursor; +@class NSEvent; +@class NSFont; +@class NSImage; +@class NSView; +@class NSWindow; +@class NSTextField; +#else +class NSCursor; +class NSEvent; +class NSFont; +class NSImage; +struct NSView; +class NSWindow; +class NSTextField; +#endif // __OBJC__ +#endif + +#if defined(OS_ANDROID) +struct ANativeWindow; +namespace ui { +class WindowAndroid; +class ViewAndroid; +} // namespace ui +#endif +class SkBitmap; + +// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch +// of lacros-chrome is complete. +#if defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS) +extern "C" { +struct _AtkObject; +typedef struct _AtkObject AtkObject; +} +#endif + +namespace gfx { + +#if defined(USE_AURA) +typedef ui::Cursor NativeCursor; +typedef aura::Window* NativeView; +typedef aura::Window* NativeWindow; +typedef ui::Event* NativeEvent; +constexpr NativeView kNullNativeView = nullptr; +constexpr NativeWindow kNullNativeWindow = nullptr; +#elif defined(OS_IOS) +typedef void* NativeCursor; +typedef UIView* NativeView; +typedef UIWindow* NativeWindow; +typedef UIEvent* NativeEvent; +constexpr NativeView kNullNativeView = nullptr; +constexpr NativeWindow kNullNativeWindow = nullptr; +#elif defined(OS_MAC) +typedef NSCursor* NativeCursor; +typedef NSEvent* NativeEvent; +// NativeViews and NativeWindows on macOS are not necessarily in the same +// process as the NSViews and NSWindows that they represent. Require an +// explicit function call (GetNativeNSView or GetNativeNSWindow) to retrieve +// the underlying NSView or NSWindow. +// https://crbug.com/893719 +class GFX_EXPORT NativeView { + public: + constexpr NativeView() {} + // TODO(ccameron): Make this constructor explicit. + constexpr NativeView(NSView* ns_view) : ns_view_(ns_view) {} + + // This function name is verbose (that is, not just GetNSView) so that it + // is easily grep-able. + NSView* GetNativeNSView() const { return ns_view_; } + + operator bool() const { return ns_view_ != 0; } + bool operator==(const NativeView& other) const { + return ns_view_ == other.ns_view_; + } + bool operator!=(const NativeView& other) const { + return ns_view_ != other.ns_view_; + } + bool operator<(const NativeView& other) const { + return ns_view_ < other.ns_view_; + } + + private: + NSView* ns_view_ = nullptr; +}; +class GFX_EXPORT NativeWindow { + public: + constexpr NativeWindow() {} + // TODO(ccameron): Make this constructor explicit. + constexpr NativeWindow(NSWindow* ns_window) : ns_window_(ns_window) {} + + // This function name is verbose (that is, not just GetNSWindow) so that it + // is easily grep-able. + NSWindow* GetNativeNSWindow() const { return ns_window_; } + + operator bool() const { return ns_window_ != 0; } + bool operator==(const NativeWindow& other) const { + return ns_window_ == other.ns_window_; + } + bool operator!=(const NativeWindow& other) const { + return ns_window_ != other.ns_window_; + } + bool operator<(const NativeWindow& other) const { + return ns_window_ < other.ns_window_; + } + + private: + NSWindow* ns_window_ = nullptr; +}; +constexpr NativeView kNullNativeView = NativeView(nullptr); +constexpr NativeWindow kNullNativeWindow = NativeWindow(nullptr); +#elif defined(OS_ANDROID) +typedef void* NativeCursor; +typedef ui::ViewAndroid* NativeView; +typedef ui::WindowAndroid* NativeWindow; +typedef base::android::ScopedJavaGlobalRef NativeEvent; +constexpr NativeView kNullNativeView = nullptr; +constexpr NativeWindow kNullNativeWindow = nullptr; +#else +#error Unknown build environment. +#endif + +#if defined(OS_WIN) +typedef HFONT NativeFont; +typedef IAccessible* NativeViewAccessible; +#elif defined(OS_IOS) +typedef UIFont* NativeFont; +typedef id NativeViewAccessible; +#elif defined(OS_MAC) +typedef NSFont* NativeFont; +typedef id NativeViewAccessible; +// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch +// of lacros-chrome is complete. +#elif defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS) +// Linux doesn't have a native font type. +typedef AtkObject* NativeViewAccessible; +#else +// Android, Chrome OS, etc. +typedef struct _UnimplementedNativeViewAccessible + UnimplementedNativeViewAccessible; +typedef UnimplementedNativeViewAccessible* NativeViewAccessible; +#endif + +// A constant value to indicate that gfx::NativeCursor refers to no cursor. +#if defined(USE_AURA) +const ui::mojom::CursorType kNullCursor = + static_cast(-1); +#else +const gfx::NativeCursor kNullCursor = static_cast(nullptr); +#endif + +// Note: for test_shell we're packing a pointer into the NativeViewId. So, if +// you make it a type which is smaller than a pointer, you have to fix +// test_shell. +// +// See comment at the top of the file for usage. +typedef intptr_t NativeViewId; + +// AcceleratedWidget provides a surface to compositors to paint pixels. +#if defined(OS_WIN) +typedef HWND AcceleratedWidget; +constexpr AcceleratedWidget kNullAcceleratedWidget = nullptr; +#elif defined(OS_IOS) +typedef UIView* AcceleratedWidget; +constexpr AcceleratedWidget kNullAcceleratedWidget = 0; +#elif defined(OS_MAC) +typedef uint64_t AcceleratedWidget; +constexpr AcceleratedWidget kNullAcceleratedWidget = 0; +#elif defined(OS_ANDROID) +typedef ANativeWindow* AcceleratedWidget; +constexpr AcceleratedWidget kNullAcceleratedWidget = 0; +#elif defined(USE_OZONE) || defined(USE_X11) +typedef uint32_t AcceleratedWidget; +constexpr AcceleratedWidget kNullAcceleratedWidget = 0; +#else +#error unknown platform +#endif + +} // namespace gfx + +#endif // UI_GFX_NATIVE_WIDGET_TYPES_H_ diff --git a/nine_image_painter.cc b/nine_image_painter.cc new file mode 100644 index 000000000000..b2292b3f7ddf --- /dev/null +++ b/nine_image_painter.cc @@ -0,0 +1,197 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/nine_image_painter.h" + +#include + +#include + +#include "base/cxx17_backports.h" +#include "base/numerics/safe_conversions.h" +#include "cc/paint/paint_flags.h" +#include "third_party/skia/include/core/SkRect.h" +#include "third_party/skia/include/core/SkScalar.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/geometry/insets.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/rect_conversions.h" +#include "ui/gfx/geometry/skia_conversions.h" +#include "ui/gfx/image/image_skia_operations.h" +#include "ui/gfx/scoped_canvas.h" + +namespace gfx { + +namespace { + +int ImageRepWidthInPixels(const ImageSkiaRep& rep) { + if (rep.is_null()) + return 0; + return rep.pixel_width(); +} + +int ImageRepHeightInPixels(const ImageSkiaRep& rep) { + if (rep.is_null()) + return 0; + return rep.pixel_height(); +} + +void Fill(Canvas* c, + const ImageSkiaRep& rep, + int x, + int y, + int w, + int h, + const cc::PaintFlags& flags) { + if (rep.is_null()) + return; + c->DrawImageIntInPixel(rep, x, y, w, h, false, flags); +} + +} // namespace + +NineImagePainter::NineImagePainter(const std::vector& images) { + DCHECK_EQ(base::size(images_), images.size()); + for (size_t i = 0; i < base::size(images_); ++i) + images_[i] = images[i]; +} + +NineImagePainter::NineImagePainter(const ImageSkia& image, + const Insets& insets) { + std::vector regions; + GetSubsetRegions(image, insets, ®ions); + DCHECK_EQ(9u, regions.size()); + + for (size_t i = 0; i < 9; ++i) + images_[i] = ImageSkiaOperations::ExtractSubset(image, regions[i]); +} + +NineImagePainter::~NineImagePainter() { +} + +bool NineImagePainter::IsEmpty() const { + return images_[0].isNull(); +} + +Size NineImagePainter::GetMinimumSize() const { + return IsEmpty() ? Size() : Size( + images_[0].width() + images_[1].width() + images_[2].width(), + images_[0].height() + images_[3].height() + images_[6].height()); +} + +void NineImagePainter::Paint(Canvas* canvas, const Rect& bounds) { + // When no alpha value is specified, use default value of 100% opacity. + Paint(canvas, bounds, std::numeric_limits::max()); +} + +void NineImagePainter::Paint(Canvas* canvas, + const Rect& bounds, + const uint8_t alpha) { + if (IsEmpty()) + return; + + ScopedCanvas scoped_canvas(canvas); + + // Painting and doing layout at physical device pixels to avoid cracks or + // overlap. + const float scale = canvas->UndoDeviceScaleFactor(); + + // Since the drawing from the following Fill() calls assumes the mapped origin + // is at (0,0), we need to translate the canvas to the mapped origin. + const int left_in_pixels = base::ClampRound(bounds.x() * scale); + const int top_in_pixels = base::ClampRound(bounds.y() * scale); + const int right_in_pixels = base::ClampRound(bounds.right() * scale); + const int bottom_in_pixels = base::ClampRound(bounds.bottom() * scale); + + const int width_in_pixels = right_in_pixels - left_in_pixels; + const int height_in_pixels = bottom_in_pixels - top_in_pixels; + + // Since the drawing from the following Fill() calls assumes the mapped origin + // is at (0,0), we need to translate the canvas to the mapped origin. + canvas->Translate(gfx::Vector2d(left_in_pixels, top_in_pixels)); + + ImageSkiaRep image_reps[9]; + static_assert(base::size(image_reps) == std::extent(), ""); + for (size_t i = 0; i < base::size(image_reps); ++i) { + image_reps[i] = images_[i].GetRepresentation(scale); + DCHECK(image_reps[i].is_null() || image_reps[i].scale() == scale); + } + + // In case the corners and edges don't all have the same width/height, we draw + // the center first, and extend it out in all directions to the edges of the + // images with the smallest widths/heights. This way there will be no + // unpainted areas, though some corners or edges might overlap the center. + int i0w = ImageRepWidthInPixels(image_reps[0]); + int i2w = ImageRepWidthInPixels(image_reps[2]); + int i3w = ImageRepWidthInPixels(image_reps[3]); + int i5w = ImageRepWidthInPixels(image_reps[5]); + int i6w = ImageRepWidthInPixels(image_reps[6]); + int i8w = ImageRepWidthInPixels(image_reps[8]); + + int i0h = ImageRepHeightInPixels(image_reps[0]); + int i1h = ImageRepHeightInPixels(image_reps[1]); + int i2h = ImageRepHeightInPixels(image_reps[2]); + int i6h = ImageRepHeightInPixels(image_reps[6]); + int i7h = ImageRepHeightInPixels(image_reps[7]); + int i8h = ImageRepHeightInPixels(image_reps[8]); + + i0w = std::min(i0w, width_in_pixels); + i2w = std::min(i2w, width_in_pixels - i0w); + i3w = std::min(i3w, width_in_pixels); + i5w = std::min(i5w, width_in_pixels - i3w); + i6w = std::min(i6w, width_in_pixels); + i8w = std::min(i8w, width_in_pixels - i6w); + + i0h = std::min(i0h, height_in_pixels); + i1h = std::min(i1h, height_in_pixels); + i2h = std::min(i2h, height_in_pixels); + i6h = std::min(i6h, height_in_pixels - i0h); + i7h = std::min(i7h, height_in_pixels - i1h); + i8h = std::min(i8h, height_in_pixels - i2h); + + int i4x = std::min({i0w, i3w, i6w}); + int i4y = std::min({i0h, i1h, i2h}); + int i4w = std::max(width_in_pixels - i4x - std::min({i2w, i5w, i8w}), 0); + int i4h = std::max(height_in_pixels - i4y - std::min({i6h, i7h, i8h}), 0); + + cc::PaintFlags flags; + flags.setAlpha(alpha); + + Fill(canvas, image_reps[4], i4x, i4y, i4w, i4h, flags); + Fill(canvas, image_reps[0], 0, 0, i0w, i0h, flags); + Fill(canvas, image_reps[1], i0w, 0, width_in_pixels - i0w - i2w, i1h, flags); + Fill(canvas, image_reps[2], width_in_pixels - i2w, 0, i2w, i2h, flags); + Fill(canvas, image_reps[3], 0, i0h, i3w, height_in_pixels - i0h - i6h, flags); + Fill(canvas, image_reps[5], width_in_pixels - i5w, i2h, i5w, + height_in_pixels - i2h - i8h, flags); + Fill(canvas, image_reps[6], 0, height_in_pixels - i6h, i6w, i6h, flags); + Fill(canvas, image_reps[7], i6w, height_in_pixels - i7h, + width_in_pixels - i6w - i8w, i7h, flags); + Fill(canvas, image_reps[8], width_in_pixels - i8w, height_in_pixels - i8h, + i8w, i8h, flags); +} + +// static +void NineImagePainter::GetSubsetRegions(const ImageSkia& image, + const Insets& insets, + std::vector* regions) { + DCHECK_GE(image.width(), insets.width()); + DCHECK_GE(image.height(), insets.height()); + + std::vector result(9); + + const int x[] = { + 0, insets.left(), image.width() - insets.right(), image.width()}; + const int y[] = { + 0, insets.top(), image.height() - insets.bottom(), image.height()}; + + for (size_t j = 0; j < 3; ++j) { + for (size_t i = 0; i < 3; ++i) { + result[i + j * 3] = Rect(x[i], y[j], x[i + 1] - x[i], y[j + 1] - y[j]); + } + } + result.swap(*regions); +} + +} // namespace gfx diff --git a/nine_image_painter.h b/nine_image_painter.h new file mode 100644 index 000000000000..caa6e3adc01e --- /dev/null +++ b/nine_image_painter.h @@ -0,0 +1,57 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_NINE_IMAGE_PAINTER_H_ +#define UI_GFX_NINE_IMAGE_PAINTER_H_ + +#include + +#include + +#include "base/gtest_prod_util.h" +#include "base/macros.h" +#include "ui/gfx/gfx_export.h" +#include "ui/gfx/image/image_skia.h" + +namespace gfx { + +class Canvas; +class Insets; +class Rect; + +class GFX_EXPORT NineImagePainter { + public: + explicit NineImagePainter(const std::vector& images); + NineImagePainter(const ImageSkia& image, const Insets& insets); + + NineImagePainter(const NineImagePainter&) = delete; + NineImagePainter& operator=(const NineImagePainter&) = delete; + + ~NineImagePainter(); + + bool IsEmpty() const; + Size GetMinimumSize() const; + void Paint(Canvas* canvas, const Rect& bounds); + void Paint(Canvas* canvas, const Rect& bounds, uint8_t alpha); + + private: + friend class NineImagePainterTest; + FRIEND_TEST_ALL_PREFIXES(NineImagePainterTest, GetSubsetRegions); + + // Gets the regions for the subimages into |regions|. + static void GetSubsetRegions(const ImageSkia& image, + const Insets& insets, + std::vector* regions); + + // Images are numbered as depicted below. + // ____________________ + // |__i0__|__i1__|__i2__| + // |__i3__|__i4__|__i5__| + // |__i6__|__i7__|__i8__| + ImageSkia images_[9]; +}; + +} // namespace gfx + +#endif // UI_GFX_NINE_IMAGE_PAINTER_H_ diff --git a/nine_image_painter_unittest.cc b/nine_image_painter_unittest.cc new file mode 100644 index 000000000000..9897d001199e --- /dev/null +++ b/nine_image_painter_unittest.cc @@ -0,0 +1,225 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/nine_image_painter.h" + +#include "base/base64.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/codec/png_codec.h" +#include "ui/gfx/geometry/insets.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/vector2d.h" +#include "ui/gfx/geometry/vector2d_conversions.h" +#include "ui/gfx/image/image_skia.h" + +namespace gfx { + +static std::string GetPNGDataUrl(const SkBitmap& bitmap) { + std::vector png_data; + gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false, &png_data); + std::string data_url; + data_url.insert(data_url.end(), png_data.begin(), png_data.end()); + base::Base64Encode(data_url, &data_url); + data_url.insert(0, "data:image/png;base64,"); + + return data_url; +} + +void ExpectRedWithGreenRect(const SkBitmap& bitmap, + const Rect& outer_rect, + const Rect& green_rect) { + for (int y = outer_rect.y(); y < outer_rect.bottom(); y++) { + SCOPED_TRACE(y); + for (int x = outer_rect.x(); x < outer_rect.right(); x++) { + SCOPED_TRACE(x); + if (green_rect.Contains(x, y)) { + ASSERT_EQ(SK_ColorGREEN, bitmap.getColor(x, y)) + << "Output image:\n" << GetPNGDataUrl(bitmap); + } else { + ASSERT_EQ(SK_ColorRED, bitmap.getColor(x, y)) << "Output image:\n" + << GetPNGDataUrl(bitmap); + } + } + } +} + +TEST(NineImagePainterTest, GetSubsetRegions) { + SkBitmap src; + src.allocN32Pixels(40, 50); + const ImageSkia image_skia(ImageSkiaRep(src, 1.0)); + const Insets insets(1, 2, 3, 4); + std::vector rects; + NineImagePainter::GetSubsetRegions(image_skia, insets, &rects); + ASSERT_EQ(9u, rects.size()); + EXPECT_EQ(gfx::Rect(0, 0, 2, 1), rects[0]); + EXPECT_EQ(gfx::Rect(2, 0, 34, 1), rects[1]); + EXPECT_EQ(gfx::Rect(36, 0, 4, 1), rects[2]); + EXPECT_EQ(gfx::Rect(0, 1, 2, 46), rects[3]); + EXPECT_EQ(gfx::Rect(2, 1, 34, 46), rects[4]); + EXPECT_EQ(gfx::Rect(36, 1, 4, 46), rects[5]); + EXPECT_EQ(gfx::Rect(0, 47, 2, 3), rects[6]); + EXPECT_EQ(gfx::Rect(2, 47, 34, 3), rects[7]); + EXPECT_EQ(gfx::Rect(36, 47, 4, 3), rects[8]); +} + +TEST(NineImagePainterTest, PaintHighDPI) { + SkBitmap src; + src.allocN32Pixels(100, 100); + src.eraseColor(SK_ColorRED); + src.eraseArea(SkIRect::MakeXYWH(10, 10, 80, 80), SK_ColorGREEN); + + float image_scale = 2.f; + + gfx::ImageSkia image = gfx::ImageSkia::CreateFromBitmap(src, image_scale); + gfx::Insets insets(10, 10, 10, 10); + gfx::NineImagePainter painter(image, insets); + + bool is_opaque = true; + gfx::Canvas canvas(gfx::Size(100, 100), image_scale, is_opaque); + + gfx::Vector2d offset(20, 10); + canvas.Translate(offset); + + gfx::Rect bounds(0, 0, 50, 50); + painter.Paint(&canvas, bounds); + + SkBitmap result = canvas.GetBitmap(); + + gfx::Vector2d paint_offset = + gfx::ToFlooredVector2d(gfx::ScaleVector2d(offset, image_scale)); + gfx::Rect green_rect = gfx::Rect(10, 10, 80, 80) + paint_offset; + gfx::Rect outer_rect = gfx::Rect(100, 100) + paint_offset; + ExpectRedWithGreenRect(result, outer_rect, green_rect); +} + +TEST(NineImagePainterTest, PaintStaysInBounds) { + // In this test the bounds rect is 1x1 but each image is 2x2. + // The NineImagePainter should not paint outside the bounds. + // The border images should be cropped, but still painted. + + SkBitmap src; + src.allocN32Pixels(6, 6); + src.eraseColor(SK_ColorGREEN); + src.erase(SK_ColorRED, SkIRect::MakeXYWH(2, 2, 2, 2)); + + gfx::ImageSkia image = gfx::ImageSkia::CreateFrom1xBitmap(src); + gfx::Insets insets(2, 2, 2, 2); + gfx::NineImagePainter painter(image, insets); + + int image_scale = 1; + bool is_opaque = true; + gfx::Canvas canvas(gfx::Size(3, 3), image_scale, is_opaque); + canvas.DrawColor(SK_ColorBLACK); + + gfx::Rect bounds(1, 1, 1, 1); + painter.Paint(&canvas, bounds); + + SkBitmap result = canvas.GetBitmap(); + + EXPECT_EQ(SK_ColorGREEN, result.getColor(1, 1)); + + EXPECT_EQ(SK_ColorBLACK, result.getColor(0, 0)); + EXPECT_EQ(SK_ColorBLACK, result.getColor(0, 1)); + EXPECT_EQ(SK_ColorBLACK, result.getColor(0, 2)); + EXPECT_EQ(SK_ColorBLACK, result.getColor(1, 0)); + EXPECT_EQ(SK_ColorBLACK, result.getColor(1, 2)); + EXPECT_EQ(SK_ColorBLACK, result.getColor(2, 0)); + EXPECT_EQ(SK_ColorBLACK, result.getColor(2, 1)); + EXPECT_EQ(SK_ColorBLACK, result.getColor(2, 2)); +} + +TEST(NineImagePainterTest, PaintWithBoundOffset) { + SkBitmap src; + src.allocN32Pixels(10, 10); + src.eraseColor(SK_ColorRED); + src.eraseArea(SkIRect::MakeXYWH(1, 1, 8, 8), SK_ColorGREEN); + + gfx::ImageSkia image = gfx::ImageSkia::CreateFrom1xBitmap(src); + gfx::Insets insets(1, 1, 1, 1); + gfx::NineImagePainter painter(image, insets); + + bool is_opaque = true; + gfx::Canvas canvas(gfx::Size(10, 10), 1, is_opaque); + + gfx::Rect bounds(1, 1, 10, 10); + painter.Paint(&canvas, bounds); + + SkBitmap result = canvas.GetBitmap(); + + SkIRect green_rect = SkIRect::MakeLTRB(2, 2, 10, 10); + for (int y = 1; y < 10; y++) { + for (int x = 1; x < 10; x++) { + if (green_rect.contains(x, y)) { + ASSERT_EQ(SK_ColorGREEN, result.getColor(x, y)); + } else { + ASSERT_EQ(SK_ColorRED, result.getColor(x, y)); + } + } + } +} + +TEST(NineImagePainterTest, PaintWithScale) { + SkBitmap src; + src.allocN32Pixels(100, 100); + src.eraseColor(SK_ColorRED); + src.eraseArea(SkIRect::MakeXYWH(10, 10, 80, 80), SK_ColorGREEN); + + float image_scale = 2.f; + + gfx::ImageSkia image = gfx::ImageSkia::CreateFromBitmap(src, image_scale); + gfx::Insets insets(10, 10, 10, 10); + gfx::NineImagePainter painter(image, insets); + + bool is_opaque = true; + gfx::Canvas canvas(gfx::Size(400, 400), image_scale, is_opaque); + + gfx::Vector2d offset(20, 10); + canvas.Translate(offset); + canvas.Scale(2, 1); + + gfx::Rect bounds(0, 0, 50, 50); + painter.Paint(&canvas, bounds); + + SkBitmap result = canvas.GetBitmap(); + + gfx::Vector2d paint_offset = + gfx::ToFlooredVector2d(gfx::ScaleVector2d(offset, image_scale)); + gfx::Rect green_rect = gfx::Rect(20, 10, 160, 80) + paint_offset; + gfx::Rect outer_rect = gfx::Rect(200, 100) + paint_offset; + ExpectRedWithGreenRect(result, outer_rect, green_rect); +} + +TEST(NineImagePainterTest, PaintWithNegativeScale) { + SkBitmap src; + src.allocN32Pixels(100, 100); + src.eraseColor(SK_ColorRED); + src.eraseArea(SkIRect::MakeXYWH(10, 10, 80, 80), SK_ColorGREEN); + + float image_scale = 2.f; + + gfx::ImageSkia image = gfx::ImageSkia::CreateFromBitmap(src, image_scale); + gfx::Insets insets(10, 10, 10, 10); + gfx::NineImagePainter painter(image, insets); + + bool is_opaque = true; + gfx::Canvas canvas(gfx::Size(400, 400), image_scale, is_opaque); + canvas.Translate(gfx::Vector2d(70, 60)); + canvas.Scale(-1, -1); + + gfx::Rect bounds(0, 0, 50, 50); + painter.Paint(&canvas, bounds); + + SkBitmap result = canvas.GetBitmap(); + + // The painting space is 50x50 and the scale of -1,-1 means an offset of 50,50 + // would put the output in the top left corner. Since the offset is 70,60 it + // moves by 20,10. Since the output is 2x DPI it will become offset by 40,20. + gfx::Vector2d paint_offset(40, 20); + gfx::Rect green_rect = gfx::Rect(10, 10, 80, 80) + paint_offset; + gfx::Rect outer_rect = gfx::Rect(100, 100) + paint_offset; + ExpectRedWithGreenRect(result, outer_rect, green_rect); +} + +} // namespace gfx diff --git a/overlay_plane_data.cc b/overlay_plane_data.cc new file mode 100644 index 000000000000..69c49bd2f4f9 --- /dev/null +++ b/overlay_plane_data.cc @@ -0,0 +1,30 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/overlay_plane_data.h" + +namespace gfx { + +OverlayPlaneData::OverlayPlaneData() = default; + +OverlayPlaneData::OverlayPlaneData(int z_order, + OverlayTransform plane_transform, + const Rect& display_bounds, + const RectF& crop_rect, + bool enable_blend, + const Rect& damage_rect, + float opacity, + OverlayPriorityHint priority_hint) + : z_order(z_order), + plane_transform(plane_transform), + display_bounds(display_bounds), + crop_rect(crop_rect), + enable_blend(enable_blend), + damage_rect(damage_rect), + opacity(opacity), + priority_hint(priority_hint) {} + +OverlayPlaneData::~OverlayPlaneData() = default; + +} // namespace gfx diff --git a/overlay_plane_data.h b/overlay_plane_data.h new file mode 100644 index 000000000000..272ee212a662 --- /dev/null +++ b/overlay_plane_data.h @@ -0,0 +1,57 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_OVERLAY_PLANE_DATA_H_ +#define UI_GFX_OVERLAY_PLANE_DATA_H_ + +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/rect_f.h" +#include "ui/gfx/gfx_export.h" +#include "ui/gfx/overlay_priority_hint.h" +#include "ui/gfx/overlay_transform.h" + +namespace gfx { + +struct GFX_EXPORT OverlayPlaneData { + OverlayPlaneData(); + OverlayPlaneData(int z_order, + OverlayTransform plane_transform, + const Rect& display_bounds, + const RectF& crop_rect, + bool enable_blend, + const Rect& damage_rect, + float opacity, + OverlayPriorityHint priority_hint); + ~OverlayPlaneData(); + + // Specifies the stacking order of the plane relative to the main framebuffer + // located at index 0. + int z_order = 0; + + // Specifies how the buffer is to be transformed during composition. + OverlayTransform plane_transform = OverlayTransform::OVERLAY_TRANSFORM_NONE; + + // Pixel bounds within the display to position the image. + Rect display_bounds; + + // Normalized bounds of the image to be displayed in |display_bounds|. + RectF crop_rect = RectF(1.f, 1.f); + + // Whether alpha blending should be enabled. + bool enable_blend = false; + + // Damage on the buffer. + Rect damage_rect; + + // Opacity of overlay plane. For a blending buffer (|enable_blend|) the total + // transparency will by = channel alpha * |opacity|. + float opacity = 1.0f; + + // Hints for overlay prioritization when delegated composition is used. + OverlayPriorityHint priority_hint = OverlayPriorityHint::kNone; +}; + +} // namespace gfx + +#endif // UI_GFX_OVERLAY_PLANE_DATA_H_ diff --git a/overlay_priority_hint.h b/overlay_priority_hint.h new file mode 100644 index 000000000000..1ae4d6e62443 --- /dev/null +++ b/overlay_priority_hint.h @@ -0,0 +1,26 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_OVERLAY_PRIORITY_HINT_H_ +#define UI_GFX_OVERLAY_PRIORITY_HINT_H_ + +namespace gfx { + +// Provides a hint to a system compositor how it should prioritize this +// overlay. Used only by Wayland. +enum OverlayPriorityHint { + // Overlay promotion is not necessary for this surface. + kNone = 0, + // The overlay could be considered as a candidate for promotion. + kRegular, + // Low latency quad. + kLowLatencyCanvas, + // The overlay contains protected content and requires to be promoted to + // overlay. + kHardwareProtection, +}; + +} // namespace gfx + +#endif // UI_GFX_OVERLAY_PRIORITY_HINT_H_ diff --git a/overlay_transform.h b/overlay_transform.h new file mode 100644 index 000000000000..252fc734d56d --- /dev/null +++ b/overlay_transform.h @@ -0,0 +1,25 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_OVERLAY_TRANSFORM_H_ +#define UI_GFX_OVERLAY_TRANSFORM_H_ + +namespace gfx { + +// Describes transformation to be applied to the buffer before presenting +// to screen. Rotations are expressed anticlockwise. +enum OverlayTransform { + OVERLAY_TRANSFORM_INVALID, + OVERLAY_TRANSFORM_NONE, + OVERLAY_TRANSFORM_FLIP_HORIZONTAL, + OVERLAY_TRANSFORM_FLIP_VERTICAL, + OVERLAY_TRANSFORM_ROTATE_90, + OVERLAY_TRANSFORM_ROTATE_180, + OVERLAY_TRANSFORM_ROTATE_270, + OVERLAY_TRANSFORM_LAST = OVERLAY_TRANSFORM_ROTATE_270 +}; + +} // namespace gfx + +#endif // UI_GFX_OVERLAY_TRANSFORM_H_ diff --git a/overlay_transform_utils.cc b/overlay_transform_utils.cc new file mode 100644 index 000000000000..c39345ee7b38 --- /dev/null +++ b/overlay_transform_utils.cc @@ -0,0 +1,65 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/overlay_transform_utils.h" + +#include "base/notreached.h" +#include "ui/gfx/geometry/rect_conversions.h" + +namespace gfx { + +gfx::Transform OverlayTransformToTransform( + gfx::OverlayTransform overlay_transform, + const gfx::SizeF& viewport_bounds) { + switch (overlay_transform) { + case gfx::OVERLAY_TRANSFORM_INVALID: + NOTREACHED(); + return gfx::Transform(); + case gfx::OVERLAY_TRANSFORM_NONE: + return gfx::Transform(); + case gfx::OVERLAY_TRANSFORM_FLIP_HORIZONTAL: + return gfx::Transform( + SkMatrix::MakeAll(-1, 0, viewport_bounds.width(), 0, 1, 0, 0, 0, 1)); + case gfx::OVERLAY_TRANSFORM_FLIP_VERTICAL: + return gfx::Transform( + SkMatrix::MakeAll(1, 0, 0, 0, -1, viewport_bounds.height(), 0, 0, 1)); + case gfx::OVERLAY_TRANSFORM_ROTATE_90: + return gfx::Transform( + SkMatrix::MakeAll(0, -1, viewport_bounds.height(), 1, 0, 0, 0, 0, 1)); + case gfx::OVERLAY_TRANSFORM_ROTATE_180: + return gfx::Transform(SkMatrix::MakeAll(-1, 0, viewport_bounds.width(), 0, + -1, viewport_bounds.height(), 0, + 0, 1)); + case gfx::OVERLAY_TRANSFORM_ROTATE_270: + return gfx::Transform( + SkMatrix::MakeAll(0, 1, 0, -1, 0, viewport_bounds.width(), 0, 0, 1)); + } + + NOTREACHED(); + return gfx::Transform(); +} + +gfx::OverlayTransform InvertOverlayTransform(gfx::OverlayTransform transform) { + switch (transform) { + case gfx::OVERLAY_TRANSFORM_INVALID: + NOTREACHED(); + return gfx::OVERLAY_TRANSFORM_NONE; + case gfx::OVERLAY_TRANSFORM_NONE: + return gfx::OVERLAY_TRANSFORM_NONE; + case gfx::OVERLAY_TRANSFORM_FLIP_HORIZONTAL: + return gfx::OVERLAY_TRANSFORM_FLIP_HORIZONTAL; + case gfx::OVERLAY_TRANSFORM_FLIP_VERTICAL: + return gfx::OVERLAY_TRANSFORM_FLIP_VERTICAL; + case gfx::OVERLAY_TRANSFORM_ROTATE_90: + return gfx::OVERLAY_TRANSFORM_ROTATE_270; + case gfx::OVERLAY_TRANSFORM_ROTATE_180: + return gfx::OVERLAY_TRANSFORM_ROTATE_180; + case gfx::OVERLAY_TRANSFORM_ROTATE_270: + return gfx::OVERLAY_TRANSFORM_ROTATE_90; + } + NOTREACHED(); + return gfx::OVERLAY_TRANSFORM_NONE; +} + +} // namespace gfx diff --git a/overlay_transform_utils.h b/overlay_transform_utils.h new file mode 100644 index 000000000000..5df8e432661e --- /dev/null +++ b/overlay_transform_utils.h @@ -0,0 +1,24 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_OVERLAY_TRANSFORM_UTILS_H_ +#define UI_GFX_OVERLAY_TRANSFORM_UTILS_H_ + +#include "ui/gfx/geometry/size_f.h" +#include "ui/gfx/geometry/transform.h" +#include "ui/gfx/gfx_export.h" +#include "ui/gfx/overlay_transform.h" + +namespace gfx { + +GFX_EXPORT gfx::Transform OverlayTransformToTransform( + gfx::OverlayTransform overlay_transform, + const gfx::SizeF& viewport_bounds); + +GFX_EXPORT gfx::OverlayTransform InvertOverlayTransform( + gfx::OverlayTransform transform); + +} // namespace gfx + +#endif // UI_GFX_OVERLAY_TRANSFORM_UTILS_H_ diff --git a/overlay_transform_utils_unittest.cc b/overlay_transform_utils_unittest.cc new file mode 100644 index 000000000000..8090a0e4c41a --- /dev/null +++ b/overlay_transform_utils_unittest.cc @@ -0,0 +1,53 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/overlay_transform_utils.h" + +#include "cc/base/math_util.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/geometry/rect.h" + +namespace gfx { +namespace { + +TEST(OverlayTransformUtilTest, All) { + const Size viewport_bounds(100, 200); + const Rect original(10, 10, 50, 100); + struct TestCase { + OverlayTransform overlay_transform; + Rect transformed; + }; + + TestCase test_cases[] = { + {OVERLAY_TRANSFORM_NONE, Rect(10, 10, 50, 100)}, + {OVERLAY_TRANSFORM_FLIP_HORIZONTAL, Rect(40, 10, 50, 100)}, + {OVERLAY_TRANSFORM_FLIP_VERTICAL, Rect(10, 90, 50, 100)}, + {OVERLAY_TRANSFORM_ROTATE_90, Rect(90, 10, 100, 50)}, + {OVERLAY_TRANSFORM_ROTATE_180, Rect(40, 90, 50, 100)}, + {OVERLAY_TRANSFORM_ROTATE_270, Rect(10, 40, 100, 50)}, + }; + + for (const auto& test_case : test_cases) { + SCOPED_TRACE(test_case.overlay_transform); + + auto transform = OverlayTransformToTransform(test_case.overlay_transform, + gfx::SizeF(viewport_bounds)); + EXPECT_EQ(test_case.transformed, + cc::MathUtil::MapEnclosedRectWith2dAxisAlignedTransform( + transform, original)); + + auto transformed_viewport_size = + cc::MathUtil::MapEnclosedRectWith2dAxisAlignedTransform( + transform, gfx::Rect(viewport_bounds)) + .size(); + auto inverse_transform = OverlayTransformToTransform( + InvertOverlayTransform(test_case.overlay_transform), + gfx::SizeF(transformed_viewport_size)); + EXPECT_EQ(original, cc::MathUtil::MapEnclosedRectWith2dAxisAlignedTransform( + inverse_transform, test_case.transformed)); + } +} + +} // namespace +} // namespace gfx diff --git a/paint_throbber.cc b/paint_throbber.cc new file mode 100644 index 000000000000..bea7d99b1b63 --- /dev/null +++ b/paint_throbber.cc @@ -0,0 +1,266 @@ +// Copyright (c) 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/paint_throbber.h" + +#include + +#include "base/numerics/safe_conversions.h" +#include "base/time/time.h" +#include "cc/paint/paint_flags.h" +#include "third_party/skia/include/core/SkPath.h" +#include "ui/gfx/animation/tween.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/color_utils.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/skia_conversions.h" + +namespace gfx { + +namespace { + +// The maximum size of the "spinning" state arc, in degrees. +constexpr int64_t kMaxArcSize = 270; + +// The amount of time it takes to grow the "spinning" arc from 0 to 270 degrees. +constexpr auto kArcTime = base::Seconds(2.0 / 3.0); + +// The amount of time it takes for the "spinning" throbber to make a full +// rotation. +constexpr auto kRotationTime = base::Milliseconds(1568); + +void PaintArc(Canvas* canvas, + const Rect& bounds, + SkColor color, + SkScalar start_angle, + SkScalar sweep, + absl::optional stroke_width) { + if (!stroke_width) { + // Stroke width depends on size. + // . For size < 28: 3 - (28 - size) / 16 + // . For 28 <= size: (8 + size) / 12 + stroke_width = bounds.width() < 28 + ? 3.0 - SkIntToScalar(28 - bounds.width()) / 16.0 + : SkIntToScalar(bounds.width() + 8) / 12.0; + } + Rect oval = bounds; + // Inset by half the stroke width to make sure the whole arc is inside + // the visible rect. + const int inset = SkScalarCeilToInt(*stroke_width / 2.0); + oval.Inset(inset, inset); + + SkPath path; + path.arcTo(RectToSkRect(oval), start_angle, sweep, true); + + cc::PaintFlags flags; + flags.setColor(color); + flags.setStrokeCap(cc::PaintFlags::kRound_Cap); + flags.setStrokeWidth(*stroke_width); + flags.setStyle(cc::PaintFlags::kStroke_Style); + flags.setAntiAlias(true); + canvas->DrawPath(path, flags); +} + +void CalculateWaitingAngles(const base::TimeDelta& elapsed_time, + int64_t* start_angle, + int64_t* sweep) { + // Calculate start and end points. The angles are counter-clockwise because + // the throbber spins counter-clockwise. The finish angle starts at 12 o'clock + // (90 degrees) and rotates steadily. The start angle trails 180 degrees + // behind, except for the first half revolution, when it stays at 12 o'clock. + constexpr auto kRevolutionTime = base::Milliseconds(1320); + int64_t twelve_oclock = 90; + int64_t finish_angle_cc = + twelve_oclock + + base::ClampRound(elapsed_time / kRevolutionTime * 360); + int64_t start_angle_cc = std::max(finish_angle_cc - 180, twelve_oclock); + + // Negate the angles to convert to the clockwise numbers Skia expects. + if (start_angle) + *start_angle = -finish_angle_cc; + if (sweep) + *sweep = finish_angle_cc - start_angle_cc; +} + +// This is a Skia port of the MD spinner SVG. The |start_angle| rotation +// here corresponds to the 'rotate' animation. +ThrobberSpinningState CalculateThrobberSpinningStateWithStartAngle( + base::TimeDelta elapsed_time, + int64_t start_angle) { + // The sweep angle ranges from -270 to 270 over 1333ms. CSS + // animation timing functions apply in between key frames, so we have to + // break up the 1333ms into two keyframes (-270 to 0, then 0 to 270). + const double elapsed_ratio = elapsed_time / kArcTime; + const int64_t sweep_frame = base::ClampFloor(elapsed_ratio); + const double arc_progress = elapsed_ratio - sweep_frame; + // This tween is equivalent to cubic-bezier(0.4, 0.0, 0.2, 1). + double sweep = kMaxArcSize * + Tween::CalculateValue(Tween::FAST_OUT_SLOW_IN, arc_progress); + if (sweep_frame % 2 == 0) + sweep -= kMaxArcSize; + + // This part makes sure the sweep is at least 5 degrees long. Roughly + // equivalent to the "magic constants" in SVG's fillunfill animation. + constexpr double kMinSweepLength = 5.0; + if (sweep >= 0.0 && sweep < kMinSweepLength) { + start_angle -= (kMinSweepLength - sweep); + sweep = kMinSweepLength; + } else if (sweep <= 0.0 && sweep > -kMinSweepLength) { + start_angle += (-kMinSweepLength - sweep); + sweep = -kMinSweepLength; + } + + // To keep the sweep smooth, we have an additional rotation after each + // arc period has elapsed. See SVG's 'rot' animation. + const int64_t rot_keyframe = (sweep_frame / 2) % 4; + start_angle = start_angle + rot_keyframe * kMaxArcSize; + return ThrobberSpinningState{ + .start_angle = static_cast(start_angle), + .sweep_angle = static_cast(sweep)}; +} + +void PaintThrobberSpinningWithState(Canvas* canvas, + const Rect& bounds, + SkColor color, + const ThrobberSpinningState& state, + absl::optional stroke_width) { + PaintArc(canvas, bounds, color, state.start_angle, state.sweep_angle, + stroke_width); +} + +void PaintThrobberSpinningWithStartAngle( + Canvas* canvas, + const Rect& bounds, + SkColor color, + const base::TimeDelta& elapsed_time, + int64_t start_angle, + absl::optional stroke_width) { + const ThrobberSpinningState state = + CalculateThrobberSpinningStateWithStartAngle(elapsed_time, start_angle); + PaintThrobberSpinningWithState(canvas, bounds, color, state, stroke_width); +} + +} // namespace + +ThrobberSpinningState CalculateThrobberSpinningState( + base::TimeDelta elapsed_time) { + const int64_t start_angle = + 270 + base::ClampRound(elapsed_time / kRotationTime * 360); + return CalculateThrobberSpinningStateWithStartAngle(elapsed_time, + start_angle); +} + +void PaintThrobberSpinning(Canvas* canvas, + const Rect& bounds, + SkColor color, + const base::TimeDelta& elapsed_time, + absl::optional stroke_width) { + const ThrobberSpinningState state = + CalculateThrobberSpinningState(elapsed_time); + PaintThrobberSpinningWithState(canvas, bounds, color, state, stroke_width); +} + +void PaintThrobberWaiting(Canvas* canvas, + const Rect& bounds, + SkColor color, + const base::TimeDelta& elapsed_time, + absl::optional stroke_width) { + int64_t start_angle = 0, sweep = 0; + CalculateWaitingAngles(elapsed_time, &start_angle, &sweep); + PaintArc(canvas, bounds, color, start_angle, sweep, stroke_width); +} + +void PaintThrobberSpinningAfterWaiting(Canvas* canvas, + const Rect& bounds, + SkColor color, + const base::TimeDelta& elapsed_time, + ThrobberWaitingState* waiting_state, + absl::optional stroke_width) { + int64_t waiting_start_angle = 0, waiting_sweep = 0; + CalculateWaitingAngles(waiting_state->elapsed_time, &waiting_start_angle, + &waiting_sweep); + + // |arc_time_offset| is the effective amount of time one would have to wait + // for the "spinning" sweep to match |waiting_sweep|. Brute force calculation. + if (waiting_state->arc_time_offset.is_zero()) { + for (int64_t arc_ms = 0; arc_ms <= kArcTime.InMillisecondsRoundedUp(); + ++arc_ms) { + const base::TimeDelta arc_time = + std::min(base::Milliseconds(arc_ms), kArcTime); + if (kMaxArcSize * Tween::CalculateValue(Tween::FAST_OUT_SLOW_IN, + arc_time / kArcTime) >= + waiting_sweep) { + // Add kArcTime to sidestep the |sweep_keyframe == 0| offset below. + waiting_state->arc_time_offset = kArcTime + arc_time; + break; + } + } + } + + // Blend the color between "waiting" and "spinning" states. + constexpr auto kColorFadeTime = base::Milliseconds(900); + const float color_progress = static_cast(Tween::CalculateValue( + Tween::LINEAR_OUT_SLOW_IN, std::min(elapsed_time / kColorFadeTime, 1.0))); + const SkColor blend_color = + color_utils::AlphaBlend(color, waiting_state->color, color_progress); + + const int64_t start_angle = + waiting_start_angle + + base::ClampRound(elapsed_time / kRotationTime * 360); + const base::TimeDelta effective_elapsed_time = + elapsed_time + waiting_state->arc_time_offset; + + PaintThrobberSpinningWithStartAngle(canvas, bounds, blend_color, + effective_elapsed_time, start_angle, + stroke_width); +} + +GFX_EXPORT void PaintNewThrobberWaiting(Canvas* canvas, + const RectF& throbber_container_bounds, + SkColor color, + const base::TimeDelta& elapsed_time) { + // Cycle time for the waiting throbber. + constexpr auto kNewThrobberWaitingCycleTime = base::Seconds(1); + + // The throbber bounces back and forth. We map the elapsed time to 0->2. Time + // 0->1 represents when the throbber moves left to right, time 1->2 represents + // right to left. + float time = 2.0f * (elapsed_time % kNewThrobberWaitingCycleTime) / + kNewThrobberWaitingCycleTime; + // 1 -> 2 values mirror back to 1 -> 0 values to represent right-to-left. + const bool going_back = time > 1.0f; + if (going_back) + time = 2.0f - time; + // This animation should be fast in the middle and slow at the edges. + time = Tween::CalculateValue(Tween::EASE_IN_OUT, time); + const float min_width = throbber_container_bounds.height(); + // The throbber animation stretches longer when moving in (left to right) than + // when going back. + const float throbber_width = + (going_back ? 0.75f : 1.0f) * throbber_container_bounds.width(); + + // These bounds keep at least |min_width| of the throbber visible (inside the + // throbber bounds). + const float min_x = + throbber_container_bounds.x() - throbber_width + min_width; + const float max_x = throbber_container_bounds.right() - min_width; + + RectF bounds = throbber_container_bounds; + // Linear interpolation between |min_x| and |max_x|. + bounds.set_x(time * (max_x - min_x) + min_x); + bounds.set_width(throbber_width); + // The throbber is designed to go out of bounds, but it should not be rendered + // outside |throbber_container_bounds|. This clips the throbber to the edges, + // which gives a smooth bouncing effect. + bounds.Intersect(throbber_container_bounds); + + cc::PaintFlags flags; + flags.setColor(color); + flags.setStyle(cc::PaintFlags::kFill_Style); + + // Draw with circular end caps. + canvas->DrawRoundRect(bounds, bounds.height() / 2, flags); +} + +} // namespace gfx diff --git a/paint_throbber.h b/paint_throbber.h new file mode 100644 index 000000000000..c22d0f93da20 --- /dev/null +++ b/paint_throbber.h @@ -0,0 +1,92 @@ +// Copyright (c) 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_PAINT_THROBBER_H_ +#define UI_GFX_PAINT_THROBBER_H_ + +#include + +#include "base/time/time.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "third_party/skia/include/core/SkColor.h" +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +class Canvas; +class Rect; +class RectF; + +// This struct describes the "spinning" state of a throb animation. +struct GFX_EXPORT ThrobberSpinningState { + // The start angle of the arc in degrees. + SkScalar start_angle = 0.f; + + // The sweep angle of the arc in degrees. Positive is clockwise. + SkScalar sweep_angle = 0.f; +}; + +// This struct describes the "waiting" mode of a throb animation. It's useful +// for building a "spinning" state animation on top of a previous "waiting" +// mode animation. See PaintThrobberSpinningAfterWaiting. +struct GFX_EXPORT ThrobberWaitingState { + // The amount of time that was spent in the waiting state. + base::TimeDelta elapsed_time; + + // The color of the arc in the waiting state. + SkColor color = SK_ColorTRANSPARENT; + + // An opaque value used to cache calculations made by + // PaintThrobberSpinningAfterWaiting. + base::TimeDelta arc_time_offset; +}; + +// Returns the calculated state for a single frame of the throbber in the +// "spinning", aka Material, state. Note that animation duration is a hardcoded +// value in line with Material design specifications but is cyclic, so the +// specified `elapsed_time` may exceed animation duration. +GFX_EXPORT ThrobberSpinningState +CalculateThrobberSpinningState(base::TimeDelta elapsed_time); + +// Paints a single frame of the throbber in the "spinning", aka Material, state. +// Note that animation duration is a hardcoded value in line with Material +// design specifications but is cyclic, so the specified `elapsed_time` may +// exceed animation duration. +GFX_EXPORT void PaintThrobberSpinning( + Canvas* canvas, + const Rect& bounds, + SkColor color, + const base::TimeDelta& elapsed_time, + absl::optional stroke_width = absl::nullopt); + +// Paints a throbber in the "waiting" state. Used when waiting on a network +// response, for example. +GFX_EXPORT void PaintThrobberWaiting( + Canvas* canvas, + const Rect& bounds, + SkColor color, + const base::TimeDelta& elapsed_time, + absl::optional stroke_width = absl::nullopt); + +// Paint a throbber in the "spinning" state, smoothly transitioning from a +// previous "waiting" state described by |waiting_state|, which is an in-out +// param. +GFX_EXPORT void PaintThrobberSpinningAfterWaiting( + Canvas* canvas, + const Rect& bounds, + SkColor color, + const base::TimeDelta& elapsed_time, + ThrobberWaitingState* waiting_state, + absl::optional stroke_width = absl::nullopt); + +// Paints a throbber in the "waiting" state (bouncing back and forth). Used when +// waiting on a network response, for example. +GFX_EXPORT void PaintNewThrobberWaiting(Canvas* canvas, + const RectF& throbber_container_bounds, + SkColor color, + const base::TimeDelta& elapsed_time); + +} // namespace gfx + +#endif // UI_GFX_PAINT_THROBBER_H_ diff --git a/paint_vector_icon.cc b/paint_vector_icon.cc new file mode 100644 index 000000000000..b998717e5369 --- /dev/null +++ b/paint_vector_icon.cc @@ -0,0 +1,607 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/paint_vector_icon.h" + +#include +#include +#include + +#include "base/i18n/rtl.h" +#include "base/lazy_instance.h" +#include "base/macros.h" +#include "base/numerics/safe_conversions.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/trace_event/trace_event.h" +#include "cc/paint/paint_canvas.h" +#include "cc/paint/paint_flags.h" +#include "third_party/skia/include/core/SkPath.h" +#include "ui/gfx/animation/tween.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/color_palette.h" +#include "ui/gfx/image/canvas_image_source.h" +#include "ui/gfx/scoped_canvas.h" +#include "ui/gfx/vector_icon_types.h" +#include "ui/gfx/vector_icon_utils.h" + +namespace gfx { + +namespace { + +// The default size of a single side of the square canvas to which path +// coordinates are relative, in device independent pixels. +constexpr int kReferenceSizeDip = 48; + +constexpr int kEmptyIconSize = -1; + +// Retrieves the specified CANVAS_DIMENSIONS size from a PathElement. +int GetCanvasDimensions(const PathElement* path) { + if (!path) + return kEmptyIconSize; + return path[0].command == CANVAS_DIMENSIONS ? path[1].arg : kReferenceSizeDip; +} + +// Retrieves the appropriate icon representation to draw when the pixel size +// requested is |icon_size_px|. +const VectorIconRep* GetRepForPxSize(const VectorIcon& icon, int icon_size_px) { + if (icon.is_empty()) + return nullptr; + + const VectorIconRep* best_rep = nullptr; + + // Prefer an exact match. If none exists, prefer the largest rep that is an + // exact divisor of the icon size (e.g. 20 for a 40px icon). If none exists, + // return the smallest rep that is larger than the target. If none exists, + // use the largest rep. The rep array is sorted by size in descending order, + // so start at the back and work towards the front. + for (int i = static_cast(icon.reps_size - 1); i >= 0; --i) { + const VectorIconRep* rep = &icon.reps[i]; + int rep_size = GetCanvasDimensions(rep->path); + if (rep_size == icon_size_px) + return rep; + + if (icon_size_px % rep_size == 0) + best_rep = rep; + + if (rep_size > icon_size_px) + return best_rep ? best_rep : &icon.reps[i]; + } + return best_rep ? best_rep : &icon.reps[0]; +} + +struct CompareIconDescription { + bool operator()(const IconDescription& a, const IconDescription& b) const { + const VectorIcon* a_icon = &a.icon; + const VectorIcon* b_icon = &b.icon; + const VectorIcon* a_badge = &a.badge_icon; + const VectorIcon* b_badge = &b.badge_icon; + return std::tie(a_icon, a.dip_size, a.color, a_badge) < + std::tie(b_icon, b.dip_size, b.color, b_badge); + } +}; + +// Helper that simplifies iterating over a sequence of PathElements. +class PathParser { + public: + PathParser(const PathElement* path_elements, size_t path_size) + : path_elements_(path_elements), path_size_(path_size) {} + + PathParser(const PathParser&) = delete; + PathParser& operator=(const PathParser&) = delete; + + ~PathParser() {} + + void Advance() { command_index_ += GetArgumentCount() + 1; } + + bool HasCommandsRemaining() const { return command_index_ < path_size_; } + + CommandType CurrentCommand() const { + return path_elements_[command_index_].command; + } + + SkScalar GetArgument(int index) const { + DCHECK_LT(index, GetArgumentCount()); + return path_elements_[command_index_ + 1 + index].arg; + } + + private: + int GetArgumentCount() const { + switch (CurrentCommand()) { + case STROKE: + case H_LINE_TO: + case R_H_LINE_TO: + case V_LINE_TO: + case R_V_LINE_TO: + case CANVAS_DIMENSIONS: + case PATH_COLOR_ALPHA: + return 1; + + case MOVE_TO: + case R_MOVE_TO: + case LINE_TO: + case R_LINE_TO: + case QUADRATIC_TO_SHORTHAND: + case R_QUADRATIC_TO_SHORTHAND: + return 2; + + case CIRCLE: + return 3; + + case PATH_COLOR_ARGB: + case CUBIC_TO_SHORTHAND: + case CLIP: + case QUADRATIC_TO: + case R_QUADRATIC_TO: + case OVAL: + return 4; + + case ROUND_RECT: + return 5; + + case CUBIC_TO: + case R_CUBIC_TO: + return 6; + + case ARC_TO: + case R_ARC_TO: + return 7; + + case NEW_PATH: + case PATH_MODE_CLEAR: + case CAP_SQUARE: + case CLOSE: + case DISABLE_AA: + case FLIPS_IN_RTL: + return 0; + } + + NOTREACHED(); + return 0; + } + + const PathElement* path_elements_; + size_t path_size_; + size_t command_index_ = 0; +}; + +// Translates a string such as "MOVE_TO" into a command such as MOVE_TO. +CommandType CommandFromString(const std::string& source) { +#define RETURN_IF_IS(command) \ + if (source == #command) \ + return command; + + RETURN_IF_IS(NEW_PATH); + RETURN_IF_IS(PATH_COLOR_ALPHA); + RETURN_IF_IS(PATH_COLOR_ARGB); + RETURN_IF_IS(PATH_MODE_CLEAR); + RETURN_IF_IS(STROKE); + RETURN_IF_IS(CAP_SQUARE); + RETURN_IF_IS(MOVE_TO); + RETURN_IF_IS(R_MOVE_TO); + RETURN_IF_IS(ARC_TO); + RETURN_IF_IS(R_ARC_TO); + RETURN_IF_IS(LINE_TO); + RETURN_IF_IS(R_LINE_TO); + RETURN_IF_IS(H_LINE_TO); + RETURN_IF_IS(R_H_LINE_TO); + RETURN_IF_IS(V_LINE_TO); + RETURN_IF_IS(R_V_LINE_TO); + RETURN_IF_IS(CUBIC_TO); + RETURN_IF_IS(R_CUBIC_TO); + RETURN_IF_IS(CUBIC_TO_SHORTHAND); + RETURN_IF_IS(QUADRATIC_TO); + RETURN_IF_IS(R_QUADRATIC_TO); + RETURN_IF_IS(QUADRATIC_TO_SHORTHAND); + RETURN_IF_IS(R_QUADRATIC_TO_SHORTHAND); + RETURN_IF_IS(CIRCLE); + RETURN_IF_IS(OVAL); + RETURN_IF_IS(ROUND_RECT); + RETURN_IF_IS(CLOSE); + RETURN_IF_IS(CANVAS_DIMENSIONS); + RETURN_IF_IS(CLIP); + RETURN_IF_IS(DISABLE_AA); + RETURN_IF_IS(FLIPS_IN_RTL); +#undef RETURN_IF_IS + + NOTREACHED() << "Unrecognized command: " << source; + return CLOSE; +} + +std::vector PathFromSource(const std::string& source) { + std::vector path; + std::vector pieces = base::SplitString( + source, "\n ,f", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); + for (const auto& piece : pieces) { + double value = 0; + int hex_value = 0; + if (base::StringToDouble(piece, &value)) + path.push_back(PathElement(SkDoubleToScalar(value))); + else if (base::HexStringToInt(piece, &hex_value)) + path.push_back(PathElement(SkIntToScalar(hex_value))); + else + path.push_back(PathElement(CommandFromString(piece))); + } + return path; +} + +bool IsCommandTypeCurve(CommandType command) { + return command == CUBIC_TO || command == R_CUBIC_TO || + command == CUBIC_TO_SHORTHAND || command == QUADRATIC_TO || + command == R_QUADRATIC_TO || command == QUADRATIC_TO_SHORTHAND || + command == R_QUADRATIC_TO_SHORTHAND; +} + +void PaintPath(Canvas* canvas, + const PathElement* path_elements, + size_t path_size, + int dip_size, + SkColor color) { + int canvas_size = kReferenceSizeDip; + std::vector paths; + std::vector flags_array; + SkRect clip_rect = SkRect::MakeEmpty(); + bool flips_in_rtl = false; + CommandType previous_command_type = NEW_PATH; + + for (PathParser parser(path_elements, path_size); + parser.HasCommandsRemaining(); parser.Advance()) { + auto arg = [&parser](int i) { return parser.GetArgument(i); }; + const CommandType command_type = parser.CurrentCommand(); + auto start_new_path = [&paths]() { + paths.push_back(SkPath()); + paths.back().setFillType(SkPathFillType::kEvenOdd); + }; + auto start_new_flags = [&flags_array, &color]() { + flags_array.push_back(cc::PaintFlags()); + flags_array.back().setColor(color); + flags_array.back().setAntiAlias(true); + flags_array.back().setStrokeCap(cc::PaintFlags::kRound_Cap); + }; + + if (paths.empty() || command_type == NEW_PATH) { + start_new_path(); + start_new_flags(); + } + + SkPath& path = paths.back(); + cc::PaintFlags& flags = flags_array.back(); + switch (command_type) { + // Handled above. + case NEW_PATH: + break; + + case PATH_COLOR_ALPHA: + flags.setAlpha(SkScalarFloorToInt(arg(0))); + break; + + case PATH_COLOR_ARGB: + flags.setColor(SkColorSetARGB( + SkScalarFloorToInt(arg(0)), SkScalarFloorToInt(arg(1)), + SkScalarFloorToInt(arg(2)), SkScalarFloorToInt(arg(3)))); + break; + + case PATH_MODE_CLEAR: + flags.setBlendMode(SkBlendMode::kClear); + break; + + case STROKE: + flags.setStyle(cc::PaintFlags::kStroke_Style); + flags.setStrokeWidth(arg(0)); + break; + + case CAP_SQUARE: + flags.setStrokeCap(cc::PaintFlags::kSquare_Cap); + break; + + case MOVE_TO: + path.moveTo(arg(0), arg(1)); + break; + + case R_MOVE_TO: + if (previous_command_type == CLOSE) { + // This triggers injectMoveToIfNeeded() so that the next subpath + // will start at the correct place. See [ + // https://www.w3.org/TR/SVG/paths.html#PathDataClosePathCommand ]. + path.rLineTo(0, 0); + } + + path.rMoveTo(arg(0), arg(1)); + break; + + case ARC_TO: + case R_ARC_TO: { + SkScalar rx = arg(0); + SkScalar ry = arg(1); + SkScalar angle = arg(2); + SkScalar large_arc_flag = arg(3); + SkScalar arc_sweep_flag = arg(4); + SkScalar x = arg(5); + SkScalar y = arg(6); + SkPath::ArcSize arc_size = + large_arc_flag ? SkPath::kLarge_ArcSize : SkPath::kSmall_ArcSize; + SkPathDirection direction = + arc_sweep_flag ? SkPathDirection::kCW : SkPathDirection::kCCW; + + if (command_type == ARC_TO) + path.arcTo(rx, ry, angle, arc_size, direction, x, y); + else + path.rArcTo(rx, ry, angle, arc_size, direction, x, y); + break; + } + + case LINE_TO: + path.lineTo(arg(0), arg(1)); + break; + + case R_LINE_TO: + path.rLineTo(arg(0), arg(1)); + break; + + case H_LINE_TO: { + SkPoint last_point; + path.getLastPt(&last_point); + path.lineTo(arg(0), last_point.fY); + break; + } + + case R_H_LINE_TO: + path.rLineTo(arg(0), 0); + break; + + case V_LINE_TO: { + SkPoint last_point; + path.getLastPt(&last_point); + path.lineTo(last_point.fX, arg(0)); + break; + } + + case R_V_LINE_TO: + path.rLineTo(0, arg(0)); + break; + + case CUBIC_TO: + path.cubicTo(arg(0), arg(1), arg(2), arg(3), arg(4), arg(5)); + break; + + case R_CUBIC_TO: + path.rCubicTo(arg(0), arg(1), arg(2), arg(3), arg(4), arg(5)); + break; + + case CUBIC_TO_SHORTHAND: + case QUADRATIC_TO_SHORTHAND: + case R_QUADRATIC_TO_SHORTHAND: { + // Compute the first control point (|x1| and |y1|) as the reflection of + // the last control point on the previous command relative to the + // current point. If there is no previous command or if the previous + // command is not a Bezier curve, the first control point is coincident + // with the current point. Refer to the SVG path specs for further + // details. + // Note that |x1| and |y1| will correspond to the sole control point if + // calculating a quadratic curve. + SkPoint last_point; + path.getLastPt(&last_point); + SkScalar delta_x = 0; + SkScalar delta_y = 0; + if (IsCommandTypeCurve(previous_command_type)) { + SkPoint last_control_point = path.getPoint(path.countPoints() - 2); + // We find what the delta was between the last curve's starting point + // and the control point. This difference is what we will reflect on + // the current point, creating our new control point. + delta_x = last_point.fX - last_control_point.fX; + delta_y = last_point.fY - last_control_point.fY; + } + + SkScalar x1 = last_point.fX + delta_x; + SkScalar y1 = last_point.fY + delta_y; + if (command_type == CUBIC_TO_SHORTHAND) + path.cubicTo(x1, y1, arg(0), arg(1), arg(2), arg(3)); + else if (command_type == QUADRATIC_TO_SHORTHAND) + path.quadTo(x1, y1, arg(0), arg(1)); + else if (command_type == R_QUADRATIC_TO_SHORTHAND) + path.rQuadTo(x1, y1, arg(0), arg(1)); + break; + } + + case QUADRATIC_TO: + path.quadTo(arg(0), arg(1), arg(2), arg(3)); + break; + + case R_QUADRATIC_TO: + path.rQuadTo(arg(0), arg(1), arg(2), arg(3)); + break; + + case OVAL: { + SkScalar x = arg(0); + SkScalar y = arg(1); + SkScalar rx = arg(2); + SkScalar ry = arg(3); + path.addOval(SkRect::MakeLTRB(x - rx, y - ry, x + rx, y + ry)); + break; + } + + case CIRCLE: + path.addCircle(arg(0), arg(1), arg(2)); + break; + + case ROUND_RECT: + path.addRoundRect(SkRect::MakeXYWH(arg(0), arg(1), arg(2), arg(3)), + arg(4), arg(4)); + break; + + case CLOSE: + path.close(); + break; + + case CANVAS_DIMENSIONS: + canvas_size = SkScalarTruncToInt(arg(0)); + break; + + case CLIP: + clip_rect = SkRect::MakeXYWH(arg(0), arg(1), arg(2), arg(3)); + break; + + case DISABLE_AA: + flags.setAntiAlias(false); + break; + + case FLIPS_IN_RTL: + flips_in_rtl = true; + break; + } + + previous_command_type = command_type; + } + + ScopedCanvas scoped_canvas(canvas); + + if (dip_size != canvas_size) { + SkScalar scale = SkIntToScalar(dip_size) / SkIntToScalar(canvas_size); + canvas->sk_canvas()->scale(scale, scale); + } + + if (flips_in_rtl) + scoped_canvas.FlipIfRTL(canvas_size); + + if (!clip_rect.isEmpty()) + canvas->sk_canvas()->clipRect(clip_rect); + + DCHECK_EQ(flags_array.size(), paths.size()); + for (size_t i = 0; i < paths.size(); ++i) + canvas->DrawPath(paths[i], flags_array[i]); +} + +class VectorIconSource : public CanvasImageSource { + public: + explicit VectorIconSource(const IconDescription& data) + : CanvasImageSource(Size(data.dip_size, data.dip_size)), data_(data) {} + + VectorIconSource(const std::string& definition, int dip_size, SkColor color) + : CanvasImageSource(Size(dip_size, dip_size)), + data_(kNoneIcon, dip_size, color, &kNoneIcon), + path_(PathFromSource(definition)) {} + + VectorIconSource(const VectorIconSource&) = delete; + VectorIconSource& operator=(const VectorIconSource&) = delete; + + ~VectorIconSource() override {} + + // CanvasImageSource: + bool HasRepresentationAtAllScales() const override { + return !data_.icon.is_empty(); + } + + void Draw(Canvas* canvas) override { + if (path_.empty()) { + PaintVectorIcon(canvas, data_.icon, size_.width(), data_.color); + if (!data_.badge_icon.is_empty()) + PaintVectorIcon(canvas, data_.badge_icon, size_.width(), data_.color); + } else { + PaintPath(canvas, path_.data(), path_.size(), size_.width(), data_.color); + } + } + + private: + const IconDescription data_; + const std::vector path_; +}; + +// This class caches vector icons (as ImageSkia) so they don't have to be drawn +// more than once. This also guarantees the backing data for the images returned +// by CreateVectorIcon will persist in memory until program termination. +class VectorIconCache { + public: + VectorIconCache() {} + + VectorIconCache(const VectorIconCache&) = delete; + VectorIconCache& operator=(const VectorIconCache&) = delete; + + ~VectorIconCache() {} + + ImageSkia GetOrCreateIcon(const IconDescription& description) { + auto iter = images_.find(description); + if (iter != images_.end()) + return iter->second; + + ImageSkia icon_image(std::make_unique(description), + Size(description.dip_size, description.dip_size)); + images_.emplace(description, icon_image); + return icon_image; + } + + private: + std::map images_; +}; + +static base::LazyInstance::DestructorAtExit g_icon_cache = + LAZY_INSTANCE_INITIALIZER; + +} // namespace + +IconDescription::IconDescription(const IconDescription& other) = default; + +IconDescription::IconDescription(const VectorIcon& icon, + int dip_size, + SkColor color, + const VectorIcon* badge_icon) + : icon(icon), + dip_size(dip_size), + color(color), + badge_icon(badge_icon ? *badge_icon : kNoneIcon) { + if (dip_size == 0) + this->dip_size = GetDefaultSizeOfVectorIcon(icon); +} + +IconDescription::~IconDescription() {} + +const VectorIcon kNoneIcon = {}; + +void PaintVectorIcon(Canvas* canvas, const VectorIcon& icon, SkColor color) { + PaintVectorIcon(canvas, icon, GetDefaultSizeOfVectorIcon(icon), color); +} + +void PaintVectorIcon(Canvas* canvas, + const VectorIcon& icon, + int dip_size, + SkColor color) { + DCHECK(!icon.is_empty()); + for (size_t i = 0; i < icon.reps_size; ++i) + DCHECK(icon.reps[i].path_size > 0); + const int px_size = base::ClampCeil(canvas->image_scale() * dip_size); + const VectorIconRep* rep = GetRepForPxSize(icon, px_size); + PaintPath(canvas, rep->path, rep->path_size, dip_size, color); +} + +ImageSkia CreateVectorIcon(const IconDescription& params) { + if (params.icon.is_empty()) + return ImageSkia(); + + return g_icon_cache.Get().GetOrCreateIcon(params); +} + +ImageSkia CreateVectorIcon(const VectorIcon& icon, SkColor color) { + return CreateVectorIcon(icon, GetDefaultSizeOfVectorIcon(icon), color); +} + +ImageSkia CreateVectorIcon(const VectorIcon& icon, + int dip_size, + SkColor color) { + return CreateVectorIcon(IconDescription(icon, dip_size, color, &kNoneIcon)); +} + +ImageSkia CreateVectorIconWithBadge(const VectorIcon& icon, + int dip_size, + SkColor color, + const VectorIcon& badge_icon) { + return CreateVectorIcon(IconDescription(icon, dip_size, color, &badge_icon)); +} + +ImageSkia CreateVectorIconFromSource(const std::string& source, + int dip_size, + SkColor color) { + return CanvasImageSource::MakeImageSkia(source, dip_size, + color); +} + +} // namespace gfx diff --git a/paint_vector_icon.h b/paint_vector_icon.h new file mode 100644 index 000000000000..c005bfab2a11 --- /dev/null +++ b/paint_vector_icon.h @@ -0,0 +1,86 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_PAINT_VECTOR_ICON_H_ +#define UI_GFX_PAINT_VECTOR_ICON_H_ + +#include "third_party/skia/include/core/SkColor.h" +#include "ui/gfx/color_palette.h" +#include "ui/gfx/gfx_export.h" +#include "ui/gfx/image/image_skia.h" + +namespace gfx { + +class Canvas; +struct VectorIcon; + +// Describes an instance of an icon: an icon definition and a set of drawing +// parameters. +struct GFX_EXPORT IconDescription { + IconDescription(const IconDescription& other); + + // If |dip_size| is 0, the default size of |icon| will be used. + // If |badge_icon| is null, the icon has no badge. + IconDescription(const VectorIcon& icon, + int dip_size = 0, + SkColor color = gfx::kPlaceholderColor, + const VectorIcon* badge_icon = nullptr); + + ~IconDescription(); + + const VectorIcon& icon; + int dip_size; + SkColor color; + const VectorIcon& badge_icon; +}; + +GFX_EXPORT extern const VectorIcon kNoneIcon; + +// Draws a vector icon identified by |id| onto |canvas| at (0, 0). |color| is +// used as the fill. The size will come from the .icon file (the 1x version, if +// multiple versions exist). +GFX_EXPORT void PaintVectorIcon(Canvas* canvas, + const VectorIcon& icon, + SkColor color); + +// As above, with a specified size. |dip_size| is the length of a single edge +// of the square icon, in device independent pixels. +GFX_EXPORT void PaintVectorIcon(Canvas* canvas, + const VectorIcon& icon, + int dip_size, + SkColor color); + +// Creates an ImageSkia which will render the icon on demand. +// TODO(estade): update clients to use this version and remove the other +// CreateVectorIcon()s. +GFX_EXPORT ImageSkia CreateVectorIcon(const IconDescription& params); + +// Creates an ImageSkia which will render the icon on demand. The size will come +// from the .icon file (the 1x version, if multiple versions exist). +GFX_EXPORT ImageSkia CreateVectorIcon(const VectorIcon& icon, SkColor color); + +// As above, but creates the image at the given size. +GFX_EXPORT ImageSkia CreateVectorIcon(const VectorIcon& icon, + int dip_size, + SkColor color); + +// As above, but also paints a badge defined by |badge_id| on top of the icon. +// The badge uses the same canvas size and default color as the icon. +GFX_EXPORT ImageSkia CreateVectorIconWithBadge(const VectorIcon& icon, + int dip_size, + SkColor color, + const VectorIcon& badge_icon); + +#if defined(GFX_VECTOR_ICONS_UNSAFE) || defined(GFX_IMPLEMENTATION) +// Takes a string of the format expected of .icon files and renders onto +// a canvas. This should only be used as a debugging aid and should never be +// used in production code. +GFX_EXPORT ImageSkia CreateVectorIconFromSource(const std::string& source, + int dip_size, + SkColor color); +#endif + +} // namespace gfx + +#endif // UI_GFX_PAINT_VECTOR_ICON_H_ diff --git a/paint_vector_icon_unittest.cc b/paint_vector_icon_unittest.cc new file mode 100644 index 000000000000..ff7bd2ea11fa --- /dev/null +++ b/paint_vector_icon_unittest.cc @@ -0,0 +1,291 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/paint_vector_icon.h" + +#include +#include + +#include "base/cxx17_backports.h" +#include "base/i18n/rtl.h" +#include "cc/paint/paint_record.h" +#include "cc/paint/paint_recorder.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "third_party/skia/include/core/SkPath.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/vector_icon_types.h" + +namespace gfx { + +namespace { + +SkColor GetColorAtTopLeft(const Canvas& canvas) { + return canvas.GetBitmap().getColor(0, 0); +} + +class MockCanvas : public SkCanvas { + public: + MockCanvas(int width, int height) : SkCanvas(width, height) {} + + MockCanvas(const MockCanvas&) = delete; + MockCanvas& operator=(const MockCanvas&) = delete; + + // SkCanvas overrides: + void onDrawPath(const SkPath& path, const SkPaint& paint) override { + paths_.push_back(path); + } + + const std::vector& paths() const { return paths_; } + + private: + std::vector paths_; +}; + +// Tests that a relative move to command (R_MOVE_TO) after a close command +// (CLOSE) uses the correct starting point. See crbug.com/697497 +TEST(VectorIconTest, RelativeMoveToAfterClose) { + cc::PaintRecorder recorder; + Canvas canvas(recorder.beginRecording(100, 100), 1.0f); + + const PathElement elements[] = { + MOVE_TO, 4, 5, LINE_TO, 10, 11, CLOSE, + // This move should use (4, 5) as the start point rather than (10, 11). + R_MOVE_TO, 20, 21, R_LINE_TO, 50, 51}; + const VectorIconRep rep_list[] = {{elements, base::size(elements)}}; + const VectorIcon icon = {rep_list, 1u}; + + PaintVectorIcon(&canvas, icon, 100, SK_ColorMAGENTA); + sk_sp record = recorder.finishRecordingAsPicture(); + + MockCanvas mock(100, 100); + record->Playback(&mock); + + ASSERT_EQ(1U, mock.paths().size()); + SkPoint last_point; + EXPECT_TRUE(mock.paths()[0].getLastPt(&last_point)); + EXPECT_EQ(SkIntToScalar(74), last_point.x()); + EXPECT_EQ(SkIntToScalar(77), last_point.y()); +} + +TEST(VectorIconTest, FlipsInRtl) { + // Set the locale to a rtl language otherwise FLIPS_IN_RTL will do nothing. + base::i18n::SetICUDefaultLocale("he"); + ASSERT_TRUE(base::i18n::IsRTL()); + + const int canvas_size = 20; + const SkColor color = SK_ColorWHITE; + + Canvas canvas(gfx::Size(canvas_size, canvas_size), 1.0f, true); + + // Create a 20x20 square icon which has FLIPS_IN_RTL, and CANVAS_DIMENSIONS + // are twice as large as |canvas|. + const PathElement elements[] = {CANVAS_DIMENSIONS, + 2 * canvas_size, + FLIPS_IN_RTL, + MOVE_TO, + 10, + 10, + R_H_LINE_TO, + 20, + R_V_LINE_TO, + 20, + R_H_LINE_TO, + -20, + CLOSE}; + const VectorIconRep rep_list[] = {{elements, base::size(elements)}}; + const VectorIcon icon = {rep_list, 1u}; + PaintVectorIcon(&canvas, icon, canvas_size, color); + + // Count the number of pixels in the canvas. + auto bitmap = canvas.GetBitmap(); + int colored_pixel_count = 0; + for (int i = 0; i < bitmap.width(); ++i) { + for (int j = 0; j < bitmap.height(); ++j) { + if (bitmap.getColor(i, j) == color) + colored_pixel_count++; + } + } + + // Verify that the amount of colored pixels on the canvas bitmap should be a + // quarter of the original icon, since each side should be scaled down by a + // factor of two. + EXPECT_EQ(100, colored_pixel_count); +} + +TEST(VectorIconTest, CorrectSizePainted) { + // Create a set of 5 icons reps, sized {48, 32, 24, 20, 16} for the test icon. + // Color each of them differently so they can be differentiated (the parts of + // an icon painted with PATH_COLOR_ARGB will not be overwritten by the color + // provided to it at creation time). + const SkColor kPath48Color = SK_ColorRED; + const PathElement elements48[] = {CANVAS_DIMENSIONS, + 48, + PATH_COLOR_ARGB, + 0xFF, + SkColorGetR(kPath48Color), + SkColorGetG(kPath48Color), + SkColorGetB(kPath48Color), + MOVE_TO, + 0, + 0, + H_LINE_TO, + 48, + V_LINE_TO, + 48, + H_LINE_TO, + 0, + V_LINE_TO, + 0, + CLOSE}; + const SkColor kPath32Color = SK_ColorGREEN; + const PathElement elements32[] = {CANVAS_DIMENSIONS, + 32, + PATH_COLOR_ARGB, + 0xFF, + SkColorGetR(kPath32Color), + SkColorGetG(kPath32Color), + SkColorGetB(kPath32Color), + MOVE_TO, + 0, + 0, + H_LINE_TO, + 32, + V_LINE_TO, + 32, + H_LINE_TO, + 0, + V_LINE_TO, + 0, + CLOSE}; + const SkColor kPath24Color = SK_ColorBLUE; + const PathElement elements24[] = {CANVAS_DIMENSIONS, + 24, + PATH_COLOR_ARGB, + 0xFF, + SkColorGetR(kPath24Color), + SkColorGetG(kPath24Color), + SkColorGetB(kPath24Color), + MOVE_TO, + 0, + 0, + H_LINE_TO, + 24, + V_LINE_TO, + 24, + H_LINE_TO, + 0, + V_LINE_TO, + 0, + CLOSE}; + const SkColor kPath20Color = SK_ColorYELLOW; + const PathElement elements20[] = {CANVAS_DIMENSIONS, + 20, + PATH_COLOR_ARGB, + 0xFF, + SkColorGetR(kPath20Color), + SkColorGetG(kPath20Color), + SkColorGetB(kPath20Color), + MOVE_TO, + 0, + 0, + H_LINE_TO, + 20, + V_LINE_TO, + 20, + H_LINE_TO, + 0, + V_LINE_TO, + 0, + CLOSE}; + const SkColor kPath16Color = SK_ColorCYAN; + const PathElement elements16[] = {CANVAS_DIMENSIONS, + 16, + PATH_COLOR_ARGB, + 0xFF, + SkColorGetR(kPath16Color), + SkColorGetG(kPath16Color), + SkColorGetB(kPath16Color), + MOVE_TO, + 0, + 0, + H_LINE_TO, + 16, + V_LINE_TO, + 16, + H_LINE_TO, + 0, + V_LINE_TO, + 0, + CLOSE}; + // VectorIconReps are always sorted in descending order of size. + const VectorIconRep rep_list[] = {{elements48, base::size(elements48)}, + {elements32, base::size(elements32)}, + {elements24, base::size(elements24)}, + {elements20, base::size(elements20)}, + {elements16, base::size(elements16)}}; + const VectorIcon icon = {rep_list, 5u}; + + // Test exact sizes paint the correctly sized icon, including the largest and + // smallest icon. + Canvas canvas_100(gfx::Size(100, 100), 1.0, true); + PaintVectorIcon(&canvas_100, icon, 48, SK_ColorBLACK); + EXPECT_EQ(kPath48Color, GetColorAtTopLeft(canvas_100)); + PaintVectorIcon(&canvas_100, icon, 32, SK_ColorBLACK); + EXPECT_EQ(kPath32Color, GetColorAtTopLeft(canvas_100)); + PaintVectorIcon(&canvas_100, icon, 16, SK_ColorBLACK); + EXPECT_EQ(kPath16Color, GetColorAtTopLeft(canvas_100)); + + // The largest icon may be upscaled to a size larger than what it was + // designed for. + PaintVectorIcon(&canvas_100, icon, 50, SK_ColorBLACK); + EXPECT_EQ(kPath48Color, GetColorAtTopLeft(canvas_100)); + + // Other requests will be satisfied by downscaling. + PaintVectorIcon(&canvas_100, icon, 27, SK_ColorBLACK); + EXPECT_EQ(kPath32Color, GetColorAtTopLeft(canvas_100)); + PaintVectorIcon(&canvas_100, icon, 8, SK_ColorBLACK); + EXPECT_EQ(kPath16Color, GetColorAtTopLeft(canvas_100)); + + // Except in cases where an exact divisor is found. + PaintVectorIcon(&canvas_100, icon, 40, SK_ColorBLACK); + EXPECT_EQ(kPath20Color, GetColorAtTopLeft(canvas_100)); + PaintVectorIcon(&canvas_100, icon, 64, SK_ColorBLACK); + EXPECT_EQ(kPath32Color, GetColorAtTopLeft(canvas_100)); + + // Test icons at a scale factor < 100%, still with an exact size, paint the + // correctly sized icon. + Canvas canvas_75(gfx::Size(100, 100), 0.75, true); + PaintVectorIcon(&canvas_75, icon, 32, SK_ColorBLACK); // 32 * 0.75 = 24. + EXPECT_EQ(kPath24Color, GetColorAtTopLeft(canvas_75)); + + // Test icons at a scale factor > 100%, still with an exact size, paint the + // correctly sized icon. + Canvas canvas_125(gfx::Size(100, 100), 1.25, true); + PaintVectorIcon(&canvas_125, icon, 16, SK_ColorBLACK); // 16 * 1.25 = 20. + EXPECT_EQ(kPath20Color, GetColorAtTopLeft(canvas_125)); + + // Inexact sizes at scale factors < 100%. + PaintVectorIcon(&canvas_75, icon, 12, SK_ColorBLACK); // 12 * 0.75 = 9. + EXPECT_EQ(kPath16Color, GetColorAtTopLeft(canvas_75)); + PaintVectorIcon(&canvas_75, icon, 28, SK_ColorBLACK); // 28 * 0.75 = 21. + EXPECT_EQ(kPath24Color, GetColorAtTopLeft(canvas_75)); + + // Inexact sizes at scale factors > 100%. + PaintVectorIcon(&canvas_125, icon, 12, SK_ColorBLACK); // 12 * 1.25 = 15. + EXPECT_EQ(kPath16Color, GetColorAtTopLeft(canvas_125)); + PaintVectorIcon(&canvas_125, icon, 28, SK_ColorBLACK); // 28 * 1.25 = 35. + EXPECT_EQ(kPath48Color, GetColorAtTopLeft(canvas_125)); + + // Painting without a requested size will default to the smallest icon rep. + PaintVectorIcon(&canvas_100, icon, SK_ColorBLACK); + EXPECT_EQ(kPath16Color, GetColorAtTopLeft(canvas_100)); + // But doing this in another scale factor should assume the smallest icon rep + // size, then scale it up by the DSF. + PaintVectorIcon(&canvas_125, icon, SK_ColorBLACK); // 16 * 1.25 = 20. + EXPECT_EQ(kPath20Color, GetColorAtTopLeft(canvas_125)); +} + +} // namespace + +} // namespace gfx diff --git a/path_mac.h b/path_mac.h new file mode 100644 index 000000000000..f20fa62d7c2b --- /dev/null +++ b/path_mac.h @@ -0,0 +1,21 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_PATH_MAC_H_ +#define UI_GFX_PATH_MAC_H_ + +#include "ui/gfx/gfx_export.h" + +@class NSBezierPath; +class SkPath; + +namespace gfx { + +// Returns an autoreleased NSBezierPath corresponding to |path|. Caller should +// call retain on the returned object, if it wishes to take ownership. +GFX_EXPORT NSBezierPath* CreateNSBezierPathFromSkPath(const SkPath& path); + +} // namespace gfx + +#endif // UI_GFX_PATH_MAC_H_ diff --git a/path_mac.mm b/path_mac.mm new file mode 100644 index 000000000000..fd82ad9ffc1a --- /dev/null +++ b/path_mac.mm @@ -0,0 +1,129 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "ui/gfx/path_mac.h" + +#include + +#import + +#include "base/cxx17_backports.h" +#include "base/notreached.h" +#include "third_party/skia/include/core/SkPath.h" +#include "third_party/skia/include/core/SkRegion.h" + +namespace { + +// Convert a quadratic bezier curve to a cubic bezier curve. Based on the +// implementation of the private SkConvertQuadToCubic method inside Skia. +void ConvertQuadToCubicBezier(NSPoint quad[3], NSPoint cubic[4]) { + // The resultant cubic will have the same endpoints. + cubic[0] = quad[0]; + cubic[3] = quad[2]; + + const double scale = 2.0 / 3.0; + + cubic[1].x = quad[0].x + scale * (quad[1].x - quad[0].x); + cubic[1].y = quad[0].y + scale * (quad[1].y - quad[0].y); + + cubic[2].x = quad[2].x + scale * (quad[1].x - quad[2].x); + cubic[2].y = quad[2].y + scale * (quad[1].y - quad[2].y); +} + +} // namespace + +namespace gfx { + +NSBezierPath* CreateNSBezierPathFromSkPath(const SkPath& path) { + NSBezierPath* result = [NSBezierPath bezierPath]; + SkPath::RawIter iter(path); + SkPoint sk_points[4] = {{0.0}}; + SkPath::Verb verb; + NSPoint points[4]; + while ((verb = iter.next(sk_points)) != SkPath::kDone_Verb) { + for (size_t i = 0; i < base::size(points); i++) + points[i] = NSMakePoint(sk_points[i].x(), sk_points[i].y()); + + switch (verb) { + case SkPath::kMove_Verb: { + [result moveToPoint:points[0]]; + break; + } + case SkPath::kLine_Verb: { + DCHECK(NSEqualPoints([result currentPoint], points[0])); + [result lineToPoint:points[1]]; + break; + } + case SkPath::kQuad_Verb: { + DCHECK(NSEqualPoints([result currentPoint], points[0])); + NSPoint quad[] = {points[0], points[1], points[2]}; + // NSBezierPath does not support quadratic bezier curves. Hence convert + // to cubic bezier curve. + ConvertQuadToCubicBezier(quad, points); + [result curveToPoint:points[3] + controlPoint1:points[1] + controlPoint2:points[2]]; + break; + } + case SkPath::kConic_Verb: { + DCHECK(NSEqualPoints([result currentPoint], points[0])); + // Approximate with quads. Use two for now, increase if more precision + // is needed. + const size_t kSubdivisionLevels = 1; + const size_t kQuadCount = 1 << kSubdivisionLevels; + // The quads will share endpoints, so we need one more point than twice + // the number of quads. + const size_t kPointCount = 1 + 2 * kQuadCount; + SkPoint quads[kPointCount]; + SkPath::ConvertConicToQuads(sk_points[0], sk_points[1], sk_points[2], + iter.conicWeight(), quads, + kSubdivisionLevels); + NSPoint ns_quads[kPointCount]; + for (size_t i = 0; i < kPointCount; i++) + ns_quads[i] = NSMakePoint(quads[i].x(), quads[i].y()); + + for (size_t i = 0; i < kQuadCount; i++) { + NSPoint quad[] = {ns_quads[2 * i], ns_quads[2 * i + 1], + ns_quads[2 * i + 2]}; + ConvertQuadToCubicBezier(quad, points); + DCHECK(NSEqualPoints([result currentPoint], points[0])); + [result curveToPoint:points[3] + controlPoint1:points[1] + controlPoint2:points[2]]; + } + break; + } + case SkPath::kCubic_Verb: { + DCHECK(NSEqualPoints([result currentPoint], points[0])); + [result curveToPoint:points[3] + controlPoint1:points[1] + controlPoint2:points[2]]; + break; + } + case SkPath::kClose_Verb: { + [result closePath]; + break; + } + default: { NOTREACHED(); } + } + } + + // Set up the fill type. + switch (path.getFillType()) { + case SkPathFillType::kWinding: + [result setWindingRule:NSNonZeroWindingRule]; + break; + case SkPathFillType::kEvenOdd: + [result setWindingRule:NSEvenOddWindingRule]; + break; + case SkPathFillType::kInverseWinding: + case SkPathFillType::kInverseEvenOdd: + NOTREACHED() << "NSBezierCurve does not support inverse fill types."; + break; + } + + return result; +} + +} // namespace gfx diff --git a/path_mac_unittest.mm b/path_mac_unittest.mm new file mode 100644 index 000000000000..149c4e3ce758 --- /dev/null +++ b/path_mac_unittest.mm @@ -0,0 +1,260 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "ui/gfx/path_mac.h" + +#include +#include + +#import + +#include "base/check_op.h" +#include "base/cxx17_backports.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkPath.h" +#include "third_party/skia/include/core/SkRegion.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/rect_conversions.h" + +namespace gfx { + +namespace { + +// Returns the point at a distance of |radius| from the point (|centre_x|, +// |centre_y|), and angle |degrees| from the positive horizontal axis, measured +// anti-clockwise. +NSPoint GetRadialPoint(double radius, + double degrees, + double centre_x, + double centre_y) { + const double radian = (degrees * SK_ScalarPI) / 180; + return NSMakePoint(centre_x + radius * std::cos(radian), + centre_y + radius * std::sin(radian)); +} + +// Returns the area of a circle with the given |radius|. +double CalculateCircleArea(double radius) { + return SK_ScalarPI * radius * radius; +} + +// Returns the area of a simple polygon. |path| should represent a simple +// polygon. +double CalculatePolygonArea(NSBezierPath* path) { + // If path represents a single polygon, it will have MoveTo, followed by + // multiple LineTo, followed By ClosePath, followed by another MoveTo + // NSBezierPathElement. + const size_t element_count = [path elementCount]; + NSPoint points[3]; + std::vector poly; + + for (size_t i = 0; i < element_count - 1; i++) { + NSBezierPathElement element = + [path elementAtIndex:i associatedPoints:points]; + poly.push_back(points[0]); + DCHECK_EQ(element, + i ? (i == element_count - 2 ? NSClosePathBezierPathElement + : NSLineToBezierPathElement) + : NSMoveToBezierPathElement); + } + DCHECK_EQ([path elementAtIndex:element_count - 1], NSMoveToBezierPathElement); + + // Shoelace Algorithm to find the area of a simple polygon. + DCHECK(NSEqualPoints(poly.front(), poly.back())); + double area = 0; + for (size_t i = 0; i < poly.size() - 1; i++) + area += poly[i].x * poly[i + 1].y - poly[i].y * poly[i + 1].x; + + return std::fabs(area) / 2.0; +} + +// Returns the area of a rounded rectangle with the given |width|, |height| and +// |radius|. +double CalculateRoundedRectangleArea(double width, + double height, + double radius) { + const double inside_width = width - 2 * radius; + const double inside_height = height - 2 * radius; + return inside_width * inside_height + + 2 * radius * (inside_width + inside_height) + + CalculateCircleArea(radius); +} + +// Returns the bounding box of |path| as a Rect. +Rect GetBoundingBox(NSBezierPath* path) { + const NSRect bounds = [path bounds]; + return ToNearestRect(RectF(bounds.origin.x, bounds.origin.y, + bounds.size.width, bounds.size.height)); +} + +} // namespace + +// Check that empty NSBezierPath is returned for empty SkPath. +TEST(CreateNSBezierPathFromSkPathTest, EmptyPath) { + NSBezierPath* result = CreateNSBezierPathFromSkPath(SkPath()); + EXPECT_TRUE([result isEmpty]); +} + +// Check that the returned NSBezierPath has the correct winding rule. +TEST(CreateNSBezierPathFromSkPathTest, FillType) { + SkPath path; + path.setFillType(SkPathFillType::kWinding); + NSBezierPath* result = CreateNSBezierPathFromSkPath(path); + EXPECT_EQ(NSNonZeroWindingRule, [result windingRule]); + + path.setFillType(SkPathFillType::kEvenOdd); + result = CreateNSBezierPathFromSkPath(path); + EXPECT_EQ(NSEvenOddWindingRule, [result windingRule]); +} + +// Check that a path containing multiple subpaths, in this case two rectangles, +// is correctly converted to a NSBezierPath. +TEST(CreateNSBezierPathFromSkPathTest, TwoRectanglesPath) { + const SkRect rects[] = { + {0, 0, 50, 50}, {100, 100, 150, 150}, + }; + const NSPoint inside_points[] = { + {1, 1}, {1, 49}, {49, 49}, {49, 1}, {25, 25}, + {101, 101}, {101, 149}, {149, 149}, {149, 101}, {125, 125}}; + const NSPoint outside_points[] = {{-1, -1}, {-1, 51}, {51, 51}, {51, -1}, + {99, 99}, {99, 151}, {151, 151}, {151, 99}, + {75, 75}, {-5, -5}}; + ASSERT_EQ(base::size(inside_points), base::size(outside_points)); + const Rect expected_bounds(0, 0, 150, 150); + + SkPath path; + path.addRect(rects[0]); + path.addRect(rects[1]); + NSBezierPath* result = CreateNSBezierPathFromSkPath(path); + + // Check points near the boundary of the path and verify that they are + // reported correctly as being inside/outside the path. + for (size_t i = 0; i < base::size(inside_points); i++) { + EXPECT_TRUE([result containsPoint:inside_points[i]]); + EXPECT_FALSE([result containsPoint:outside_points[i]]); + } + + // Check that the returned result has the correct bounding box. GetBoundingBox + // rounds the coordinates to nearest integer values. + EXPECT_EQ(expected_bounds, GetBoundingBox(result)); +} + +// Test that an SKPath containing a circle is converted correctly to a +// NSBezierPath. +TEST(CreateNSBezierPathFromSkPathTest, CirclePath) { + const int kRadius = 5; + const int kCentreX = 10; + const int kCentreY = 15; + const double kCushion = 0.1; + // Expected bounding box of the circle. + const Rect expected_bounds(kCentreX - kRadius, kCentreY - kRadius, + 2 * kRadius, 2 * kRadius); + + SkPath path; + path.addCircle(SkIntToScalar(kCentreX), SkIntToScalar(kCentreY), + SkIntToScalar(kRadius)); + NSBezierPath* result = CreateNSBezierPathFromSkPath(path); + + // Check points near the boundary of the circle and verify that they are + // reported correctly as being inside/outside the path. + for (size_t deg = 0; deg < 360; deg++) { + NSPoint inside_point = + GetRadialPoint(kRadius - kCushion, deg, kCentreX, kCentreY); + NSPoint outside_point = + GetRadialPoint(kRadius + kCushion, deg, kCentreX, kCentreY); + EXPECT_TRUE([result containsPoint:inside_point]); + EXPECT_FALSE([result containsPoint:outside_point]); + } + + // Check that the returned result has the correct bounding box. GetBoundingBox + // rounds the coordinates to nearest integer values. + EXPECT_EQ(expected_bounds, GetBoundingBox(result)); + + // Check area of converted path is correct up to a certain tolerance value. To + // find the area of the NSBezierPath returned, flatten it i.e. convert it to a + // polygon. + [NSBezierPath setDefaultFlatness:0.01]; + NSBezierPath* polygon = [result bezierPathByFlatteningPath]; + const double kTolerance = 0.14; + EXPECT_NEAR(CalculateCircleArea(kRadius), CalculatePolygonArea(polygon), + kTolerance); +} + +// Test that an SKPath containing a rounded rectangle is converted correctly to +// a NSBezierPath. +TEST(CreateNSBezierPathFromSkPathTest, RoundedRectanglePath) { + const int kRectangleWidth = 50; + const int kRectangleHeight = 100; + const int kCornerRadius = 5; + const double kCushion = 0.1; + // Expected bounding box of the rounded rectangle. + const Rect expected_bounds(kRectangleWidth, kRectangleHeight); + + SkRRect rrect; + rrect.setRectXY(SkRect::MakeWH(kRectangleWidth, kRectangleHeight), + kCornerRadius, kCornerRadius); + + const NSPoint inside_points[] = { + // Bottom left corner. + {kCornerRadius / 2.0, kCornerRadius / 2.0}, + // Bottom right corner. + {kRectangleWidth - kCornerRadius / 2.0, kCornerRadius / 2.0}, + // Top Right corner. + {kRectangleWidth - kCornerRadius / 2.0, + kRectangleHeight - kCornerRadius / 2.0}, + // Top left corner. + {kCornerRadius / 2.0, kRectangleHeight - kCornerRadius / 2.0}, + // Bottom middle. + {kRectangleWidth / 2.0, kCushion}, + // Right middle. + {kRectangleWidth - kCushion, kRectangleHeight / 2.0}, + // Top middle. + {kRectangleWidth / 2.0, kRectangleHeight - kCushion}, + // Left middle. + {kCushion, kRectangleHeight / 2.0}}; + const NSPoint outside_points[] = { + // Bottom left corner. + {0, 0}, + // Bottom right corner. + {kRectangleWidth, 0}, + // Top right corner. + {kRectangleWidth, kRectangleHeight}, + // Top left corner. + {0, kRectangleHeight}, + // Bottom middle. + {kRectangleWidth / 2.0, -kCushion}, + // Right middle. + {kRectangleWidth + kCushion, kRectangleHeight / 2.0}, + // Top middle. + {kRectangleWidth / 2.0, kRectangleHeight + kCushion}, + // Left middle. + {-kCushion, kRectangleHeight / 2.0}}; + ASSERT_EQ(base::size(inside_points), base::size(outside_points)); + + SkPath path; + path.addRRect(rrect); + NSBezierPath* result = CreateNSBezierPathFromSkPath(path); + + // Check points near the boundary of the path and verify that they are + // reported correctly as being inside/outside the path. + for (size_t i = 0; i < base::size(inside_points); i++) { + EXPECT_TRUE([result containsPoint:inside_points[i]]); + EXPECT_FALSE([result containsPoint:outside_points[i]]); + } + + // Check that the returned result has the correct bounding box. GetBoundingBox + // rounds the coordinates to nearest integer values. + EXPECT_EQ(expected_bounds, GetBoundingBox(result)); + + // Check area of converted path is correct up to a certain tolerance value. To + // find the area of the NSBezierPath returned, flatten it i.e. convert it to a + // polygon. + [NSBezierPath setDefaultFlatness:0.01]; + NSBezierPath* polygon = [result bezierPathByFlatteningPath]; + const double kTolerance = 0.14; + EXPECT_NEAR(CalculateRoundedRectangleArea(kRectangleWidth, kRectangleHeight, + kCornerRadius), + CalculatePolygonArea(polygon), kTolerance); +} + +} // namespace gfx diff --git a/path_win.cc b/path_win.cc new file mode 100644 index 000000000000..3a73d9171f29 --- /dev/null +++ b/path_win.cc @@ -0,0 +1,37 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/path_win.h" + +#include + +#include "base/win/scoped_gdi_object.h" +#include "third_party/skia/include/core/SkPath.h" +#include "third_party/skia/include/core/SkRegion.h" + +namespace gfx { + +HRGN CreateHRGNFromSkRegion(const SkRegion& region) { + base::win::ScopedRegion temp(::CreateRectRgn(0, 0, 0, 0)); + base::win::ScopedRegion result(::CreateRectRgn(0, 0, 0, 0)); + + for (SkRegion::Iterator i(region); !i.done(); i.next()) { + const SkIRect& rect = i.rect(); + ::SetRectRgn(temp.get(), + rect.left(), rect.top(), rect.right(), rect.bottom()); + ::CombineRgn(result.get(), result.get(), temp.get(), RGN_OR); + } + + return result.release(); +} + +HRGN CreateHRGNFromSkPath(const SkPath& path) { + SkRegion clip_region; + clip_region.setRect(path.getBounds().round()); + SkRegion region; + region.setPath(path, clip_region); + return CreateHRGNFromSkRegion(region); +} + +} // namespace gfx diff --git a/path_win.h b/path_win.h new file mode 100644 index 000000000000..2b850753f984 --- /dev/null +++ b/path_win.h @@ -0,0 +1,27 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_PATH_WIN_H_ +#define UI_GFX_PATH_WIN_H_ + +#include + +#include "ui/gfx/gfx_export.h" + +class SkPath; +class SkRegion; + +namespace gfx { + +// Creates a new HRGN given |region|. The caller is responsible for destroying +// the returned region. +GFX_EXPORT HRGN CreateHRGNFromSkRegion(const SkRegion& path); + +// Creates a new HRGN given |path|. The caller is responsible for destroying +// the returned region. Returns empty region (not NULL) for empty path. +GFX_EXPORT HRGN CreateHRGNFromSkPath(const SkPath& path); + +} // namespace gfx + +#endif // UI_GFX_PATH_WIN_H_ diff --git a/path_win_unittest.cc b/path_win_unittest.cc new file mode 100644 index 000000000000..c001370cfded --- /dev/null +++ b/path_win_unittest.cc @@ -0,0 +1,120 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/path_win.h" + +#include + +#include +#include + +#include "base/check_op.h" +#include "base/cxx17_backports.h" +#include "base/win/scoped_gdi_object.h" +#include "skia/ext/skia_utils_win.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkPath.h" +#include "third_party/skia/include/core/SkRRect.h" + +namespace gfx { + +namespace { + +// Get rectangles from the |region| and convert them to SkIRect. +std::vector GetRectsFromHRGN(HRGN region) { + // Determine the size of output buffer required to receive the region. + DWORD bytes_size = GetRegionData(region, 0, NULL); + CHECK_NE((DWORD)0, bytes_size); + + // Fetch the Windows RECTs that comprise the region. + std::vector buffer(bytes_size); + LPRGNDATA region_data = reinterpret_cast(buffer.data()); + DWORD result = GetRegionData(region, bytes_size, region_data); + CHECK_EQ(bytes_size, result); + + // Pull out the rectangles into a SkIRect vector to return to caller. + const LPRECT rects = reinterpret_cast(®ion_data->Buffer[0]); + std::vector sk_rects(region_data->rdh.nCount); + std::transform(rects, rects + region_data->rdh.nCount, + sk_rects.begin(), skia::RECTToSkIRect); + + return sk_rects; +} + +} // namespace + +// Test that rectangle with round corners stil has round corners after +// converting from SkPath to the HRGN. +// FIXME: this test is fragile (it depends on rrect rasterization impl) +TEST(CreateHRGNFromSkPathTest, RoundCornerTest) { + const SkIRect rects[] = { + { 16, 0, 34, 1 }, + { 12, 1, 38, 2 }, + { 10, 2, 40, 3 }, + { 9, 3, 41, 4 }, + { 7, 4, 43, 5 }, + { 6, 5, 44, 6 }, + { 5, 6, 45, 7 }, + { 4, 7, 45, 8 }, + { 4, 8, 46, 9 }, + { 3, 9, 47, 10 }, + { 2, 10, 47, 11 }, + { 2, 11, 48, 12 }, + { 1, 12, 49, 16 }, + { 0, 16, 50, 34 }, + { 1, 34, 49, 38 }, + { 2, 38, 48, 39 }, + { 2, 39, 47, 40 }, + { 3, 40, 47, 41 }, + { 4, 41, 46, 42 }, + { 4, 42, 45, 43 }, + { 5, 43, 45, 44 }, + { 6, 44, 44, 45 }, + { 8, 45, 42, 46 }, + { 9, 46, 41, 47 }, + { 11, 47, 39, 48 }, + { 12, 48, 38, 49 }, + { 16, 49, 34, 50 }, + }; + + SkPath path; + SkRRect rrect; + rrect.setRectXY(SkRect::MakeWH(50, 50), 20, 20); + path.addRRect(rrect); + base::win::ScopedRegion region(CreateHRGNFromSkPath(path)); + const std::vector& region_rects = GetRectsFromHRGN(region.get()); + EXPECT_EQ(base::size(rects), region_rects.size()); + for (size_t i = 0; i < base::size(rects) && i < region_rects.size(); ++i) + EXPECT_EQ(rects[i], region_rects[i]); +} + +// Check that a path enclosing two non-adjacent areas is correctly translated +// to a non-contiguous region. +TEST(CreateHRGNFromSkPathTest, NonContiguousPath) { + const SkIRect rects[] = { + { 0, 0, 50, 50}, + { 100, 100, 150, 150}, + }; + + SkPath path; + for (const SkIRect& rect : rects) { + path.addRect(SkRect::Make(rect)); + } + base::win::ScopedRegion region(CreateHRGNFromSkPath(path)); + const std::vector& region_rects = GetRectsFromHRGN(region.get()); + ASSERT_EQ(base::size(rects), region_rects.size()); + for (size_t i = 0; i < base::size(rects); ++i) + EXPECT_EQ(rects[i], region_rects[i]); +} + +// Check that empty region is returned for empty path. +TEST(CreateHRGNFromSkPathTest, EmptyPath) { + SkPath path; + base::win::ScopedRegion empty_region(::CreateRectRgn(0, 0, 0, 0)); + base::win::ScopedRegion region(CreateHRGNFromSkPath(path)); + EXPECT_TRUE(::EqualRgn(empty_region.get(), region.get())); +} + +} // namespace gfx + diff --git a/platform_font.h b/platform_font.h new file mode 100644 index 000000000000..6b0025ca3d6b --- /dev/null +++ b/platform_font.h @@ -0,0 +1,118 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_PLATFORM_FONT_H_ +#define UI_GFX_PLATFORM_FONT_H_ + +#include + +#include "base/memory/ref_counted.h" +#include "build/build_config.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "third_party/skia/include/core/SkRefCnt.h" +#include "third_party/skia/include/core/SkTypeface.h" +#include "ui/gfx/font.h" +#include "ui/gfx/font_render_params.h" +#include "ui/gfx/gfx_export.h" +#include "ui/gfx/native_widget_types.h" + +namespace gfx { + +class GFX_EXPORT PlatformFont : public base::RefCounted { + public: +// The size of the font returned by CreateDefault() on a "default" platform +// configuration. This allows UI that wants to target a particular size of font +// to obtain that size for the majority of users, while still compensating for a +// user preference for a larger or smaller font. +#if defined(OS_APPLE) + static constexpr int kDefaultBaseFontSize = 13; +#else + static constexpr int kDefaultBaseFontSize = 12; +#endif + + // Creates an appropriate PlatformFont implementation. + static PlatformFont* CreateDefault(); +#if defined(OS_APPLE) + static PlatformFont* CreateFromNativeFont(NativeFont native_font); +#endif + // Creates a PlatformFont implementation with the specified |font_name| + // (encoded in UTF-8) and |font_size| in pixels. + static PlatformFont* CreateFromNameAndSize(const std::string& font_name, + int font_size); + + // Creates a PlatformFont instance from the provided SkTypeface, ideally by + // just wrapping it without triggering a new font match. Implemented for + // PlatformFontSkia which provides true wrapping of the provided SkTypeface. + // The FontRenderParams can be provided or they will be determined by using + // gfx::GetFontRenderParams(...) otherwise. + static PlatformFont* CreateFromSkTypeface( + sk_sp typeface, + int font_size, + const absl::optional& params); + + // Returns a new Font derived from the existing font. + // |size_delta| is the size in pixels to add to the current font. + // The style parameter specifies the new style for the font, and is a + // bitmask of the values: ITALIC and UNDERLINE. + // The weight parameter specifies the desired weight of the font. + virtual Font DeriveFont(int size_delta, + int style, + Font::Weight weight) const = 0; + + // Returns the number of vertical pixels needed to display characters from + // the specified font. This may include some leading, i.e. height may be + // greater than just ascent + descent. Specifically, the Windows and Mac + // implementations include leading and the Linux one does not. This may + // need to be revisited in the future. + virtual int GetHeight() = 0; + + // Returns the font weight. + virtual Font::Weight GetWeight() const = 0; + + // Returns the baseline, or ascent, of the font. + virtual int GetBaseline() = 0; + + // Returns the cap height of the font. + virtual int GetCapHeight() = 0; + + // Returns the expected number of horizontal pixels needed to display the + // specified length of characters. Call GetStringWidth() to retrieve the + // actual number. + virtual int GetExpectedTextWidth(int length) = 0; + + // Returns the style of the font. + virtual int GetStyle() const = 0; + + // Returns the specified font name in UTF-8. + virtual const std::string& GetFontName() const = 0; + + // Returns the actually used font name in UTF-8. + virtual std::string GetActualFontName() const = 0; + + // Returns the font size in pixels. + virtual int GetFontSize() const = 0; + + // Returns an object describing how the font should be rendered. + virtual const FontRenderParams& GetFontRenderParams() = 0; + +#if defined(OS_APPLE) + // Returns the native font handle. + virtual NativeFont GetNativeFont() const = 0; +#endif + + // Returns the underlying Skia typeface. Used in RenderTextHarfBuzz for having + // access to the exact Skia typeface returned by font fallback, as we would + // otherwise lose the handle to the correct platform font instance. + virtual sk_sp GetNativeSkTypeface() const = 0; + + protected: + virtual ~PlatformFont() {} + + private: + friend class base::RefCounted; +}; + +} // namespace gfx + +#endif // UI_GFX_PLATFORM_FONT_H_ diff --git a/platform_font_ios.h b/platform_font_ios.h new file mode 100644 index 000000000000..70af74517531 --- /dev/null +++ b/platform_font_ios.h @@ -0,0 +1,70 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_PLATFORM_FONT_IOS_H_ +#define UI_GFX_PLATFORM_FONT_IOS_H_ + +#include "base/macros.h" +#include "ui/gfx/platform_font.h" + +namespace gfx { + +class PlatformFontIOS : public PlatformFont { + public: + PlatformFontIOS(); + explicit PlatformFontIOS(NativeFont native_font); + PlatformFontIOS(const std::string& font_name, + int font_size); + + PlatformFontIOS(const PlatformFontIOS&) = delete; + PlatformFontIOS& operator=(const PlatformFontIOS&) = delete; + + // Overridden from PlatformFont: + Font DeriveFont(int size_delta, + int style, + Font::Weight weight) const override; + int GetHeight() override; + Font::Weight GetWeight() const override; + int GetBaseline() override; + int GetCapHeight() override; + int GetExpectedTextWidth(int length) override; + int GetStyle() const override; + const std::string& GetFontName() const override; + std::string GetActualFontName() const override; + int GetFontSize() const override; + const FontRenderParams& GetFontRenderParams() override; + NativeFont GetNativeFont() const override; + sk_sp GetNativeSkTypeface() const override; + + private: + PlatformFontIOS(const std::string& font_name, + int font_size, + int style, + Font::Weight weight); + ~PlatformFontIOS() override {} + + // Initialize the object with the specified parameters. + void InitWithNameSizeAndStyle(const std::string& font_name, + int font_size, + int style, + Font::Weight weight); + + // Calculate and cache the font metrics. + void CalculateMetrics(); + + std::string font_name_; + int font_size_; + int style_; + Font::Weight weight_; + + // Cached metrics, generated at construction. + int height_; + int ascent_; + int cap_height_; + int average_width_; +}; + +} // namespace gfx + +#endif // UI_GFX_PLATFORM_FONT_IOS_H_ diff --git a/platform_font_ios.mm b/platform_font_ios.mm new file mode 100644 index 000000000000..5a59235b5d9d --- /dev/null +++ b/platform_font_ios.mm @@ -0,0 +1,154 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/platform_font_ios.h" + +#import + +#include + +#import "base/mac/foundation_util.h" +#include "base/notreached.h" +#include "base/strings/sys_string_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "third_party/skia/include/ports/SkTypeface_mac.h" +#include "ui/gfx/font.h" +#include "ui/gfx/font_render_params.h" +#include "ui/gfx/ios/NSString+CrStringDrawing.h" + +namespace gfx { + +//////////////////////////////////////////////////////////////////////////////// +// PlatformFontIOS, public: + +PlatformFontIOS::PlatformFontIOS() { + font_size_ = [UIFont systemFontSize]; + style_ = Font::NORMAL; + weight_ = Font::Weight::NORMAL; + UIFont* system_font = [UIFont systemFontOfSize:font_size_]; + font_name_ = base::SysNSStringToUTF8([system_font fontName]); + CalculateMetrics(); +} + +PlatformFontIOS::PlatformFontIOS(NativeFont native_font) { + std::string font_name = base::SysNSStringToUTF8([native_font fontName]); + InitWithNameSizeAndStyle(font_name, [native_font pointSize], + Font::NORMAL, Font::Weight::NORMAL); +} + +PlatformFontIOS::PlatformFontIOS(const std::string& font_name, int font_size) { + InitWithNameSizeAndStyle(font_name, font_size, Font::NORMAL, + Font::Weight::NORMAL); +} + +//////////////////////////////////////////////////////////////////////////////// +// PlatformFontIOS, PlatformFont implementation: + +Font PlatformFontIOS::DeriveFont(int size_delta, + int style, + Font::Weight weight) const { + return Font( + new PlatformFontIOS(font_name_, font_size_ + size_delta, style, weight)); +} + +int PlatformFontIOS::GetHeight() { + return height_; +} + +int PlatformFontIOS::GetBaseline() { + return ascent_; +} + +int PlatformFontIOS::GetCapHeight() { + return cap_height_; +} + +int PlatformFontIOS::GetExpectedTextWidth(int length) { + return length * average_width_; +} + +int PlatformFontIOS::GetStyle() const { + return style_; +} + +Font::Weight PlatformFontIOS::GetWeight() const { + return weight_; +} + +const std::string& PlatformFontIOS::GetFontName() const { + return font_name_; +} + +std::string PlatformFontIOS::GetActualFontName() const { + return base::SysNSStringToUTF8([GetNativeFont() familyName]); +} + +int PlatformFontIOS::GetFontSize() const { + return font_size_; +} + +const FontRenderParams& PlatformFontIOS::GetFontRenderParams() { + NOTIMPLEMENTED(); + static FontRenderParams params; + return params; +} + +NativeFont PlatformFontIOS::GetNativeFont() const { + return [UIFont fontWithName:base::SysUTF8ToNSString(font_name_) + size:font_size_]; +} + +sk_sp PlatformFontIOS::GetNativeSkTypeface() const { + return SkMakeTypefaceFromCTFont(base::mac::NSToCFCast(GetNativeFont())); +} + +//////////////////////////////////////////////////////////////////////////////// +// PlatformFontIOS, private: + +PlatformFontIOS::PlatformFontIOS(const std::string& font_name, + int font_size, + int style, + Font::Weight weight) { + InitWithNameSizeAndStyle(font_name, font_size, style, weight); +} + +void PlatformFontIOS::InitWithNameSizeAndStyle(const std::string& font_name, + int font_size, + int style, + Font::Weight weight) { + font_name_ = font_name; + font_size_ = font_size; + style_ = style; + weight_ = weight; + CalculateMetrics(); +} + +void PlatformFontIOS::CalculateMetrics() { + UIFont* font = GetNativeFont(); + height_ = font.lineHeight; + ascent_ = font.ascender; + cap_height_ = font.capHeight; + average_width_ = [@"x" cr_sizeWithFont:font].width; +} + +//////////////////////////////////////////////////////////////////////////////// +// PlatformFont, public: + +// static +PlatformFont* PlatformFont::CreateDefault() { + return new PlatformFontIOS; +} + +// static +PlatformFont* PlatformFont::CreateFromNativeFont(NativeFont native_font) { + return new PlatformFontIOS(native_font); +} + +// static +PlatformFont* PlatformFont::CreateFromNameAndSize(const std::string& font_name, + int font_size) { + return new PlatformFontIOS(font_name, font_size); +} + +} // namespace gfx diff --git a/platform_font_mac.h b/platform_font_mac.h new file mode 100644 index 000000000000..b4e09f6115e6 --- /dev/null +++ b/platform_font_mac.h @@ -0,0 +1,121 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_PLATFORM_FONT_MAC_H_ +#define UI_GFX_PLATFORM_FONT_MAC_H_ + +#include "base/compiler_specific.h" +#include "base/mac/scoped_nsobject.h" +#include "base/macros.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/gfx/font_render_params.h" +#include "ui/gfx/platform_font.h" + +namespace gfx { + +class GFX_EXPORT PlatformFontMac : public PlatformFont { + public: + // An enum indicating a type of system-specified font. + // - kGeneral: +[NSFont systemFontOfSize:(weight:)] + // - kMenu: +[NSFont menuFontOfSize:] + // - kToolTip: +[NSFont toolTipsFontOfSize:] + enum class SystemFontType { kGeneral, kMenu, kToolTip }; + + // Constructs a PlatformFontMac for a system-specified font of + // |system_font_type| type. For a non-system-specified font, use any other + // constructor. + explicit PlatformFontMac(SystemFontType system_font_type); + + // Constructs a PlatformFontMac for containing the NSFont* |native_font|. Do + // not call this for a system-specified font; use the |SystemFontType| + // constructor for that. |native_font| must not be null. + explicit PlatformFontMac(NativeFont native_font); + + // Constructs a PlatformFontMac representing the font with name |font_name| + // and the size |font_size|. Do not call this for a system-specified font; use + // the |SystemFontType| constructor for that. + PlatformFontMac(const std::string& font_name, + int font_size); + + // Constructs a PlatformFontMac representing the font specified by |typeface| + // and the size |font_size_pixels|. Do not call this for a system-specified + // font; use the |SystemFontType| constructor for that. + PlatformFontMac(sk_sp typeface, + int font_size_pixels, + const absl::optional& params); + + PlatformFontMac(const PlatformFontMac&) = delete; + PlatformFontMac& operator=(const PlatformFontMac&) = delete; + + // Overridden from PlatformFont: + Font DeriveFont(int size_delta, + int style, + Font::Weight weight) const override; + int GetHeight() override; + Font::Weight GetWeight() const override; + int GetBaseline() override; + int GetCapHeight() override; + int GetExpectedTextWidth(int length) override; + int GetStyle() const override; + const std::string& GetFontName() const override; + std::string GetActualFontName() const override; + int GetFontSize() const override; + const FontRenderParams& GetFontRenderParams() override; + NativeFont GetNativeFont() const override; + sk_sp GetNativeSkTypeface() const override; + + // A utility function to get the weight of an NSFont. Used by the unit test. + static Font::Weight GetFontWeightFromNSFontForTesting(NSFont* font); + + private: + struct FontSpec { + std::string name; // Corresponds to -[NSFont fontFamily]. + int size; + int style; + Font::Weight weight; + }; + + PlatformFontMac(NativeFont font, + absl::optional system_font_type); + + PlatformFontMac(NativeFont font, + absl::optional system_font_type, + FontSpec spec); + + ~PlatformFontMac() override; + + // Calculates and caches the font metrics and initializes |render_params_|. + void CalculateMetricsAndInitRenderParams(); + + // Returns an autoreleased NSFont created with the passed-in specifications. + NSFont* NSFontWithSpec(FontSpec font_spec) const; + + // The NSFont instance for this object. If this object was constructed from an + // NSFont instance, this holds that NSFont instance. Otherwise this NSFont + // instance is constructed from the name, size, and style. If there is no + // active font that matched those criteria a default font is used. + base::scoped_nsobject native_font_; + + // If the font is a system font, and if so, what kind. + const absl::optional system_font_type_; + + // The name/size/style/weight quartet that specify the font. Initialized in + // the constructors. + const FontSpec font_spec_; + + // Cached metrics, generated in CalculateMetrics(). + int height_; + int ascent_; + int cap_height_; + + // Cached average width, generated in GetExpectedTextWidth(). + float average_width_ = 0.0; + + // Details about how the font should be rendered. + FontRenderParams render_params_; +}; + +} // namespace gfx + +#endif // UI_GFX_PLATFORM_FONT_MAC_H_ diff --git a/platform_font_mac.mm b/platform_font_mac.mm new file mode 100644 index 000000000000..99b4dffbd41d --- /dev/null +++ b/platform_font_mac.mm @@ -0,0 +1,553 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/platform_font_mac.h" + +#include +#include + +#include + +#import "base/mac/foundation_util.h" +#include "base/mac/scoped_cftyperef.h" +#import "base/mac/scoped_nsobject.h" +#include "base/no_destructor.h" +#include "base/numerics/safe_conversions.h" +#include "base/strings/sys_string_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "third_party/skia/include/ports/SkTypeface_mac.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/font.h" +#include "ui/gfx/font_render_params.h" + +namespace gfx { + +using Weight = Font::Weight; + +extern "C" { +bool CTFontDescriptorIsSystemUIFont(CTFontDescriptorRef); +} + +namespace { + +// Returns the font style for |font|. Disregards Font::UNDERLINE, since NSFont +// does not support it as a trait. +int GetFontStyleFromNSFont(NSFont* font) { + int font_style = Font::NORMAL; + NSFontSymbolicTraits traits = [[font fontDescriptor] symbolicTraits]; + if (traits & NSFontItalicTrait) + font_style |= Font::ITALIC; + return font_style; +} + +// Returns the Font::Weight for |font|. +Weight GetFontWeightFromNSFont(NSFont* font) { + DCHECK(font); + + // Map CoreText weights in a manner similar to ct_weight_to_fontstyle() from + // SkFontHost_mac.cpp, but adjusted for the weights actually used by the + // system fonts. See PlatformFontMacTest.FontWeightAPIConsistency for details. + // macOS uses specific float values in its constants, but individual fonts can + // and do specify arbitrary values in the -1.0 to 1.0 range. Therefore, to + // accomodate that, and to avoid float comparison issues, use ranges. + constexpr struct { + // A range of CoreText weights. + CGFloat weight_lower; + CGFloat weight_upper; + Weight gfx_weight; + } weight_map[] = { + // NSFontWeight constants introduced in 10.11: + // NSFontWeightUltraLight: -0.80 + // NSFontWeightThin: -0.60 + // NSFontWeightLight: -0.40 + // NSFontWeightRegular: 0.0 + // NSFontWeightMedium: 0.23 + // NSFontWeightSemibold: 0.30 + // NSFontWeightBold: 0.40 + // NSFontWeightHeavy: 0.56 + // NSFontWeightBlack: 0.62 + // + // Actual system font weights: + // 10.10: + // .HelveticaNeueDeskInterface-UltraLightP2: -0.80 + // .HelveticaNeueDeskInterface-Thin: -0.50 + // .HelveticaNeueDeskInterface-Light: -0.425 + // .HelveticaNeueDeskInterface-Regular: 0.0 + // .HelveticaNeueDeskInterface-MediumP4: 0.23 + // .HelveticaNeueDeskInterface-Bold (if requested as semibold): 0.24 + // .HelveticaNeueDeskInterface-Bold (if requested as bold): 0.4 + // .HelveticaNeueDeskInterface-Heavy (if requested as heavy): 0.576 + // .HelveticaNeueDeskInterface-Heavy (if requested as black): 0.662 + // 10.11-: + // .AppleSystemUIFontUltraLight: -0.80 + // .AppleSystemUIFontThin: -0.60 + // .AppleSystemUIFontLight: -0.40 + // .AppleSystemUIFont: 0.0 + // .AppleSystemUIFontMedium: 0.23 + // .AppleSystemUIFontDemi: 0.30 + // .AppleSystemUIFontBold (10.11): 0.40 + // .AppleSystemUIFontEmphasized (10.12-): 0.40 + // .AppleSystemUIFontHeavy: 0.56 + // .AppleSystemUIFontBlack: 0.62 + {-1.0, -0.70, Weight::THIN}, // NSFontWeightUltraLight + {-0.70, -0.45, Weight::EXTRA_LIGHT}, // NSFontWeightThin + {-0.45, -0.10, Weight::LIGHT}, // NSFontWeightLight + {-0.10, 0.10, Weight::NORMAL}, // NSFontWeightRegular + {0.10, 0.27, Weight::MEDIUM}, // NSFontWeightMedium + {0.27, 0.35, Weight::SEMIBOLD}, // NSFontWeightSemibold + {0.35, 0.50, Weight::BOLD}, // NSFontWeightBold + {0.50, 0.60, Weight::EXTRA_BOLD}, // NSFontWeightHeavy + {0.60, 1.0, Weight::BLACK}, // NSFontWeightBlack + }; + + base::ScopedCFTypeRef traits( + CTFontCopyTraits(base::mac::NSToCFCast(font))); + DCHECK(traits); + CFNumberRef cf_weight = base::mac::GetValueFromDictionary( + traits, kCTFontWeightTrait); + // A missing weight attribute just means 0 -> NORMAL. + if (!cf_weight) + return Weight::NORMAL; + + // The value of kCTFontWeightTrait empirically is a kCFNumberFloat64Type + // (double) on all tested versions of macOS. However, that doesn't really + // matter as only the first two decimal digits need to be tested. Do not check + // for the success of CFNumberGetValue() as it returns false for any loss of + // value and all that is needed here is two digits of accuracy. + CGFloat weight; + CFNumberGetValue(cf_weight, kCFNumberCGFloatType, &weight); + for (const auto& item : weight_map) { + if (item.weight_lower <= weight && weight <= item.weight_upper) + return item.gfx_weight; + } + return Weight::INVALID; +} + +// Converts a Font::Weight value to the corresponding NSFontWeight value. +NSFontWeight ToNSFontWeight(Weight weight) { + switch (weight) { + case Weight::THIN: + return NSFontWeightUltraLight; + case Weight::EXTRA_LIGHT: + return NSFontWeightThin; + case Weight::LIGHT: + return NSFontWeightLight; + case Weight::INVALID: + case Weight::NORMAL: + return NSFontWeightRegular; + case Weight::MEDIUM: + return NSFontWeightMedium; + case Weight::SEMIBOLD: + return NSFontWeightSemibold; + case Weight::BOLD: + return NSFontWeightBold; + case Weight::EXTRA_BOLD: + return NSFontWeightHeavy; + case Weight::BLACK: + return NSFontWeightBlack; + } +} + +// Chromium uses the ISO-style, 9-value ladder of font weights (THIN-BLACK). The +// new font API in macOS also uses these weights, though they are constants +// defined in terms of CGFloat with values from -1.0 to 1.0. +// +// However, the old API used by the NSFontManager uses integer values on a +// "scale of 0 to 15". These values are used in: +// +// -[NSFontManager availableMembersOfFontFamily:] +// -[NSFontManager convertWeight:ofFont:] +// -[NSFontManager fontWithFamily:traits:weight:size:] +// -[NSFontManager weightOfFont:] +// +// Apple provides a chart of how the ISO values correspond: +// https://developer.apple.com/reference/appkit/nsfontmanager/1462321-convertweight +// However, it's more complicated than that. A survey of fonts yields the +// correspondence in this function, but the outliers imply that the ISO-style +// weight is more along the lines of "weight role within the font family" vs +// this number which is more like "how weighty is this font compared to all +// other fonts". +// +// These numbers can't really be forced to line up; different fonts disagree on +// how to map them. This function mostly follows the documented chart as +// inspired by actual fonts, and should be good enough. +NSInteger ToNSFontManagerWeight(Weight weight) { + switch (weight) { + case Weight::THIN: + return 2; + case Weight::EXTRA_LIGHT: + return 3; + case Weight::LIGHT: + return 4; + case Weight::INVALID: + case Weight::NORMAL: + return 5; + case Weight::MEDIUM: + return 6; + case Weight::SEMIBOLD: + return 8; + case Weight::BOLD: + return 9; + case Weight::EXTRA_BOLD: + return 10; + case Weight::BLACK: + return 11; + } +} + +std::string GetFamilyNameFromTypeface(sk_sp typeface) { + SkString family; + typeface->getFamilyName(&family); + return family.c_str(); +} + +NSFont* SystemFontForConstructorOfType(PlatformFontMac::SystemFontType type) { + switch (type) { + case PlatformFontMac::SystemFontType::kGeneral: + return [NSFont systemFontOfSize:[NSFont systemFontSize]]; + case PlatformFontMac::SystemFontType::kMenu: + return [NSFont menuFontOfSize:0]; + case PlatformFontMac::SystemFontType::kToolTip: + return [NSFont toolTipsFontOfSize:0]; + } +} + +absl::optional +SystemFontTypeFromUndocumentedCTFontRefInternals(CTFontRef font) { + // The macOS APIs can't reliably derive one font from another. That's why for + // non-system fonts PlatformFontMac::DeriveFont() uses the family name of the + // font to find look up new fonts from scratch, and why, for system fonts, it + // uses the system font APIs to generate new system fonts. + // + // Skia's font handling assumes that given a font object, new fonts can be + // derived from it. That's absolutely not true on the Mac. However, this needs + // to be fixed, and a rewrite of how Skia handles fonts is not on the table. + // + // Therefore this sad hack. If Skia provides an SkTypeface, dig into the + // undocumented bowels of CoreText and magically determine if the font is a + // system font. This allows PlatformFontMac to correctly derive variants of + // the provided font. + // + // TODO(avi, etienneb): Figure out this font stuff. + base::ScopedCFTypeRef descriptor( + CTFontCopyFontDescriptor(font)); + if (CTFontDescriptorIsSystemUIFont(descriptor.get())) { + // Assume it's the standard system font. The fact that this much is known is + // enough. + return PlatformFontMac::SystemFontType::kGeneral; + } else { + return absl::nullopt; + } +} + +#if DCHECK_IS_ON() + +const std::set& SystemFontNames() { + static const base::NoDestructor> names([] { + std::set names; + names.insert(base::SysNSStringToUTF8( + [NSFont systemFontOfSize:[NSFont systemFontSize]].familyName)); + names.insert(base::SysNSStringToUTF8([NSFont menuFontOfSize:0].familyName)); + names.insert( + base::SysNSStringToUTF8([NSFont toolTipsFontOfSize:0].familyName)); + return names; + }()); + + return *names; +} + +#endif // DCHECK_IS_ON() + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// +// PlatformFontMac, public: + +PlatformFontMac::PlatformFontMac(SystemFontType system_font_type) + : PlatformFontMac(SystemFontForConstructorOfType(system_font_type), + system_font_type) {} + +PlatformFontMac::PlatformFontMac(NativeFont native_font) + : PlatformFontMac(native_font, absl::nullopt) { + DCHECK(native_font); // nil should not be passed to this constructor. +} + +PlatformFontMac::PlatformFontMac(const std::string& font_name, int font_size) + : PlatformFontMac( + NSFontWithSpec({font_name, font_size, Font::NORMAL, Weight::NORMAL}), + absl::nullopt, + {font_name, font_size, Font::NORMAL, Weight::NORMAL}) {} + +PlatformFontMac::PlatformFontMac(sk_sp typeface, + int font_size_pixels, + const absl::optional& params) + : PlatformFontMac( + base::mac::CFToNSCast(SkTypeface_GetCTFontRef(typeface.get())), + SystemFontTypeFromUndocumentedCTFontRefInternals( + SkTypeface_GetCTFontRef(typeface.get())), + {GetFamilyNameFromTypeface(typeface), font_size_pixels, + (typeface->isItalic() ? Font::ITALIC : Font::NORMAL), + FontWeightFromInt(typeface->fontStyle().weight())}) {} + +//////////////////////////////////////////////////////////////////////////////// +// PlatformFontMac, PlatformFont implementation: + +Font PlatformFontMac::DeriveFont(int size_delta, + int style, + Weight weight) const { + // What doesn't work? + // + // For all fonts, -[NSFontManager convertWeight:ofFont:] will reliably + // misbehave, skipping over particular weights of fonts, refusing to go + // lighter than regular unless you go heavier first, and in earlier versions + // of the system would accidentally introduce italic fonts when changing + // weights from a non-italic instance. + // + // For system fonts, -[NSFontManager convertFont:to(Not)HaveTrait:], if used + // to change weight, will sometimes switch to a compatibility system font that + // does not have all the weights available. + // + // For system fonts, the most reliable call to use is +[NSFont + // systemFontOfSize:weight:]. This uses the new-style NSFontWeight which maps + // perfectly to the ISO weights that Chromium uses. For non-system fonts, + // -[NSFontManager fontWithFamily:traits:weight:size:] is the only reasonable + // way to query fonts with more granularity than bold/non-bold short of + // walking the font family and querying their kCTFontWeightTrait values. Font + // descriptors hold promise but querying using them often fails to find fonts + // that match; hopefully their matching abilities will improve in future + // versions of the macOS. + + if (system_font_type_ == SystemFontType::kGeneral) { + NSFont* derived = [NSFont systemFontOfSize:font_spec_.size + size_delta + weight:ToNSFontWeight(weight)]; + NSFontTraitMask italic_trait_mask = + (style & Font::ITALIC) ? NSItalicFontMask : NSUnitalicFontMask; + derived = [[NSFontManager sharedFontManager] convertFont:derived + toHaveTrait:italic_trait_mask]; + + return Font(new PlatformFontMac( + derived, SystemFontType::kGeneral, + {font_spec_.name, font_spec_.size + size_delta, style, weight})); + } else if (system_font_type_ == SystemFontType::kMenu) { + NSFont* derived = [NSFont menuFontOfSize:font_spec_.size + size_delta]; + return Font(new PlatformFontMac( + derived, SystemFontType::kMenu, + {font_spec_.name, font_spec_.size + size_delta, style, weight})); + } else if (system_font_type_ == SystemFontType::kToolTip) { + NSFont* derived = [NSFont toolTipsFontOfSize:font_spec_.size + size_delta]; + return Font(new PlatformFontMac( + derived, SystemFontType::kToolTip, + {font_spec_.name, font_spec_.size + size_delta, style, weight})); + } else { + NSFont* derived = NSFontWithSpec( + {font_spec_.name, font_spec_.size + size_delta, style, weight}); + return Font(new PlatformFontMac( + derived, absl::nullopt, + {font_spec_.name, font_spec_.size + size_delta, style, weight})); + } +} + +int PlatformFontMac::GetHeight() { + return height_; +} + +int PlatformFontMac::GetBaseline() { + return ascent_; +} + +int PlatformFontMac::GetCapHeight() { + return cap_height_; +} + +int PlatformFontMac::GetExpectedTextWidth(int length) { + if (!average_width_) { + // -[NSFont boundingRectForGlyph:] seems to always return the largest + // bounding rect that could be needed, which produces very wide expected + // widths for strings. Instead, compute the actual width of a string + // containing all the lowercase characters to find a reasonable guess at the + // average. + base::scoped_nsobject attr_string( + [[NSAttributedString alloc] + initWithString:@"abcdefghijklmnopqrstuvwxyz" + attributes:@{NSFontAttributeName : native_font_.get()}]); + average_width_ = [attr_string size].width / [attr_string length]; + DCHECK_NE(0, average_width_); + } + return ceil(length * average_width_); +} + +int PlatformFontMac::GetStyle() const { + return font_spec_.style; +} + +Weight PlatformFontMac::GetWeight() const { + return font_spec_.weight; +} + +const std::string& PlatformFontMac::GetFontName() const { + return font_spec_.name; +} + +std::string PlatformFontMac::GetActualFontName() const { + return base::SysNSStringToUTF8([native_font_ familyName]); +} + +int PlatformFontMac::GetFontSize() const { + return font_spec_.size; +} + +const FontRenderParams& PlatformFontMac::GetFontRenderParams() { + return render_params_; +} + +NativeFont PlatformFontMac::GetNativeFont() const { + return [[native_font_.get() retain] autorelease]; +} + +sk_sp PlatformFontMac::GetNativeSkTypeface() const { + return SkMakeTypefaceFromCTFont(base::mac::NSToCFCast(GetNativeFont())); +} + +// static +Weight PlatformFontMac::GetFontWeightFromNSFontForTesting(NSFont* font) { + return GetFontWeightFromNSFont(font); +} + +//////////////////////////////////////////////////////////////////////////////// +// PlatformFontMac, private: + +PlatformFontMac::PlatformFontMac( + NativeFont font, + absl::optional system_font_type) + : PlatformFontMac( + font, + system_font_type, + {base::SysNSStringToUTF8([font familyName]), + base::ClampRound([font pointSize]), GetFontStyleFromNSFont(font), + GetFontWeightFromNSFont(font)}) {} + +PlatformFontMac::PlatformFontMac( + NativeFont font, + absl::optional system_font_type, + FontSpec spec) + : native_font_([font retain]), + system_font_type_(system_font_type), + font_spec_(spec) { +#if DCHECK_IS_ON() + DCHECK(system_font_type.has_value() || + SystemFontNames().count(spec.name) == 0) + << "Do not pass a system font (" << spec.name << ") to PlatformFontMac; " + << "use the SystemFontType constructor. Extend the SystemFontType enum " + << "if necessary."; +#endif // DCHECK_IS_ON() + CalculateMetricsAndInitRenderParams(); +} + +PlatformFontMac::~PlatformFontMac() { +} + +void PlatformFontMac::CalculateMetricsAndInitRenderParams() { + NSFont* font = native_font_.get(); + DCHECK(font); + ascent_ = ceil([font ascender]); + cap_height_ = ceil([font capHeight]); + + // PlatformFontMac once used -[NSLayoutManager defaultLineHeightForFont:] to + // initialize |height_|. However, it has a silly rounding bug. Essentially, it + // gives round(ascent) + round(descent). E.g. Helvetica Neue at size 16 gives + // ascent=15.4634, descent=3.38208 -> 15 + 3 = 18. When the height should be + // at least 19. According to the OpenType specification, these values should + // simply be added, so do that. Note this uses the already-rounded |ascent_| + // to ensure GetBaseline() + descender fits within GetHeight() during layout. + height_ = ceil(ascent_ + std::abs([font descender]) + [font leading]); + + FontRenderParamsQuery query; + query.families.push_back(font_spec_.name); + query.pixel_size = font_spec_.size; + query.style = font_spec_.style; + query.weight = font_spec_.weight; + render_params_ = gfx::GetFontRenderParams(query, nullptr); +} + +NSFont* PlatformFontMac::NSFontWithSpec(FontSpec font_spec) const { + // One might think that a font descriptor with the NSFontWeightTrait/ + // kCTFontWeightTrait trait could be used to look up a font with a specific + // weight. That doesn't work, though. You can ask a font for its weight, but + // you can't use weight to query for the font. + // + // The way that does work is to use the old-style integer weight API. + + NSFontManager* font_manager = [NSFontManager sharedFontManager]; + + NSFontTraitMask traits = 0; + if (font_spec.style & Font::ITALIC) + traits |= NSItalicFontMask; + // The Mac doesn't support underline as a font trait, so just drop it. + // (Underlines must be added as an attribute on an NSAttributedString.) Do not + // add NSBoldFontMask here; if it is added then the weight parameter below + // will be ignored. + + NSFont* font = + [font_manager fontWithFamily:base::SysUTF8ToNSString(font_spec.name) + traits:traits + weight:ToNSFontManagerWeight(font_spec.weight) + size:font_spec.size]; + if (font) + return font; + + // Make one fallback attempt by looking up via font name rather than font + // family name. With this API, the available granularity of font weight is + // bold/not-bold, but that's what's available. + NSFontSymbolicTraits trait_bits = 0; + if (font_spec.weight >= Weight::BOLD) + trait_bits |= NSFontBoldTrait; + if (font_spec.style & Font::ITALIC) + trait_bits |= NSFontItalicTrait; + + NSDictionary* attrs = @{ + NSFontNameAttribute : base::SysUTF8ToNSString(font_spec.name), + NSFontTraitsAttribute : @{NSFontSymbolicTrait : @(trait_bits)}, + }; + NSFontDescriptor* descriptor = + [NSFontDescriptor fontDescriptorWithFontAttributes:attrs]; + + font = [NSFont fontWithDescriptor:descriptor size:font_spec.size]; + if (font) + return font; + + // If that doesn't find a font, whip up a system font to stand in for the + // specified font. + font = [NSFont systemFontOfSize:font_spec.size + weight:ToNSFontWeight(font_spec.weight)]; + return [font_manager convertFont:font toHaveTrait:traits]; +} + +//////////////////////////////////////////////////////////////////////////////// +// PlatformFont, public: + +// static +PlatformFont* PlatformFont::CreateDefault() { + return new PlatformFontMac(PlatformFontMac::SystemFontType::kGeneral); +} + +// static +PlatformFont* PlatformFont::CreateFromNativeFont(NativeFont native_font) { + return new PlatformFontMac(native_font); +} + +// static +PlatformFont* PlatformFont::CreateFromNameAndSize(const std::string& font_name, + int font_size) { + return new PlatformFontMac(font_name, font_size); +} + +// static +PlatformFont* PlatformFont::CreateFromSkTypeface( + sk_sp typeface, + int font_size_pixels, + const absl::optional& params) { + return new PlatformFontMac(typeface, font_size_pixels, params); +} + +} // namespace gfx diff --git a/platform_font_mac_unittest.mm b/platform_font_mac_unittest.mm new file mode 100644 index 000000000000..46b2e6d9df34 --- /dev/null +++ b/platform_font_mac_unittest.mm @@ -0,0 +1,243 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/platform_font_mac.h" + +#include +#include + +#include "base/cxx17_backports.h" +#import "base/mac/foundation_util.h" +#include "base/mac/scoped_cftyperef.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/font.h" + +namespace gfx { + +using Weight = Font::Weight; + +TEST(PlatformFontMacTest, DeriveFont) { + // |weight_tri| is either -1, 0, or 1 meaning "light", "normal", or "bold". + auto CheckExpected = [](const Font& font, int weight_tri, bool isItalic) { + base::ScopedCFTypeRef traits( + CTFontCopyTraits(base::mac::NSToCFCast(font.GetNativeFont()))); + DCHECK(traits); + + CFNumberRef cf_slant = base::mac::GetValueFromDictionary( + traits, kCTFontSlantTrait); + CGFloat slant; + CFNumberGetValue(cf_slant, kCFNumberCGFloatType, &slant); + if (isItalic) + EXPECT_GT(slant, 0); + else + EXPECT_EQ(slant, 0); + + CFNumberRef cf_weight = base::mac::GetValueFromDictionary( + traits, kCTFontWeightTrait); + CGFloat weight; + CFNumberGetValue(cf_weight, kCFNumberCGFloatType, &weight); + if (weight_tri < 0) + EXPECT_LT(weight, 0); + else if (weight_tri == 0) + EXPECT_EQ(weight, 0); + else + EXPECT_GT(weight, 0); + }; + + // Use a base font that support all traits. + Font base_font("Helvetica", 13); + { + SCOPED_TRACE("plain font"); + CheckExpected(base_font, 0, false); + } + + // Italic + Font italic_font(base_font.Derive(0, Font::ITALIC, Weight::NORMAL)); + { + SCOPED_TRACE("italic font"); + CheckExpected(italic_font, 0, true); + } + + // Bold + Font bold_font(base_font.Derive(0, Font::NORMAL, Weight::BOLD)); + { + SCOPED_TRACE("bold font"); + CheckExpected(bold_font, 1, false); + } + + // Bold italic + Font bold_italic_font(base_font.Derive(0, Font::ITALIC, Weight::BOLD)); + { + SCOPED_TRACE("bold italic font"); + CheckExpected(bold_italic_font, 1, true); + } + + // Non-existent thin will return the closest weight, light + Font thin_font(base_font.Derive(0, Font::NORMAL, Weight::THIN)); + { + SCOPED_TRACE("thin font"); + CheckExpected(thin_font, -1, false); + } + + // Non-existent black will return the closest weight, bold + Font black_font(base_font.Derive(0, Font::NORMAL, Weight::BLACK)); + { + SCOPED_TRACE("black font"); + CheckExpected(black_font, 1, false); + } +} + +TEST(PlatformFontMacTest, DeriveFontUnderline) { + // Create a default font. + Font base_font; + + // Make the font underlined. + Font derived_font(base_font.Derive(0, base_font.GetStyle() | Font::UNDERLINE, + base_font.GetWeight())); + + // Validate the derived font properties against its native font instance. + NSFontTraitMask traits = [[NSFontManager sharedFontManager] + traitsOfFont:derived_font.GetNativeFont()]; + Weight actual_weight = + (traits & NSFontBoldTrait) ? Weight::BOLD : Weight::NORMAL; + + int actual_style = Font::UNDERLINE; + if (traits & NSFontItalicTrait) + actual_style |= Font::ITALIC; + + EXPECT_TRUE(derived_font.GetStyle() & Font::UNDERLINE); + EXPECT_EQ(derived_font.GetStyle(), actual_style); + EXPECT_EQ(derived_font.GetWeight(), actual_weight); +} + +// Tests internal methods for extracting Font properties from the +// underlying CTFont representation. +TEST(PlatformFontMacTest, ConstructFromNativeFont) { + Font light_font([NSFont fontWithName:@"Helvetica-Light" size:12]); + EXPECT_EQ(12, light_font.GetFontSize()); + EXPECT_EQ("Helvetica", light_font.GetFontName()); + EXPECT_EQ(Font::NORMAL, light_font.GetStyle()); + EXPECT_EQ(Weight::LIGHT, light_font.GetWeight()); + + Font light_italic_font([NSFont fontWithName:@"Helvetica-LightOblique" + size:14]); + EXPECT_EQ(14, light_italic_font.GetFontSize()); + EXPECT_EQ("Helvetica", light_italic_font.GetFontName()); + EXPECT_EQ(Font::ITALIC, light_italic_font.GetStyle()); + EXPECT_EQ(Weight::LIGHT, light_italic_font.GetWeight()); + + Font normal_font([NSFont fontWithName:@"Helvetica" size:12]); + EXPECT_EQ(12, normal_font.GetFontSize()); + EXPECT_EQ("Helvetica", normal_font.GetFontName()); + EXPECT_EQ(Font::NORMAL, normal_font.GetStyle()); + EXPECT_EQ(Weight::NORMAL, normal_font.GetWeight()); + + Font italic_font([NSFont fontWithName:@"Helvetica-Oblique" size:14]); + EXPECT_EQ(14, italic_font.GetFontSize()); + EXPECT_EQ("Helvetica", italic_font.GetFontName()); + EXPECT_EQ(Font::ITALIC, italic_font.GetStyle()); + EXPECT_EQ(Weight::NORMAL, italic_font.GetWeight()); + + Font bold_font([NSFont fontWithName:@"Helvetica-Bold" size:12]); + EXPECT_EQ(12, bold_font.GetFontSize()); + EXPECT_EQ("Helvetica", bold_font.GetFontName()); + EXPECT_EQ(Font::NORMAL, bold_font.GetStyle()); + EXPECT_EQ(Weight::BOLD, bold_font.GetWeight()); + + Font bold_italic_font([NSFont fontWithName:@"Helvetica-BoldOblique" size:14]); + EXPECT_EQ(14, bold_italic_font.GetFontSize()); + EXPECT_EQ("Helvetica", bold_italic_font.GetFontName()); + EXPECT_EQ(Font::ITALIC, bold_italic_font.GetStyle()); + EXPECT_EQ(Weight::BOLD, bold_italic_font.GetWeight()); +} + +// Test font derivation for fine-grained font weights. +TEST(PlatformFontMacTest, DerivedFineGrainedFonts) { + // The resulting, actual font weight after deriving |weight| from |base|. + auto DerivedIntWeight = [](Weight weight) { + Font base; // The default system font. + Font derived(base.Derive(0, 0, weight)); + // PlatformFont should always pass the requested weight, not what the OS + // could provide. This just checks a constructor argument, so not very + // interesting. + EXPECT_EQ(static_cast(weight), static_cast(derived.GetWeight())); + + return static_cast(PlatformFontMac::GetFontWeightFromNSFontForTesting( + derived.GetNativeFont())); + }; + + EXPECT_EQ(static_cast(Weight::THIN), DerivedIntWeight(Weight::THIN)); + EXPECT_EQ(static_cast(Weight::EXTRA_LIGHT), + DerivedIntWeight(Weight::EXTRA_LIGHT)); + EXPECT_EQ(static_cast(Weight::LIGHT), DerivedIntWeight(Weight::LIGHT)); + EXPECT_EQ(static_cast(Weight::NORMAL), DerivedIntWeight(Weight::NORMAL)); + EXPECT_EQ(static_cast(Weight::MEDIUM), DerivedIntWeight(Weight::MEDIUM)); + EXPECT_EQ(static_cast(Weight::SEMIBOLD), + DerivedIntWeight(Weight::SEMIBOLD)); + EXPECT_EQ(static_cast(Weight::BOLD), DerivedIntWeight(Weight::BOLD)); + EXPECT_EQ(static_cast(Weight::EXTRA_BOLD), + DerivedIntWeight(Weight::EXTRA_BOLD)); + EXPECT_EQ(static_cast(Weight::BLACK), DerivedIntWeight(Weight::BLACK)); +} + +// Ensures that the Font's reported height is consistent with the native font's +// ascender and descender metrics. +TEST(PlatformFontMacTest, ValidateFontHeight) { + // Use the default ResourceBundle system font. E.g. Helvetica Neue in 10.10, + // Lucida Grande before that, and San Francisco after. + Font default_font; + Font::FontStyle styles[] = {Font::NORMAL, Font::ITALIC, Font::UNDERLINE}; + + for (size_t i = 0; i < base::size(styles); ++i) { + SCOPED_TRACE(testing::Message() << "Font::FontStyle: " << styles[i]); + // Include the range of sizes used by ResourceBundle::FontStyle (-1 to +8). + for (int delta = -1; delta <= 8; ++delta) { + Font font = default_font.Derive(delta, styles[i], Weight::NORMAL); + SCOPED_TRACE(testing::Message() << "FontSize(): " << font.GetFontSize()); + NSFont* native_font = font.GetNativeFont(); + + // Font height (an integer) should be the sum of these. + CGFloat ascender = [native_font ascender]; + CGFloat descender = [native_font descender]; + CGFloat leading = [native_font leading]; + + // NSFont always gives a negative value for descender. Others positive. + EXPECT_GE(0, descender); + EXPECT_LE(0, ascender); + EXPECT_LE(0, leading); + + int sum = ceil(ascender - descender + leading); + + // Text layout is performed using an integral baseline offset derived from + // the ascender. The height needs to be enough to fit the full descender + // (plus baseline). So the height depends on the rounding of the ascender, + // and can be as much as 1 greater than the simple sum of floats. + EXPECT_LE(sum, font.GetHeight()); + EXPECT_GE(sum + 1, font.GetHeight()); + + // Recreate the rounding performed for GetBaseLine(). + EXPECT_EQ(ceil(ceil(ascender) - descender + leading), font.GetHeight()); + } + } +} + +// Test to ensure we cater for the AppKit quirk that can make the font italic +// when asking for a fine-grained weight. See http://crbug.com/742261. Note that +// Appkit's bug was detected on macOS 10.10 which uses Helvetica Neue as the +// system font. +TEST(PlatformFontMacTest, DerivedSemiboldFontIsNotItalic) { + Font base_font; + { + NSFontTraitMask traits = [[NSFontManager sharedFontManager] + traitsOfFont:base_font.GetNativeFont()]; + ASSERT_FALSE(traits & NSItalicFontMask); + } + + Font semibold_font(base_font.Derive(0, Font::NORMAL, Weight::SEMIBOLD)); + NSFontTraitMask traits = [[NSFontManager sharedFontManager] + traitsOfFont:semibold_font.GetNativeFont()]; + EXPECT_FALSE(traits & NSItalicFontMask); +} + +} // namespace gfx diff --git a/platform_font_skia.cc b/platform_font_skia.cc new file mode 100644 index 000000000000..fe80a2c138f2 --- /dev/null +++ b/platform_font_skia.cc @@ -0,0 +1,469 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/platform_font_skia.h" + +#include +#include + +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/strings/string_piece.h" +#include "base/strings/string_split.h" +#include "base/strings/utf_string_conversions.h" +#include "base/trace_event/trace_event.h" +#include "build/build_config.h" +#include "build/chromeos_buildflags.h" +#include "third_party/skia/include/core/SkFont.h" +#include "third_party/skia/include/core/SkFontMetrics.h" +#include "third_party/skia/include/core/SkFontStyle.h" +#include "third_party/skia/include/core/SkString.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/font.h" +#include "ui/gfx/font_list.h" +#include "ui/gfx/font_render_params.h" +#include "ui/gfx/skia_font_delegate.h" +#include "ui/gfx/text_utils.h" + +#if defined(OS_WIN) +#include "ui/gfx/system_fonts_win.h" +#endif + +namespace gfx { +namespace { + +// The font family name which is used when a user's application font for +// GNOME/KDE is a non-scalable one. The name should be listed in the +// IsFallbackFontAllowed function in skia/ext/SkFontHost_fontconfig_direct.cpp. +#if defined(OS_ANDROID) +const char kFallbackFontFamilyName[] = "serif"; +#else +const char kFallbackFontFamilyName[] = "sans"; +#endif + +constexpr SkGlyphID kUnsupportedGlyph = 0; + +// The default font, used for the default constructor. +base::LazyInstance>::Leaky g_default_font = + LAZY_INSTANCE_INITIALIZER; + +// Creates a SkTypeface for the passed-in Font::FontStyle and family. If a +// fallback typeface is used instead of the requested family, |family| will be +// updated to contain the fallback's family name. +sk_sp CreateSkTypeface(bool italic, + gfx::Font::Weight weight, + std::string* family, + bool* out_success) { + DCHECK(family); + TRACE_EVENT0("fonts", "gfx::CreateSkTypeface"); + + const int font_weight = (weight == Font::Weight::INVALID) + ? static_cast(Font::Weight::NORMAL) + : static_cast(weight); + SkFontStyle sk_style( + font_weight, SkFontStyle::kNormal_Width, + italic ? SkFontStyle::kItalic_Slant : SkFontStyle::kUpright_Slant); + sk_sp typeface; + { + TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("fonts"), "SkTypeface::MakeFromName", + "family", *family); + typeface = SkTypeface::MakeFromName(family->c_str(), sk_style); + } + if (!typeface) { + TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("fonts"), "SkTypeface::MakeFromName", + "family", kFallbackFontFamilyName); + // A non-scalable font such as .pcf is specified. Fall back to a default + // scalable font. + typeface = sk_sp( + SkTypeface::MakeFromName(kFallbackFontFamilyName, sk_style)); + if (!typeface) { + *out_success = false; + return nullptr; + } + *family = kFallbackFontFamilyName; + } + *out_success = true; + return typeface; +} + +} // namespace + +std::string* PlatformFontSkia::default_font_description_ = NULL; + +//////////////////////////////////////////////////////////////////////////////// +// PlatformFontSkia, public: + +PlatformFontSkia::PlatformFontSkia() { + CHECK(InitDefaultFont()) << "Could not find the default font"; + InitFromPlatformFont(g_default_font.Get().get()); +} + +PlatformFontSkia::PlatformFontSkia(const std::string& font_name, + int font_size_pixels) { + FontRenderParamsQuery query; + query.families.push_back(font_name); + query.pixel_size = font_size_pixels; + query.weight = Font::Weight::NORMAL; + InitFromDetails(nullptr, font_name, font_size_pixels, Font::NORMAL, + query.weight, gfx::GetFontRenderParams(query, nullptr)); +} + +PlatformFontSkia::PlatformFontSkia( + sk_sp typeface, + int font_size_pixels, + const absl::optional& params) { + DCHECK(typeface); + + SkString family_name; + typeface->getFamilyName(&family_name); + + SkFontStyle font_style = typeface->fontStyle(); + Font::Weight font_weight = FontWeightFromInt(font_style.weight()); + + int style = typeface->isItalic() ? Font::ITALIC : Font::NORMAL; + + FontRenderParams actual_render_params; + if (!params) { + FontRenderParamsQuery query; + query.families.push_back(family_name.c_str()); + query.pixel_size = font_size_pixels; + query.weight = font_weight; + actual_render_params = gfx::GetFontRenderParams(query, nullptr); + } else { + actual_render_params = params.value(); + } + + InitFromDetails(std::move(typeface), family_name.c_str(), font_size_pixels, + style, font_weight, actual_render_params); +} + +//////////////////////////////////////////////////////////////////////////////// +// PlatformFontSkia, PlatformFont implementation: + +// static +bool PlatformFontSkia::InitDefaultFont() { + if (g_default_font.Get()) + return true; + + bool success = false; + std::string family = kFallbackFontFamilyName; + int size_pixels = PlatformFont::kDefaultBaseFontSize; + int style = Font::NORMAL; + Font::Weight weight = Font::Weight::NORMAL; + FontRenderParams params; + +#if defined(OS_WIN) + // On windows, the system default font is retrieved by using the GDI API + // SystemParametersInfo(...) (see struct NONCLIENTMETRICS). The font + // properties need to be converted as close as possible to a skia font. + // The style must be kept (see http://crbug/989476). + gfx::Font system_font = win::GetDefaultSystemFont(); + family = system_font.GetFontName(); + size_pixels = system_font.GetFontSize(); + style = system_font.GetStyle(); + weight = system_font.GetWeight(); +#endif // OS_WIN + + // On Linux, SkiaFontDelegate is used to query the native toolkit (e.g. + // GTK+) for the default UI font. + const SkiaFontDelegate* delegate = SkiaFontDelegate::instance(); + if (delegate) { + delegate->GetDefaultFontDescription(&family, &size_pixels, &style, &weight, + ¶ms); + } else if (default_font_description_) { +#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS) + // On ChromeOS, a FontList font description string is stored as a + // translatable resource and passed in via SetDefaultFontDescription(). + FontRenderParamsQuery query; + CHECK(FontList::ParseDescription(*default_font_description_, + &query.families, &query.style, + &query.pixel_size, &query.weight)) + << "Failed to parse font description " << *default_font_description_; + params = gfx::GetFontRenderParams(query, &family); + size_pixels = query.pixel_size; + style = query.style; + weight = query.weight; +#else + NOTREACHED(); +#endif + } else { + params = gfx::GetFontRenderParams(FontRenderParamsQuery(), nullptr); + } + + sk_sp typeface = + CreateSkTypeface(style & Font::ITALIC, weight, &family, &success); + if (!success) + return false; + g_default_font.Get() = new PlatformFontSkia( + std::move(typeface), family, size_pixels, style, weight, params); + return true; +} + +// static +void PlatformFontSkia::ReloadDefaultFont() { + // Reset the scoped_refptr. + g_default_font.Get() = nullptr; +} + +// static +void PlatformFontSkia::SetDefaultFontDescription( + const std::string& font_description) { + delete default_font_description_; + default_font_description_ = new std::string(font_description); +} + +Font PlatformFontSkia::DeriveFont(int size_delta, + int style, + Font::Weight weight) const { +#if defined(OS_WIN) + const int new_size = win::AdjustFontSize(font_size_pixels_, size_delta); +#else + const int new_size = font_size_pixels_ + size_delta; +#endif + + DCHECK_GT(new_size, 0); + + // If the style changed, we may need to load a new face. + std::string new_family = font_family_; + bool success = true; + sk_sp typeface = + (weight == weight_ && style == style_) + ? typeface_ + : CreateSkTypeface(style, weight, &new_family, &success); + if (!success) { + LOG(ERROR) << "Could not find any font: " << new_family << ", " + << kFallbackFontFamilyName << ". Falling back to the default"; + return Font(new PlatformFontSkia); + } + + FontRenderParamsQuery query; + query.families.push_back(new_family); + query.pixel_size = new_size; + query.style = style; + + return Font(new PlatformFontSkia(std::move(typeface), new_family, new_size, + style, weight, + gfx::GetFontRenderParams(query, NULL))); +} + +int PlatformFontSkia::GetHeight() { + ComputeMetricsIfNecessary(); + return height_pixels_; +} + +Font::Weight PlatformFontSkia::GetWeight() const { + return weight_; +} + +int PlatformFontSkia::GetBaseline() { + ComputeMetricsIfNecessary(); + return ascent_pixels_; +} + +int PlatformFontSkia::GetCapHeight() { + ComputeMetricsIfNecessary(); + return cap_height_pixels_; +} + +int PlatformFontSkia::GetExpectedTextWidth(int length) { + ComputeMetricsIfNecessary(); + return round(static_cast(length) * average_width_pixels_); +} + +int PlatformFontSkia::GetStyle() const { + return style_; +} + +const std::string& PlatformFontSkia::GetFontName() const { + return font_family_; +} + +std::string PlatformFontSkia::GetActualFontName() const { + SkString family_name; + typeface_->getFamilyName(&family_name); + return family_name.c_str(); +} + +int PlatformFontSkia::GetFontSize() const { + return font_size_pixels_; +} + +const FontRenderParams& PlatformFontSkia::GetFontRenderParams() { + TRACE_EVENT0("fonts", "PlatformFontSkia::GetFontRenderParams"); + float current_scale_factor = GetFontRenderParamsDeviceScaleFactor(); + if (current_scale_factor != device_scale_factor_) { + FontRenderParamsQuery query; + query.families.push_back(font_family_); + query.pixel_size = font_size_pixels_; + query.style = style_; + query.weight = weight_; + query.device_scale_factor = current_scale_factor; + font_render_params_ = gfx::GetFontRenderParams(query, nullptr); + device_scale_factor_ = current_scale_factor; + } + return font_render_params_; +} + +sk_sp PlatformFontSkia::GetNativeSkTypeface() const { + DCHECK(typeface_); + return sk_sp(typeface_); +} + +//////////////////////////////////////////////////////////////////////////////// +// PlatformFontSkia, private: + +PlatformFontSkia::PlatformFontSkia(sk_sp typeface, + const std::string& family, + int size_pixels, + int style, + Font::Weight weight, + const FontRenderParams& render_params) { + InitFromDetails(std::move(typeface), family, size_pixels, style, weight, + render_params); +} + +PlatformFontSkia::~PlatformFontSkia() {} + +void PlatformFontSkia::InitFromDetails(sk_sp typeface, + const std::string& font_family, + int font_size_pixels, + int style, + Font::Weight weight, + const FontRenderParams& render_params) { + TRACE_EVENT0("fonts", "PlatformFontSkia::InitFromDetails"); + DCHECK_GT(font_size_pixels, 0); + + font_family_ = font_family; + bool success = true; + typeface_ = typeface ? std::move(typeface) + : CreateSkTypeface(style & Font::ITALIC, weight, + &font_family_, &success); + + if (!success) { + LOG(ERROR) << "Could not find any font: " << font_family << ", " + << kFallbackFontFamilyName << ". Falling back to the default"; + + InitFromPlatformFont(g_default_font.Get().get()); + return; + } + + font_size_pixels_ = font_size_pixels; + style_ = style; + weight_ = weight; + device_scale_factor_ = GetFontRenderParamsDeviceScaleFactor(); + font_render_params_ = render_params; +} + +void PlatformFontSkia::InitFromPlatformFont(const PlatformFontSkia* other) { + TRACE_EVENT0("fonts", "PlatformFontSkia::InitFromPlatformFont"); + typeface_ = other->typeface_; + font_family_ = other->font_family_; + font_size_pixels_ = other->font_size_pixels_; + style_ = other->style_; + weight_ = other->weight_; + device_scale_factor_ = other->device_scale_factor_; + font_render_params_ = other->font_render_params_; + + if (!other->metrics_need_computation_) { + metrics_need_computation_ = false; + ascent_pixels_ = other->ascent_pixels_; + height_pixels_ = other->height_pixels_; + cap_height_pixels_ = other->cap_height_pixels_; + average_width_pixels_ = other->average_width_pixels_; + } +} + +void PlatformFontSkia::ComputeMetricsIfNecessary() { + if (metrics_need_computation_) { + TRACE_EVENT0("fonts", "PlatformFontSkia::ComputeMetricsIfNecessary"); + + metrics_need_computation_ = false; + + SkFont font(typeface_, font_size_pixels_); + const FontRenderParams& params = GetFontRenderParams(); + if (!params.antialiasing) { + font.setEdging(SkFont::Edging::kAlias); + } else if (params.subpixel_rendering == + FontRenderParams::SUBPIXEL_RENDERING_NONE) { + font.setEdging(SkFont::Edging::kAntiAlias); + } else { + font.setEdging(SkFont::Edging::kSubpixelAntiAlias); + } + + font.setEmbolden(weight_ >= Font::Weight::BOLD && !typeface_->isBold()); + font.setSkewX((Font::ITALIC & style_) && !typeface_->isItalic() + ? -SK_Scalar1 / 4 + : 0); + SkFontMetrics metrics; + font.getMetrics(&metrics); + ascent_pixels_ = SkScalarCeilToInt(-metrics.fAscent); + cap_height_pixels_ = SkScalarCeilToInt(metrics.fCapHeight); + + // There is a mismatch between the way the PlatformFontWin was computing the + // font height in pixel. The font height may vary by one pixel due to + // decimal rounding. + // Windows Skia implements : ceil(descent - ascent) + // Linux Skia implements : ceil(-ascent) + ceil(descent) + // TODO(etienneb): Make both implementation consistent and fix the broken + // unittests. +#if defined(OS_WIN) + height_pixels_ = SkScalarCeilToInt(metrics.fDescent - metrics.fAscent); +#else + height_pixels_ = ascent_pixels_ + SkScalarCeilToInt(metrics.fDescent); +#endif + + if (metrics.fAvgCharWidth) { + average_width_pixels_ = SkScalarToDouble(metrics.fAvgCharWidth); + } else { + // Some Skia fonts manager do not compute the average character size + // (e.g. Direct Write). The following code computes the average character + // width the same way Blink (e.g. SimpleFontData) does. Use the width of + // the letter 'x' when available, otherwise use the max character width. + SkGlyphID glyph = typeface_->unicharToGlyph('x'); + if (glyph != kUnsupportedGlyph) { + SkScalar sk_width; + font.getWidths(&glyph, 1, &sk_width); + average_width_pixels_ = SkScalarToDouble(sk_width); + } + if (!average_width_pixels_) { + if (metrics.fMaxCharWidth) { + average_width_pixels_ = SkScalarToDouble(metrics.fMaxCharWidth); + } else { + // Older version of the DirectWrite API doesn't implement support for + // max char width. Fall back on a multiple of the ascent. This is + // entirely arbitrary but comes pretty close to the expected value in + // most cases. + average_width_pixels_ = ascent_pixels_ * 2; + } + } + } + DCHECK_NE(average_width_pixels_, 0); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// PlatformFont, public: + +// static +PlatformFont* PlatformFont::CreateDefault() { + return new PlatformFontSkia; +} + +// static +PlatformFont* PlatformFont::CreateFromNameAndSize(const std::string& font_name, + int font_size) { + TRACE_EVENT0("fonts", "PlatformFont::CreateFromNameAndSize"); + return new PlatformFontSkia(font_name, font_size); +} + +// static +PlatformFont* PlatformFont::CreateFromSkTypeface( + sk_sp typeface, + int font_size_pixels, + const absl::optional& params) { + TRACE_EVENT0("fonts", "PlatformFont::CreateFromSkTypeface"); + return new PlatformFontSkia(typeface, font_size_pixels, params); +} + +} // namespace gfx diff --git a/platform_font_skia.h b/platform_font_skia.h new file mode 100644 index 000000000000..7a92f59fa8c1 --- /dev/null +++ b/platform_font_skia.h @@ -0,0 +1,121 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_PLATFORM_FONT_SKIA_H_ +#define UI_GFX_PLATFORM_FONT_SKIA_H_ + +#include +#include + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "build/build_config.h" +#include "third_party/skia/include/core/SkTypeface.h" +#include "ui/gfx/font_render_params.h" +#include "ui/gfx/platform_font.h" + +namespace gfx { + +class GFX_EXPORT PlatformFontSkia : public PlatformFont { + public: + // TODO(derat): Get rid of the default constructor in favor of using + // FontList (which also has the concept of a default font but may contain + // multiple font families) everywhere. See http://crbug.com/398885#c16. + PlatformFontSkia(); + PlatformFontSkia(const std::string& font_name, int font_size_pixels); + + // Wraps the provided SkTypeface without triggering a font rematch. + PlatformFontSkia(sk_sp typeface, + int font_size_pixels, + const absl::optional& params); + + PlatformFontSkia(const PlatformFontSkia&) = delete; + PlatformFontSkia& operator=(const PlatformFontSkia&) = delete; + + // Initials the default PlatformFont. Returns true if this is successful, or + // false if fonts resources are not available. If this returns false, the + // calling service should shut down. + static bool InitDefaultFont(); + + // Resets and reloads the cached system font used by the default constructor. + // This function is useful when the system font has changed, for example, when + // the locale has changed. + static void ReloadDefaultFont(); + + // Sets the default font. |font_description| is a FontList font description; + // only the first family will be used. + // TODO(sergeyu): Remove this function. Currently it is used only on ChromeOS + // to set the default font to the one loaded from resources. + // + static void SetDefaultFontDescription(const std::string& font_description); + + // Overridden from PlatformFont: + Font DeriveFont(int size_delta, + int style, + Font::Weight weight) const override; + int GetHeight() override; + Font::Weight GetWeight() const override; + int GetBaseline() override; + int GetCapHeight() override; + int GetExpectedTextWidth(int length) override; + int GetStyle() const override; + const std::string& GetFontName() const override; + std::string GetActualFontName() const override; + int GetFontSize() const override; + const FontRenderParams& GetFontRenderParams() override; + sk_sp GetNativeSkTypeface() const override; + + private: + // Create a new instance of this object with the specified properties. Called + // from DeriveFont. + PlatformFontSkia(sk_sp typeface, + const std::string& family, + int size_pixels, + int style, + Font::Weight weight, + const FontRenderParams& params); + ~PlatformFontSkia() override; + + // Initializes this object based on the passed-in details. If |typeface| is + // empty, a new typeface will be loaded. + void InitFromDetails(sk_sp typeface, + const std::string& font_family, + int font_size_pixels, + int style, + Font::Weight weight, + const FontRenderParams& params); + + // Initializes this object as a copy of another PlatformFontSkia. + void InitFromPlatformFont(const PlatformFontSkia* other); + + // Computes the metrics if they have not yet been computed. + void ComputeMetricsIfNecessary(); + + sk_sp typeface_; + + // Additional information about the face. + // Skia actually expects a family name and not a font name. + std::string font_family_; + int font_size_pixels_; + int style_; + float device_scale_factor_; + + // Information describing how the font should be rendered. + FontRenderParams font_render_params_; + + // Cached metrics, generated on demand. + bool metrics_need_computation_ = true; + int ascent_pixels_; + int height_pixels_; + int cap_height_pixels_; + double average_width_pixels_; + Font::Weight weight_; + + // A font description string of the format used by FontList. + static std::string* default_font_description_; +}; + +} // namespace gfx + +#endif // UI_GFX_PLATFORM_FONT_SKIA_H_ diff --git a/platform_font_skia_unittest.cc b/platform_font_skia_unittest.cc new file mode 100644 index 000000000000..545f3d13f16e --- /dev/null +++ b/platform_font_skia_unittest.cc @@ -0,0 +1,159 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/platform_font_skia.h" + +#include + +#include "base/check_op.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/notreached.h" +#include "build/build_config.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/font.h" +#include "ui/gfx/font_names_testing.h" +#include "ui/gfx/font_render_params.h" +#include "ui/gfx/skia_font_delegate.h" + +#if defined(OS_WIN) +#include "ui/gfx/system_fonts_win.h" +#endif + +namespace gfx { + +// Implementation of SkiaFontDelegate used to control the default font +// description. +class TestFontDelegate : public SkiaFontDelegate { + public: + TestFontDelegate() = default; + + TestFontDelegate(const TestFontDelegate&) = delete; + TestFontDelegate& operator=(const TestFontDelegate&) = delete; + + ~TestFontDelegate() override = default; + + void set_family(const std::string& family) { family_ = family; } + void set_size_pixels(int size_pixels) { size_pixels_ = size_pixels; } + void set_style(int style) { style_ = style; } + void set_weight(gfx::Font::Weight weight) { weight_ = weight; } + void set_params(const FontRenderParams& params) { params_ = params; } + + FontRenderParams GetDefaultFontRenderParams() const override { + NOTIMPLEMENTED(); + return FontRenderParams(); + } + + void GetDefaultFontDescription(std::string* family_out, + int* size_pixels_out, + int* style_out, + Font::Weight* weight_out, + FontRenderParams* params_out) const override { + *family_out = family_; + *size_pixels_out = size_pixels_; + *style_out = style_; + *weight_out = weight_; + *params_out = params_; + } + + private: + // Default values to be returned. + std::string family_; + int size_pixels_ = 0; + int style_ = Font::NORMAL; + gfx::Font::Weight weight_ = Font::Weight::NORMAL; + FontRenderParams params_; +}; + +class PlatformFontSkiaTest : public testing::Test { + public: + PlatformFontSkiaTest() = default; + + PlatformFontSkiaTest(const PlatformFontSkiaTest&) = delete; + PlatformFontSkiaTest& operator=(const PlatformFontSkiaTest&) = delete; + + ~PlatformFontSkiaTest() override = default; + + void SetUp() override { + original_font_delegate_ = SkiaFontDelegate::instance(); + SkiaFontDelegate::SetInstance(&test_font_delegate_); + PlatformFontSkia::ReloadDefaultFont(); + } + + void TearDown() override { + DCHECK_EQ(&test_font_delegate_, SkiaFontDelegate::instance()); + SkiaFontDelegate::SetInstance( + const_cast(original_font_delegate_)); + PlatformFontSkia::ReloadDefaultFont(); + } + + protected: + TestFontDelegate test_font_delegate_; + + private: + // Originally-registered delegate. + const SkiaFontDelegate* original_font_delegate_; +}; + +// Test that PlatformFontSkia's default constructor initializes the instance +// with the correct parameters. +TEST_F(PlatformFontSkiaTest, DefaultFont) { + test_font_delegate_.set_family(kTestFontName); + test_font_delegate_.set_size_pixels(13); + test_font_delegate_.set_style(Font::NORMAL); + FontRenderParams params; + params.antialiasing = false; + params.hinting = FontRenderParams::HINTING_FULL; + test_font_delegate_.set_params(params); + scoped_refptr font(new gfx::PlatformFontSkia()); + EXPECT_EQ(kTestFontName, font->GetFontName()); + EXPECT_EQ(13, font->GetFontSize()); + EXPECT_EQ(gfx::Font::NORMAL, font->GetStyle()); + + EXPECT_EQ(params.antialiasing, font->GetFontRenderParams().antialiasing); + EXPECT_EQ(params.hinting, font->GetFontRenderParams().hinting); + + // Drop the old default font and check that new settings are loaded. + test_font_delegate_.set_family(kSymbolFontName); + test_font_delegate_.set_size_pixels(15); + test_font_delegate_.set_style(gfx::Font::ITALIC); + test_font_delegate_.set_weight(gfx::Font::Weight::BOLD); + PlatformFontSkia::ReloadDefaultFont(); + scoped_refptr font2(new gfx::PlatformFontSkia()); + EXPECT_EQ(kSymbolFontName, font2->GetFontName()); + EXPECT_EQ(15, font2->GetFontSize()); + EXPECT_NE(font2->GetStyle() & Font::ITALIC, 0); + EXPECT_EQ(gfx::Font::Weight::BOLD, font2->GetWeight()); +} + +TEST(PlatformFontSkiaRenderParamsTest, DefaultFontRenderParams) { + scoped_refptr default_font(new PlatformFontSkia()); + scoped_refptr named_font(new PlatformFontSkia( + default_font->GetFontName(), default_font->GetFontSize())); + + // Ensures that both constructors are producing fonts with the same render + // params. + EXPECT_EQ(default_font->GetFontRenderParams(), + named_font->GetFontRenderParams()); +} + +#if defined(OS_WIN) +TEST(PlatformFontSkiaOnWindowsTest, SystemFont) { + // Ensures that the font styles are kept while creating the default font. + gfx::Font system_font = win::GetDefaultSystemFont(); + gfx::Font default_font; + + EXPECT_EQ(system_font.GetFontName(), default_font.GetFontName()); + EXPECT_EQ(system_font.GetFontSize(), default_font.GetFontSize()); + EXPECT_EQ(system_font.GetStyle(), default_font.GetStyle()); + EXPECT_EQ(system_font.GetWeight(), default_font.GetWeight()); + EXPECT_EQ(system_font.GetHeight(), default_font.GetHeight()); + EXPECT_EQ(system_font.GetBaseline(), default_font.GetBaseline()); + EXPECT_EQ(system_font.GetBaseline(), default_font.GetBaseline()); + EXPECT_EQ(system_font.GetFontRenderParams(), + default_font.GetFontRenderParams()); +} +#endif // OS_WIN + +} // namespace gfx diff --git a/presentation_feedback.h b/presentation_feedback.h new file mode 100644 index 000000000000..d1b950255378 --- /dev/null +++ b/presentation_feedback.h @@ -0,0 +1,108 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_PRESENTATION_FEEDBACK_H_ +#define UI_GFX_PRESENTATION_FEEDBACK_H_ + +#include + +#include "base/time/time.h" + +namespace gfx { + +// The feedback for gl::GLSurface methods |SwapBuffers|, |SwapBuffersAsync|, +// |SwapBuffersWithBounds|, |PostSubBuffer|, |PostSubBufferAsync|, +// |CommitOverlayPlanes|,|CommitOverlayPlanesAsync|, etc. +struct PresentationFeedback { + enum Flags { + // The presentation was synchronized to VSYNC. + kVSync = 1 << 0, + + // The presentation |timestamp| is converted from hardware clock by driver. + // Sampling a clock in user space is not acceptable for this flag. + kHWClock = 1 << 1, + + // The display hardware signalled that it started using the new content. The + // opposite of this is e.g. a timer being used to guess when the display + // hardware has switched to the new image content. + kHWCompletion = 1 << 2, + + // The presentation of this update was done zero-copy. Possible zero-copy + // cases include direct scanout of a fullscreen surface and a surface on a + // hardware overlay. + kZeroCopy = 1 << 3, + + // The presentation of this update failed. |timestamp| is the time of the + // failure. + kFailure = 1 << 4, + }; + + PresentationFeedback() = default; + PresentationFeedback(base::TimeTicks timestamp, + base::TimeDelta interval, + uint32_t flags) + : timestamp(timestamp), interval(interval), flags(flags) {} + + static PresentationFeedback Failure() { + return {base::TimeTicks::Now(), base::TimeDelta(), Flags::kFailure}; + } + + bool failed() const { return !!(flags & Flags::kFailure); } + + // The time when a buffer begins scan-out. If a buffer is never presented on + // a screen, the |timestamp| will be set to the time of the failure. + base::TimeTicks timestamp; + + // An estimated interval from the |timestamp| to the next refresh. + base::TimeDelta interval; + + // A combination of Flags. It indicates the kind of the |timestamp|. + uint32_t flags = 0; + + // The following are additional timestamps that are reported if available on + // the underlying platform. If not available, the timestamp is set to 0. + + // A buffer sent to the system compositor or display controller for + // presentation is returned to chromium's compositor with an out fence for + // synchronization. This fence indicates when reads from this buffer for + // presentation (on the GPU or display controller) have been finished and it + // is safe to write new data to this buffer. Since this fence may not have + // been signalled when the swap for a new frame is issued, this timestamp is + // meant to track the latency from when a swap is issued on the GPU thread to + // when the GPU can start rendering to this buffer. + base::TimeTicks available_timestamp; + + // The time when the GPU has finished completing all the drawing commands on + // the primary plane. On Android, SurfaceFlinger does not latch to a buffer + // until this fence has been signalled. + base::TimeTicks ready_timestamp; + + // The time when the primary plane is latched by the system compositor for its + // next rendering update. On Android this corresponds to the SurfaceFlinger + // latch time. + base::TimeTicks latch_timestamp; + + // The time when write operations have completed, corresponding to the time + // when rendering on the GPU finished. + base::TimeTicks writes_done_timestamp; +}; + +inline bool operator==(const PresentationFeedback& lhs, + const PresentationFeedback& rhs) { + return lhs.timestamp == rhs.timestamp && lhs.interval == rhs.interval && + lhs.flags == rhs.flags && + lhs.available_timestamp == rhs.available_timestamp && + lhs.ready_timestamp == rhs.ready_timestamp && + lhs.latch_timestamp == rhs.latch_timestamp && + lhs.writes_done_timestamp == rhs.writes_done_timestamp; +} + +inline bool operator!=(const PresentationFeedback& lhs, + const PresentationFeedback& rhs) { + return !(lhs == rhs); +} + +} // namespace gfx + +#endif // UI_GFX_PRESENTATION_FEEDBACK_H_ diff --git a/range/BUILD.gn b/range/BUILD.gn new file mode 100644 index 000000000000..a2e22191e407 --- /dev/null +++ b/range/BUILD.gn @@ -0,0 +1,24 @@ +# Copyright 2016 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +component("range") { + sources = [ + "gfx_range_export.h", + "range.cc", + "range.h", + "range_f.cc", + "range_f.h", + ] + + if (is_apple) { + sources += [ "range_mac.mm" ] + } + + defines = [ "GFX_RANGE_IMPLEMENTATION" ] + + deps = [ + "//base", + "//ui/gfx:gfx_export", + ] +} diff --git a/range/OWNERS b/range/OWNERS new file mode 100644 index 000000000000..14fce2ae686e --- /dev/null +++ b/range/OWNERS @@ -0,0 +1 @@ +rsesek@chromium.org diff --git a/range/gfx_range_export.h b/range/gfx_range_export.h new file mode 100644 index 000000000000..dafcefdf9d5b --- /dev/null +++ b/range/gfx_range_export.h @@ -0,0 +1,29 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_RANGE_GFX_RANGE_EXPORT_H_ +#define UI_GFX_RANGE_GFX_RANGE_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(GFX_RANGE_IMPLEMENTATION) +#define GFX_RANGE_EXPORT __declspec(dllexport) +#else +#define GFX_RANGE_EXPORT __declspec(dllimport) +#endif // defined(GFX_RANGE_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(GFX_RANGE_IMPLEMENTATION) +#define GFX_RANGE_EXPORT __attribute__((visibility("default"))) +#else +#define GFX_RANGE_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define GFX_RANGE_EXPORT +#endif + +#endif // UI_GFX_RANGE_GFX_RANGE_EXPORT_H_ diff --git a/range/mojom/BUILD.gn b/range/mojom/BUILD.gn new file mode 100644 index 000000000000..3f5fa85ae186 --- /dev/null +++ b/range/mojom/BUILD.gn @@ -0,0 +1,61 @@ +# Copyright 2016 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//mojo/public/tools/bindings/mojom.gni") + +# This target does NOT depend on skia. One can depend on this target to avoid +# picking up a dependency on skia. +mojom("mojom") { + generate_java = true + sources = [ "range.mojom" ] + + shared_cpp_typemap = { + types = [ + { + mojom = "gfx.mojom.Range" + cpp = "::gfx::Range" + }, + { + mojom = "gfx.mojom.RangeF" + cpp = "::gfx::RangeF" + }, + ] + + traits_headers = [ "range_mojom_traits.h" ] + traits_public_deps = [ ":mojom_traits" ] + } + cpp_typemaps = [ shared_cpp_typemap ] + blink_cpp_typemaps = [ shared_cpp_typemap ] + + webui_module_path = "chrome://resources/mojo/ui/gfx/range/mojom" +} + +mojom("test_interfaces") { + sources = [ "range_traits_test_service.mojom" ] + + public_deps = [ ":mojom" ] +} + +source_set("unit_test") { + testonly = true + + sources = [ "range_mojom_traits_unittest.cc" ] + + deps = [ + ":test_interfaces", + "//base", + "//base/test:test_support", + "//mojo/public/cpp/bindings", + "//testing/gtest", + "//ui/gfx/range", + ] +} + +source_set("mojom_traits") { + sources = [ "range_mojom_traits.h" ] + public_deps = [ + ":mojom_shared", + "//ui/gfx/range", + ] +} diff --git a/range/mojom/DEPS b/range/mojom/DEPS new file mode 100644 index 000000000000..418fc69e2eb3 --- /dev/null +++ b/range/mojom/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + "+mojo/public", + "+ui/gfx/range", +] diff --git a/range/mojom/OWNERS b/range/mojom/OWNERS new file mode 100644 index 000000000000..ab87d1e6148b --- /dev/null +++ b/range/mojom/OWNERS @@ -0,0 +1,8 @@ +per-file *.mojom=set noparent +per-file *.mojom=file://ipc/SECURITY_OWNERS + +per-file *_mojom_traits*.*=set noparent +per-file *_mojom_traits*.*=file://ipc/SECURITY_OWNERS + +per-file *.typemap=set noparent +per-file *.typemap=file://ipc/SECURITY_OWNERS diff --git a/range/mojom/range.mojom b/range/mojom/range.mojom new file mode 100644 index 000000000000..079c14611e0e --- /dev/null +++ b/range/mojom/range.mojom @@ -0,0 +1,15 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module gfx.mojom; + +struct Range { + uint32 start; + uint32 end; +}; + +struct RangeF { + float start; + float end; +}; diff --git a/range/mojom/range_mojom_traits.h b/range/mojom/range_mojom_traits.h new file mode 100644 index 000000000000..5db4501e6c89 --- /dev/null +++ b/range/mojom/range_mojom_traits.h @@ -0,0 +1,38 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_RANGE_MOJOM_RANGE_MOJOM_TRAITS_H_ +#define UI_GFX_RANGE_MOJOM_RANGE_MOJOM_TRAITS_H_ + +#include "ui/gfx/range/mojom/range.mojom-shared.h" +#include "ui/gfx/range/range.h" +#include "ui/gfx/range/range_f.h" + +namespace mojo { + +template <> +struct StructTraits { + static uint32_t start(const gfx::Range& r) { return r.start(); } + static uint32_t end(const gfx::Range& r) { return r.end(); } + static bool Read(gfx::mojom::RangeDataView data, gfx::Range* out) { + out->set_start(data.start()); + out->set_end(data.end()); + return true; + } +}; + +template <> +struct StructTraits { + static float start(const gfx::RangeF& r) { return r.start(); } + static float end(const gfx::RangeF& r) { return r.end(); } + static bool Read(gfx::mojom::RangeFDataView data, gfx::RangeF* out) { + out->set_start(data.start()); + out->set_end(data.end()); + return true; + } +}; + +} // namespace mojo + +#endif // UI_GFX_RANGE_MOJOM_RANGE_MOJOM_TRAITS_H_ diff --git a/range/mojom/range_mojom_traits_unittest.cc b/range/mojom/range_mojom_traits_unittest.cc new file mode 100644 index 000000000000..ba49cb82c2a4 --- /dev/null +++ b/range/mojom/range_mojom_traits_unittest.cc @@ -0,0 +1,70 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/test/task_environment.h" +#include "mojo/public/cpp/bindings/receiver_set.h" +#include "mojo/public/cpp/bindings/remote.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/range/mojom/range_traits_test_service.mojom.h" + +namespace gfx { + +namespace { + +class RangeStructTraitsTest : public testing::Test, + public mojom::RangeTraitsTestService { + public: + RangeStructTraitsTest() {} + + RangeStructTraitsTest(const RangeStructTraitsTest&) = delete; + RangeStructTraitsTest& operator=(const RangeStructTraitsTest&) = delete; + + protected: + mojo::Remote GetTraitsTestRemote() { + mojo::Remote remote; + traits_test_receivers_.Add(this, remote.BindNewPipeAndPassReceiver()); + return remote; + } + + private: + // RangeTraitsTestService: + void EchoRange(const Range& p, EchoRangeCallback callback) override { + std::move(callback).Run(p); + } + + void EchoRangeF(const RangeF& p, EchoRangeFCallback callback) override { + std::move(callback).Run(p); + } + + base::test::TaskEnvironment task_environment_; + mojo::ReceiverSet traits_test_receivers_; +}; + +} // namespace + +TEST_F(RangeStructTraitsTest, Range) { + const uint32_t start = 1234; + const uint32_t end = 5678; + gfx::Range input(start, end); + mojo::Remote remote = GetTraitsTestRemote(); + gfx::Range output; + remote->EchoRange(input, &output); + EXPECT_EQ(start, output.start()); + EXPECT_EQ(end, output.end()); +} + +TEST_F(RangeStructTraitsTest, RangeF) { + const float start = 1234.5f; + const float end = 6789.6f; + gfx::RangeF input(start, end); + mojo::Remote remote = GetTraitsTestRemote(); + gfx::RangeF output; + remote->EchoRangeF(input, &output); + EXPECT_EQ(start, output.start()); + EXPECT_EQ(end, output.end()); +} + +} // namespace gfx diff --git a/range/mojom/range_traits_test_service.mojom b/range/mojom/range_traits_test_service.mojom new file mode 100644 index 000000000000..7a0f7a93a519 --- /dev/null +++ b/range/mojom/range_traits_test_service.mojom @@ -0,0 +1,17 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module gfx.mojom; + +import "ui/gfx/range/mojom/range.mojom"; + +// All functions on this interface echo their arguments to test StructTraits +// serialization and deserialization. +interface RangeTraitsTestService { + [Sync] + EchoRange(Range p) => (Range pass); + + [Sync] + EchoRangeF(RangeF p) => (RangeF pass); +}; diff --git a/range/range.cc b/range/range.cc new file mode 100644 index 000000000000..2776f3d75eff --- /dev/null +++ b/range/range.cc @@ -0,0 +1,23 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/range/range.h" + +#include + +#include + +#include "base/strings/stringprintf.h" + +namespace gfx { + +std::string Range::ToString() const { + return base::StringPrintf("{%" PRIu32 ",%" PRIu32 "}", start(), end()); +} + +std::ostream& operator<<(std::ostream& os, const Range& range) { + return os << range.ToString(); +} + +} // namespace gfx diff --git a/range/range.h b/range/range.h new file mode 100644 index 000000000000..b2f6cc44c5c0 --- /dev/null +++ b/range/range.h @@ -0,0 +1,141 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_RANGE_RANGE_H_ +#define UI_GFX_RANGE_RANGE_H_ + +#include +#include + +#include +#include +#include + +#include "build/build_config.h" +#include "ui/gfx/range/gfx_range_export.h" + +#if defined(OS_APPLE) +#if __OBJC__ +#import +#else +typedef struct _NSRange NSRange; +#endif +#endif // defined(OS_APPLE) + +namespace gfx { + +// This class represents either a forward range [min, max) or a reverse range +// (max, min]. |start_| is always the first of these and |end_| the second; as a +// result, the range is forward if (start_ <= end_). The zero-width range +// [val, val) is legal, contains and intersects itself, and is contained by and +// intersects any nonempty range [min, max) where min <= val < max. +class GFX_RANGE_EXPORT Range { + public: + // Creates an empty range {0,0}. + constexpr Range() : Range(0) {} + + // Initializes the range with a start and end. + constexpr Range(uint32_t start, uint32_t end) : start_(start), end_(end) {} + + // Initializes the range with the same start and end positions. + constexpr explicit Range(uint32_t position) : Range(position, position) {} + + // Platform constructors. +#if defined(OS_APPLE) + explicit Range(const NSRange& range); +#endif + + // Returns a range that is invalid, which is {UINT32_MAX,UINT32_MAX}. + static constexpr Range InvalidRange() { + return Range(std::numeric_limits::max()); + } + + // Checks if the range is valid through comparison to InvalidRange(). + constexpr bool IsValid() const { return *this != InvalidRange(); } + + // Getters and setters. + constexpr uint32_t start() const { return start_; } + void set_start(uint32_t start) { start_ = start; } + + constexpr uint32_t end() const { return end_; } + void set_end(uint32_t end) { end_ = end; } + + // Returns the absolute value of the length. + constexpr uint32_t length() const { return GetMax() - GetMin(); } + + constexpr bool is_reversed() const { return start() > end(); } + constexpr bool is_empty() const { return start() == end(); } + + // Returns the minimum and maximum values. + constexpr uint32_t GetMin() const { + return start() < end() ? start() : end(); + } + constexpr uint32_t GetMax() const { + return start() > end() ? start() : end(); + } + + constexpr bool operator==(const Range& other) const { + return start() == other.start() && end() == other.end(); + } + constexpr bool operator!=(const Range& other) const { + return !(*this == other); + } + constexpr bool EqualsIgnoringDirection(const Range& other) const { + return GetMin() == other.GetMin() && GetMax() == other.GetMax(); + } + + // Returns true if this range intersects the specified |range|. + constexpr bool Intersects(const Range& range) const { + return Intersect(range).IsValid(); + } + + // Returns true if this range contains the specified |range|. + constexpr bool Contains(const Range& range) const { + return range.IsBoundedBy(*this) && + // A non-empty range doesn't contain the range [max, max). + (range.GetMax() != GetMax() || range.is_empty() == is_empty()); + } + + // Returns true if this range is contained by the specified |range| or it is + // an empty range and ending the range |range|. + constexpr bool IsBoundedBy(const Range& range) const { + return IsValid() && range.IsValid() && GetMin() >= range.GetMin() && + GetMax() <= range.GetMax(); + } + + // Computes the intersection of this range with the given |range|. + // If they don't intersect, it returns an InvalidRange(). + // The returned range is always empty or forward (never reversed). + constexpr Range Intersect(const Range& range) const { + const uint32_t min = std::max(GetMin(), range.GetMin()); + const uint32_t max = std::min(GetMax(), range.GetMax()); + return (min < max || Contains(range) || range.Contains(*this)) + ? Range(min, max) + : InvalidRange(); + } + +#if defined(OS_APPLE) + Range& operator=(const NSRange& range); + + // NSRange does not store the directionality of a range, so if this + // is_reversed(), the range will get flipped when converted to an NSRange. + NSRange ToNSRange() const; +#endif + // GTK+ has no concept of a range. + + std::string ToString() const; + + private: + // Note: we use uint32_t instead of size_t because this struct is sent over + // IPC which could span 32 & 64 bit processes. This is fine since text spans + // shouldn't exceed UINT32_MAX even on 64 bit builds. + uint32_t start_; + uint32_t end_; +}; + +GFX_RANGE_EXPORT std::ostream& operator<<(std::ostream& os, const Range& range); + +} // namespace gfx + +#endif // UI_GFX_RANGE_RANGE_H_ diff --git a/range/range_f.cc b/range/range_f.cc new file mode 100644 index 000000000000..f3af360b8d50 --- /dev/null +++ b/range/range_f.cc @@ -0,0 +1,30 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/range/range_f.h" + +#include + +#include +#include + +#include "base/format_macros.h" +#include "base/strings/stringprintf.h" + +namespace gfx { + +RangeF RangeF::Intersect(const Range& range) const { + RangeF range_f(range.start(), range.end()); + return Intersect(range_f); +} + +std::string RangeF::ToString() const { + return base::StringPrintf("{%f,%f}", start(), end()); +} + +std::ostream& operator<<(std::ostream& os, const RangeF& range) { + return os << range.ToString(); +} + +} // namespace gfx diff --git a/range/range_f.h b/range/range_f.h new file mode 100644 index 000000000000..1d51b2919e4d --- /dev/null +++ b/range/range_f.h @@ -0,0 +1,116 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_RANGE_RANGE_F_H_ +#define UI_GFX_RANGE_RANGE_F_H_ + +#include +#include +#include + +#include "ui/gfx/range/gfx_range_export.h" +#include "ui/gfx/range/range.h" + +namespace gfx { + +// A float version of Range. RangeF is made of a start and end position; when +// they are the same, the range is empty. Note that |start_| can be greater +// than |end_| to respect the directionality of the range. +class GFX_RANGE_EXPORT RangeF { + public: + // Creates an empty range {0,0}. + constexpr RangeF() : RangeF(0.f) {} + + // Initializes the range with a start and end. + constexpr RangeF(float start, float end) : start_(start), end_(end) {} + + // Initializes the range with the same start and end positions. + constexpr explicit RangeF(float position) : RangeF(position, position) {} + + // Returns a range that is invalid, which is {float_max,float_max}. + static constexpr RangeF InvalidRange() { + return RangeF(std::numeric_limits::max()); + } + + // Checks if the range is valid through comparison to InvalidRange(). + constexpr bool IsValid() const { return *this != InvalidRange(); } + + // Getters and setters. + constexpr float start() const { return start_; } + void set_start(float start) { start_ = start; } + + constexpr float end() const { return end_; } + void set_end(float end) { end_ = end; } + + // Returns the absolute value of the length. + constexpr float length() const { return GetMax() - GetMin(); } + + constexpr bool is_reversed() const { return start() > end(); } + constexpr bool is_empty() const { return start() == end(); } + + // Returns the minimum and maximum values. + constexpr float GetMin() const { return start() < end() ? start() : end(); } + constexpr float GetMax() const { return start() > end() ? start() : end(); } + + constexpr bool operator==(const RangeF& other) const { + return start() == other.start() && end() == other.end(); + } + constexpr bool operator!=(const RangeF& other) const { + return !(*this == other); + } + constexpr bool EqualsIgnoringDirection(const RangeF& other) const { + return GetMin() == other.GetMin() && GetMax() == other.GetMax(); + } + + // Returns true if this range intersects the specified |range|. + constexpr bool Intersects(const RangeF& range) const { + return Intersect(range).IsValid(); + } + + // Returns true if this range is contained by the specified |range| or it is + // an empty range and ending the range |range|. (copied from gfx::Range) + constexpr bool IsBoundedBy(const RangeF& range) const { + return IsValid() && range.IsValid() && GetMin() >= range.GetMin() && + GetMax() <= range.GetMax(); + } + + // Returns true if this range contains the specified |range|. + constexpr bool Contains(const RangeF& range) const { + return range.IsBoundedBy(*this) && + // A non-empty range doesn't contain the range [max, max). + (range.GetMax() != GetMax() || range.is_empty() == is_empty()); + } + + // Computes the intersection of this range with the given |range|. + // If they don't intersect, it returns an InvalidRange(). + // The returned range is always empty or forward (never reversed). + constexpr RangeF Intersect(const RangeF& range) const { + const float min = std::max(GetMin(), range.GetMin()); + const float max = std::min(GetMax(), range.GetMax()); + + return (min < max || Contains(range) || range.Contains(*this)) + ? RangeF(min, max) + : InvalidRange(); + } + + RangeF Intersect(const Range& range) const; + + // Floor/Ceil/Round the start and end values of the given RangeF. + Range Floor() const; + Range Ceil() const; + Range Round() const; + + std::string ToString() const; + + private: + float start_; + float end_; +}; + +GFX_RANGE_EXPORT std::ostream& operator<<(std::ostream& os, + const RangeF& range); + +} // namespace gfx + +#endif // UI_GFX_RANGE_RANGE_F_H_ diff --git a/range/range_mac.mm b/range/range_mac.mm new file mode 100644 index 000000000000..cc1aa1ee4e25 --- /dev/null +++ b/range/range_mac.mm @@ -0,0 +1,38 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/range/range.h" + +#include + +#include + +#include "base/check_op.h" + +namespace gfx { + +Range::Range(const NSRange& range) { + *this = range; +} + +Range& Range::operator=(const NSRange& range) { + if (range.location == NSNotFound) { + DCHECK_EQ(0U, range.length); + *this = InvalidRange(); + } else { + set_start(range.location); + // Don't overflow |end_|. + DCHECK_LE(range.length, std::numeric_limits::max() - start()); + set_end(start() + range.length); + } + return *this; +} + +NSRange Range::ToNSRange() const { + if (!IsValid()) + return NSMakeRange(NSNotFound, 0); + return NSMakeRange(GetMin(), length()); +} + +} // namespace gfx diff --git a/range/range_mac_unittest.mm b/range/range_mac_unittest.mm new file mode 100644 index 000000000000..85323f27ce88 --- /dev/null +++ b/range/range_mac_unittest.mm @@ -0,0 +1,43 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/range/range.h" + +TEST(RangeTest, FromNSRange) { + NSRange nsr = NSMakeRange(10, 3); + gfx::Range r(nsr); + EXPECT_EQ(nsr.location, r.start()); + EXPECT_EQ(13U, r.end()); + EXPECT_EQ(nsr.length, r.length()); + EXPECT_FALSE(r.is_reversed()); + EXPECT_TRUE(r.IsValid()); +} + +TEST(RangeTest, ToNSRange) { + gfx::Range r(10, 12); + NSRange nsr = r.ToNSRange(); + EXPECT_EQ(10U, nsr.location); + EXPECT_EQ(2U, nsr.length); +} + +TEST(RangeTest, ReversedToNSRange) { + gfx::Range r(20, 10); + NSRange nsr = r.ToNSRange(); + EXPECT_EQ(10U, nsr.location); + EXPECT_EQ(10U, nsr.length); +} + +TEST(RangeTest, FromNSRangeInvalid) { + NSRange nsr = NSMakeRange(NSNotFound, 0); + gfx::Range r(nsr); + EXPECT_FALSE(r.IsValid()); +} + +TEST(RangeTest, ToNSRangeInvalid) { + gfx::Range r(gfx::Range::InvalidRange()); + NSRange nsr = r.ToNSRange(); + EXPECT_EQ(static_cast(NSNotFound), nsr.location); + EXPECT_EQ(0U, nsr.length); +} diff --git a/range/range_unittest.cc b/range/range_unittest.cc new file mode 100644 index 000000000000..8fba7406ee91 --- /dev/null +++ b/range/range_unittest.cc @@ -0,0 +1,526 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/range/range.h" +#include "ui/gfx/range/range_f.h" + +namespace { + +template +class RangeTest : public testing::Test { +}; + +typedef testing::Types RangeTypes; +TYPED_TEST_SUITE(RangeTest, RangeTypes); + +template +void TestContainsAndIntersects(const T& r1, + const T& r2, + const T& r3) { + EXPECT_TRUE(r1.Intersects(r1)); + EXPECT_TRUE(r1.Contains(r1)); + EXPECT_EQ(T(10, 12), r1.Intersect(r1)); + + EXPECT_FALSE(r1.Intersects(r2)); + EXPECT_FALSE(r1.Contains(r2)); + EXPECT_TRUE(r1.Intersect(r2).is_empty()); + EXPECT_FALSE(r2.Intersects(r1)); + EXPECT_FALSE(r2.Contains(r1)); + EXPECT_TRUE(r2.Intersect(r1).is_empty()); + + EXPECT_TRUE(r1.Intersects(r3)); + EXPECT_TRUE(r3.Intersects(r1)); + EXPECT_TRUE(r3.Contains(r1)); + EXPECT_FALSE(r1.Contains(r3)); + EXPECT_EQ(T(10, 12), r1.Intersect(r3)); + EXPECT_EQ(T(10, 12), r3.Intersect(r1)); + + EXPECT_TRUE(r2.Intersects(r3)); + EXPECT_TRUE(r3.Intersects(r2)); + EXPECT_FALSE(r3.Contains(r2)); + EXPECT_FALSE(r2.Contains(r3)); + EXPECT_EQ(T(5, 8), r2.Intersect(r3)); + EXPECT_EQ(T(5, 8), r3.Intersect(r2)); +} + +} // namespace + +TYPED_TEST(RangeTest, EmptyInit) { + TypeParam r; + EXPECT_EQ(0U, r.start()); + EXPECT_EQ(0U, r.end()); + EXPECT_EQ(0U, r.length()); + EXPECT_FALSE(r.is_reversed()); + EXPECT_TRUE(r.is_empty()); + EXPECT_TRUE(r.IsValid()); + EXPECT_EQ(0U, r.GetMin()); + EXPECT_EQ(0U, r.GetMax()); +} + +TYPED_TEST(RangeTest, StartEndInit) { + TypeParam r(10, 15); + EXPECT_EQ(10U, r.start()); + EXPECT_EQ(15U, r.end()); + EXPECT_EQ(5U, r.length()); + EXPECT_FALSE(r.is_reversed()); + EXPECT_FALSE(r.is_empty()); + EXPECT_TRUE(r.IsValid()); + EXPECT_EQ(10U, r.GetMin()); + EXPECT_EQ(15U, r.GetMax()); +} + +TYPED_TEST(RangeTest, StartEndReversedInit) { + TypeParam r(10, 5); + EXPECT_EQ(10U, r.start()); + EXPECT_EQ(5U, r.end()); + EXPECT_EQ(5U, r.length()); + EXPECT_TRUE(r.is_reversed()); + EXPECT_FALSE(r.is_empty()); + EXPECT_TRUE(r.IsValid()); + EXPECT_EQ(5U, r.GetMin()); + EXPECT_EQ(10U, r.GetMax()); +} + +TYPED_TEST(RangeTest, PositionInit) { + TypeParam r(12); + EXPECT_EQ(12U, r.start()); + EXPECT_EQ(12U, r.end()); + EXPECT_EQ(0U, r.length()); + EXPECT_FALSE(r.is_reversed()); + EXPECT_TRUE(r.is_empty()); + EXPECT_TRUE(r.IsValid()); + EXPECT_EQ(12U, r.GetMin()); + EXPECT_EQ(12U, r.GetMax()); +} + +TYPED_TEST(RangeTest, InvalidRange) { + TypeParam r(TypeParam::InvalidRange()); + EXPECT_EQ(0U, r.length()); + EXPECT_EQ(r.start(), r.end()); + EXPECT_EQ(r.GetMax(), r.GetMin()); + EXPECT_FALSE(r.is_reversed()); + EXPECT_TRUE(r.is_empty()); + EXPECT_FALSE(r.IsValid()); + EXPECT_EQ(r, TypeParam::InvalidRange()); + EXPECT_TRUE(r.EqualsIgnoringDirection(TypeParam::InvalidRange())); +} + +TYPED_TEST(RangeTest, Equality) { + TypeParam r1(10, 4); + TypeParam r2(10, 4); + TypeParam r3(10, 2); + EXPECT_EQ(r1, r2); + EXPECT_NE(r1, r3); + EXPECT_NE(r2, r3); + + TypeParam r4(11, 4); + EXPECT_NE(r1, r4); + EXPECT_NE(r2, r4); + EXPECT_NE(r3, r4); + + TypeParam r5(12, 5); + EXPECT_NE(r1, r5); + EXPECT_NE(r2, r5); + EXPECT_NE(r3, r5); +} + +TYPED_TEST(RangeTest, EqualsIgnoringDirection) { + TypeParam r1(10, 5); + TypeParam r2(5, 10); + EXPECT_TRUE(r1.EqualsIgnoringDirection(r2)); +} + +TYPED_TEST(RangeTest, SetStart) { + TypeParam r(10, 20); + EXPECT_EQ(10U, r.start()); + EXPECT_EQ(10U, r.length()); + + r.set_start(42); + EXPECT_EQ(42U, r.start()); + EXPECT_EQ(20U, r.end()); + EXPECT_EQ(22U, r.length()); + EXPECT_TRUE(r.is_reversed()); +} + +TYPED_TEST(RangeTest, SetEnd) { + TypeParam r(10, 13); + EXPECT_EQ(10U, r.start()); + EXPECT_EQ(3U, r.length()); + + r.set_end(20); + EXPECT_EQ(10U, r.start()); + EXPECT_EQ(20U, r.end()); + EXPECT_EQ(10U, r.length()); +} + +TYPED_TEST(RangeTest, SetStartAndEnd) { + TypeParam r; + r.set_end(5); + r.set_start(1); + EXPECT_EQ(1U, r.start()); + EXPECT_EQ(5U, r.end()); + EXPECT_EQ(4U, r.length()); + EXPECT_EQ(1U, r.GetMin()); + EXPECT_EQ(5U, r.GetMax()); +} + +TYPED_TEST(RangeTest, ReversedRange) { + TypeParam r(10, 5); + EXPECT_EQ(10U, r.start()); + EXPECT_EQ(5U, r.end()); + EXPECT_EQ(5U, r.length()); + EXPECT_TRUE(r.is_reversed()); + EXPECT_TRUE(r.IsValid()); + EXPECT_EQ(5U, r.GetMin()); + EXPECT_EQ(10U, r.GetMax()); +} + +TYPED_TEST(RangeTest, SetReversedRange) { + TypeParam r(10, 20); + r.set_start(25); + EXPECT_EQ(25U, r.start()); + EXPECT_EQ(20U, r.end()); + EXPECT_EQ(5U, r.length()); + EXPECT_TRUE(r.is_reversed()); + EXPECT_TRUE(r.IsValid()); + + r.set_end(21); + EXPECT_EQ(25U, r.start()); + EXPECT_EQ(21U, r.end()); + EXPECT_EQ(4U, r.length()); + EXPECT_TRUE(r.IsValid()); + EXPECT_EQ(21U, r.GetMin()); + EXPECT_EQ(25U, r.GetMax()); +} + +TYPED_TEST(RangeTest, ContainAndIntersect) { + { + SCOPED_TRACE("contain and intersect"); + TypeParam r1(10, 12); + TypeParam r2(1, 8); + TypeParam r3(5, 12); + TestContainsAndIntersects(r1, r2, r3); + } + { + SCOPED_TRACE("contain and intersect: reversed"); + TypeParam r1(12, 10); + TypeParam r2(8, 1); + TypeParam r3(12, 5); + TestContainsAndIntersects(r1, r2, r3); + } + // Invalid rect tests + TypeParam r1(10, 12); + TypeParam r2(8, 1); + TypeParam invalid = r1.Intersect(r2); + EXPECT_FALSE(invalid.IsValid()); + EXPECT_FALSE(invalid.Contains(invalid)); + EXPECT_FALSE(invalid.Contains(r1)); + EXPECT_FALSE(invalid.Intersects(invalid)); + EXPECT_FALSE(invalid.Intersects(r1)); + EXPECT_FALSE(r1.Contains(invalid)); + EXPECT_FALSE(r1.Intersects(invalid)); +} + +TEST(RangeTest, RangeOperations) { + constexpr gfx::Range invalid_range = gfx::Range::InvalidRange(); + constexpr gfx::Range ranges[] = {{0, 0}, {0, 1}, {0, 2}, {1, 0}, {1, 1}, + {1, 2}, {2, 0}, {2, 1}, {2, 2}}; + + // Ensures valid behavior over same range. + for (const auto& range : ranges) { + SCOPED_TRACE(range.ToString()); + // A range should contain itself. + EXPECT_TRUE(range.Contains(range)); + // A ranges should intersect with itself. + EXPECT_TRUE(range.Intersects(range)); + } + + // Ensures valid behavior with an invalid range. + for (const auto& range : ranges) { + SCOPED_TRACE(range.ToString()); + EXPECT_FALSE(invalid_range.Contains(range)); + EXPECT_FALSE(invalid_range.Intersects(range)); + EXPECT_FALSE(range.Contains(invalid_range)); + EXPECT_FALSE(range.Intersects(invalid_range)); + } + EXPECT_FALSE(invalid_range.Contains(invalid_range)); + EXPECT_FALSE(invalid_range.Intersects(invalid_range)); + + // Ensures consistent operations between Contains(...) and Intersects(...). + for (const auto& range1 : ranges) { + for (const auto& range2 : ranges) { + SCOPED_TRACE(testing::Message() + << "range1=" << range1 << " range2=" << range2); + if (range1.Contains(range2)) { + EXPECT_TRUE(range1.Intersects(range2)); + EXPECT_EQ(range2.Contains(range1), + range1.EqualsIgnoringDirection(range2)); + } + EXPECT_EQ(range2.Intersects(range1), range1.Intersects(range2)); + + EXPECT_EQ(range1.Intersect(range2) != invalid_range, + range1.Intersects(range2)); + } + } + + // Ranges should behave the same way no matter the direction. + for (const auto& range1 : ranges) { + for (const auto& range2 : ranges) { + SCOPED_TRACE(testing::Message() + << "range1=" << range1 << " range2=" << range2); + EXPECT_EQ(range1.Contains(range2), + range1.Contains(gfx::Range(range2.GetMax(), range2.GetMin()))); + EXPECT_EQ( + range1.Intersects(range2), + range1.Intersects(gfx::Range(range2.GetMax(), range2.GetMin()))); + } + } +} + +TEST(RangeTest, RangeFOperations) { + constexpr gfx::RangeF invalid_range = gfx::RangeF::InvalidRange(); + constexpr gfx::RangeF ranges[] = {{0.f, 0.f}, {0.f, 1.f}, {0.f, 2.f}, + {1.f, 0.f}, {1.f, 1.f}, {1.f, 2.f}, + {2.f, 0.f}, {2.f, 1.f}, {2.f, 2.f}}; + + // Ensures valid behavior over same range. + for (const auto& range : ranges) { + SCOPED_TRACE(range.ToString()); + // A range should contain itself. + EXPECT_TRUE(range.Contains(range)); + // A ranges should intersect with itself. + EXPECT_TRUE(range.Intersects(range)); + } + + // Ensures valid behavior with an invalid range. + for (const auto& range : ranges) { + SCOPED_TRACE(range.ToString()); + EXPECT_FALSE(invalid_range.Contains(range)); + EXPECT_FALSE(invalid_range.Intersects(range)); + EXPECT_FALSE(range.Contains(invalid_range)); + EXPECT_FALSE(range.Intersects(invalid_range)); + } + EXPECT_FALSE(invalid_range.Contains(invalid_range)); + EXPECT_FALSE(invalid_range.Intersects(invalid_range)); + + // Ensures consistent operations between Contains(...) and Intersects(...). + for (const auto& range1 : ranges) { + for (const auto& range2 : ranges) { + SCOPED_TRACE(testing::Message() + << "range1=" << range1 << " range2=" << range2); + if (range1.Contains(range2)) { + EXPECT_TRUE(range1.Intersects(range2)); + EXPECT_EQ(range2.Contains(range1), + range1.EqualsIgnoringDirection(range2)); + } + EXPECT_EQ(range2.Intersects(range1), range1.Intersects(range2)); + + EXPECT_EQ(range1.Intersect(range2) != invalid_range, + range1.Intersects(range2)); + } + } + + // Ranges should behave the same way no matter the direction. + for (const auto& range1 : ranges) { + for (const auto& range2 : ranges) { + SCOPED_TRACE(testing::Message() + << "range1=" << range1 << " range2=" << range2); + EXPECT_EQ(range1.Contains(range2), + range1.Contains(gfx::RangeF(range2.GetMax(), range2.GetMin()))); + EXPECT_EQ( + range1.Intersects(range2), + range1.Intersects(gfx::RangeF(range2.GetMax(), range2.GetMin()))); + } + } +} + +TEST(RangeTest, ContainsAndIntersects) { + constexpr gfx::Range r1(0, 0); + constexpr gfx::Range r2(0, 1); + constexpr gfx::Range r3(1, 2); + constexpr gfx::Range r4(1, 0); + constexpr gfx::Range r5(2, 1); + constexpr gfx::Range r6(0, 2); + constexpr gfx::Range r7(2, 0); + constexpr gfx::Range r8(1, 1); + + // Ensures Contains(...) handle the open range. + EXPECT_TRUE(r2.Contains(r1)); + EXPECT_TRUE(r4.Contains(r1)); + EXPECT_TRUE(r3.Contains(r5)); + EXPECT_TRUE(r5.Contains(r3)); + + // Ensures larger ranges contains smaller ranges. + EXPECT_TRUE(r6.Contains(r1)); + EXPECT_TRUE(r6.Contains(r2)); + EXPECT_TRUE(r6.Contains(r3)); + EXPECT_TRUE(r6.Contains(r4)); + EXPECT_TRUE(r6.Contains(r5)); + + EXPECT_TRUE(r7.Contains(r1)); + EXPECT_TRUE(r7.Contains(r2)); + EXPECT_TRUE(r7.Contains(r3)); + EXPECT_TRUE(r7.Contains(r4)); + EXPECT_TRUE(r7.Contains(r5)); + + // Ensures Intersects(...) handle the open range. + EXPECT_TRUE(r2.Intersects(r1)); + EXPECT_TRUE(r4.Intersects(r1)); + + // Ensures larger ranges intersects smaller ranges. + EXPECT_TRUE(r6.Intersects(r1)); + EXPECT_TRUE(r6.Intersects(r2)); + EXPECT_TRUE(r6.Intersects(r3)); + EXPECT_TRUE(r6.Intersects(r4)); + EXPECT_TRUE(r6.Intersects(r5)); + + EXPECT_TRUE(r7.Intersects(r1)); + EXPECT_TRUE(r7.Intersects(r2)); + EXPECT_TRUE(r7.Intersects(r3)); + EXPECT_TRUE(r7.Intersects(r4)); + EXPECT_TRUE(r7.Intersects(r5)); + + // Ensures adjacent ranges don't overlap. + EXPECT_FALSE(r2.Intersects(r3)); + EXPECT_FALSE(r5.Intersects(r4)); + + // Ensures empty ranges are handled correctly. + EXPECT_FALSE(r1.Contains(r8)); + EXPECT_FALSE(r2.Contains(r8)); + EXPECT_TRUE(r3.Contains(r8)); + EXPECT_TRUE(r8.Contains(r8)); + + EXPECT_FALSE(r1.Intersects(r8)); + EXPECT_FALSE(r2.Intersects(r8)); + EXPECT_TRUE(r3.Intersects(r8)); + EXPECT_TRUE(r8.Intersects(r8)); +} + +TEST(RangeTest, ContainsAndIntersectsRangeF) { + constexpr gfx::RangeF r1(0.f, 0.f); + constexpr gfx::RangeF r2(0.f, 1.f); + constexpr gfx::RangeF r3(1.f, 2.f); + constexpr gfx::RangeF r4(1.f, 0.f); + constexpr gfx::RangeF r5(2.f, 1.f); + constexpr gfx::RangeF r6(0.f, 2.f); + constexpr gfx::RangeF r7(2.f, 0.f); + constexpr gfx::RangeF r8(1.f, 1.f); + + // Ensures Contains(...) handle the open range. + EXPECT_TRUE(r2.Contains(r1)); + EXPECT_TRUE(r4.Contains(r1)); + EXPECT_TRUE(r3.Contains(r5)); + EXPECT_TRUE(r5.Contains(r3)); + + // Ensures larger ranges contains smaller ranges. + EXPECT_TRUE(r6.Contains(r1)); + EXPECT_TRUE(r6.Contains(r2)); + EXPECT_TRUE(r6.Contains(r3)); + EXPECT_TRUE(r6.Contains(r4)); + EXPECT_TRUE(r6.Contains(r5)); + + EXPECT_TRUE(r7.Contains(r1)); + EXPECT_TRUE(r7.Contains(r2)); + EXPECT_TRUE(r7.Contains(r3)); + EXPECT_TRUE(r7.Contains(r4)); + EXPECT_TRUE(r7.Contains(r5)); + + // Ensures Intersects(...) handle the open range. + EXPECT_TRUE(r2.Intersects(r1)); + EXPECT_TRUE(r4.Intersects(r1)); + + // Ensures larger ranges intersects smaller ranges. + EXPECT_TRUE(r6.Intersects(r1)); + EXPECT_TRUE(r6.Intersects(r2)); + EXPECT_TRUE(r6.Intersects(r3)); + EXPECT_TRUE(r6.Intersects(r4)); + EXPECT_TRUE(r6.Intersects(r5)); + + EXPECT_TRUE(r7.Intersects(r1)); + EXPECT_TRUE(r7.Intersects(r2)); + EXPECT_TRUE(r7.Intersects(r3)); + EXPECT_TRUE(r7.Intersects(r4)); + EXPECT_TRUE(r7.Intersects(r5)); + + // Ensures adjacent ranges don't overlap. + EXPECT_FALSE(r2.Intersects(r3)); + EXPECT_FALSE(r5.Intersects(r4)); + + // Ensures empty ranges are handled correctly. + EXPECT_FALSE(r1.Contains(r8)); + EXPECT_FALSE(r2.Contains(r8)); + EXPECT_TRUE(r3.Contains(r8)); + EXPECT_TRUE(r8.Contains(r8)); + + EXPECT_FALSE(r1.Intersects(r8)); + EXPECT_FALSE(r2.Intersects(r8)); + EXPECT_TRUE(r3.Intersects(r8)); + EXPECT_TRUE(r8.Intersects(r8)); +} + +TEST(RangeTest, Intersect) { + EXPECT_EQ(gfx::Range(0, 1).Intersect({0, 1}), gfx::Range(0, 1)); + EXPECT_EQ(gfx::Range(0, 1).Intersect({0, 0}), gfx::Range(0, 0)); + EXPECT_EQ(gfx::Range(0, 0).Intersect({1, 0}), gfx::Range(0, 0)); + EXPECT_EQ(gfx::Range(0, 4).Intersect({2, 3}), gfx::Range(2, 3)); + EXPECT_EQ(gfx::Range(0, 4).Intersect({2, 7}), gfx::Range(2, 4)); + EXPECT_EQ(gfx::Range(0, 4).Intersect({3, 4}), gfx::Range(3, 4)); + + EXPECT_EQ(gfx::Range(0, 1).Intersect({1, 1}), gfx::Range::InvalidRange()); + EXPECT_EQ(gfx::Range(1, 1).Intersect({1, 0}), gfx::Range::InvalidRange()); + EXPECT_EQ(gfx::Range(0, 1).Intersect({1, 2}), gfx::Range::InvalidRange()); + EXPECT_EQ(gfx::Range(0, 1).Intersect({2, 1}), gfx::Range::InvalidRange()); + EXPECT_EQ(gfx::Range(2, 1).Intersect({1, 0}), gfx::Range::InvalidRange()); +} + +TEST(RangeTest, IntersectRangeF) { + EXPECT_EQ(gfx::RangeF(0.f, 1.f).Intersect(gfx::RangeF(0.f, 1.f)), + gfx::RangeF(0.f, 1.f)); + EXPECT_EQ(gfx::RangeF(0.f, 1.f).Intersect(gfx::RangeF(0.f, 0.f)), + gfx::RangeF(0.f, 0.f)); + EXPECT_EQ(gfx::RangeF(0.f, 0.f).Intersect(gfx::RangeF(1.f, 0.f)), + gfx::RangeF(0.f, 0.f)); + EXPECT_EQ(gfx::RangeF(0.f, 4.f).Intersect(gfx::RangeF(2.f, 3.f)), + gfx::RangeF(2.f, 3.f)); + EXPECT_EQ(gfx::RangeF(0.f, 4.f).Intersect(gfx::RangeF(2.f, 7.f)), + gfx::RangeF(2.f, 4.f)); + EXPECT_EQ(gfx::RangeF(0.f, 4.f).Intersect(gfx::RangeF(3.f, 4.f)), + gfx::RangeF(3.f, 4.f)); + + EXPECT_EQ(gfx::RangeF(0.f, 1.f).Intersect(gfx::RangeF(1.f, 1.f)), + gfx::RangeF::InvalidRange()); + EXPECT_EQ(gfx::RangeF(1.f, 1.f).Intersect(gfx::RangeF(1.f, 0.f)), + gfx::RangeF::InvalidRange()); + EXPECT_EQ(gfx::RangeF(0.f, 1.f).Intersect(gfx::RangeF(1.f, 2.f)), + gfx::RangeF::InvalidRange()); + EXPECT_EQ(gfx::RangeF(0.f, 1.f).Intersect(gfx::RangeF(2.f, 1.f)), + gfx::RangeF::InvalidRange()); + EXPECT_EQ(gfx::RangeF(2.f, 1.f).Intersect(gfx::RangeF(1.f, 0.f)), + gfx::RangeF::InvalidRange()); +} + +TEST(RangeTest, IsBoundedBy) { + constexpr gfx::Range r1(0, 0); + constexpr gfx::Range r2(0, 1); + EXPECT_TRUE(r1.IsBoundedBy(r1)); + EXPECT_FALSE(r2.IsBoundedBy(r1)); + + constexpr gfx::Range r3(0, 2); + constexpr gfx::Range r4(2, 2); + EXPECT_TRUE(r4.IsBoundedBy(r3)); + EXPECT_FALSE(r3.IsBoundedBy(r4)); +} + +TEST(RangeTest, ToString) { + gfx::Range range(4, 7); + EXPECT_EQ("{4,7}", range.ToString()); + + range = gfx::Range::InvalidRange(); + std::ostringstream expected; + expected << "{" << range.start() << "," << range.end() << "}"; + EXPECT_EQ(expected.str(), range.ToString()); +} diff --git a/render_text.cc b/render_text.cc new file mode 100644 index 000000000000..99120dd6d7fb --- /dev/null +++ b/render_text.cc @@ -0,0 +1,2351 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/render_text.h" + +#include + +#include +#include + +#include "base/check_op.h" +#include "base/command_line.h" +#include "base/cxx17_backports.h" +#include "base/i18n/break_iterator.h" +#include "base/i18n/char_iterator.h" +#include "base/notreached.h" +#include "base/numerics/safe_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/trace_event/trace_event.h" +#include "build/build_config.h" +#include "cc/paint/paint_canvas.h" +#include "cc/paint/paint_shader.h" +#include "third_party/icu/source/common/unicode/rbbi.h" +#include "third_party/icu/source/common/unicode/uchar.h" +#include "third_party/icu/source/common/unicode/utf16.h" +#include "third_party/skia/include/core/SkDrawLooper.h" +#include "third_party/skia/include/core/SkFontStyle.h" +#include "third_party/skia/include/core/SkTextBlob.h" +#include "third_party/skia/include/core/SkTypeface.h" +#include "third_party/skia/include/effects/SkGradientShader.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/geometry/insets.h" +#include "ui/gfx/geometry/size_conversions.h" +#include "ui/gfx/geometry/skia_conversions.h" +#include "ui/gfx/platform_font.h" +#include "ui/gfx/render_text_harfbuzz.h" +#include "ui/gfx/scoped_canvas.h" +#include "ui/gfx/skia_paint_util.h" +#include "ui/gfx/text_elider.h" +#include "ui/gfx/text_utils.h" +#include "ui/gfx/utf16_indexing.h" + +#if defined(OS_WIN) +#include "base/win/windows_version.h" +#endif + +namespace gfx { + +namespace { + +// Replacement codepoint for elided text. +constexpr char16_t kEllipsisCodepoint = 0x2026; + +// Fraction of the text size to raise the center of a strike-through line above +// the baseline. +const SkScalar kStrikeThroughOffset = (SK_Scalar1 * 65 / 252); +// Fraction of the text size to lower an underline below the baseline. +const SkScalar kUnderlineOffset = (SK_Scalar1 / 9); + +// Float comparison needs epsilon to consider rounding errors in float +// arithmetic. Epsilon should be dependent on the context and here, we are +// dealing with glyph widths, use a fairly large number. +const float kFloatComparisonEpsilon = 0.001f; +float Clamp(float f) { + return f < kFloatComparisonEpsilon ? 0 : f; +} + +// Given |font| and |display_width|, returns the width of the fade gradient. +int CalculateFadeGradientWidth(const FontList& font_list, int display_width) { + // Fade in/out about 3 characters of the beginning/end of the string. + // Use a 1/3 of the display width if the display width is very short. + const int narrow_width = font_list.GetExpectedTextWidth(3); + const int gradient_width = + std::min(narrow_width, base::ClampRound(display_width / 3.f)); + DCHECK_GE(gradient_width, 0); + return gradient_width; +} + +// Appends to |positions| and |colors| values corresponding to the fade over +// |fade_rect| from color |c0| to color |c1|. +void AddFadeEffect(const Rect& text_rect, + const Rect& fade_rect, + SkColor c0, + SkColor c1, + std::vector* positions, + std::vector* colors) { + const SkScalar left = static_cast(fade_rect.x() - text_rect.x()); + const SkScalar width = static_cast(fade_rect.width()); + const SkScalar p0 = left / text_rect.width(); + const SkScalar p1 = (left + width) / text_rect.width(); + // Prepend 0.0 to |positions|, as required by Skia. + if (positions->empty() && p0 != 0.0) { + positions->push_back(0.0); + colors->push_back(c0); + } + positions->push_back(p0); + colors->push_back(c0); + positions->push_back(p1); + colors->push_back(c1); +} + +// Creates a SkShader to fade the text, with |left_part| specifying the left +// fade effect, if any, and |right_part| specifying the right fade effect. +sk_sp CreateFadeShader(const FontList& font_list, + const Rect& text_rect, + const Rect& left_part, + const Rect& right_part, + SkColor color) { + // The shader should only specify transparency of the fade itself, not the + // original transparency, which will be applied by the actual renderer. + DCHECK_EQ(SkColorGetA(color), static_cast(0xff)); + + // In general, fade down to 0 alpha. But when the available width is less + // than four characters, linearly ramp up the fade target alpha to as high as + // 20% at zero width. This allows the user to see the last faded characters a + // little better when there are only a few characters shown. + const float width_fraction = + text_rect.width() / static_cast(font_list.GetExpectedTextWidth(4)); + const SkAlpha kAlphaAtZeroWidth = 51; + const SkAlpha alpha = + (width_fraction < 1) + ? base::ClampRound((1 - width_fraction) * kAlphaAtZeroWidth) + : 0; + const SkColor fade_color = SkColorSetA(color, alpha); + + std::vector positions; + std::vector colors; + + if (!left_part.IsEmpty()) + AddFadeEffect(text_rect, left_part, fade_color, color, + &positions, &colors); + if (!right_part.IsEmpty()) + AddFadeEffect(text_rect, right_part, color, fade_color, + &positions, &colors); + DCHECK(!positions.empty()); + + // Terminate |positions| with 1.0, as required by Skia. + if (positions.back() != 1.0) { + positions.push_back(1.0); + colors.push_back(colors.back()); + } + + const SkPoint points[2] = { PointToSkPoint(text_rect.origin()), + PointToSkPoint(text_rect.top_right()) }; + return cc::PaintShader::MakeLinearGradient( + &points[0], &colors[0], &positions[0], static_cast(colors.size()), + SkTileMode::kClamp); +} + +// Converts a FontRenderParams::Hinting value to the corresponding +// SkFontHinting value. +SkFontHinting FontRenderParamsHintingToSkFontHinting( + FontRenderParams::Hinting params_hinting) { + switch (params_hinting) { + case FontRenderParams::HINTING_NONE: + return SkFontHinting::kNone; + case FontRenderParams::HINTING_SLIGHT: + return SkFontHinting::kSlight; + case FontRenderParams::HINTING_MEDIUM: + return SkFontHinting::kNormal; + case FontRenderParams::HINTING_FULL: + return SkFontHinting::kFull; + } + return SkFontHinting::kNone; +} + +// Make sure ranges don't break text graphemes. If a range in |break_list| +// does break a grapheme in |render_text|, the range will be slightly +// extended to encompass the grapheme. +template +void RestoreBreakList(RenderText* render_text, BreakList* break_list) { + break_list->SetMax(render_text->text().length()); + Range range; + while (range.end() < break_list->max()) { + const auto& current_break = break_list->GetBreak(range.end()); + range = break_list->GetRange(current_break); + if (range.end() < break_list->max() && + !render_text->IsValidCursorIndex(range.end())) { + range.set_end( + render_text->IndexOfAdjacentGrapheme(range.end(), CURSOR_FORWARD)); + break_list->ApplyValue(current_break->second, range); + } + } +} + +// Move the iterator |iter| forward until |position| is included in the range. +template +typename BreakList::const_iterator IncrementBreakListIteratorToPosition( + const BreakList& break_list, + typename BreakList::const_iterator iter, + size_t position) { + for (; iter != break_list.breaks().end(); ++iter) { + const Range range = break_list.GetRange(iter); + if (position >= range.start() && position < range.end()) + break; + } + return iter; +} + +// Replaces the unicode control characters, control characters and PUA (Private +// Use Areas) codepoints. +UChar32 ReplaceControlCharacter(UChar32 codepoint) { + // 'REPLACEMENT CHARACTER' used to replace an unknown, + // unrecognized or unrepresentable character. + constexpr char16_t kReplacementCodepoint = 0xFFFD; + // Control Pictures block (see: + // https://unicode.org/charts/PDF/U2400.pdf). + constexpr char16_t kSymbolsCodepoint = 0x2400; + + if (codepoint >= 0 && codepoint <= 0x1F) { + // Replace codepoints with their visual symbols, which are + // at the same offset from kSymbolsCodepoint. + return kSymbolsCodepoint + codepoint; + } + if (codepoint == 0x7F) { + // Replace the 'del' codepoint by its symbol (u2421). + return kSymbolsCodepoint + 0x21; + } + if (!U_IS_UNICODE_CHAR(codepoint)) { + // Unicode codepoint that can't be assigned a character. + // This handles: + // - single surrogate codepoints, + // - last two codepoints on each plane, + // - invalid characters (e.g. u+fdd0..u+fdef) + // - codepoints above u+10ffff + return kReplacementCodepoint; + } + if (codepoint > 0x7F) { + // Private use codepoints are working with a pair of font + // and codepoint, but they are not used in Chrome. +#if defined(OS_MAC) + // Support Apple defined PUA on Mac. + // see: http://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/CORPCHAR.TXT + if (codepoint == 0xF8FF) + return codepoint; +#endif +#if defined(OS_WIN) + // Support Microsoft defined PUA on Windows. + // see: + // https://docs.microsoft.com/en-us/windows/uwp/design/style/segoe-ui-symbol-font + if (base::win::GetVersion() >= base::win::Version::WIN10) { + switch (codepoint) { + case 0xF093: // ButtonA + case 0xF094: // ButtonB + case 0xF095: // ButtonY + case 0xF096: // ButtonX + case 0xF108: // LeftStick + case 0xF109: // RightStick + case 0xF10A: // TriggerLeft + case 0xF10B: // TriggerRight + case 0xF10C: // BumperLeft + case 0xF10D: // BumperRight + case 0xF10E: // Dpad + case 0xEECA: // ButtonView2 + case 0xEDE3: // ButtonMenu + return codepoint; + default: + break; + } + } +#endif + const int8_t codepoint_category = u_charType(codepoint); + if (codepoint_category == U_PRIVATE_USE_CHAR || + codepoint_category == U_CONTROL_CHAR) { + return kReplacementCodepoint; + } + } + + return codepoint; +} + +// Returns the line segment index for the |line|, |text_x| pair. |text_x| is +// relative to text in the given line. Returns -1 if |text_x| is to the left +// of text in the line and |line|.segments.size() if it's to the right. +// |offset_relative_segment| will contain the offset of |text_x| relative to +// the start of the segment it is contained in. +int GetLineSegmentContainingXCoord(const internal::Line& line, + float line_x, + float* offset_relative_segment) { + DCHECK(offset_relative_segment); + + *offset_relative_segment = 0; + if (line_x < 0) + return -1; + for (size_t i = 0; i < line.segments.size(); i++) { + const internal::LineSegment& segment = line.segments[i]; + // segment.x_range is not used because it is in text space. + if (line_x < segment.width()) { + *offset_relative_segment = line_x; + return i; + } + line_x -= segment.width(); + } + return line.segments.size(); +} + +} // namespace + +namespace internal { + +SkiaTextRenderer::SkiaTextRenderer(Canvas* canvas) + : canvas_(canvas), canvas_skia_(canvas->sk_canvas()) { + DCHECK(canvas_skia_); + flags_.setStyle(cc::PaintFlags::kFill_Style); + + font_.setEdging(SkFont::Edging::kSubpixelAntiAlias); + font_.setSubpixel(true); + font_.setHinting(SkFontHinting::kNormal); +} + +SkiaTextRenderer::~SkiaTextRenderer() { +} + +void SkiaTextRenderer::SetDrawLooper(sk_sp draw_looper) { + flags_.setLooper(std::move(draw_looper)); +} + +void SkiaTextRenderer::SetFontRenderParams(const FontRenderParams& params, + bool subpixel_rendering_suppressed) { + ApplyRenderParams(params, subpixel_rendering_suppressed, &font_); +} + +void SkiaTextRenderer::SetTypeface(sk_sp typeface) { + font_.setTypeface(std::move(typeface)); +} + +void SkiaTextRenderer::SetTextSize(SkScalar size) { + font_.setSize(size); +} + +void SkiaTextRenderer::SetForegroundColor(SkColor foreground) { + flags_.setColor(foreground); +} + +void SkiaTextRenderer::SetShader(sk_sp shader) { + flags_.setShader(std::move(shader)); +} + +void SkiaTextRenderer::DrawPosText(const SkPoint* pos, + const uint16_t* glyphs, + size_t glyph_count) { + SkTextBlobBuilder builder; + const auto& run_buffer = builder.allocRunPos(font_, glyph_count); + + static_assert(sizeof(*glyphs) == sizeof(*run_buffer.glyphs), ""); + memcpy(run_buffer.glyphs, glyphs, glyph_count * sizeof(*glyphs)); + + static_assert(sizeof(*pos) == 2 * sizeof(*run_buffer.pos), ""); + memcpy(run_buffer.pos, pos, glyph_count * sizeof(*pos)); + + canvas_skia_->drawTextBlob(builder.make(), 0, 0, flags_); +} + +void SkiaTextRenderer::DrawUnderline(int x, + int y, + int width, + SkScalar thickness_factor) { + SkScalar x_scalar = SkIntToScalar(x); + const SkScalar text_size = font_.getSize(); + SkRect r = SkRect::MakeLTRB( + x_scalar, y + text_size * kUnderlineOffset, x_scalar + width, + y + (text_size * + (kUnderlineOffset + + (thickness_factor * RenderText::kLineThicknessFactor)))); + canvas_skia_->drawRect(r, flags_); +} + +void SkiaTextRenderer::DrawStrike(int x, + int y, + int width, + SkScalar thickness_factor) { + const SkScalar text_size = font_.getSize(); + const SkScalar height = text_size * thickness_factor; + const SkScalar top = y - text_size * kStrikeThroughOffset - height / 2; + SkScalar x_scalar = SkIntToScalar(x); + const SkRect r = + SkRect::MakeLTRB(x_scalar, top, x_scalar + width, top + height); + canvas_skia_->drawRect(r, flags_); +} + +StyleIterator::StyleIterator(const BreakList* colors, + const BreakList* baselines, + const BreakList* font_size_overrides, + const BreakList* weights, + const StyleArray* styles) + : colors_(colors), + baselines_(baselines), + font_size_overrides_(font_size_overrides), + weights_(weights), + styles_(styles) { + color_ = colors_->breaks().begin(); + baseline_ = baselines_->breaks().begin(); + font_size_override_ = font_size_overrides_->breaks().begin(); + weight_ = weights_->breaks().begin(); + for (size_t i = 0; i < styles_->size(); ++i) + style_[i] = (*styles_)[i].breaks().begin(); +} + +StyleIterator::StyleIterator(const StyleIterator& style) = default; +StyleIterator::~StyleIterator() = default; +StyleIterator& StyleIterator::operator=(const StyleIterator& style) = default; + +Range StyleIterator::GetRange() const { + return GetTextBreakingRange().Intersect(colors_->GetRange(color_)); +} + +Range StyleIterator::GetTextBreakingRange() const { + Range range = baselines_->GetRange(baseline_); + range = range.Intersect(font_size_overrides_->GetRange(font_size_override_)); + range = range.Intersect(weights_->GetRange(weight_)); + for (size_t i = 0; i < styles_->size(); ++i) + range = range.Intersect((*styles_)[i].GetRange(style_[i])); + return range; +} + +void StyleIterator::IncrementToPosition(size_t position) { + color_ = IncrementBreakListIteratorToPosition(*colors_, color_, position); + baseline_ = + IncrementBreakListIteratorToPosition(*baselines_, baseline_, position); + font_size_override_ = IncrementBreakListIteratorToPosition( + *font_size_overrides_, font_size_override_, position); + weight_ = IncrementBreakListIteratorToPosition(*weights_, weight_, position); + for (size_t i = 0; i < styles_->size(); ++i) { + style_[i] = IncrementBreakListIteratorToPosition((*styles_)[i], style_[i], + position); + } +} + +LineSegment::LineSegment() : run(0) {} + +LineSegment::~LineSegment() {} + +Line::Line() : preceding_heights(0), baseline(0) {} + +Line::Line(const Line& other) = default; + +Line::~Line() {} + +ShapedText::ShapedText(std::vector lines) : lines_(std::move(lines)) {} +ShapedText::~ShapedText() = default; + +void ApplyRenderParams(const FontRenderParams& params, + bool subpixel_rendering_suppressed, + SkFont* font) { + if (!params.antialiasing) { + font->setEdging(SkFont::Edging::kAlias); + } else if (subpixel_rendering_suppressed || + params.subpixel_rendering == + FontRenderParams::SUBPIXEL_RENDERING_NONE) { + font->setEdging(SkFont::Edging::kAntiAlias); + } else { + font->setEdging(SkFont::Edging::kSubpixelAntiAlias); + } + + font->setSubpixel(params.subpixel_positioning); + font->setForceAutoHinting(params.autohinter); + font->setHinting(FontRenderParamsHintingToSkFontHinting(params.hinting)); +} + +} // namespace internal + +// static +constexpr char16_t RenderText::kPasswordReplacementChar; +constexpr bool RenderText::kDragToEndIfOutsideVerticalBounds; +constexpr int RenderText::kInvalidBaseline; +constexpr SkScalar RenderText::kLineThicknessFactor; + +RenderText::~RenderText() = default; + +// static +std::unique_ptr RenderText::CreateRenderText() { + return std::make_unique(); +} + +std::unique_ptr RenderText::CreateInstanceOfSameStyle( + const std::u16string& text) const { + std::unique_ptr render_text = CreateRenderText(); + // |SetText()| must be called before styles are set. + render_text->SetText(text); + render_text->SetFontList(font_list_); + render_text->SetDirectionalityMode(directionality_mode_); + render_text->SetCursorEnabled(cursor_enabled_); + render_text->set_truncate_length(truncate_length_); + render_text->styles_ = styles_; + render_text->baselines_ = baselines_; + render_text->font_size_overrides_ = font_size_overrides_; + render_text->colors_ = colors_; + render_text->weights_ = weights_; + render_text->glyph_width_for_test_ = glyph_width_for_test_; + return render_text; +} + +void RenderText::SetText(const std::u16string& text) { + DCHECK(!composition_range_.IsValid()); + if (text_ == text) + return; + text_ = text; + UpdateStyleLengths(); + + // Clear style ranges as they might break new text graphemes and apply + // the first style to the whole text instead. + colors_.SetValue(colors_.breaks().front().second); + baselines_.SetValue(baselines_.breaks().front().second); + font_size_overrides_.SetValue(font_size_overrides_.breaks().front().second); + weights_.SetValue(weights_.breaks().front().second); + for (auto& style : styles_) + style.SetValue(style.breaks().front().second); + cached_bounds_and_offset_valid_ = false; + + // Reset selection model. SetText should always followed by SetSelectionModel + // or SetCursorPosition in upper layer. + SetSelectionModel(SelectionModel()); + + // Invalidate the cached text direction if it depends on the text contents. + if (directionality_mode_ == DIRECTIONALITY_FROM_TEXT) + text_direction_ = base::i18n::UNKNOWN_DIRECTION; + + obscured_reveal_index_ = -1; + OnTextAttributeChanged(); +} + +void RenderText::AppendText(const std::u16string& text) { + text_ += text; + UpdateStyleLengths(); + cached_bounds_and_offset_valid_ = false; + obscured_reveal_index_ = -1; + + // Invalidate the cached text direction if it depends on the text contents. + if (directionality_mode_ == DIRECTIONALITY_FROM_TEXT) + text_direction_ = base::i18n::UNKNOWN_DIRECTION; + + OnTextAttributeChanged(); +} + +void RenderText::SetHorizontalAlignment(HorizontalAlignment alignment) { + if (horizontal_alignment_ != alignment) { + horizontal_alignment_ = alignment; + display_offset_ = Vector2d(); + cached_bounds_and_offset_valid_ = false; + } +} + +void RenderText::SetVerticalAlignment(VerticalAlignment alignment) { + if (vertical_alignment_ != alignment) { + vertical_alignment_ = alignment; + display_offset_ = Vector2d(); + cached_bounds_and_offset_valid_ = false; + } +} + +void RenderText::SetFontList(const FontList& font_list) { + font_list_ = font_list; + const int font_style = font_list.GetFontStyle(); + weights_.SetValue(font_list.GetFontWeight()); + styles_[TEXT_STYLE_ITALIC].SetValue((font_style & Font::ITALIC) != 0); + styles_[TEXT_STYLE_UNDERLINE].SetValue((font_style & Font::UNDERLINE) != 0); + styles_[TEXT_STYLE_HEAVY_UNDERLINE].SetValue(false); + baseline_ = kInvalidBaseline; + cached_bounds_and_offset_valid_ = false; + OnLayoutTextAttributeChanged(false); +} + +void RenderText::SetCursorEnabled(bool cursor_enabled) { + cursor_enabled_ = cursor_enabled; + cached_bounds_and_offset_valid_ = false; +} + +void RenderText::SetObscured(bool obscured) { + if (obscured != obscured_) { + obscured_ = obscured; + obscured_reveal_index_ = -1; + cached_bounds_and_offset_valid_ = false; + OnTextAttributeChanged(); + } +} + +void RenderText::SetObscuredRevealIndex(int index) { + if (obscured_reveal_index_ != index) { + obscured_reveal_index_ = index; + cached_bounds_and_offset_valid_ = false; + OnTextAttributeChanged(); + } +} + +void RenderText::SetObscuredGlyphSpacing(int spacing) { + if (obscured_glyph_spacing_ != spacing) { + obscured_glyph_spacing_ = spacing; + OnLayoutTextAttributeChanged(true); + } +} + +void RenderText::SetMultiline(bool multiline) { + if (multiline != multiline_) { + multiline_ = multiline; + cached_bounds_and_offset_valid_ = false; + OnTextAttributeChanged(); + } +} + +void RenderText::SetMaxLines(size_t max_lines) { + max_lines_ = max_lines; + OnDisplayTextAttributeChanged(); +} + +size_t RenderText::GetNumLines() { + return GetShapedText()->lines().size(); +} + +size_t RenderText::GetTextIndexOfLine(size_t line) { + const std::vector& lines = GetShapedText()->lines(); + if (line >= lines.size()) + return text_.size(); + return DisplayIndexToTextIndex(lines[line].display_text_index); +} + +void RenderText::SetWordWrapBehavior(WordWrapBehavior behavior) { + // TODO(1150235): ELIDE_LONG_WORDS is not supported. + DCHECK_NE(behavior, ELIDE_LONG_WORDS); + + if (word_wrap_behavior_ != behavior) { + word_wrap_behavior_ = behavior; + if (multiline_) { + cached_bounds_and_offset_valid_ = false; + OnTextAttributeChanged(); + } + } +} + +void RenderText::SetMinLineHeight(int line_height) { + if (min_line_height_ != line_height) { + min_line_height_ = line_height; + cached_bounds_and_offset_valid_ = false; + OnDisplayTextAttributeChanged(); + } +} + +void RenderText::SetElideBehavior(ElideBehavior elide_behavior) { + if (elide_behavior_ != elide_behavior) { + elide_behavior_ = elide_behavior; + OnDisplayTextAttributeChanged(); + } +} + +void RenderText::SetWhitespaceElision(absl::optional whitespace_elision) { + if (whitespace_elision_ != whitespace_elision) { + whitespace_elision_ = whitespace_elision; + OnDisplayTextAttributeChanged(); + } +} + +void RenderText::SetDisplayRect(const Rect& r) { + if (r != display_rect_) { + display_rect_ = r; + baseline_ = kInvalidBaseline; + cached_bounds_and_offset_valid_ = false; + OnDisplayTextAttributeChanged(); + } +} + +const std::vector RenderText::GetAllSelections() const { + return selection_model_.GetAllSelections(); +} + +void RenderText::SetCursorPosition(size_t position) { + size_t cursor = std::min(position, text().length()); + if (IsValidCursorIndex(cursor)) { + SetSelectionModel(SelectionModel( + cursor, (cursor == 0) ? CURSOR_FORWARD : CURSOR_BACKWARD)); + } +} + +void RenderText::MoveCursor(BreakType break_type, + VisualCursorDirection direction, + SelectionBehavior selection_behavior) { + SelectionModel cursor(cursor_position(), selection_model_.caret_affinity()); + + // Ensure |cursor| is at the "end" of the current selection, since this + // determines which side should grow or shrink. If the prior change to the + // selection wasn't from cursor movement, the selection may be undirected. Or, + // the selection may be collapsing. In these cases, pick the "end" using + // |direction| (e.g. the arrow key) rather than the current selection range. + if ((!has_directed_selection_ || selection_behavior == SELECTION_NONE) && + !selection().is_empty()) { + SelectionModel selection_start = GetSelectionModelForSelectionStart(); + Point start = GetCursorBounds(selection_start, true).origin(); + Point end = GetCursorBounds(cursor, true).origin(); + + // Use the selection start if it is left (when |direction| is CURSOR_LEFT) + // or right (when |direction| is CURSOR_RIGHT) of the selection end. + // Consider only the y-coordinates if the selection start and end are on + // different lines. + const bool cursor_is_leading = + (start.y() > end.y()) || + ((start.y() == end.y()) && (start.x() > end.x())); + const bool cursor_should_be_trailing = + (direction == CURSOR_RIGHT) || (direction == CURSOR_DOWN); + if (cursor_is_leading == cursor_should_be_trailing) { + // In this case, a direction has been chosen that doesn't match + // |selection_model|, so the range must be reversed to place the cursor at + // the other end. Note the affinity won't matter: only the affinity of + // |start| (which points "in" to the selection) determines the movement. + Range range = selection_model_.selection(); + selection_model_ = SelectionModel(Range(range.end(), range.start()), + selection_model_.caret_affinity()); + cursor = selection_start; + } + } + + // Cancelling a selection moves to the edge of the selection. + if (break_type != FIELD_BREAK && break_type != LINE_BREAK && + !selection().is_empty() && selection_behavior == SELECTION_NONE) { + // Use the nearest word boundary in the proper |direction| for word breaks. + if (break_type == WORD_BREAK) + cursor = GetAdjacentSelectionModel(cursor, break_type, direction); + // Use an adjacent selection model if the cursor is not at a valid position. + if (!IsValidCursorIndex(cursor.caret_pos())) + cursor = GetAdjacentSelectionModel(cursor, CHARACTER_BREAK, direction); + } else { + cursor = GetAdjacentSelectionModel(cursor, break_type, direction); + } + + // |cursor| corresponds to the tentative end point of the new selection. The + // selection direction is reversed iff the current selection is non-empty and + // the old selection end point and |cursor| are at the opposite ends of the + // old selection start point. + uint32_t min_end = std::min(selection().end(), cursor.selection().end()); + uint32_t max_end = std::max(selection().end(), cursor.selection().end()); + uint32_t current_start = selection().start(); + + bool selection_reversed = !selection().is_empty() && + min_end <= current_start && + current_start <= max_end; + + // Take |selection_behavior| into account. + switch (selection_behavior) { + case SELECTION_RETAIN: + cursor.set_selection_start(current_start); + break; + case SELECTION_EXTEND: + cursor.set_selection_start(selection_reversed ? selection().end() + : current_start); + break; + case SELECTION_CARET: + if (selection_reversed) { + cursor = + SelectionModel(current_start, selection_model_.caret_affinity()); + } else { + cursor.set_selection_start(current_start); + } + break; + case SELECTION_NONE: + // Do nothing. + break; + } + + SetSelection(cursor); + has_directed_selection_ = true; + + // |cached_cursor_x| keeps the initial x-coordinates where CURSOR_UP or + // CURSOR_DOWN starts. This enables the cursor to keep the same x-coordinates + // even when the cursor passes through empty or short lines. The cached + // x-coordinates should be reset when the cursor moves in a horizontal + // direction. + if (direction != CURSOR_UP && direction != CURSOR_DOWN) + reset_cached_cursor_x(); +} + +bool RenderText::SetSelection(const SelectionModel& model) { + // Enforce valid selection model components. + uint32_t text_length = static_cast(text().length()); + std::vector ranges = model.GetAllSelections(); + for (auto& range : ranges) { + range = {std::min(range.start(), text_length), + std::min(range.end(), text_length)}; + // The current model only supports caret positions at valid cursor indices. + if (!IsValidCursorIndex(range.start()) || !IsValidCursorIndex(range.end())) + return false; + } + SelectionModel sel = SelectionModel(ranges, model.caret_affinity()); + bool changed = sel != selection_model_; + SetSelectionModel(sel); + return changed; +} + +bool RenderText::MoveCursorToPoint(const Point& point, + bool select, + const Point& drag_origin) { + reset_cached_cursor_x(); + SelectionModel model = FindCursorPosition(point, drag_origin); + if (select) + model.set_selection_start(selection().start()); + return SetSelection(model); +} + +bool RenderText::SelectRange(const Range& range, bool primary) { + uint32_t text_length = static_cast(text().length()); + Range sel(std::min(range.start(), text_length), + std::min(range.end(), text_length)); + // Allow selection bounds at valid indices amid multi-character graphemes. + if (!IsValidLogicalIndex(sel.start()) || !IsValidLogicalIndex(sel.end())) + return false; + if (primary) { + LogicalCursorDirection affinity = (sel.is_reversed() || sel.is_empty()) + ? CURSOR_FORWARD + : CURSOR_BACKWARD; + SetSelectionModel(SelectionModel(sel, affinity)); + } else { + AddSecondarySelection(sel); + } + return true; +} + +bool RenderText::IsPointInSelection(const Point& point) { + if (selection().is_empty()) + return false; + SelectionModel cursor = FindCursorPosition(point); + return RangeContainsCaret( + selection(), cursor.caret_pos(), cursor.caret_affinity()); +} + +void RenderText::ClearSelection() { + SetSelectionModel( + SelectionModel(cursor_position(), selection_model_.caret_affinity())); +} + +void RenderText::SelectAll(bool reversed) { + const size_t length = text().length(); + const Range all = reversed ? Range(length, 0) : Range(0, length); + const bool success = SelectRange(all); + DCHECK(success); +} + +void RenderText::SelectWord() { + SelectRange(ExpandRangeToWordBoundary(selection())); +} + +void RenderText::SetCompositionRange(const Range& composition_range) { + CHECK(!composition_range.IsValid() || + Range(0, text_.length()).Contains(composition_range)); + composition_range_.set_end(composition_range.end()); + composition_range_.set_start(composition_range.start()); + OnLayoutTextAttributeChanged(false); +} + +void RenderText::SetColor(SkColor value) { + colors_.SetValue(value); + OnLayoutTextAttributeChanged(false); +} + +void RenderText::ApplyColor(SkColor value, const Range& range) { + colors_.ApplyValue(value, range); + OnLayoutTextAttributeChanged(false); +} + +void RenderText::SetBaselineStyle(BaselineStyle value) { + baselines_.SetValue(value); + OnLayoutTextAttributeChanged(false); +} + +void RenderText::ApplyBaselineStyle(BaselineStyle value, const Range& range) { + baselines_.ApplyValue(value, range); + OnLayoutTextAttributeChanged(false); +} + +void RenderText::ApplyFontSizeOverride(int font_size_override, + const Range& range) { + font_size_overrides_.ApplyValue(font_size_override, range); + OnLayoutTextAttributeChanged(false); +} + +void RenderText::SetStyle(TextStyle style, bool value) { + styles_[style].SetValue(value); + + cached_bounds_and_offset_valid_ = false; + // TODO(oshima|msw): Not all style change requires layout changes. + // Consider optimizing based on the type of change. + OnLayoutTextAttributeChanged(false); +} + +void RenderText::ApplyStyle(TextStyle style, bool value, const Range& range) { + styles_[style].ApplyValue(value, range); + + cached_bounds_and_offset_valid_ = false; + // TODO(oshima|msw): Not all style change requires layout changes. + // Consider optimizing based on the type of change. + OnLayoutTextAttributeChanged(false); +} + +void RenderText::SetWeight(Font::Weight weight) { + weights_.SetValue(weight); + + cached_bounds_and_offset_valid_ = false; + OnLayoutTextAttributeChanged(false); +} + +void RenderText::ApplyWeight(Font::Weight weight, const Range& range) { + weights_.ApplyValue(weight, range); + + cached_bounds_and_offset_valid_ = false; + OnLayoutTextAttributeChanged(false); +} + +bool RenderText::GetStyle(TextStyle style) const { + return (styles_[style].breaks().size() == 1) && + styles_[style].breaks().front().second; +} + +void RenderText::SetDirectionalityMode(DirectionalityMode mode) { + if (mode != directionality_mode_) { + directionality_mode_ = mode; + text_direction_ = base::i18n::UNKNOWN_DIRECTION; + cached_bounds_and_offset_valid_ = false; + OnLayoutTextAttributeChanged(false); + } +} + +base::i18n::TextDirection RenderText::GetTextDirection() const { + if (text_direction_ == base::i18n::UNKNOWN_DIRECTION) + text_direction_ = GetTextDirectionForGivenText(text_); + return text_direction_; +} + +base::i18n::TextDirection RenderText::GetDisplayTextDirection() { + EnsureLayout(); + if (display_text_direction_ == base::i18n::UNKNOWN_DIRECTION) + display_text_direction_ = GetTextDirectionForGivenText(GetDisplayText()); + return display_text_direction_; +} + +VisualCursorDirection RenderText::GetVisualDirectionOfLogicalEnd() { + return GetDisplayTextDirection() == base::i18n::LEFT_TO_RIGHT ? CURSOR_RIGHT + : CURSOR_LEFT; +} + +VisualCursorDirection RenderText::GetVisualDirectionOfLogicalBeginning() { + return GetDisplayTextDirection() == base::i18n::RIGHT_TO_LEFT ? CURSOR_RIGHT + : CURSOR_LEFT; +} + +Size RenderText::GetStringSize() { + const SizeF size_f = GetStringSizeF(); + return Size(base::ClampCeil(size_f.width()), + base::ClampCeil(size_f.height())); +} + +float RenderText::TotalLineWidth() { + float total_width = 0; + const internal::ShapedText* shaped_text = GetShapedText(); + for (const auto& line : shaped_text->lines()) + total_width += line.size.width(); + return total_width; +} + +float RenderText::GetContentWidthF() { + const float string_size = GetStringSizeF().width(); + // The cursor is drawn one pixel beyond the int-enclosed text bounds. + return cursor_enabled_ ? std::ceil(string_size) + 1 : string_size; +} + +int RenderText::GetContentWidth() { + return base::ClampCeil(GetContentWidthF()); +} + +int RenderText::GetBaseline() { + if (baseline_ == kInvalidBaseline) { + baseline_ = + DetermineBaselineCenteringText(display_rect().height(), font_list()); + } + DCHECK_NE(kInvalidBaseline, baseline_); + return baseline_; +} + +void RenderText::Draw(Canvas* canvas, bool select_all) { + EnsureLayout(); + + if (clip_to_display_rect()) { + Rect clip_rect(display_rect()); + clip_rect.Inset(ShadowValue::GetMargin(shadows_)); + + canvas->Save(); + canvas->ClipRect(clip_rect); + } + + if (!text().empty()) { + std::vector draw_selections; + if (select_all) + draw_selections = {Range(0, text().length())}; + else if (focused()) + draw_selections = GetAllSelections(); + + DrawSelections(canvas, draw_selections); + internal::SkiaTextRenderer renderer(canvas); + DrawVisualText(&renderer, draw_selections); + } + + if (clip_to_display_rect()) + canvas->Restore(); +} + +SelectionModel RenderText::FindCursorPosition(const Point& view_point, + const Point& drag_origin) { + const internal::ShapedText* shaped_text = GetShapedText(); + DCHECK(!shaped_text->lines().empty()); + + int line_index = GetLineContainingYCoord((view_point - GetLineOffset(0)).y()); + // Handle kDragToEndIfOutsideVerticalBounds above or below the text in a + // single-line by extending towards the mouse cursor. + if (RenderText::kDragToEndIfOutsideVerticalBounds && !multiline() && + (line_index < 0 || + line_index >= static_cast(shaped_text->lines().size()))) { + SelectionModel selection_start = GetSelectionModelForSelectionStart(); + int edge = drag_origin.x() == 0 ? GetCursorBounds(selection_start, true).x() + : drag_origin.x(); + bool left = view_point.x() < edge; + return EdgeSelectionModel(left ? CURSOR_LEFT : CURSOR_RIGHT); + } + // Otherwise, clamp |line_index| to a valid value or drag to logical ends. + if (line_index < 0) { + if (RenderText::kDragToEndIfOutsideVerticalBounds) + return EdgeSelectionModel(GetVisualDirectionOfLogicalBeginning()); + line_index = 0; + } + if (line_index >= static_cast(shaped_text->lines().size())) { + if (RenderText::kDragToEndIfOutsideVerticalBounds) + return EdgeSelectionModel(GetVisualDirectionOfLogicalEnd()); + line_index = shaped_text->lines().size() - 1; + } + const internal::Line& line = shaped_text->lines()[line_index]; + // Newline segment should be ignored in finding segment index with x + // coordinate because it's not drawn. + Vector2d newline_offset; + if (line.segments.size() >= 1 && IsNewlineSegment(line.segments.front())) + newline_offset.set_x(line.segments.front().width()); + + float point_offset_relative_segment = 0; + const int segment_index = GetLineSegmentContainingXCoord( + line, (view_point - GetLineOffset(line_index) + newline_offset).x(), + &point_offset_relative_segment); + if (segment_index < 0) + return LineSelectionModel(line_index, CURSOR_LEFT); + if (segment_index >= static_cast(line.segments.size())) + return LineSelectionModel(line_index, CURSOR_RIGHT); + const internal::LineSegment& segment = line.segments[segment_index]; + + const internal::TextRunHarfBuzz& run = *GetRunList()->runs()[segment.run]; + const size_t segment_min_glyph_index = + run.CharRangeToGlyphRange(segment.char_range).GetMin(); + const float segment_offset_relative_run = + segment_min_glyph_index != 0 + ? SkScalarToFloat(run.shape.positions[segment_min_glyph_index].x()) + : 0; + const float point_offset_relative_run = + point_offset_relative_segment + segment_offset_relative_run; + + // TODO(crbug.com/676287): Use offset within the glyph to return the correct + // grapheme position within a multi-grapheme glyph. + for (size_t i = 0; i < run.shape.glyph_count; ++i) { + const float end = i + 1 == run.shape.glyph_count + ? run.shape.width + : SkScalarToFloat(run.shape.positions[i + 1].x()); + const float middle = + (end + SkScalarToFloat(run.shape.positions[i].x())) / 2; + const size_t index = DisplayIndexToTextIndex(run.shape.glyph_to_char[i]); + if (point_offset_relative_run < middle) { + return run.font_params.is_rtl ? SelectionModel(IndexOfAdjacentGrapheme( + index, CURSOR_FORWARD), + CURSOR_BACKWARD) + : SelectionModel(index, CURSOR_FORWARD); + } + if (point_offset_relative_run < end) { + return run.font_params.is_rtl ? SelectionModel(index, CURSOR_FORWARD) + : SelectionModel(IndexOfAdjacentGrapheme( + index, CURSOR_FORWARD), + CURSOR_BACKWARD); + } + } + + return LineSelectionModel(line_index, CURSOR_RIGHT); +} + +bool RenderText::IsValidLogicalIndex(size_t index) const { + // Check that the index is at a valid code point (not mid-surrogate-pair) and + // that it's not truncated from the display text (its glyph may be shown). + // + // Indices within truncated text are disallowed so users can easily interact + // with the underlying truncated text using the ellipsis as a proxy. This lets + // users select all text, select the truncated text, and transition from the + // last rendered glyph to the end of the text without getting invisible cursor + // positions nor needing unbounded arrow key presses to traverse the ellipsis. + return index == 0 || index == text().length() || + (index < text().length() && + (truncate_length_ == 0 || index < truncate_length_) && + IsValidCodePointIndex(text(), index)); +} + +bool RenderText::IsValidCursorIndex(size_t index) const { + return index == 0 || index == text().length() || + (IsValidLogicalIndex(index) && IsGraphemeBoundary(index)); +} + +Rect RenderText::GetCursorBounds(const SelectionModel& caret, + bool insert_mode) { + EnsureLayout(); + size_t caret_pos = caret.caret_pos(); + DCHECK(IsValidLogicalIndex(caret_pos)); + + // In overtype mode, ignore the affinity and always indicate that we will + // overtype the next character. + LogicalCursorDirection caret_affinity = + insert_mode ? caret.caret_affinity() : CURSOR_FORWARD; + float x = 0; + int width = 1; + + // Check whether the caret is attached to a boundary. Always return a 1-dip + // width caret at the boundary. Avoid calling IndexOfAdjacentGrapheme(), since + // it is slow and can impact browser startup here. + // In insert mode, index 0 is always a boundary. The end, however, is not at a + // boundary when the string ends in RTL text and there is LTR text around it. + const bool at_boundary = + (insert_mode && caret_pos == 0) || + caret_pos == (caret_affinity == CURSOR_BACKWARD ? 0 : text().length()); + if (at_boundary) { + const bool rtl = GetDisplayTextDirection() == base::i18n::RIGHT_TO_LEFT; + if (rtl == (caret_pos == 0)) + x = TotalLineWidth(); + } else { + // Find the next grapheme continuing in the current direction. This + // determines the substring range that should be highlighted. + size_t caret_end = IndexOfAdjacentGrapheme(caret_pos, caret_affinity); + if (caret_end < caret_pos) + std::swap(caret_end, caret_pos); + + const RangeF xspan = GetCursorSpan(Range(caret_pos, caret_end)); + if (insert_mode) { + x = (caret_affinity == CURSOR_BACKWARD) ? xspan.end() : xspan.start(); + } else { // overtype mode + x = xspan.GetMin(); + // Ceil the start and end of the |xspan| because the cursor x-coordinates + // are always ceiled. + width = base::ClampCeil(Clamp(xspan.GetMax())) - + base::ClampCeil(Clamp(xspan.GetMin())); + } + } + Size line_size = gfx::ToCeiledSize(GetLineSizeF(caret)); + size_t line = GetLineContainingCaret(caret); + return Rect(ToViewPoint(PointF(x, 0), line), Size(width, line_size.height())); +} + +const Rect& RenderText::GetUpdatedCursorBounds() { + UpdateCachedBoundsAndOffset(); + return cursor_bounds_; +} + +internal::GraphemeIterator RenderText::GetGraphemeIteratorAtTextIndex( + size_t index) const { + EnsureLayoutTextUpdated(); + return GetGraphemeIteratorAtIndex( + text_, &internal::TextToDisplayIndex::text_index, index); +} + +internal::GraphemeIterator RenderText::GetGraphemeIteratorAtDisplayTextIndex( + size_t index) const { + EnsureLayoutTextUpdated(); + return GetGraphemeIteratorAtIndex( + layout_text_, &internal::TextToDisplayIndex::display_index, index); +} + +size_t RenderText::GetTextIndex(internal::GraphemeIterator iter) const { + DCHECK(layout_text_up_to_date_); + return iter == text_to_display_indices_.end() ? text_.length() + : iter->text_index; +} + +size_t RenderText::GetDisplayTextIndex(internal::GraphemeIterator iter) const { + DCHECK(layout_text_up_to_date_); + return iter == text_to_display_indices_.end() ? layout_text_.length() + : iter->display_index; +} + +bool RenderText::IsGraphemeBoundary(size_t index) const { + return index >= text_.length() || + GetTextIndex(GetGraphemeIteratorAtTextIndex(index)) == index; +} + +size_t RenderText::IndexOfAdjacentGrapheme( + size_t index, + LogicalCursorDirection direction) const { + // The input is clamped if it is out of that range. + if (text_.empty()) + return 0; + if (index > text_.length()) + return text_.length(); + + EnsureLayoutTextUpdated(); + + internal::GraphemeIterator iter = index == text_.length() + ? text_to_display_indices_.end() + : GetGraphemeIteratorAtTextIndex(index); + if (direction == CURSOR_FORWARD) { + if (iter != text_to_display_indices_.end()) + ++iter; + } else { + DCHECK_EQ(direction, CURSOR_BACKWARD); + // If the index was not at the beginning of the grapheme, it will have been + // moved back to the grapheme start. + if (iter != text_to_display_indices_.begin() && GetTextIndex(iter) == index) + --iter; + } + return GetTextIndex(iter); +} + +SelectionModel RenderText::GetSelectionModelForSelectionStart() const { + const Range& sel = selection(); + if (sel.is_empty()) + return selection_model_; + return SelectionModel(sel.start(), + sel.is_reversed() ? CURSOR_BACKWARD : CURSOR_FORWARD); +} + +const Vector2d& RenderText::GetUpdatedDisplayOffset() { + UpdateCachedBoundsAndOffset(); + return display_offset_; +} + +void RenderText::SetDisplayOffset(int horizontal_offset) { + SetDisplayOffset({horizontal_offset, display_offset_.y()}); +} + +void RenderText::SetDisplayOffset(Vector2d offset) { + const int extra_content = GetContentWidth() - display_rect_.width(); + const int cursor_width = cursor_enabled_ ? 1 : 0; + + int min_offset = 0; + int max_offset = 0; + if (extra_content > 0) { + switch (GetCurrentHorizontalAlignment()) { + case ALIGN_LEFT: + min_offset = -extra_content; + break; + case ALIGN_RIGHT: + max_offset = extra_content; + break; + case ALIGN_CENTER: + // The extra space reserved for cursor at the end of the text is ignored + // when centering text. So, to calculate the valid range for offset, we + // exclude that extra space, calculate the range, and add it back to the + // range (if cursor is enabled). + min_offset = -(extra_content - cursor_width + 1) / 2 - cursor_width; + max_offset = (extra_content - cursor_width) / 2; + break; + default: + break; + } + } + + const int horizontal_offset = base::clamp(offset.x(), min_offset, max_offset); + + // y-offset is set only when the vertical alignment is ALIGN_TOP. + // TODO(jongkown.lee): Support other vertical alignments. + DCHECK(vertical_alignment_ == ALIGN_TOP || offset.y() == 0); + const int vertical_offset = base::clamp( + offset.y(), + std::min(display_rect_.height() - GetStringSize().height(), 0), 0); + + cached_bounds_and_offset_valid_ = true; + display_offset_ = {horizontal_offset, vertical_offset}; + cursor_bounds_ = GetCursorBounds(selection_model_, true); +} + +Vector2d RenderText::GetLineOffset(size_t line_number) { + const internal::ShapedText* shaped_text = GetShapedText(); + Vector2d offset = display_rect().OffsetFromOrigin(); + if (!multiline()) { + offset.Add(GetUpdatedDisplayOffset()); + } else { + DCHECK_LT(line_number, shaped_text->lines().size()); + offset.Add(GetUpdatedDisplayOffset()); + offset.Add( + Vector2d(0, shaped_text->lines()[line_number].preceding_heights)); + } + offset.Add(GetAlignmentOffset(line_number)); + return offset; +} + +bool RenderText::GetWordLookupDataAtPoint(const Point& point, + DecoratedText* decorated_word, + Point* baseline_point) { + if (obscured()) + return false; + + EnsureLayout(); + const SelectionModel model_at_point = FindCursorPosition(point); + const size_t word_index = + GetNearestWordStartBoundary(model_at_point.caret_pos()); + if (word_index >= text().length()) + return false; + + const Range word_range = ExpandRangeToWordBoundary(Range(word_index)); + DCHECK(!word_range.is_reversed()); + DCHECK(!word_range.is_empty()); + + return GetLookupDataForRange(word_range, decorated_word, baseline_point); +} + +bool RenderText::GetLookupDataForRange(const Range& range, + DecoratedText* decorated_text, + Point* baseline_point) { + const internal::ShapedText* shaped_text = GetShapedText(); + + const std::vector word_bounds = GetSubstringBounds(range); + if (word_bounds.empty() || !GetDecoratedTextForRange(range, decorated_text)) { + return false; + } + + // Retrieve the baseline origin of the left-most glyph. + const auto left_rect = std::min_element( + word_bounds.begin(), word_bounds.end(), + [](const Rect& lhs, const Rect& rhs) { return lhs.x() < rhs.x(); }); + const int line_index = GetLineContainingYCoord(left_rect->CenterPoint().y() - + GetLineOffset(0).y()); + if (line_index < 0 || + line_index >= static_cast(shaped_text->lines().size())) + return false; + *baseline_point = left_rect->origin() + + Vector2d(0, shaped_text->lines()[line_index].baseline); + return true; +} + +std::u16string RenderText::GetTextFromRange(const Range& range) const { + if (range.IsValid() && range.GetMin() < text().length()) + return text().substr(range.GetMin(), range.length()); + return std::u16string(); +} + +Range RenderText::ExpandRangeToGraphemeBoundary(const Range& range) const { + const auto snap_to_grapheme = [this](auto index, auto direction) { + return IsValidCursorIndex(index) + ? index + : IndexOfAdjacentGrapheme(index, direction); + }; + + const size_t min_index = snap_to_grapheme(range.GetMin(), CURSOR_BACKWARD); + const size_t max_index = snap_to_grapheme(range.GetMax(), CURSOR_FORWARD); + return range.is_reversed() ? Range(max_index, min_index) + : Range(min_index, max_index); +} + +bool RenderText::IsNewlineSegment(const internal::LineSegment& segment) const { + return IsNewlineSegment(text_, segment); +} + +bool RenderText::IsNewlineSegment(const std::u16string& text, + const internal::LineSegment& segment) const { + const size_t offset = segment.char_range.start(); + const size_t length = segment.char_range.length(); + DCHECK_LT(offset + length - 1, text.length()); + return (length == 1 && (text[offset] == '\r' || text[offset] == '\n')) || + (length == 2 && text[offset] == '\r' && text[offset + 1] == '\n'); +} + +Range RenderText::GetLineRange(const std::u16string& text, + const internal::Line& line) const { + // This will find the logical start and end indices of the given line. + size_t max_index = 0; + size_t min_index = text.length(); + for (const auto& segment : line.segments) { + min_index = std::min(min_index, segment.char_range.GetMin()); + max_index = std::max(max_index, segment.char_range.GetMax()); + } + + // Do not include the newline character, as that could be considered leading + // into the next line. Note that the newline character is always the last + // character of the line regardless of the text direction, so decrease the + // |max_index|. + if (!line.segments.empty() && + (IsNewlineSegment(text, line.segments.back()) || + IsNewlineSegment(text, line.segments.front()))) { + --max_index; + } + + return Range(min_index, max_index); +} + +RenderText::RenderText() = default; + +internal::StyleIterator RenderText::GetTextStyleIterator() const { + return internal::StyleIterator(&colors_, &baselines_, &font_size_overrides_, + &weights_, &styles_); +} + +internal::StyleIterator RenderText::GetLayoutTextStyleIterator() const { + EnsureLayoutTextUpdated(); + return internal::StyleIterator(&layout_colors_, &layout_baselines_, + &layout_font_size_overrides_, &layout_weights_, + &layout_styles_); +} + +bool RenderText::IsHomogeneous() const { + if (colors().breaks().size() > 1 || baselines().breaks().size() > 1 || + font_size_overrides().breaks().size() > 1 || + weights().breaks().size() > 1) { + return false; + } + + return std::none_of( + styles().cbegin(), styles().cend(), + [](const auto& style) { return style.breaks().size() > 1; }); +} + +internal::ShapedText* RenderText::GetShapedText() { + EnsureLayout(); + DCHECK(shaped_text_); + return shaped_text_.get(); +} + +int RenderText::GetDisplayTextBaseline() { + DCHECK(!GetShapedText()->lines().empty()); + return GetShapedText()->lines()[0].baseline; +} + +SelectionModel RenderText::GetAdjacentSelectionModel( + const SelectionModel& current, + BreakType break_type, + VisualCursorDirection direction) { + EnsureLayout(); + + if (direction == CURSOR_UP || direction == CURSOR_DOWN) + return AdjacentLineSelectionModel(current, direction); + if (break_type == FIELD_BREAK || text().empty()) + return EdgeSelectionModel(direction); + if (break_type == LINE_BREAK) + return LineSelectionModel(GetLineContainingCaret(current), direction); + if (break_type == CHARACTER_BREAK) + return AdjacentCharSelectionModel(current, direction); + DCHECK(break_type == WORD_BREAK); + return AdjacentWordSelectionModel(current, direction); +} + +SelectionModel RenderText::EdgeSelectionModel( + VisualCursorDirection direction) { + if (direction == GetVisualDirectionOfLogicalEnd()) + return SelectionModel(text().length(), CURSOR_FORWARD); + return SelectionModel(0, CURSOR_BACKWARD); +} + +SelectionModel RenderText::LineSelectionModel(size_t line_index, + VisualCursorDirection direction) { + DCHECK(direction == CURSOR_LEFT || direction == CURSOR_RIGHT); + DCHECK_LT(line_index, GetShapedText()->lines().size()); + const internal::Line& line = GetShapedText()->lines()[line_index]; + if (line.segments.empty()) { + // Only the last line can be empty. + DCHECK_EQ(GetShapedText()->lines().size() - 1, line_index); + return EdgeSelectionModel(GetVisualDirectionOfLogicalEnd()); + } + if (line_index == + (direction == GetVisualDirectionOfLogicalEnd() ? GetNumLines() - 1 : 0)) { + return EdgeSelectionModel(direction); + } + + DCHECK_GT(GetNumLines(), 1U); + Range line_range = GetLineRange(text(), line); + + // Cursor affinity should be the opposite of visual direction to preserve the + // line number of the cursor in multiline text. + return direction == GetVisualDirectionOfLogicalEnd() + ? SelectionModel(DisplayIndexToTextIndex(line_range.end()), + CURSOR_BACKWARD) + : SelectionModel(DisplayIndexToTextIndex(line_range.start()), + CURSOR_FORWARD); +} + +void RenderText::SetSelectionModel(const SelectionModel& model) { + DCHECK_LE(model.selection().GetMax(), text().length()); + selection_model_ = model; + cached_bounds_and_offset_valid_ = false; + has_directed_selection_ = kSelectionIsAlwaysDirected; +} + +void RenderText::AddSecondarySelection(const Range selection) { + DCHECK_LE(selection.GetMax(), text().length()); + selection_model_.AddSecondarySelection(selection); +} + +size_t RenderText::TextIndexToDisplayIndex(size_t index) const { + return GetDisplayTextIndex(GetGraphemeIteratorAtTextIndex(index)); +} + +size_t RenderText::DisplayIndexToTextIndex(size_t index) const { + return GetTextIndex(GetGraphemeIteratorAtDisplayTextIndex(index)); +} + +void RenderText::OnLayoutTextAttributeChanged(bool text_changed) { + layout_text_up_to_date_ = false; +} + +void RenderText::EnsureLayoutTextUpdated() const { + if (layout_text_up_to_date_) + return; + + layout_text_.clear(); + text_to_display_indices_.clear(); + + display_text_direction_ = base::i18n::UNKNOWN_DIRECTION; + + // Reset the previous layout text attributes. Allocate enough space for + // layout text attributes (upper limit to 2x characters per codepoint). The + // actual size will be updated at the end of the function. + UpdateLayoutStyleLengths(2 * text_.length()); + + // Create an grapheme iterator to ensure layout BreakLists don't break + // graphemes. + base::i18n::BreakIterator grapheme_iter( + text_, base::i18n::BreakIterator::BREAK_CHARACTER); + bool success = grapheme_iter.Init(); + DCHECK(success); + + // Ensures the reveal index is at a codepoint boundary (e.g. not in a middle + // of a surrogate pairs). + size_t reveal_index = text_.size(); + if (obscured_reveal_index_ != -1) { + reveal_index = base::checked_cast(obscured_reveal_index_); + // Move |reveal_index| to the beginning of the surrogate pair, if needed. + if (reveal_index < text_.size()) + U16_SET_CP_START(text_.data(), 0, reveal_index); + } + + // Iterates through graphemes from |text_| and rewrite its codepoints to + // |layout_text_|. + base::i18n::UTF16CharIterator text_iter(text_); + internal::StyleIterator styles = GetTextStyleIterator(); + bool text_truncated = false; + while (!text_iter.end() && !text_truncated) { + std::vector grapheme_codepoints; + const size_t text_grapheme_start_position = text_iter.array_pos(); + const size_t layout_grapheme_start_position = layout_text_.size(); + + // Retrieve codepoints of the current grapheme. + do { + grapheme_codepoints.push_back(text_iter.get()); + text_iter.Advance(); + } while (!grapheme_iter.IsGraphemeBoundary(text_iter.array_pos()) && + !text_iter.end()); + const size_t text_grapheme_end_position = text_iter.array_pos(); + + // Keep track of the mapping between |text_| and |layout_text_| indices. + internal::TextToDisplayIndex mapping = {text_grapheme_start_position, + layout_grapheme_start_position}; + text_to_display_indices_.push_back(mapping); + + // Flag telling if the current grapheme is a newline control sequence. + const bool is_newline_grapheme = + (grapheme_codepoints.size() == 1 && + (grapheme_codepoints[0] == '\r' || grapheme_codepoints[0] == '\n')) || + (grapheme_codepoints.size() == 2 && grapheme_codepoints[0] == '\r' && + grapheme_codepoints[1] == '\n'); + + // Obscure the layout text by replacing the grapheme by a bullet. + if (obscured_ && + (reveal_index < text_grapheme_start_position || + reveal_index >= text_grapheme_end_position) && + (!is_newline_grapheme || !multiline_)) { + grapheme_codepoints.clear(); + grapheme_codepoints.push_back(RenderText::kPasswordReplacementChar); + } + + // Rewrite each codepoint of the grapheme. + for (uint32_t codepoint : grapheme_codepoints) { + // Handle unicode control characters ISO 6429 (block C0). Range from 0 to + // 0x1F and 0x7F. The newline character should be kept as-is when + // rendertext is multiline. + if (!multiline_ || !is_newline_grapheme) + codepoint = ReplaceControlCharacter(codepoint); + + // Truncate the remaining codepoints if appending the codepoint to + // |layout_text_| is making the text larger than |truncate_length_|. + size_t codepoint_length = U16_LENGTH(codepoint); + text_truncated = + (truncate_length_ != 0 && + ((layout_text_.size() + codepoint_length > truncate_length_) || + (!text_iter.end() && + (layout_text_.size() + codepoint_length == truncate_length_)))); + + if (text_truncated) { + codepoint = kEllipsisCodepoint; + codepoint_length = U16_LENGTH(codepoint); + // On truncate, remove the whole current grapheme. + layout_text_.resize(layout_grapheme_start_position); + } + + // Append the codepoint to the layout text. + const size_t current_layout_text_position = layout_text_.size(); + if (codepoint_length == 1) { + layout_text_ += codepoint; + } else { + layout_text_ += U16_LEAD(codepoint); + layout_text_ += U16_TRAIL(codepoint); + } + + // Apply the style at current grapheme position to the layout text. + styles.IncrementToPosition(text_grapheme_start_position); + + Range range(current_layout_text_position, layout_text_.size()); + layout_colors_.ApplyValue(styles.color(), range); + layout_baselines_.ApplyValue(styles.baseline(), range); + layout_font_size_overrides_.ApplyValue(styles.font_size_override(), + range); + layout_weights_.ApplyValue(styles.weight(), range); + for (size_t i = 0; i < layout_styles_.size(); ++i) { + layout_styles_[i].ApplyValue(styles.style(static_cast(i)), + range); + } + + // Apply an underline to the composition range in |underlines|. + const Range grapheme_start_range(text_grapheme_start_position, + text_grapheme_start_position + 1); + if (composition_range_.Contains(grapheme_start_range)) + layout_styles_[TEXT_STYLE_HEAVY_UNDERLINE].ApplyValue(true, range); + + // Stop appending characters if the text is truncated. + if (text_truncated) + break; + } + } + + // Resize the layout text attributes to the actual layout text length. + UpdateLayoutStyleLengths(layout_text_.length()); + + // Ensures that the text got truncated correctly, when needed. + DCHECK(truncate_length_ == 0 || layout_text_.size() <= truncate_length_); + + // Wait to reset |layout_text_up_to_date_| until the end, to ensure this + // function's implementation doesn't indirectly rely on it being up to date + // anywhere. + layout_text_up_to_date_ = true; +} + +const std::u16string& RenderText::GetLayoutText() const { + EnsureLayoutTextUpdated(); + return layout_text_; +} + +void RenderText::UpdateDisplayText(float text_width) { + EnsureLayoutTextUpdated(); + + // TODO(krb): Consider other elision modes for multiline. + if ((multiline_ && (!max_lines_ || elide_behavior() != ELIDE_TAIL)) || + elide_behavior() == NO_ELIDE || elide_behavior() == FADE_TAIL || + (text_width > 0 && text_width < display_rect_.width()) || + layout_text_.empty()) { + text_elided_ = false; + display_text_.clear(); + return; + } + + if (!multiline_) { + // This doesn't trim styles so ellipsis may get rendered as a different + // style than the preceding text. See crbug.com/327850. + display_text_.assign(Elide(layout_text_, text_width, + static_cast(display_rect_.width()), + elide_behavior_)); + } else { + bool was_elided = text_elided_; + text_elided_ = false; + display_text_.clear(); + + std::unique_ptr render_text( + CreateInstanceOfSameStyle(layout_text_)); + render_text->SetMultiline(true); + render_text->SetWordWrapBehavior(word_wrap_behavior_); + render_text->SetDisplayRect(display_rect_); + // Have it arrange words on |lines_|. + render_text->EnsureLayout(); + + if (render_text->GetShapedText()->lines().size() > max_lines_) { + // Find the start and end index of the line to be elided. + Range line_range = GetLineRange( + layout_text_, render_text->GetShapedText()->lines()[max_lines_ - 1]); + // Add an ellipsis character in case the last line is short enough to fit + // on a single line. Otherwise that character will be elided anyway. + std::u16string text_to_elide = + layout_text_.substr(line_range.start(), line_range.length()) + + std::u16string(kEllipsisUTF16); + display_text_.assign(layout_text_.substr(0, line_range.start()) + + Elide(text_to_elide, 0, + static_cast(display_rect_.width()), + ELIDE_TAIL)); + // Have GetLineBreaks() re-calculate. + line_breaks_.SetMax(0); + } else { + // If elision changed, re-calculate. + if (was_elided) + line_breaks_.SetMax(0); + // Initial state above is fine. + return; + } + } + text_elided_ = display_text_ != layout_text_; + if (!text_elided_) + display_text_.clear(); +} + +const BreakList& RenderText::GetLineBreaks() { + if (line_breaks_.max() != 0) + return line_breaks_; + + const std::u16string& layout_text = GetDisplayText(); + const size_t text_length = layout_text.length(); + line_breaks_.SetValue(0); + line_breaks_.SetMax(text_length); + base::i18n::BreakIterator iter(layout_text, + base::i18n::BreakIterator::BREAK_LINE); + const bool success = iter.Init(); + DCHECK(success); + if (success) { + do { + line_breaks_.ApplyValue(iter.pos(), Range(iter.pos(), text_length)); + } while (iter.Advance()); + } + return line_breaks_; +} + +Point RenderText::ToViewPoint(const PointF& point, size_t line) { + if (GetNumLines() == 1) { + return Point(base::ClampCeil(Clamp(point.x())), + base::ClampRound(point.y())) + + GetLineOffset(0); + } + + const internal::ShapedText* shaped_text = GetShapedText(); + float x = point.x(); + + if (GetDisplayTextDirection() == base::i18n::RIGHT_TO_LEFT) { + // |xspan| returned from |GetCursorSpan| in |GetCursorBounds| starts to grow + // from the last character in RTL. On the other hand, the last character is + // positioned in the last line in RTL. So, traverse from the last line. + for (size_t l = GetNumLines() - 1; l > line; --l) { + x -= shaped_text->lines()[l].size.width(); + } + } else { + // TODO(crbug.com/1163587): This doesn't account for line breaks caused by + // wrapping, in which case the cursor may end up right after the trailing + // space on the top line instead of before the first character of the second + // line depending on which direction the cursor is moving. Both positions + // are "correct" but most text editors only allow one or the other for + // consistency. + for (size_t l = 0; l < line; ++l) { + x -= shaped_text->lines()[l].size.width(); + } + } + + return Point(base::ClampCeil(Clamp(x)), base::ClampRound(point.y())) + + GetLineOffset(line); +} + +HorizontalAlignment RenderText::GetCurrentHorizontalAlignment() { + if (horizontal_alignment_ != ALIGN_TO_HEAD) + return horizontal_alignment_; + return GetDisplayTextDirection() == base::i18n::RIGHT_TO_LEFT ? + ALIGN_RIGHT : ALIGN_LEFT; +} + +Vector2d RenderText::GetAlignmentOffset(size_t line_number) { + DCHECK(!multiline_ || (line_number < GetShapedText()->lines().size())); + + Vector2d offset; + HorizontalAlignment horizontal_alignment = GetCurrentHorizontalAlignment(); + if (horizontal_alignment != ALIGN_LEFT) { + const int width = + multiline_ + ? std::ceil(GetShapedText()->lines()[line_number].size.width()) + + (cursor_enabled_ ? 1 : 0) + : GetContentWidth(); + offset.set_x(display_rect().width() - width); + // Put any extra margin pixel on the left to match legacy behavior. + if (horizontal_alignment == ALIGN_CENTER) + offset.set_x((offset.x() + 1) / 2); + } + + switch (vertical_alignment_) { + case ALIGN_TOP: + offset.set_y(0); + break; + case ALIGN_MIDDLE: + if (multiline_) + offset.set_y((display_rect_.height() - GetStringSize().height()) / 2); + else + offset.set_y(GetBaseline() - GetDisplayTextBaseline()); + break; + case ALIGN_BOTTOM: + offset.set_y(display_rect_.height() - GetStringSize().height()); + break; + } + + return offset; +} + +void RenderText::ApplyFadeEffects(internal::SkiaTextRenderer* renderer) { + const int width = display_rect().width(); + if (multiline() || elide_behavior_ != FADE_TAIL || GetContentWidth() <= width) + return; + + const int gradient_width = CalculateFadeGradientWidth(font_list(), width); + if (gradient_width == 0) + return; + + HorizontalAlignment horizontal_alignment = GetCurrentHorizontalAlignment(); + Rect solid_part = display_rect(); + Rect left_part; + Rect right_part; + if (horizontal_alignment != ALIGN_LEFT) { + left_part = solid_part; + left_part.Inset(0, 0, solid_part.width() - gradient_width, 0); + solid_part.Inset(gradient_width, 0, 0, 0); + } + if (horizontal_alignment != ALIGN_RIGHT) { + right_part = solid_part; + right_part.Inset(solid_part.width() - gradient_width, 0, 0, 0); + solid_part.Inset(0, 0, gradient_width, 0); + } + + // CreateFadeShader() expects at least one part to not be empty. + // See https://crbug.com/706835. + if (left_part.IsEmpty() && right_part.IsEmpty()) + return; + + Rect text_rect = display_rect(); + text_rect.Inset(GetAlignmentOffset(0).x(), 0, 0, 0); + + // TODO(msw): Use the actual text colors corresponding to each faded part. + renderer->SetShader( + CreateFadeShader(font_list(), text_rect, left_part, right_part, + SkColorSetA(colors_.breaks().front().second, 0xff))); +} + +void RenderText::ApplyTextShadows(internal::SkiaTextRenderer* renderer) { + renderer->SetDrawLooper(CreateShadowDrawLooper(shadows_)); +} + +base::i18n::TextDirection RenderText::GetTextDirectionForGivenText( + const std::u16string& text) const { + switch (directionality_mode_) { + case DIRECTIONALITY_FROM_TEXT: + // Derive the direction from the display text, which differs from text() + // in the case of obscured (password) textfields. + return base::i18n::GetFirstStrongCharacterDirection(text); + case DIRECTIONALITY_FROM_UI: + return base::i18n::IsRTL() ? base::i18n::RIGHT_TO_LEFT + : base::i18n::LEFT_TO_RIGHT; + case DIRECTIONALITY_FORCE_LTR: + return base::i18n::LEFT_TO_RIGHT; + case DIRECTIONALITY_FORCE_RTL: + return base::i18n::RIGHT_TO_LEFT; + case DIRECTIONALITY_AS_URL: + // Rendering as a URL implies left-to-right paragraph direction. + // URL Standard specifies that a URL "should be rendered as if it were + // in a left-to-right embedding". + // https://url.spec.whatwg.org/#url-rendering + // + // Consider logical string for domain "ABC.com/hello" (where ABC are + // Hebrew (RTL) characters). The normal Bidi algorithm renders this as + // "com/hello.CBA"; by forcing LTR, it is rendered as "CBA.com/hello". + // + // Note that this only applies a LTR embedding at the top level; it + // doesn't change the Bidi algorithm, so there are still some URLs that + // will render in a confusing order. Consider the logical string + // "abc.COM/HELLO/world", which will render as "abc.OLLEH/MOC/world". + // See https://crbug.com/351639. + // + // Note that the LeftToRightUrls feature flag enables additional + // behaviour for DIRECTIONALITY_AS_URL, but the left-to-right embedding + // behaviour is always enabled, regardless of the flag. + return base::i18n::LEFT_TO_RIGHT; + default: + NOTREACHED(); + return base::i18n::UNKNOWN_DIRECTION; + } +} + +void RenderText::UpdateStyleLengths() { + const size_t text_length = text_.length(); + colors_.SetMax(text_length); + baselines_.SetMax(text_length); + font_size_overrides_.SetMax(text_length); + weights_.SetMax(text_length); + for (auto& style : styles_) + style.SetMax(text_length); +} + +void RenderText::UpdateLayoutStyleLengths(size_t max_length) const { + layout_colors_.SetMax(max_length); + layout_baselines_.SetMax(max_length); + layout_font_size_overrides_.SetMax(max_length); + layout_weights_.SetMax(max_length); + for (auto& layout_style : layout_styles_) + layout_style.SetMax(max_length); +} + +int RenderText::GetLineContainingYCoord(float text_y) { + if (text_y < 0) + return -1; + + internal::ShapedText* shaper_text = GetShapedText(); + for (size_t i = 0; i < shaper_text->lines().size(); i++) { + const internal::Line& line = shaper_text->lines()[i]; + + if (text_y <= line.size.height()) + return i; + text_y -= line.size.height(); + } + + return shaper_text->lines().size(); +} + +// static +bool RenderText::RangeContainsCaret(const Range& range, + size_t caret_pos, + LogicalCursorDirection caret_affinity) { + // NB: exploits unsigned wraparound (WG14/N1124 section 6.2.5 paragraph 9). + size_t adjacent = (caret_affinity == CURSOR_BACKWARD) ? + caret_pos - 1 : caret_pos + 1; + return range.Contains(Range(caret_pos, adjacent)); +} + +// static +int RenderText::DetermineBaselineCenteringText(const int display_height, + const FontList& font_list) { + const int font_height = font_list.GetHeight(); + // Lower and upper bound of baseline shift as we try to show as much area of + // text as possible. In particular case of |display_height| == |font_height|, + // we do not want to shift the baseline. + const int min_shift = std::min(0, display_height - font_height); + const int max_shift = std::abs(display_height - font_height); + const int baseline = font_list.GetBaseline(); + const int cap_height = font_list.GetCapHeight(); + const int internal_leading = baseline - cap_height; + // Some platforms don't support getting the cap height, and simply return + // the entire font ascent from GetCapHeight(). Centering the ascent makes + // the font look too low, so if GetCapHeight() returns the ascent, center + // the entire font height instead. + const int space = + display_height - ((internal_leading != 0) ? cap_height : font_height); + const int baseline_shift = space / 2 - internal_leading; + return baseline + base::clamp(baseline_shift, min_shift, max_shift); +} + +// static +Rect RenderText::ExpandToBeVerticallySymmetric(const Rect& rect, + const Rect& display_rect) { + // Mirror |rect| across the horizontal line dividing |display_rect| in half. + Rect result = rect; + int mid_y = display_rect.CenterPoint().y(); + // The top of the mirror rect must be equidistant with the bottom of the + // original rect from the mid-line. + result.set_y(mid_y + (mid_y - rect.bottom())); + + // Now make a union with the original rect to ensure we are encompassing both. + result.Union(rect); + return result; +} + +// static +void RenderText::MergeIntersectingRects(std::vector& rects) { + if (rects.size() < 2) + return; + + std::sort(rects.begin(), rects.end(), + [](const Rect& a, const Rect& b) { return a.x() < b.x(); }); + + size_t merge_candidate = 0; + for (size_t i = 1; i < rects.size(); i++) { + if (rects[i].Intersects(rects[merge_candidate]) || + rects[i].SharesEdgeWith(rects[merge_candidate])) { + DCHECK_EQ(rects[i].y(), rects[merge_candidate].y()); + DCHECK_EQ(rects[i].height(), rects[merge_candidate].height()); + rects[merge_candidate].Union(rects[i]); + } else { + merge_candidate++; + if (merge_candidate != i) + rects[merge_candidate] = rects[i]; + } + } + + rects.resize(merge_candidate + 1); +} + +void RenderText::OnTextAttributeChanged() { + layout_text_.clear(); + display_text_.clear(); + text_elided_ = false; + line_breaks_.SetMax(0); + + layout_text_up_to_date_ = false; + + OnLayoutTextAttributeChanged(true); +} + +std::u16string RenderText::Elide(const std::u16string& text, + float text_width, + float available_width, + ElideBehavior behavior) { + if (available_width <= 0 || text.empty()) + return std::u16string(); + if (behavior == ELIDE_EMAIL) + return ElideEmail(text, available_width); + if (text_width > 0 && text_width <= available_width) + return text; + + TRACE_EVENT0("ui", "RenderText::Elide"); + + // Create a RenderText copy with attributes that affect the rendering width. + std::unique_ptr render_text = CreateInstanceOfSameStyle(text); + render_text->UpdateStyleLengths(); + if (text_width == 0) + text_width = render_text->GetContentWidthF(); + if (text_width <= available_width) + return text; + + const std::u16string ellipsis = std::u16string(kEllipsisUTF16); + const bool insert_ellipsis = (behavior != TRUNCATE); + const bool elide_in_middle = (behavior == ELIDE_MIDDLE); + const bool elide_at_beginning = (behavior == ELIDE_HEAD); + + if (insert_ellipsis) { + render_text->SetText(ellipsis); + const float ellipsis_width = render_text->GetContentWidthF(); + if (ellipsis_width > available_width) + return std::u16string(); + } + + StringSlicer slicer(text, ellipsis, elide_in_middle, elide_at_beginning, + whitespace_elision_); + + // Use binary(-like) search to compute the elided text. In particular, do + // an interpolation search, which is a binary search in which each guess + // is an attempt to smartly calculate the right point rather than blindly + // guessing midway between the endpoints. + size_t lo = 0; + size_t hi = text.length() - 1; + size_t guess = std::string::npos; + // These two widths are not exactly right but they're good enough to provide + // some guidance to the search. For example, |text_width| is actually the + // length of text.length(), not text.length()-1. + float lo_width = 0; + float hi_width = text_width; + const base::i18n::TextDirection text_direction = GetTextDirection(); + while (lo <= hi) { + // Linearly interpolate between |lo| and |hi|, which correspond to widths + // of |lo_width| and |hi_width| to estimate at what position + // |available_width| would be at. Because |lo_width| and |hi_width| are + // both estimates (may be off by a little because, for example, |lo_width| + // may have been calculated from |lo| minus one, not |lo|), we clamp to the + // the valid range. + // |last_guess| is merely used to verify that we're not repeating guesses. + const size_t last_guess = guess; + if (hi_width != lo_width) { + guess = lo + base::ClampRound((available_width - lo_width) * + (hi - lo) / (hi_width - lo_width)); + } + guess = base::clamp(guess, lo, hi); + DCHECK_NE(last_guess, guess); + + // Restore colors. They will be truncated to size by SetText. + render_text->colors_ = colors_; + std::u16string new_text = + slicer.CutString(guess, insert_ellipsis && behavior != ELIDE_TAIL); + + // This has to be an additional step so that the ellipsis is rendered with + // same style as trailing part of the text. + if (insert_ellipsis && behavior == ELIDE_TAIL) { + // When ellipsis follows text whose directionality is not the same as that + // of the whole text, it will be rendered with the directionality of the + // whole text. Since we want ellipsis to indicate continuation of the + // preceding text, we force the directionality of ellipsis to be same as + // the preceding text using LTR or RTL markers. + base::i18n::TextDirection trailing_text_direction = + base::i18n::GetLastStrongCharacterDirection(new_text); + + // Ensures that the |new_text| will always be smaller or equal to the + // original text. There is a corner case when only one character is elided + // and two characters are added back (ellipsis and directional marker). + if (trailing_text_direction != text_direction && + new_text.length() + 2 > text.length() && guess >= 1) { + new_text = slicer.CutString(guess - 1, false); + trailing_text_direction = + base::i18n::GetLastStrongCharacterDirection(new_text); + } + + // Append the ellipsis and the optional directional marker characters. + // Do not append the BiDi marker if the only codepoint in the text is + // an ellipsis. + new_text.append(ellipsis); + if (new_text.size() != 1 && trailing_text_direction != text_direction) { + if (trailing_text_direction == base::i18n::LEFT_TO_RIGHT) + new_text += base::i18n::kLeftToRightMark; + else + new_text += base::i18n::kRightToLeftMark; + } + } + + // The elided text must be smaller in bytes. Otherwise, break-lists are not + // consistent and the characters after the last range are not styled. + DCHECK_LE(new_text.size(), text.size()); + render_text->SetText(new_text); + + // Restore styles and baselines without breaking multi-character graphemes. + render_text->styles_ = styles_; + for (auto& style : render_text->styles_) + RestoreBreakList(render_text.get(), &style); + RestoreBreakList(render_text.get(), &render_text->baselines_); + RestoreBreakList(render_text.get(), &render_text->font_size_overrides_); + render_text->weights_ = weights_; + RestoreBreakList(render_text.get(), &render_text->weights_); + + // We check the width of the whole desired string at once to ensure we + // handle kerning/ligatures/etc. correctly. + const float guess_width = render_text->GetContentWidthF(); + if (guess_width == available_width) + break; + if (guess_width > available_width) { + hi = guess - 1; + hi_width = guess_width; + // Move back on the loop terminating condition when the guess is too wide. + if (hi < lo) { + lo = hi; + lo_width = guess_width; + } + } else { + lo = guess + 1; + lo_width = guess_width; + } + } + + return render_text->text(); +} + +std::u16string RenderText::ElideEmail(const std::u16string& email, + float available_width) { + // The returned string will have at least one character besides the ellipsis + // on either side of '@'; if that's impossible, a single ellipsis is returned. + // If possible, only the username is elided. Otherwise, the domain is elided + // in the middle, splitting available width equally with the elided username. + // If the username is short enough that it doesn't need half the available + // width, the elided domain will occupy that extra width. + + // Split the email into its local-part (username) and domain-part. The email + // spec allows for @ symbols in the username under some special requirements, + // but not in the domain part, so splitting at the last @ symbol is safe. + const size_t split_index = email.find_last_of('@'); + if (split_index == std::u16string::npos) + return Elide(email, 0, available_width, ELIDE_TAIL); + + std::u16string username = email.substr(0, split_index); + std::u16string domain = email.substr(split_index + 1); + + // TODO(http://crbug.com/1085014): Fix eliding of text with styles. + DCHECK(IsHomogeneous()) + << "ElideEmail(...) doesn't work with non homogeneous styles."; + auto render_text = CreateInstanceOfSameStyle(std::u16string()); + auto get_string_width = [&](const std::u16string& text) { + render_text->SetText(text); + return render_text->GetStringSizeF().width(); + }; + + // Subtract the @ symbol from the available width as it is mandatory. + const std::u16string kAtSignUTF16 = u"@"; + float at_width = get_string_width(kAtSignUTF16); + if (available_width < at_width) + return Elide(kEllipsisUTF16, 0, available_width, ELIDE_TAIL); + const float remaining_width = available_width - at_width; + + // Handle corner cases where one of username or domain is empty. + if (username.empty() && domain.empty()) { + return Elide(email, 0, available_width, ELIDE_TAIL); + } else if (username.empty()) { + domain = Elide(domain, 0, remaining_width, ELIDE_MIDDLE); + if (domain.empty() || domain == kEllipsisUTF16) + return Elide(kEllipsisUTF16, 0, available_width, ELIDE_TAIL); + return kAtSignUTF16 + domain; + } else if (domain.empty()) { + username = Elide(username, 0, remaining_width, ELIDE_TAIL); + if (username.empty() || username == kEllipsisUTF16) + return Elide(kEllipsisUTF16, 0, available_width, ELIDE_TAIL); + return username + kAtSignUTF16; + } + + // Check whether eliding the domain is necessary: if eliding the username + // is sufficient, the domain will not be elided. + const float full_username_width = get_string_width(username); + const float available_domain_width = + remaining_width - + std::min(full_username_width, + get_string_width(username.substr(0, 1) + kEllipsisUTF16)); + if (get_string_width(domain) > available_domain_width) { + // Elide the domain so that it only takes half of the available width. + // Should the username not need all the width available in its half, the + // domain will occupy the leftover width. + // If |desired_domain_width| is greater than |available_domain_width|: the + // minimal username elision allowed by the specifications will not fit; thus + // |desired_domain_width| must be <= |available_domain_width| at all cost. + const float desired_domain_width = + std::min(available_domain_width, + std::max(remaining_width - full_username_width, + remaining_width / 2)); + domain = Elide(domain, 0, desired_domain_width, ELIDE_MIDDLE); + // Failing to elide the domain such that at least one character remains + // (other than the ellipsis itself) remains: return a single ellipsis. + if (domain.empty() || domain == kEllipsisUTF16) + return Elide(kEllipsisUTF16, 0, available_width, ELIDE_TAIL); + } + + // Fit the username in the remaining width (at this point the elided username + // is guaranteed to fit with at least one character remaining given all the + // precautions taken earlier). + const float domain_width = get_string_width(domain); + const float available_username_width = remaining_width - domain_width; + username = Elide(username, 0, available_username_width, ELIDE_TAIL); + + return username + kAtSignUTF16 + domain; +} + +void RenderText::UpdateCachedBoundsAndOffset() { + if (cached_bounds_and_offset_valid_) + return; + + int delta_x = 0; + int delta_y = 0; + + if (cursor_enabled()) { + // When cursor is enabled, ensure it is visible. For this, set the valid + // flag true and calculate the current cursor bounds using the stale + // |display_offset_|. Then calculate the change in offset needed to move the + // cursor into the visible area. + cached_bounds_and_offset_valid_ = true; + cursor_bounds_ = GetCursorBounds(selection_model_, true); + + // TODO(bidi): Show RTL glyphs at the cursor position for ALIGN_LEFT, etc. + if (cursor_bounds_.right() > display_rect_.right()) + delta_x = display_rect_.right() - cursor_bounds_.right(); + else if (cursor_bounds_.x() < display_rect_.x()) + delta_x = display_rect_.x() - cursor_bounds_.x(); + + if (vertical_alignment_ == ALIGN_TOP) { + if (cursor_bounds_.bottom() > display_rect_.bottom()) + delta_y = display_rect_.bottom() - cursor_bounds_.bottom(); + else if (cursor_bounds_.y() < display_rect_.y()) + delta_y = display_rect_.y() - cursor_bounds_.y(); + } + } + + SetDisplayOffset(display_offset_ + Vector2d(delta_x, delta_y)); +} + +internal::GraphemeIterator RenderText::GetGraphemeIteratorAtIndex( + const std::u16string& text, + const size_t internal::TextToDisplayIndex::*field, + size_t index) const { + DCHECK_LE(index, text.length()); + if (index == text.length()) + return text_to_display_indices_.end(); + + DCHECK(layout_text_up_to_date_); + DCHECK(!text_to_display_indices_.empty()); + + // The function std::lower_bound(...) finds the first not less than |index|. + internal::GraphemeIterator iter = std::lower_bound( + text_to_display_indices_.begin(), text_to_display_indices_.end(), index, + [field](const internal::TextToDisplayIndex& lhs, size_t rhs) { + return lhs.*field < rhs; + }); + + if (iter == text_to_display_indices_.end() || *iter.*field != index) { + DCHECK(iter != text_to_display_indices_.begin()); + --iter; + } + + return iter; +} + +void RenderText::DrawSelections(Canvas* canvas, + const std::vector& selections) { + for (auto selection : selections) { + if (!selection.is_empty()) { + for (Rect s : GetSubstringBounds(selection)) { + if (symmetric_selection_visual_bounds() && !multiline()) + s = ExpandToBeVerticallySymmetric(s, display_rect()); + canvas->FillRect(s, selection_background_focused_color_); + } + } + } +} + +size_t RenderText::GetNearestWordStartBoundary(size_t index) const { + const size_t length = text().length(); + if (obscured() || length == 0) + return length; + + base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD); + const bool success = iter.Init(); + DCHECK(success); + if (!success) + return length; + + // First search for the word start boundary in the CURSOR_BACKWARD direction, + // then in the CURSOR_FORWARD direction. + for (int i = static_cast(std::min(index, length - 1)); i >= 0; i--) + if (iter.IsStartOfWord(i)) + return i; + + for (size_t i = index + 1; i < length; i++) + if (iter.IsStartOfWord(i)) + return i; + + return length; +} + +Range RenderText::ExpandRangeToWordBoundary(const Range& range) const { + const size_t length = text().length(); + DCHECK_LE(range.GetMax(), length); + if (obscured()) + return range.is_reversed() ? Range(length, 0) : Range(0, length); + + base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD); + const bool success = iter.Init(); + DCHECK(success); + if (!success) + return range; + + size_t range_min = range.GetMin(); + if (range_min == length && range_min != 0) + --range_min; + + for (; range_min != 0; --range_min) + if (iter.IsStartOfWord(range_min) || iter.IsEndOfWord(range_min)) + break; + + size_t range_max = range.GetMax(); + if (range_min == range_max && range_max != length) + ++range_max; + + for (; range_max < length; ++range_max) + if (iter.IsEndOfWord(range_max) || iter.IsStartOfWord(range_max)) + break; + + return range.is_reversed() ? Range(range_max, range_min) + : Range(range_min, range_max); +} + +} // namespace gfx diff --git a/render_text.h b/render_text.h new file mode 100644 index 000000000000..bb01b826dbe5 --- /dev/null +++ b/render_text.h @@ -0,0 +1,1059 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_RENDER_TEXT_H_ +#define UI_GFX_RENDER_TEXT_H_ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "base/i18n/rtl.h" +#include "base/macros.h" +#include "build/build_config.h" +#include "cc/paint/paint_canvas.h" +#include "cc/paint/paint_flags.h" +#include "third_party/skia/include/core/SkColor.h" +#include "third_party/skia/include/core/SkFont.h" +#include "third_party/skia/include/core/SkRefCnt.h" +#include "ui/gfx/break_list.h" +#include "ui/gfx/color_palette.h" +#include "ui/gfx/font_list.h" +#include "ui/gfx/font_render_params.h" +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/rect_f.h" +#include "ui/gfx/geometry/size_f.h" +#include "ui/gfx/geometry/vector2d.h" +#include "ui/gfx/range/range.h" +#include "ui/gfx/range/range_f.h" +#include "ui/gfx/selection_model.h" +#include "ui/gfx/shadow_value.h" +#include "ui/gfx/text_constants.h" + +class SkDrawLooper; +struct SkPoint; +class SkTypeface; + +namespace gfx { +namespace test { +class RenderTextTestApi; +} + +class Canvas; +struct DecoratedText; +class Font; + +namespace internal { + +class TextRunList; + +// Internal helper class used by derived classes to draw text through Skia. +class GFX_EXPORT SkiaTextRenderer { + public: + explicit SkiaTextRenderer(Canvas* canvas); + SkiaTextRenderer(const SkiaTextRenderer&) = delete; + SkiaTextRenderer& operator=(const SkiaTextRenderer&) = delete; + virtual ~SkiaTextRenderer(); + + void SetDrawLooper(sk_sp draw_looper); + void SetFontRenderParams(const FontRenderParams& params, + bool subpixel_rendering_suppressed); + void SetTypeface(sk_sp typeface); + void SetTextSize(SkScalar size); + void SetForegroundColor(SkColor foreground); + void SetShader(sk_sp shader); + // TODO(vmpstr): Change this API to mimic SkCanvas::drawTextBlob instead. + virtual void DrawPosText(const SkPoint* pos, + const uint16_t* glyphs, + size_t glyph_count); + void DrawUnderline(int x, int y, int width, SkScalar thickness_factor = 1.0); + void DrawStrike(int x, int y, int width, SkScalar thickness_factor); + + private: + friend class test::RenderTextTestApi; + + Canvas* canvas_; + cc::PaintCanvas* canvas_skia_; + cc::PaintFlags flags_; + SkFont font_; +}; + +struct TextToDisplayIndex { + size_t text_index = 0; + size_t display_index = 0; +}; +using TextToDisplaySequence = std::vector; +using GraphemeIterator = TextToDisplaySequence::const_iterator; +using StyleArray = std::array, TEXT_STYLE_COUNT>; + +// Internal helper class used to iterate colors, baselines, and styles. +class StyleIterator { + public: + StyleIterator(const BreakList* colors, + const BreakList* baselines, + const BreakList* font_size_overrides, + const BreakList* weights, + const StyleArray* styles); + StyleIterator(const StyleIterator& style); + ~StyleIterator(); + StyleIterator& operator=(const StyleIterator& style); + + // Get the colors and styles at the current iterator position. + SkColor color() const { return color_->second; } + BaselineStyle baseline() const { return baseline_->second; } + int font_size_override() const { return font_size_override_->second; } + bool style(TextStyle s) const { return style_[s]->second; } + Font::Weight weight() const { return weight_->second; } + + // Get the intersecting range of the current iterator set. + Range GetRange() const; + + // Get the intersecting range of the current iterator set for attributes that + // can break text (e.g. not color). + Range GetTextBreakingRange() const; + + // Update the iterator to point to colors and styles applicable at |position|. + void IncrementToPosition(size_t position); + + private: + // Pointers to the breaklists to iterate through. These pointers can't be + // nullptr and the breaklists must outlive this object. + const BreakList* colors_; + const BreakList* baselines_; + const BreakList* font_size_overrides_; + const BreakList* weights_; + const StyleArray* styles_; + + BreakList::const_iterator color_; + BreakList::const_iterator baseline_; + BreakList::const_iterator font_size_override_; + BreakList::const_iterator weight_; + std::array::const_iterator, TEXT_STYLE_COUNT> style_; +}; + +// Line segments are slices of the display text to be rendered on a single line. +struct LineSegment { + LineSegment(); + ~LineSegment(); + + // X coordinates of this line segment in text space. + RangeF x_range; + + // The character range this segment corresponds to. + Range char_range; + + // Index of the text run that generated this segment. + size_t run; + + // Returns the width of this line segment in text space. + float width() const { return x_range.length(); } +}; + +// A line of display text, comprised of a line segment list and some metrics. +struct Line { + Line(); + Line(const Line& other); + ~Line(); + + // Segments that make up this line in visual order. + std::vector segments; + + // The sum of segment widths and the maximum of segment heights. + SizeF size; + + // Sum of preceding lines' heights. + int preceding_heights; + + // Maximum baseline of all segments on this line. + int baseline; + + // The text index of this line in |text_|. + int display_text_index = 0; +}; + +// Internal class that contains the results of the text layout and shaping. +class ShapedText { + public: + explicit ShapedText(std::vector lines); + ~ShapedText(); + + const std::vector& lines() const { return lines_; } + + private: + std::vector lines_; +}; + +// Creates an SkTypeface from a font, |italic| and a desired |weight|. +// May return null. +sk_sp CreateSkiaTypeface(const Font& font, + bool italic, + Font::Weight weight); + +// Applies the given FontRenderParams to the SkFont. +void ApplyRenderParams(const FontRenderParams& params, + bool subpixel_rendering_suppressed, + SkFont* font); + +} // namespace internal + +// RenderText represents an abstract model of styled text and its corresponding +// visual layout. Support is built in for a cursor, selections, simple styling, +// complex scripts, and bi-directional text. Implementations provide mechanisms +// for rendering and translation between logical and visual data. +class GFX_EXPORT RenderText { + public: +#if defined(OS_APPLE) + // On Mac, while selecting text if the cursor is outside the vertical text + // bounds, drag to the end of the text. + static constexpr bool kDragToEndIfOutsideVerticalBounds = true; + // Mac supports a selection that is "undirected". When undirected, the cursor + // doesn't know which end of the selection it's at until it first moves. + static constexpr bool kSelectionIsAlwaysDirected = false; +#else + static constexpr bool kDragToEndIfOutsideVerticalBounds = false; + static constexpr bool kSelectionIsAlwaysDirected = true; +#endif + + // Invalid value of baseline. Assigning this value to |baseline_| causes + // re-calculation of baseline. + static constexpr int kInvalidBaseline = INT_MAX; + + // Default fraction of the text size to use for a strike-through or underline. + static constexpr SkScalar kLineThicknessFactor = (SK_Scalar1 / 18); + + // The character used for displaying obscured text. Use a bullet character. + // TODO(pbos): This is highly font dependent, consider replacing the character + // with a vector glyph. + static constexpr char16_t kPasswordReplacementChar = 0x2022; + + RenderText(const RenderText&) = delete; + RenderText& operator=(const RenderText&) = delete; + virtual ~RenderText(); + + // Creates a RenderText instance. + static std::unique_ptr CreateRenderText(); + + // Like above but copies all style settings too. + std::unique_ptr CreateInstanceOfSameStyle( + const std::u16string& text) const; + + const std::u16string& text() const { return text_; } + void SetText(const std::u16string& text); + void AppendText(const std::u16string& text); + + HorizontalAlignment horizontal_alignment() const { + return horizontal_alignment_; + } + void SetHorizontalAlignment(HorizontalAlignment alignment); + + VerticalAlignment vertical_alignment() const { return vertical_alignment_; } + void SetVerticalAlignment(VerticalAlignment alignment); + + const FontList& font_list() const { return font_list_; } + void SetFontList(const FontList& font_list); + + bool cursor_enabled() const { return cursor_enabled_; } + void SetCursorEnabled(bool cursor_enabled); + + SkColor selection_color() const { return selection_color_; } + void set_selection_color(SkColor color) { selection_color_ = color; } + + SkColor selection_background_focused_color() const { + return selection_background_focused_color_; + } + void set_selection_background_focused_color(SkColor color) { + selection_background_focused_color_ = color; + } + + bool symmetric_selection_visual_bounds() const { + return symmetric_selection_visual_bounds_; + } + void set_symmetric_selection_visual_bounds(bool symmetric) { + symmetric_selection_visual_bounds_ = symmetric; + } + + bool focused() const { return focused_; } + void set_focused(bool focused) { focused_ = focused; } + + bool clip_to_display_rect() const { return clip_to_display_rect_; } + void set_clip_to_display_rect(bool clip) { clip_to_display_rect_ = clip; } + + // In an obscured (password) field, all text is drawn as bullets. + bool obscured() const { return obscured_; } + void SetObscured(bool obscured); + + // Makes a char in obscured text at |index| to be revealed. |index| should be + // a UTF16 text index. If there is a previous revealed index, the previous one + // is cleared and only the last set index will be revealed. If |index| is -1 + // or out of range, no char will be revealed. The revealed index is also + // cleared when SetText or SetObscured is called. + void SetObscuredRevealIndex(int index); + + // For obscured (password) fields, the extra spacing between glyphs. + int obscured_glyph_spacing() const { return obscured_glyph_spacing_; } + void SetObscuredGlyphSpacing(int spacing); + + bool multiline() const { return multiline_; } + void SetMultiline(bool multiline); + + // If multiline, a non-zero value will cap the number of lines rendered, + // and elide the rest (currently only ELIDE_TAIL supported.) + void SetMaxLines(size_t max_lines); + size_t max_lines() const { return max_lines_; } + + // Returns the actual number of lines, broken by |lines_|. + size_t GetNumLines(); + + // Returns the text index of the given line |line|. Returns the text length + // for any |line| above the number of lines. + size_t GetTextIndexOfLine(size_t line); + + // TODO(mukai): ELIDE_LONG_WORDS is not supported. + WordWrapBehavior word_wrap_behavior() const { return word_wrap_behavior_; } + void SetWordWrapBehavior(WordWrapBehavior behavior); + + // TODO(ckocagil): Add vertical alignment and line spacing support instead. + int min_line_height() const { return min_line_height_; } + void SetMinLineHeight(int line_height); + + // Set the maximum length of the layout text, not the actual text. + // A |length| of 0 forgoes a hard limit, but does not guarantee proper + // functionality of very long strings. Applies to subsequent SetText calls. + // WARNING: Only use this for system limits, it lacks complex text support. + void set_truncate_length(size_t length) { truncate_length_ = length; } + + // The display text will be elided to fit |display_rect| using this behavior. + void SetElideBehavior(ElideBehavior elide_behavior); + ElideBehavior elide_behavior() const { return elide_behavior_; } + + // When display text is elided, determines how whitespace is handled. + // If absl::nullopt is specified, the default elision for the current elide + // behavior will be applied. + void SetWhitespaceElision(absl::optional elide_whitespace); + absl::optional whitespace_elision() const { + return whitespace_elision_; + } + + const Rect& display_rect() const { return display_rect_; } + void SetDisplayRect(const Rect& r); + + bool subpixel_rendering_suppressed() const { + return subpixel_rendering_suppressed_; + } + void set_subpixel_rendering_suppressed(bool suppressed) { + subpixel_rendering_suppressed_ = suppressed; + } + + const SelectionModel& selection_model() const { return selection_model_; } + const Range& selection() const { return selection_model_.selection(); } + size_t cursor_position() const { return selection_model_.caret_pos(); } + const std::vector& secondary_selections() const { + return selection_model_.secondary_selections(); + } + const std::vector GetAllSelections() const; + + // Set the cursor to |position|, with the caret affinity trailing the previous + // grapheme, or if there is no previous grapheme, leading the cursor position. + // See SelectionModel::caret_affinity_ for details. + void SetCursorPosition(size_t position); + + // Moves the cursor left or right. Cursor movement is visual, meaning that + // left and right are relative to screen, not the directionality of the text. + // |selection_behavior| determines whether a selection is to be made and it's + // behavior. + void MoveCursor(BreakType break_type, + VisualCursorDirection direction, + SelectionBehavior selection_behavior); + + // Set the selection_model_ to the value of |selection|. + // The selection range is clamped to text().length() if out of range. + // Returns true if the cursor position or selection range changed. + // If any index in |selection_model| is not a cursorable position (not on a + // grapheme boundary), it is a no-op and returns false. + bool SetSelection(const SelectionModel& selection); + + // Moves the cursor to the text index corresponding to |point|. If |select| is + // true, a selection is made with the current selection start index. If the + // resultant text indices do not lie on valid grapheme boundaries, it is a no- + // op and returns false. If this move is happening because of a drag causing a + // selection change, and |drag_origin| is not the zero point, then + // |drag_origin| overrides the default origin for a select-to-drag + // (usually the existing text insertion cursor). + bool MoveCursorToPoint(const gfx::Point& point, + bool select, + const gfx::Point& drag_origin = gfx::Point()); + + // Set the |selection_model_| based on |range|. If the |range| start or end is + // greater than text length, it is modified to be the text length. If the + // |range| start or end is not a cursorable position (not on grapheme + // boundary), it is a NO-OP and returns false. Otherwise, returns true. If + // |primary| is true, secondary selections are cleared; otherwise, the range + // will be added as a secondary selection not associated with the cursor. In + // the latter case, |range| should not overlap with existing selections. + bool SelectRange(const Range& range, bool primary = true); + + // Returns true if the local point is over selected text. + bool IsPointInSelection(const Point& point); + + // Selects no text, keeping the current cursor position and caret affinity. + void ClearSelection(); + + // Select the entire text range. If |reversed| is true, the range will end at + // the logical beginning of the text; this generally shows the leading portion + // of text that overflows its display area. + void SelectAll(bool reversed); + + // Selects the word at the current cursor position. If there is a non-empty + // selection, the selection bounds are extended to their nearest word + // boundaries. + void SelectWord(); + + void SetCompositionRange(const Range& composition_range); + + // Set the text color over the entire text or a logical character range. + // The |range| should be valid, non-reversed, and within [0, text().length()]. + void SetColor(SkColor value); + void ApplyColor(SkColor value, const Range& range); + + // DEPRECATED. + // Set the baseline style over the entire text or a logical character range. + // The |range| should be valid, non-reversed, and within [0, text().length()]. + // TODO(tapted): Remove this. The only client is moving to + // ApplyFontSizeOverride. + void SetBaselineStyle(BaselineStyle value); + void ApplyBaselineStyle(BaselineStyle value, const Range& range); + + // Alters the font size in |range|. + void ApplyFontSizeOverride(int font_size_override, const Range& range); + + // Set various text styles over the entire text or a logical character range. + // The respective |style| is applied if |value| is true, or removed if false. + // The |range| should be valid, non-reversed, and within [0, text().length()]. + void SetStyle(TextStyle style, bool value); + void ApplyStyle(TextStyle style, bool value, const Range& range); + + void SetWeight(Font::Weight weight); + void ApplyWeight(Font::Weight weight, const Range& range); + + // Returns whether this style is enabled consistently across the entire + // RenderText. + bool GetStyle(TextStyle style) const; + + // Set or get the text directionality mode and get the text direction yielded. + void SetDirectionalityMode(DirectionalityMode mode); + DirectionalityMode directionality_mode() const { + return directionality_mode_; + } + + base::i18n::TextDirection GetTextDirection() const; + base::i18n::TextDirection GetDisplayTextDirection(); + + // Returns the visual movement direction corresponding to the logical + // end/beginning of the text, considering only the dominant direction returned + // by |GetDisplayTextDirection()|, not the direction of a particular run. + VisualCursorDirection GetVisualDirectionOfLogicalEnd(); + VisualCursorDirection GetVisualDirectionOfLogicalBeginning(); + + // Returns the text used to display, which may be obscured, truncated or + // elided. The subclass may compute elided text on the fly, or use + // precomputed the elided text. + virtual const std::u16string& GetDisplayText() = 0; + + // Returns the size required to display the current string (which is the + // wrapped size in multiline mode). The returned size does not include space + // reserved for the cursor or the offset text shadows. + Size GetStringSize(); + + // This is same as GetStringSize except that fractional size is returned. + // The default implementation is same as GetStringSize. Certain platforms that + // compute the text size as floating-point values, like Mac, will override + // this method. + // See comment in Canvas::GetStringWidthF for its usage. + virtual SizeF GetStringSizeF() = 0; + + // Returns the size of the line containing |caret|. + virtual SizeF GetLineSizeF(const SelectionModel& caret) = 0; + + // Returns the sum of all the line widths. + float TotalLineWidth(); + + // Returns the width of the content (which is the wrapped width in multiline + // mode). Reserves room for the cursor if |cursor_enabled_| is true. + float GetContentWidthF(); + + // Same as GetContentWidthF with the width rounded up. + int GetContentWidth(); + + // Returns the common baseline of the text. The return value is the vertical + // offset from the top of |display_rect_| to the text baseline, in pixels. + // The baseline is determined from the font list and display rect, and does + // not depend on the text. + int GetBaseline(); + + // If |select_all| is true, draws as focused with all text selected. + void Draw(Canvas* canvas, bool select_all = false); + + // Gets the SelectionModel from a visual point in local coordinates. If + // |drag_origin| is nonzero, it is used as the baseline for + // out-of-vertical-bounds drags on platforms that have them, instead of the + // default origin (the insertion cursor's position). + SelectionModel FindCursorPosition(const Point& point, + const Point& drag_origin = gfx::Point()); + + // Returns true if the position is a valid logical index into text(). Indices + // amid multi-character graphemes are allowed here, unlike IsValidCursorIndex. + bool IsValidLogicalIndex(size_t index) const; + + // Returns true if the position is a valid logical index into text(), and is + // also a valid grapheme boundary, which may be used as a cursor position. + bool IsValidCursorIndex(size_t index) const; + + // Get the visual bounds of a cursor at |caret|. These bounds typically + // represent a vertical line if |insert_mode| is true. Pass false for + // |insert_mode| to retrieve the bounds of the associated glyph. These bounds + // are in local coordinates, but may be outside the visible region if the text + // is longer than the textfield. Subsequent text, cursor, or bounds changes + // may invalidate returned values. Note that |caret| must be placed at + // grapheme boundary, i.e. caret.caret_pos() must be a cursorable position. + // TODO(crbug.com/248597): Add multiline support. + Rect GetCursorBounds(const SelectionModel& caret, bool insert_mode); + + // Compute the current cursor bounds, panning the text to show the cursor in + // the display rect if necessary. These bounds are in local coordinates. + // Subsequent text, cursor, or bounds changes may invalidate returned values. + const Rect& GetUpdatedCursorBounds(); + + // Returns a grapheme iterator that contains the codepoint at |index|. + internal::GraphemeIterator GetGraphemeIteratorAtTextIndex(size_t index) const; + internal::GraphemeIterator GetGraphemeIteratorAtDisplayTextIndex( + size_t index) const; + + // For a given grapheme iterator, returns its index. + size_t GetTextIndex(internal::GraphemeIterator iter) const; + size_t GetDisplayTextIndex(internal::GraphemeIterator iter) const; + + // Returns true of the current index is at the start of a grapheme. + bool IsGraphemeBoundary(size_t index) const; + + // Given an |index| in text(), return the next or previous grapheme boundary + // in logical order (i.e. the nearest cursorable index). The return value is + // in the range 0 to text().length() inclusive (the input is clamped if it is + // out of that range). Always moves by at least one character index unless the + // supplied index is already at the boundary of the string. + size_t IndexOfAdjacentGrapheme(size_t index, + LogicalCursorDirection direction) const; + + // Return a SelectionModel with the cursor at the current selection's start. + // The returned value represents a cursor/caret position without a selection. + SelectionModel GetSelectionModelForSelectionStart() const; + + // Sets shadows to drawn with text. + void set_shadows(const ShadowValues& shadows) { shadows_ = shadows; } + const ShadowValues& shadows() const { return shadows_; } + + // Get the visual bounds containing the logical substring within the |range|. + // If |range| is empty, the result is empty. This method rounds internally so + // the returned bounds may be slightly larger than the |range|, but are + // guaranteed not to be smaller. These bounds could be visually discontinuous + // if the substring is split by a LTR/RTL level change. These bounds are in + // local coordinates, but may be outside the visible region if the text is + // larger than the available space. Subsequent text, cursor, or bounds changes + // may invalidate returned values. + virtual std::vector GetSubstringBounds(const Range& range) = 0; + + // Gets the horizontal span (relative to the left of the text, not the view) + // of the sequence of glyphs in |text_range|, over which the cursor will + // jump when breaking by characters. If the glyphs are RTL then the returned + // Range will have is_reversed() true. (This does not return a Rect because a + // Rect can't have a negative width.) + virtual RangeF GetCursorSpan(const Range& text_range) = 0; + + const Vector2d& GetUpdatedDisplayOffset(); + void SetDisplayOffset(int horizontal_offset); + void SetDisplayOffset(Vector2d offset); + + // Returns the line offset from the origin after applying the text alignment + // and the display offset. + Vector2d GetLineOffset(size_t line_number); + + // Retrieves the word displayed at the given |point| along with its styling + // information. |point| is in the view's coordinates. If no word is displayed + // at the point, returns a nearby word. |baseline_point| should correspond to + // the baseline point of the leftmost glyph of the |word| in the view's + // coordinates. Returns false, if no word can be retrieved. + bool GetWordLookupDataAtPoint(const Point& point, + DecoratedText* decorated_word, + Point* baseline_point); + + // Retrieves the text at |range| along with its styling information. + // |baseline_point| should correspond to the baseline point of + // the leftmost glyph of the text in the view's coordinates. If the text + // spans multiple lines, |baseline_point| will correspond with the leftmost + // glyph on the first line in the range. Returns false, if no text can be + // retrieved. + bool GetLookupDataForRange(const Range& range, + DecoratedText* decorated_text, + Point* baseline_point); + + // Retrieves the text in the given |range|. + std::u16string GetTextFromRange(const Range& range) const; + + void set_strike_thickness_factor(SkScalar f) { strike_thickness_factor_ = f; } + + // Return the line index that contains the argument; or the index of the last + // line if the |caret| exceeds the text length. + virtual size_t GetLineContainingCaret(const SelectionModel& caret) = 0; + + // Expands |range| to its nearest grapheme boundaries and returns the + // resulting range. Maintains directionality of |range|. + Range ExpandRangeToGraphemeBoundary(const Range& range) const; + + // Specify the width/height of a glyph for test. The width/height of glyphs is + // very platform-dependent and environment-dependent. Otherwise multiline text + // will become really flaky. + void set_glyph_width_for_test(float width) { glyph_width_for_test_ = width; } + void set_glyph_height_for_test(float height) { + glyph_height_for_test_ = height; + } + + protected: + RenderText(); + + // Whether |segment| corresponds to the newline character. This uses |text_| + // to look up the corresponding character. + bool IsNewlineSegment(const internal::LineSegment& segment) const; + + // Whether |segment| corresponds to the newline character inside |text|. + bool IsNewlineSegment(const std::u16string& text, + const internal::LineSegment& segment) const; + + // Returns the character range of segments in |line| excluding the trailing + // newline segment. + Range GetLineRange(const std::u16string& text, + const internal::Line& line) const; + + // Returns the text used for layout (e.g. after rewriting, eliding and + // obscuring characters). + const std::u16string& GetLayoutText() const; + + // NOTE: The value of these accessors may be stale. Please make sure + // that these fields are up to date before accessing them. + const std::u16string& display_text() const { return display_text_; } + bool text_elided() const { return text_elided_; } + + // Returns an iterator over the |text_| attributes. + internal::StyleIterator GetTextStyleIterator() const; + // Returns an iterator over the |layout_text_| attributes. + internal::StyleIterator GetLayoutTextStyleIterator() const; + + const BreakList& colors() const { return colors_; } + const BreakList& baselines() const { return baselines_; } + const BreakList& font_size_overrides() const { + return font_size_overrides_; + } + const BreakList& weights() const { return weights_; } + const internal::StyleArray& styles() const { return styles_; } + SkScalar strike_thickness_factor() const { return strike_thickness_factor_; } + + const BreakList& layout_colors() const { return layout_colors_; } + + // Whether all the BreakLists have only one break. + bool IsHomogeneous() const; + + // Returns the shaped text structure. The shaped text contains the visual + // positions of glyphs required to render the text. + bool has_shaped_text() const { return shaped_text_ != nullptr; } + internal::ShapedText* GetShapedText(); + void set_shaped_text(std::unique_ptr shaped_text) { + shaped_text_ = std::move(shaped_text); + } + + // Returns the baseline of the current text. The return value depends on + // the text and its layout while the return value of GetBaseline() doesn't. + // GetAlignmentOffset() takes into account the difference between them. + // + // We'd like a RenderText to show the text always on the same baseline + // regardless of the text, so the text does not jump up or down depending + // on the text. However, underlying layout engines return different baselines + // depending on the text. In general, layout engines determine the minimum + // bounding box for the text and return the baseline from the top of the + // bounding box. So the baseline changes depending on font metrics used to + // layout the text. + // + // For example, suppose there are FontA and FontB and the baseline of FontA + // is smaller than the one of FontB. If the text is laid out only with FontA, + // then the baseline of FontA may be returned. If the text includes some + // characters which are laid out with FontB, then the baseline of FontB may + // be returned. + // + // GetBaseline() returns the fixed baseline regardless of the text. + // GetDisplayTextBaseline() returns the baseline determined by the underlying + // layout engine, and it changes depending on the text. GetAlignmentOffset() + // returns the difference between them. + int GetDisplayTextBaseline(); + + // Get the selection model that visually neighbors |position| by |break_type|. + // The returned value represents a cursor/caret position without a selection. + SelectionModel GetAdjacentSelectionModel(const SelectionModel& current, + BreakType break_type, + VisualCursorDirection direction); + + // Get the selection model visually left/right of |selection| by one grapheme. + // The returned value represents a cursor/caret position without a selection. + virtual SelectionModel AdjacentCharSelectionModel( + const SelectionModel& selection, + VisualCursorDirection direction) = 0; + + // Get the selection model visually left/right of |selection| by one word. + // The returned value represents a cursor/caret position without a selection. + virtual SelectionModel AdjacentWordSelectionModel( + const SelectionModel& selection, + VisualCursorDirection direction) = 0; + + // Get the selection model visually above/below |selection| by one line. + // The returned value represents a cursor/caret position without a selection. + virtual SelectionModel AdjacentLineSelectionModel( + const SelectionModel& selection, + VisualCursorDirection direction) = 0; + + // Get the selection model corresponding to visual text ends. + // The returned value represents a cursor/caret position without a selection. + SelectionModel EdgeSelectionModel(VisualCursorDirection direction); + + // Get the selection model corresponding to visual text ends for |line_index|. + // The returned value represents a cursor/caret position without a selection. + SelectionModel LineSelectionModel(size_t line_index, + gfx::VisualCursorDirection direction); + + // Sets the selection model. |model| should be valid. + void SetSelectionModel(const SelectionModel& model); + // Adds a secondary selection. |selection| should not overlap with existing + // selections. + void AddSecondarySelection(const Range selection); + + // Convert between indices into |text_| and indices into + // GetDisplayText(), which differ when the text is obscured, + // truncated or elided. + size_t TextIndexToDisplayIndex(size_t index) const; + size_t DisplayIndexToTextIndex(size_t index) const; + + // Notifies that layout text, or attributes that affect the layout text + // shape have changed. |text_changed| is true if the content of the + // |layout_text_| has changed, not just attributes. + virtual void OnLayoutTextAttributeChanged(bool text_changed); + + // Notifies that attributes that affect the display text shape have changed. + virtual void OnDisplayTextAttributeChanged() = 0; + + // Ensure the text is laid out, lines are computed, and |lines_| is valid. + virtual void EnsureLayout() = 0; + + // Draw all text and make the given ranges appear selected. + virtual void DrawVisualText(internal::SkiaTextRenderer* renderer, + const std::vector& selections) = 0; + + // Update the display text. + void UpdateDisplayText(float text_width); + + // Returns display text positions that are suitable for breaking lines. + const BreakList& GetLineBreaks(); + + // Convert points from the text space to the view space. Handles the display + // area, display offset, application LTR/RTL mode and multiline. |line| is the + // index of the line in which |point| is found, and is required to be passed + // because by the time |point| is in text space, the information to account + // for certain zero-width characters (such as empty lines) is lost. + Point ToViewPoint(const PointF& point, size_t line); + + // Get the alignment, resolving ALIGN_TO_HEAD with the current text direction. + HorizontalAlignment GetCurrentHorizontalAlignment(); + + // Returns the line offset from the origin, accounts for text alignment only. + Vector2d GetAlignmentOffset(size_t line_number); + + // Applies fade effects to |renderer|. + void ApplyFadeEffects(internal::SkiaTextRenderer* renderer); + + // Applies text shadows to |renderer|. + void ApplyTextShadows(internal::SkiaTextRenderer* renderer); + + // Get the text direction for the current directionality mode and given + // |text|. + base::i18n::TextDirection GetTextDirectionForGivenText( + const std::u16string& text) const; + + // Adjust ranged styles to accommodate a new |text_| length. + void UpdateStyleLengths(); + + // Adjust ranged styles to accommodate a new |layout_text_| length. + void UpdateLayoutStyleLengths(size_t max_length) const; + + // Returns the line index for the given argument. |text_y| is relative to + // the text bounds. Returns -1 if |text_y| is above the text and + // lines().size() if |text_y| is below it. + int GetLineContainingYCoord(float text_y); + + // A convenience function to check whether the glyph attached to the caret + // is within the given range. + static bool RangeContainsCaret(const Range& range, + size_t caret_pos, + LogicalCursorDirection caret_affinity); + + // Returns the baseline, with which the text best appears vertically centered. + static int DetermineBaselineCenteringText(const int display_height, + const FontList& font_list); + + // Returns an expanded version of |rect| that is vertically symmetric with + // respect to the center of |display_rect|. + static gfx::Rect ExpandToBeVerticallySymmetric(const gfx::Rect& rect, + const gfx::Rect& display_rect); + + // Given |rects|, sort them along the x-axis and merge intersecting rects + // using union. Expects all selections in the text to be from the same line. + static void MergeIntersectingRects(std::vector& rects); + + // Resets |cached_cursor_x_| to null. When non-null, CURSOR_UP, CURSOR_DOWN + // movements use this value instead of the current cursor x position to + // determine the next cursor x position. + void reset_cached_cursor_x() { cached_cursor_x_.reset(); } + + void set_cached_cursor_x(int x) { cached_cursor_x_ = x; } + absl::optional cached_cursor_x() const { return cached_cursor_x_; } + + // Fixed width of glyphs. This should only be set in test environments. + float glyph_width_for_test_ = 0; + // Fixed height of glyphs. This should only be set in test environments. + float glyph_height_for_test_ = 0; + + private: + friend class test::RenderTextTestApi; + + // Resets |layout_text_| and |display_text_| and marks them dirty. + void OnTextAttributeChanged(); + + // Computes the |layout_text_| by rewriting it from |text_|, if needed. + // Computes the layout break lists, if needed. + void EnsureLayoutTextUpdated() const; + + // Elides |text| as needed to fit in the |available_width| using |behavior|. + // |text_width| is the pre-calculated width of the text shaped by this render + // text, or pass 0 if the width is unknown. + std::u16string Elide(const std::u16string& text, + float text_width, + float available_width, + ElideBehavior behavior); + + // Elides |email| as needed to fit the |available_width|. + std::u16string ElideEmail(const std::u16string& email, float available_width); + + // Update the cached bounds and display offset to ensure that the current + // cursor is within the visible display area. + void UpdateCachedBoundsAndOffset(); + + // Draws the specified ranges of text with a selected appearance. + void DrawSelections(Canvas* canvas, const std::vector& selections); + + // Returns a grapheme iterator that contains the codepoint at |index|. + internal::GraphemeIterator GetGraphemeIteratorAtIndex( + const std::u16string& text, + const size_t internal::TextToDisplayIndex::*field, + size_t index) const; + + // Returns the nearest word start boundary for |index|. First searches in the + // CURSOR_BACKWARD direction, then in the CURSOR_FORWARD direction. Returns + // the text length if no valid boundary is found. + size_t GetNearestWordStartBoundary(size_t index) const; + + // Expands |range| to its nearest word boundaries and returns the resulting + // range. Maintains directionality of |range|. + Range ExpandRangeToWordBoundary(const Range& range) const; + + // Returns an implementation-specific run list, if implemented. + virtual internal::TextRunList* GetRunList() = 0; + virtual const internal::TextRunList* GetRunList() const = 0; + + // Returns the decorated text corresponding to |range|. Returns false if the + // text cannot be retrieved, e.g. if the text is obscured. + virtual bool GetDecoratedTextForRange(const Range& range, + DecoratedText* decorated_text) = 0; + + // Logical UTF-16 string data to be drawn. + std::u16string text_; + + // Horizontal alignment of the text with respect to |display_rect_|. The + // default is to align left if the application UI is LTR and right if RTL. + HorizontalAlignment horizontal_alignment_{base::i18n::IsRTL() ? ALIGN_RIGHT + : ALIGN_LEFT}; + + // Vertical alignment of the text with respect to |display_rect_|. Only + // applicable when |multiline_| is true. The default is to align center. + VerticalAlignment vertical_alignment_ = ALIGN_MIDDLE; + + // The text directionality mode, defaults to DIRECTIONALITY_FROM_TEXT. + DirectionalityMode directionality_mode_ = DIRECTIONALITY_FROM_TEXT; + + // The cached text direction, potentially computed from the text or UI locale. + // Use GetTextDirection(), do not use this potentially invalid value directly! + mutable base::i18n::TextDirection text_direction_ = + base::i18n::UNKNOWN_DIRECTION; + mutable base::i18n::TextDirection display_text_direction_ = + base::i18n::UNKNOWN_DIRECTION; + + // A list of fonts used to render |text_|. + FontList font_list_; + + // Logical selection ranges and visual cursor position. + SelectionModel selection_model_; + + // The cached cursor bounds; get these bounds with GetUpdatedCursorBounds. + Rect cursor_bounds_; + + // Specifies whether the cursor is enabled. If disabled, no space is reserved + // for the cursor when positioning text. + bool cursor_enabled_ = true; + + // Whether the current selection has a known direction. That is, whether a + // directional input (e.g. arrow key) has been received for the current + // selection to indicate which end of the selection has the caret. When true, + // directed inputs preserve (rather than replace) the selection affinity. + bool has_directed_selection_ = kSelectionIsAlwaysDirected; + + // The color used for drawing selected text. + SkColor selection_color_ = kPlaceholderColor; + + // The background color used for drawing the selection when focused. + SkColor selection_background_focused_color_ = kPlaceholderColor; + + // Whether the selection visual bounds should be expanded vertically to be + // vertically symmetric with respect to the display rect. Note this flag has + // no effect on multi-line text. + bool symmetric_selection_visual_bounds_ = false; + + // The focus state of the text. + bool focused_ = false; + + // Composition text range. + Range composition_range_ = Range::InvalidRange(); + + // Color, baseline, and style breaks, used to modify ranges of text. + // BreakList positions are stored with text indices, not display indices. + // TODO(msw): Expand to support cursor, selection, background, etc. colors. + BreakList colors_{kPlaceholderColor}; + BreakList baselines_{NORMAL_BASELINE}; + BreakList font_size_overrides_{0}; + BreakList weights_{Font::Weight::NORMAL}; + internal::StyleArray styles_; + + mutable BreakList layout_colors_; + mutable BreakList layout_baselines_; + mutable BreakList layout_font_size_overrides_; + mutable BreakList layout_weights_; + mutable internal::StyleArray layout_styles_; + + // A mapping from text to display text indices for each grapheme. The vector + // contains an ordered sequence of indice pairs. Both sequence |text_index| + // and |display_index| are sorted. + mutable internal::TextToDisplaySequence text_to_display_indices_; + + // A flag to obscure actual text with asterisks for password fields. + bool obscured_ = false; + // The index at which the char should be revealed in the obscured text. + int obscured_reveal_index_ = -1; + + // The maximum length of text to display, 0 forgoes a hard limit. + size_t truncate_length_ = 0; + + // The obscured and/or truncated text used to layout the text to display. + mutable std::u16string layout_text_; + + // The elided text displayed visually. This is empty if the text + // does not have to be elided, or became empty as a result of eliding. + // TODO(oshima): When the text is elided, painting can be done only with + // display text info, so it should be able to clear the |layout_text_| and + // associated information. + mutable std::u16string display_text_; + + // The behavior for eliding, fading, or truncating. + ElideBehavior elide_behavior_ = NO_ELIDE; + + // The behavior for eliding whitespace when eliding or truncating. + absl::optional whitespace_elision_; + + // True if the text is elided given the current behavior and display area. + bool text_elided_ = false; + + // The minimum height a line should have. + int min_line_height_ = 0; + + // Whether the text should be broken into multiple lines. Uses the width of + // |display_rect_| as the width cap. + bool multiline_ = false; + + // If multiple lines, the maximum number of lines to render, or 0. + size_t max_lines_ = 0; + + // The wrap behavior when the text is broken into lines. Do nothing unless + // |multiline_| is set. The default value is IGNORE_LONG_WORDS. + WordWrapBehavior word_wrap_behavior_ = IGNORE_LONG_WORDS; + + // Set to true to suppress subpixel rendering due to non-font reasons (eg. + // if the background is transparent). The default value is false. + bool subpixel_rendering_suppressed_ = false; + + // The local display area for rendering the text. + Rect display_rect_; + + // Flag to work around a Skia bug with the PDF path (http://crbug.com/133548) + // that results in incorrect clipping when drawing to the document margins. + // This field allows disabling clipping to work around the issue. + // TODO(asvitkine): Remove this when the underlying Skia bug is fixed. + bool clip_to_display_rect_ = true; + + // The offset for the text to be drawn, relative to the display area. + // Get this point with GetUpdatedDisplayOffset (or risk using a stale value). + Vector2d display_offset_; + + // The baseline of the text. This is determined from the height of the + // display area and the cap height of the font list so the text is vertically + // centered. + int baseline_ = kInvalidBaseline; + + // The cached bounds and offset are invalidated by changes to the cursor, + // selection, font, and other operations that adjust the visible text bounds. + bool cached_bounds_and_offset_valid_ = false; + + // Text shadows to be drawn. + ShadowValues shadows_; + + // A list of valid display text line break positions. + BreakList line_breaks_; + + // Text shaping computed by EnsureLayout. This should be invalidated upon + // OnLayoutTextAttributeChanged and OnDisplayTextAttributeChanged calls. + std::unique_ptr shaped_text_; + + // The ratio of strike-through line thickness to text height. + SkScalar strike_thickness_factor_ = kLineThicknessFactor; + + // Extra spacing placed between glyphs; used only for obscured text styling. + int obscured_glyph_spacing_ = 0; + + // The cursor position in view space, used to traverse lines of varied widths. + absl::optional cached_cursor_x_; + + // Tell whether or not the |layout_text_| needs an update or is up to date. + mutable bool layout_text_up_to_date_ = false; +}; + +} // namespace gfx + +#endif // UI_GFX_RENDER_TEXT_H_ diff --git a/render_text_api_fuzzer.cc b/render_text_api_fuzzer.cc new file mode 100644 index 000000000000..8d39dfc2ee00 --- /dev/null +++ b/render_text_api_fuzzer.cc @@ -0,0 +1,376 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +#include "base/at_exit.h" +#include "base/command_line.h" +#include "base/i18n/icu_util.h" +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" +#include "base/test/task_environment.h" +#include "base/test/test_timeouts.h" +#include "build/build_config.h" +#include "build/chromeos_buildflags.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/render_text.h" + +// TODO(crbug.com/1052397): Revisit once build flag switch of lacros-chrome is +// complete. +#if defined(OS_ANDROID) || (defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)) +#include "base/test/test_discardable_memory_allocator.h" +#endif + +namespace { + +#if defined(OS_WIN) +const char kFontDescription[] = "Segoe UI, 13px"; +#elif defined(OS_ANDROID) +const char kFontDescription[] = "serif, 13px"; +#else +const char kFontDescription[] = "sans, 13px"; +#endif + +struct Environment { + Environment() + : task_environment((base::CommandLine::Init(0, nullptr), + TestTimeouts::Initialize(), + base::test::TaskEnvironment::MainThreadType::UI)) { + logging::SetMinLogLevel(logging::LOG_FATAL); +// TODO(crbug.com/1052397): Revisit once build flag switch of lacros-chrome is +// complete. +#if defined(OS_ANDROID) || (defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)) + // Some platforms require discardable memory to use bitmap fonts. + base::DiscardableMemoryAllocator::SetInstance( + &discardable_memory_allocator); +#endif + CHECK(base::i18n::InitializeICU()); + gfx::FontList::SetDefaultFontDescription(kFontDescription); + } + +// TODO(crbug.com/1052397): Revisit once build flag switch of lacros-chrome is +// complete. +#if defined(OS_ANDROID) || (defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)) + base::TestDiscardableMemoryAllocator discardable_memory_allocator; +#endif + + base::AtExitManager at_exit_manager; + base::test::TaskEnvironment task_environment; +}; + +// Commands recognized to drive the API calls on RenderText. +enum class RenderTextAPI { + kDraw, + kSetText, + kAppendText, + kSetHorizontalAlignment, + kSetVerticalAlignment, + kSetCursorEnabled, + kSetSelectionColor, + kSetSelectionBackgroundFocusedColor, + kSetSymmetricSelectionVisualBounds, + kSetFocused, + kSetClipToDisplayRect, + kSetObscured, + kSetObscuredRevealIndex, + kSetMultiline, + kSetMaxLines, + kSetWordWrapBehavior, + kSetWhitespaceElision, + kSetSubpixelRenderingSuppressed, + kSetColor, + kApplyColor, + kSetStyle, + kApplyStyle, + kSetWeight, + kApplyWeight, + kSetDirectionalityMode, + kSetElideBehavior, + kIsGraphemeBoundary, + kIndexOfAdjacentGrapheme, + kSetObscuredGlyphSpacing, + kSetDisplayRect, + kGetSubstringBounds, + kGetCursorSpan, + kMaxValue = kGetCursorSpan +}; + +gfx::DirectionalityMode ConsumeDirectionalityMode(FuzzedDataProvider* fdp) { + if (fdp->ConsumeBool()) + return gfx::DIRECTIONALITY_FORCE_RTL; + return gfx::DIRECTIONALITY_FORCE_LTR; +} + +gfx::HorizontalAlignment ConsumeHorizontalAlignment(FuzzedDataProvider* fdp) { + switch (fdp->ConsumeIntegralInRange(0, 4)) { + case 0: + return gfx::ALIGN_LEFT; + case 1: + return gfx::ALIGN_CENTER; + case 2: + return gfx::ALIGN_RIGHT; + case 3: + return gfx::ALIGN_TO_HEAD; + default: + return gfx::ALIGN_LEFT; + } +} + +gfx::VerticalAlignment ConsumeVerticalAlignment(FuzzedDataProvider* fdp) { + switch (fdp->ConsumeIntegralInRange(0, 3)) { + case 0: + return gfx::ALIGN_TOP; + case 1: + return gfx::ALIGN_MIDDLE; + case 2: + return gfx::ALIGN_BOTTOM; + default: + return gfx::ALIGN_TOP; + } +} + +gfx::TextStyle ConsumeStyle(FuzzedDataProvider* fdp) { + switch (fdp->ConsumeIntegralInRange(0, 4)) { + case 0: + return gfx::TEXT_STYLE_ITALIC; + case 1: + return gfx::TEXT_STYLE_STRIKE; + case 2: + return gfx::TEXT_STYLE_UNDERLINE; + case 3: + return gfx::TEXT_STYLE_HEAVY_UNDERLINE; + default: + return gfx::TEXT_STYLE_ITALIC; + } +} + +gfx::WordWrapBehavior ConsumeWordWrap(FuzzedDataProvider* fdp) { + // TODO(1150235): ELIDE_LONG_WORDS is not supported. + switch (fdp->ConsumeIntegralInRange(0, 3)) { + case 0: + return gfx::IGNORE_LONG_WORDS; + case 1: + return gfx::TRUNCATE_LONG_WORDS; + case 2: + return gfx::WRAP_LONG_WORDS; + default: + return gfx::IGNORE_LONG_WORDS; + } +} + +gfx::ElideBehavior ConsumeElideBehavior(FuzzedDataProvider* fdp) { + switch (fdp->ConsumeIntegralInRange(0, 7)) { + case 0: + return gfx::NO_ELIDE; + case 1: + return gfx::TRUNCATE; + case 2: + return gfx::ELIDE_HEAD; + case 3: + return gfx::ELIDE_MIDDLE; + case 4: + return gfx::ELIDE_TAIL; + case 5: + return gfx::ELIDE_EMAIL; + case 6: + return gfx::FADE_TAIL; + default: + return gfx::NO_ELIDE; + } +} + +gfx::LogicalCursorDirection ConsumeLogicalCursorDirection( + FuzzedDataProvider* fdp) { + switch (fdp->ConsumeIntegralInRange(0, 1)) { + case 0: + return gfx::CURSOR_BACKWARD; + default: + return gfx::CURSOR_FORWARD; + } +} + +gfx::Font::Weight ConsumeWeight(FuzzedDataProvider* fdp) { + if (fdp->ConsumeBool()) + return gfx::Font::Weight::BOLD; + return gfx::Font::Weight::NORMAL; +} + +SkColor ConsumeSkColor(FuzzedDataProvider* fdp) { + return static_cast(fdp->ConsumeIntegral()); +} + +gfx::Range ConsumeRange(FuzzedDataProvider* fdp, size_t max) { + size_t start = fdp->ConsumeIntegralInRange(0, max); + size_t end = fdp->ConsumeIntegralInRange(start, max); + return gfx::Range(start, end); +} + +const int kMaxStringLength = 128; + +} // anonymous namespace + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + static Environment env; + + std::unique_ptr render_text = + gfx::RenderText::CreateRenderText(); + gfx::Canvas canvas; + + FuzzedDataProvider fdp(data, size); + while (fdp.remaining_bytes() != 0) { + const RenderTextAPI command = fdp.ConsumeEnum(); + switch (command) { + case RenderTextAPI::kDraw: + render_text->Draw(&canvas); + break; + + case RenderTextAPI::kSetText: + render_text->SetText( + base::UTF8ToUTF16(fdp.ConsumeRandomLengthString(kMaxStringLength))); + break; + + case RenderTextAPI::kAppendText: + render_text->AppendText( + base::UTF8ToUTF16(fdp.ConsumeRandomLengthString(kMaxStringLength))); + break; + + case RenderTextAPI::kSetHorizontalAlignment: + render_text->SetHorizontalAlignment(ConsumeHorizontalAlignment(&fdp)); + break; + + case RenderTextAPI::kSetVerticalAlignment: + render_text->SetVerticalAlignment(ConsumeVerticalAlignment(&fdp)); + break; + + case RenderTextAPI::kSetCursorEnabled: + render_text->SetCursorEnabled(fdp.ConsumeBool()); + break; + + case RenderTextAPI::kSetSelectionColor: + render_text->set_selection_color(ConsumeSkColor(&fdp)); + break; + + case RenderTextAPI::kSetSelectionBackgroundFocusedColor: + render_text->set_selection_background_focused_color( + ConsumeSkColor(&fdp)); + break; + + case RenderTextAPI::kSetSymmetricSelectionVisualBounds: + render_text->set_symmetric_selection_visual_bounds(fdp.ConsumeBool()); + break; + + case RenderTextAPI::kSetFocused: + render_text->set_focused(fdp.ConsumeBool()); + break; + + case RenderTextAPI::kSetClipToDisplayRect: + render_text->set_clip_to_display_rect(fdp.ConsumeBool()); + break; + + case RenderTextAPI::kSetObscured: + render_text->SetObscured(fdp.ConsumeBool()); + break; + + case RenderTextAPI::kSetObscuredRevealIndex: + render_text->SetObscuredRevealIndex(fdp.ConsumeIntegralInRange( + 0, render_text->text().length())); + break; + + case RenderTextAPI::kSetMultiline: + render_text->SetMultiline(fdp.ConsumeBool()); + break; + + case RenderTextAPI::kSetMaxLines: + render_text->SetMaxLines(fdp.ConsumeIntegralInRange(0, 5)); + break; + + case RenderTextAPI::kSetWordWrapBehavior: + render_text->SetWordWrapBehavior(ConsumeWordWrap(&fdp)); + break; + + case RenderTextAPI::kSetWhitespaceElision: + render_text->SetWhitespaceElision(fdp.ConsumeBool()); + break; + + case RenderTextAPI::kSetSubpixelRenderingSuppressed: + render_text->set_subpixel_rendering_suppressed(fdp.ConsumeBool()); + break; + + case RenderTextAPI::kSetColor: + render_text->SetColor(ConsumeSkColor(&fdp)); + break; + + case RenderTextAPI::kApplyColor: + render_text->ApplyColor( + ConsumeSkColor(&fdp), + ConsumeRange(&fdp, render_text->text().length())); + break; + + case RenderTextAPI::kSetStyle: + render_text->SetStyle(ConsumeStyle(&fdp), fdp.ConsumeBool()); + break; + + case RenderTextAPI::kApplyStyle: + render_text->ApplyStyle( + ConsumeStyle(&fdp), fdp.ConsumeBool(), + ConsumeRange(&fdp, render_text->text().length())); + break; + + case RenderTextAPI::kSetWeight: + render_text->SetWeight(ConsumeWeight(&fdp)); + break; + + case RenderTextAPI::kApplyWeight: + render_text->ApplyWeight( + ConsumeWeight(&fdp), + ConsumeRange(&fdp, render_text->text().length())); + break; + + case RenderTextAPI::kSetDirectionalityMode: + render_text->SetDirectionalityMode(ConsumeDirectionalityMode(&fdp)); + break; + + case RenderTextAPI::kSetElideBehavior: + render_text->SetElideBehavior(ConsumeElideBehavior(&fdp)); + break; + + case RenderTextAPI::kIsGraphemeBoundary: + render_text->IsGraphemeBoundary(fdp.ConsumeIntegralInRange( + 0, render_text->text().length())); + break; + + case RenderTextAPI::kIndexOfAdjacentGrapheme: { + size_t index = render_text->IndexOfAdjacentGrapheme( + fdp.ConsumeIntegralInRange(0, render_text->text().length()), + ConsumeLogicalCursorDirection(&fdp)); + bool is_grapheme = render_text->IsGraphemeBoundary(index); + DCHECK(is_grapheme); + break; + } + case RenderTextAPI::kSetObscuredGlyphSpacing: + render_text->SetObscuredGlyphSpacing( + fdp.ConsumeIntegralInRange(0, 10)); + break; + case RenderTextAPI::kSetDisplayRect: + render_text->SetDisplayRect( + gfx::Rect(fdp.ConsumeIntegralInRange(-30, 30), + fdp.ConsumeIntegralInRange(-30, 30), + fdp.ConsumeIntegralInRange(0, 200), + fdp.ConsumeIntegralInRange(0, 30))); + break; + case RenderTextAPI::kGetSubstringBounds: + render_text->GetSubstringBounds( + ConsumeRange(&fdp, render_text->text().length())); + break; + case RenderTextAPI::kGetCursorSpan: + render_text->GetCursorSpan( + ConsumeRange(&fdp, render_text->text().length())); + break; + } + } + + return 0; +} diff --git a/render_text_fuzzer.cc b/render_text_fuzzer.cc new file mode 100644 index 000000000000..9ce8bc6680d5 --- /dev/null +++ b/render_text_fuzzer.cc @@ -0,0 +1,53 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/at_exit.h" +#include "base/command_line.h" +#include "base/i18n/icu_util.h" +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" +#include "base/test/task_environment.h" +#include "base/test/test_timeouts.h" +#include "build/build_config.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/render_text.h" + +namespace { + +#if defined(OS_WIN) +const char kFontDescription[] = "Segoe UI, 13px"; +#elif defined(OS_ANDROID) +const char kFontDescription[] = "serif, 13px"; +#else +const char kFontDescription[] = "sans, 13px"; +#endif + +struct Environment { + Environment() + : task_environment((base::CommandLine::Init(0, nullptr), + TestTimeouts::Initialize(), + base::test::TaskEnvironment::MainThreadType::UI)) { + logging::SetMinLogLevel(logging::LOG_FATAL); + + CHECK(base::i18n::InitializeICU()); + gfx::FontList::SetDefaultFontDescription(kFontDescription); + } + + base::AtExitManager at_exit_manager; + base::test::TaskEnvironment task_environment; +}; + +} // anonymous namespace + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + static Environment env; + + std::unique_ptr render_text = + gfx::RenderText::CreateRenderText(); + gfx::Canvas canvas; + render_text->SetText(base::UTF8ToUTF16( + base::StringPiece(reinterpret_cast(data), size))); + render_text->Draw(&canvas); + return 0; +} diff --git a/render_text_harfbuzz.cc b/render_text_harfbuzz.cc new file mode 100644 index 000000000000..9e992566de36 --- /dev/null +++ b/render_text_harfbuzz.cc @@ -0,0 +1,2277 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/render_text_harfbuzz.h" + +#include +#include + +#include "base/command_line.h" +#include "base/containers/contains.h" +#include "base/containers/mru_cache.h" +#include "base/containers/span.h" +#include "base/feature_list.h" +#include "base/hash/hash.h" +#include "base/i18n/base_i18n_switches.h" +#include "base/i18n/break_iterator.h" +#include "base/i18n/char_iterator.h" +#include "base/i18n/rtl.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/metrics/histogram_macros.h" +#include "base/no_destructor.h" +#include "base/numerics/safe_conversions.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/task/current_thread.h" +#include "base/trace_event/trace_event.h" +#include "build/build_config.h" +#include "third_party/icu/source/common/unicode/ubidi.h" +#include "third_party/icu/source/common/unicode/uscript.h" +#include "third_party/icu/source/common/unicode/utf16.h" +#include "third_party/skia/include/core/SkColor.h" +#include "third_party/skia/include/core/SkFontMetrics.h" +#include "third_party/skia/include/core/SkTypeface.h" +#include "ui/gfx/bidi_line_iterator.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/decorated_text.h" +#include "ui/gfx/font.h" +#include "ui/gfx/font_fallback.h" +#include "ui/gfx/font_render_params.h" +#include "ui/gfx/harfbuzz_font_skia.h" +#include "ui/gfx/platform_font.h" +#include "ui/gfx/range/range_f.h" +#include "ui/gfx/skia_util.h" +#include "ui/gfx/switches.h" +#include "ui/gfx/text_utils.h" +#include "ui/gfx/utf16_indexing.h" + +#if defined(OS_APPLE) +#include "base/mac/foundation_util.h" +#include "base/mac/mac_util.h" +#include "third_party/skia/include/ports/SkTypeface_mac.h" +#endif + +#if defined(OS_ANDROID) +#include "base/android/locale_utils.h" +#endif // defined(OS_ANDROID) + +#include + +namespace gfx { + +namespace { + +// Text length limit. Longer strings are slow and not fully tested. +const size_t kMaxTextLength = 10000; + +// The maximum number of scripts a Unicode character can belong to. This value +// is arbitrarily chosen to be a good limit because it is unlikely for a single +// character to belong to more scripts. +const size_t kMaxScripts = 32; + +// Font fallback mechanism used to Shape runs (see ShapeRuns(...)). +// These values are persisted to logs. Entries should not be renumbered and +// numeric values should never be reused. +enum class ShapeRunFallback { + FAILED = 0, + NO_FALLBACK = 1, + FALLBACK = 2, + FALLBACKS = 3, + kMaxValue = FALLBACKS +}; + +// Log the fallback font mechanism used for shaping to UMA (see ShapeRuns(...)). +void RecordShapeRunsFallback(ShapeRunFallback fallback) { + UMA_HISTOGRAM_ENUMERATION("RenderTextHarfBuzz.ShapeRunsFallback", fallback); +} + +// Returns whether the codepoint has the 'extended pictographic' property. +bool IsExtendedPictographicCodepoint(UChar32 codepoint) { + return u_hasBinaryProperty(codepoint, UCHAR_EXTENDED_PICTOGRAPHIC); +} + +// Returns whether the codepoint has emoji properties. +bool IsEmojiRelatedCodepoint(UChar32 codepoint) { + return u_hasBinaryProperty(codepoint, UCHAR_EMOJI) || + u_hasBinaryProperty(codepoint, UCHAR_EMOJI_PRESENTATION) || + u_hasBinaryProperty(codepoint, UCHAR_REGIONAL_INDICATOR); +} + +// Returns true if |codepoint| is a bracket. This is used to avoid "matching" +// brackets picking different font fallbacks, thereby appearing mismatched. +bool IsBracket(UChar32 codepoint) { + return u_getIntPropertyValue(codepoint, UCHAR_BIDI_PAIRED_BRACKET_TYPE) != + U_BPT_NONE; +} + +// Writes the script and the script extensions of the Unicode |codepoint|. +// Returns the number of written scripts. +size_t GetScriptExtensions(UChar32 codepoint, UScriptCode* scripts) { + // Fill |scripts| with the script extensions. + UErrorCode icu_error = U_ZERO_ERROR; + size_t count = + uscript_getScriptExtensions(codepoint, scripts, kMaxScripts, &icu_error); + DCHECK_NE(icu_error, U_BUFFER_OVERFLOW_ERROR) << " #ext: " << count; + if (U_FAILURE(icu_error)) + return 0; + + return count; +} + +// Intersects the script extensions set of |codepoint| with |result| and writes +// to |result|, reading and updating |result_size|. The output |result| will be +// a subset of the input |result| (thus |result_size| can only be smaller). +void ScriptSetIntersect(UChar32 codepoint, + UScriptCode* result, + size_t* result_size) { + // Each codepoint has a Script property and a Script Extensions (Scx) + // property. + // + // The implicit Script property values 'Common' and 'Inherited' indicate that + // a codepoint is widely used in many scripts, rather than being associated + // to a specific script. + // + // However, some codepoints that are assigned a value of 'Common' or + // 'Inherited' are not commonly used with all scripts, but rather only with a + // limited set of scripts. The Script Extension property is used to specify + // the set of script which borrow the codepoint. + // + // Calls to GetScriptExtensions(...) return the set of scripts where the + // codepoints can be used. + // (see table 7 from http://www.unicode.org/reports/tr24/tr24-29.html) + // + // Script Script Extensions -> Results + // 1) Common {Common} -> {Common} + // Inherited {Inherited} -> {Inherited} + // 2) Latin {Latn} -> {Latn} + // Inherited {Latn} -> {Latn} + // 3) Common {Hira Kana} -> {Hira Kana} + // Inherited {Hira Kana} -> {Hira Kana} + // 4) Devanagari {Deva Dogr Kthi Mahj} -> {Deva Dogr Kthi Mahj} + // Myanmar {Cakm Mymr Tale} -> {Cakm Mymr Tale} + // + // For most of the codepoints, the script extensions set contains only one + // element. For CJK codepoints, it's common to see 3-4 scripts. For really + // rare cases, the set can go above 20 scripts. + UScriptCode scripts[kMaxScripts] = { USCRIPT_INVALID_CODE }; + size_t count = GetScriptExtensions(codepoint, scripts); + + // Implicit script 'inherited' is inheriting scripts from preceding codepoint. + if (count == 1 && scripts[0] == USCRIPT_INHERITED) + return; + + // Perform the intersection of both script set. + auto scripts_span = base::span(scripts, count); + DCHECK(!base::Contains(scripts_span, USCRIPT_INHERITED)); + auto results_span = base::span(result, *result_size); + + size_t out_size = 0; + for (UScriptCode current : results_span) { + if (base::Contains(scripts_span, current)) + result[out_size++] = current; + } + + *result_size = out_size; +} + +struct GraphemeProperties { + bool has_control = false; + bool has_bracket = false; + bool has_pictographic = false; + bool has_emoji = false; + UBlockCode block = UBLOCK_NO_BLOCK; +}; + +// Returns the properties for the codepoints part of the given text. +GraphemeProperties RetrieveGraphemeProperties(const base::StringPiece16& text, + bool retrieve_block) { + GraphemeProperties properties; + bool first_char = true; + for (base::i18n::UTF16CharIterator iter(text); !iter.end(); iter.Advance()) { + const UChar32 codepoint = iter.get(); + + if (first_char) { + first_char = false; + if (retrieve_block) + properties.block = ublock_getCode(codepoint); + } + + if (codepoint == '\n' || codepoint == '\r' || codepoint == ' ') + properties.has_control = true; + if (IsBracket(codepoint)) + properties.has_bracket = true; + if (IsExtendedPictographicCodepoint(codepoint)) + properties.has_pictographic = true; + if (IsEmojiRelatedCodepoint(codepoint)) + properties.has_emoji = true; + } + + return properties; +} + +// Return whether the grapheme properties are compatible and the grapheme can +// be merge together in the same grapheme cluster. +bool AreGraphemePropertiesCompatible(const GraphemeProperties& first, + const GraphemeProperties& second) { + // There are 5 constrains to grapheme to be compatible. + // 1) The newline character and control characters should form a single run so + // that the line breaker can handle them easily. + // 2) Parentheses should be put in a separate run to avoid using different + // fonts while rendering matching parentheses (see http://crbug.com/396776). + // 3) Pictographic graphemes should be put in separate run to avoid altering + // fonts selection while rendering adjacent text (see + // http://crbug.com/278913). + // 4) Emoji graphemes should be put in separate run (see + // http://crbug.com/530021 and http://crbug.com/533721). + // 5) The 'COMMON' script needs to be split by unicode block. Codepoints are + // spread across blocks and supported with different fonts. + return !first.has_control && !second.has_control && + first.has_bracket == second.has_bracket && + first.has_pictographic == second.has_pictographic && + first.has_emoji == second.has_emoji && first.block == second.block; +} + +// Returns the end of the current grapheme cluster. This function is finding the +// breaking point where grapheme properties are no longer compatible +// (see: UNICODE TEXT SEGMENTATION (http://unicode.org/reports/tr29/). +// Breaks between |run_start| and |run_end| and force break after the grapheme +// starting at |run_break|. +size_t FindRunBreakingCharacter(const std::u16string& text, + UScriptCode script, + size_t run_start, + size_t run_break, + size_t run_end) { + const size_t run_length = run_end - run_start; + const base::StringPiece16 run_text(text.c_str() + run_start, run_length); + const bool is_common_script = (script == USCRIPT_COMMON); + + DCHECK(!run_text.empty()); + + // Create an iterator to split the text in graphemes. + base::i18n::BreakIterator grapheme_iterator( + run_text, base::i18n::BreakIterator::BREAK_CHARACTER); + if (!grapheme_iterator.Init() || !grapheme_iterator.Advance()) { + // In case of error, isolate the first character in a separate run. + NOTREACHED(); + return run_start + 1; + } + + // Retrieve the first grapheme and its codepoint properties. + const base::StringPiece16 first_grapheme_text = + grapheme_iterator.GetStringPiece(); + const GraphemeProperties first_grapheme_properties = + RetrieveGraphemeProperties(first_grapheme_text, is_common_script); + + // Append subsequent graphemes in this grapheme cluster if they are + // compatible, otherwise break the current run. + while (grapheme_iterator.Advance()) { + const base::StringPiece16 current_grapheme_text = + grapheme_iterator.GetStringPiece(); + const GraphemeProperties current_grapheme_properties = + RetrieveGraphemeProperties(current_grapheme_text, is_common_script); + + const size_t current_breaking_position = + run_start + grapheme_iterator.prev(); + if (!AreGraphemePropertiesCompatible(first_grapheme_properties, + current_grapheme_properties)) { + return current_breaking_position; + } + + // Break if the beginning of this grapheme is after |run_break|. + if (run_start + grapheme_iterator.prev() >= run_break) { + DCHECK_LE(current_breaking_position, run_end); + return current_breaking_position; + } + } + + // Do not break this run, returns end of the text. + return run_end; +} + +// Find the longest sequence of characters from 0 and up to |length| that have +// at least one common UScriptCode value. Writes the common script value to +// |script| and returns the length of the sequence. Takes the characters' script +// extensions into account. http://www.unicode.org/reports/tr24/#ScriptX +// +// Consider 3 characters with the script values {Kana}, {Hira, Kana}, {Kana}. +// Without script extensions only the first script in each set would be taken +// into account, resulting in 3 runs where 1 would be enough. +size_t ScriptInterval(const std::u16string& text, + size_t start, + size_t length, + UScriptCode* script) { + DCHECK_GT(length, 0U); + + UScriptCode scripts[kMaxScripts] = { USCRIPT_INVALID_CODE }; + + base::i18n::UTF16CharIterator char_iterator( + base::StringPiece16(text.c_str() + start, length)); + size_t scripts_size = GetScriptExtensions(char_iterator.get(), scripts); + *script = scripts[0]; + + while (char_iterator.Advance()) { + ScriptSetIntersect(char_iterator.get(), scripts, &scripts_size); + if (scripts_size == 0U) + return char_iterator.array_pos(); + *script = scripts[0]; + } + + return length; +} + +// A port of hb_icu_script_to_script because harfbuzz on CrOS is built without +// hb-icu. See http://crbug.com/356929 +inline hb_script_t ICUScriptToHBScript(UScriptCode script) { + if (script == USCRIPT_INVALID_CODE) + return HB_SCRIPT_INVALID; + return hb_script_from_string(uscript_getShortName(script), -1); +} + +bool FontWasAlreadyTried(sk_sp typeface, + std::set* fallback_fonts) { + return fallback_fonts->count(typeface->uniqueID()) != 0; +} + +void MarkFontAsTried(sk_sp typeface, + std::set* fallback_fonts) { + fallback_fonts->insert(typeface->uniqueID()); +} + +// Whether |segment| corresponds to the newline character. +bool IsNewlineSegment(const std::u16string& text, + const internal::LineSegment& segment) { + const size_t offset = segment.char_range.start(); + const size_t length = segment.char_range.length(); + DCHECK_LT(segment.char_range.start() + length - 1, text.length()); + return (length == 1 && (text[offset] == '\r' || text[offset] == '\n')) || + (length == 2 && text[offset] == '\r' && text[offset + 1] == '\n'); +} + +// Returns the line index considering the newline character. Line index is +// incremented if the caret is right after the newline character, i.e, the +// cursor affinity is |CURSOR_BACKWARD| while containing the newline character. +size_t LineIndexForNewline(const size_t line_index, + const std::u16string& text, + const internal::LineSegment& segment, + const SelectionModel& caret) { + bool at_newline = IsNewlineSegment(text, segment) && + caret.caret_affinity() == CURSOR_BACKWARD; + return line_index + (at_newline ? 1 : 0); +} + +// Helper template function for |TextRunHarfBuzz::GetClusterAt()|. |Iterator| +// can be a forward or reverse iterator type depending on the text direction. +// Returns true on success, or false if an error is encountered. +template +bool GetClusterAtImpl(size_t pos, + Range range, + Iterator elements_begin, + Iterator elements_end, + bool reversed, + Range* chars, + Range* glyphs) { + Iterator element = std::upper_bound(elements_begin, elements_end, pos); + if (element == elements_begin) { + *chars = range; + *glyphs = Range(); + return false; + } + + chars->set_end(element == elements_end ? range.end() : *element); + glyphs->set_end(reversed ? elements_end - element : element - elements_begin); + while (--element != elements_begin && *element == *(element - 1)); + chars->set_start(*element); + glyphs->set_start( + reversed ? elements_end - element : element - elements_begin); + if (reversed) + *glyphs = Range(glyphs->end(), glyphs->start()); + + DCHECK(!chars->is_reversed()); + DCHECK(!chars->is_empty()); + DCHECK(!glyphs->is_reversed()); + DCHECK(!glyphs->is_empty()); + return true; +} + +// Internal class to generate Line structures. If |multiline| is true, the text +// is broken into lines at |words| boundaries such that each line is no longer +// than |max_width|. If |multiline| is false, only outputs a single Line from +// the given runs. |min_baseline| and |min_height| are the minimum baseline and +// height for each line. +// TODO(ckocagil): Expose the interface of this class in the header and test +// this class directly. +class HarfBuzzLineBreaker { + public: + HarfBuzzLineBreaker(size_t max_width, + int min_baseline, + float min_height, + float glyph_height_for_test, + WordWrapBehavior word_wrap_behavior, + const std::u16string& text, + const BreakList* words, + const internal::TextRunList& run_list) + : max_width_((max_width == 0) ? SK_ScalarMax : SkIntToScalar(max_width)), + min_baseline_(min_baseline), + min_height_(min_height), + glyph_height_for_test_(glyph_height_for_test), + word_wrap_behavior_(word_wrap_behavior), + text_(text), + words_(words), + run_list_(run_list), + max_descent_(0), + max_ascent_(0), + text_x_(0), + available_width_(max_width_) { + AdvanceLine(); + } + + HarfBuzzLineBreaker(const HarfBuzzLineBreaker&) = delete; + HarfBuzzLineBreaker& operator=(const HarfBuzzLineBreaker&) = delete; + + // Constructs a single line for |text_| using |run_list_|. + void ConstructSingleLine() { + for (size_t i = 0; i < run_list_.size(); i++) { + const internal::TextRunHarfBuzz& run = *(run_list_.runs()[i]); + internal::LineSegment segment; + segment.run = i; + segment.char_range = run.range; + segment.x_range = RangeF(SkScalarToFloat(text_x_), + SkScalarToFloat(text_x_) + run.shape.width); + AddLineSegment(segment, false); + } + } + + // Constructs multiple lines for |text_| based on words iteration approach. + void ConstructMultiLines() { + DCHECK(words_); + for (auto iter = words_->breaks().begin(); iter != words_->breaks().end(); + iter++) { + const Range word_range = words_->GetRange(iter); + std::vector word_segments; + SkScalar word_width = GetWordWidth(word_range, &word_segments); + + // If the last word is '\n', we should advance a new line after adding + // the word to the current line. + bool new_line = false; + if (!word_segments.empty() && + IsNewlineSegment(text_, word_segments.back())) { + new_line = true; + + // Subtract the width of newline segments, they are not drawn. + if (word_segments.size() != 1u || available_width_ != max_width_) + word_width -= word_segments.back().width(); + } + + // If the word is not the first word in the line and it can't fit into + // the current line, advance a new line. + if (word_width > available_width_ && available_width_ != max_width_) + AdvanceLine(); + if (!word_segments.empty()) + AddWordToLine(word_segments); + if (new_line) + AdvanceLine(); + } + } + + // Finishes line breaking and outputs the results. Can be called at most once. + void FinalizeLines(std::vector* lines, SizeF* size) { + DCHECK(!lines_.empty()); + // If the last character of the text is a new line character, then the last + // line is any empty string, which contains no segments. This means that the + // display_text_index will not have been set in AdvanceLine. So here, set + // display_text_index to the text length, which is the true text index of + // the final line. + internal::Line* line = &lines_.back(); + if (line->display_text_index == 0) + line->display_text_index = text_.size(); + // Add an empty line to finish the line size calculation and remove it. + AdvanceLine(); + lines_.pop_back(); + *size = total_size_; + lines->swap(lines_); + } + + private: + // A (line index, segment index) pair that specifies a segment in |lines_|. + typedef std::pair SegmentHandle; + + internal::LineSegment* SegmentFromHandle(const SegmentHandle& handle) { + return &lines_[handle.first].segments[handle.second]; + } + + // Finishes the size calculations of the last Line in |lines_|. Adds a new + // Line to the back of |lines_|. + void AdvanceLine() { + if (!lines_.empty()) { + internal::Line* line = &lines_.back(); + // Compute the line start while the line segments are in the logical order + // so that the start of the line is the start of the char range, + // regardless of i18n. + if (!line->segments.empty()) + line->display_text_index = line->segments[0].char_range.start(); + std::sort(line->segments.begin(), line->segments.end(), + [this](const internal::LineSegment& s1, + const internal::LineSegment& s2) -> bool { + return run_list_.logical_to_visual(s1.run) < + run_list_.logical_to_visual(s2.run); + }); + + line->size.set_height( + glyph_height_for_test_ + ? glyph_height_for_test_ + : std::max(min_height_, max_descent_ + max_ascent_)); + + line->baseline = std::max(min_baseline_, SkScalarRoundToInt(max_ascent_)); + line->preceding_heights = base::ClampCeil(total_size_.height()); + // Subtract newline segment's width from |total_size_| because it's not + // drawn. + float line_width = line->size.width(); + if (!line->segments.empty() && + IsNewlineSegment(text_, line->segments.back())) { + line_width -= line->segments.back().width(); + } + if (line->segments.size() > 1 && + IsNewlineSegment(text_, line->segments.front())) { + line_width -= line->segments.front().width(); + } + total_size_.set_height(total_size_.height() + line->size.height()); + total_size_.set_width(std::max(total_size_.width(), line_width)); + } + max_descent_ = 0; + max_ascent_ = 0; + available_width_ = max_width_; + lines_.push_back(internal::Line()); + } + + // Adds word to the current line. A word may contain multiple segments. If the + // word is the first word in line and its width exceeds |available_width_|, + // ignore/truncate/wrap it according to |word_wrap_behavior_|. + void AddWordToLine(const std::vector& word_segments) { + DCHECK(!lines_.empty()); + DCHECK(!word_segments.empty()); + + bool has_truncated = false; + for (const internal::LineSegment& segment : word_segments) { + if (has_truncated) + break; + + if (IsNewlineSegment(text_, segment) || + segment.width() <= available_width_ || + word_wrap_behavior_ == IGNORE_LONG_WORDS) { + AddLineSegment(segment, true); + } else { + DCHECK(word_wrap_behavior_ == TRUNCATE_LONG_WORDS || + word_wrap_behavior_ == WRAP_LONG_WORDS); + has_truncated = (word_wrap_behavior_ == TRUNCATE_LONG_WORDS); + + const internal::TextRunHarfBuzz& run = *(run_list_.runs()[segment.run]); + internal::LineSegment remaining_segment = segment; + while (!remaining_segment.char_range.is_empty()) { + size_t cutoff_pos = GetCutoffPos(remaining_segment); + SkScalar width = run.GetGlyphWidthForCharRange( + Range(remaining_segment.char_range.start(), cutoff_pos)); + if (width > 0) { + internal::LineSegment cut_segment; + cut_segment.run = remaining_segment.run; + cut_segment.char_range = + Range(remaining_segment.char_range.start(), cutoff_pos); + cut_segment.x_range = RangeF(SkScalarToFloat(text_x_), + SkScalarToFloat(text_x_ + width)); + AddLineSegment(cut_segment, true); + // Updates old segment range. + remaining_segment.char_range.set_start(cutoff_pos); + remaining_segment.x_range.set_start(SkScalarToFloat(text_x_)); + } + if (has_truncated) + break; + if (!remaining_segment.char_range.is_empty()) + AdvanceLine(); + } + } + } + } + + // Add a line segment to the current line. Note that, in order to keep the + // visual order correct for ltr and rtl language, we need to merge segments + // that belong to the same run. + void AddLineSegment(const internal::LineSegment& segment, bool multiline) { + DCHECK(!lines_.empty()); + internal::Line* line = &lines_.back(); + const internal::TextRunHarfBuzz& run = *(run_list_.runs()[segment.run]); + if (!line->segments.empty()) { + internal::LineSegment& last_segment = line->segments.back(); + // Merge segments that belong to the same run. + if (last_segment.run == segment.run) { + DCHECK_EQ(last_segment.char_range.end(), segment.char_range.start()); + // Check there is less than a pixel between one run and the next. + DCHECK_LE( + std::abs(last_segment.x_range.end() - segment.x_range.start()), + 1.0f); + last_segment.char_range.set_end(segment.char_range.end()); + last_segment.x_range.set_end(SkScalarToFloat(text_x_) + + segment.width()); + if (run.font_params.is_rtl && + last_segment.char_range.end() == run.range.end()) + UpdateRTLSegmentRanges(); + line->size.set_width(line->size.width() + segment.width()); + text_x_ += segment.width(); + available_width_ -= segment.width(); + return; + } + } + line->segments.push_back(segment); + line->size.set_width(line->size.width() + segment.width()); + + // Newline characters are not drawn for multi-line, ignore their metrics. + if (!multiline || !IsNewlineSegment(text_, segment)) { + SkFont font(run.font_params.skia_face, run.font_params.font_size); + font.setEdging(run.font_params.render_params.antialiasing + ? SkFont::Edging::kAntiAlias + : SkFont::Edging::kAlias); + SkFontMetrics metrics; + font.getMetrics(&metrics); + + // max_descent_ is y-down, fDescent is y-down, baseline_offset is y-down + max_descent_ = std::max( + max_descent_, metrics.fDescent + run.font_params.baseline_offset); + // max_ascent_ is y-up, fAscent is y-down, baseline_offset is y-down + max_ascent_ = std::max( + max_ascent_, -(metrics.fAscent + run.font_params.baseline_offset)); + } + + if (run.font_params.is_rtl) { + rtl_segments_.push_back( + SegmentHandle(lines_.size() - 1, line->segments.size() - 1)); + // If this is the last segment of an RTL run, reprocess the text-space x + // ranges of all segments from the run. + if (segment.char_range.end() == run.range.end()) + UpdateRTLSegmentRanges(); + } + text_x_ += segment.width(); + available_width_ -= segment.width(); + } + + // Finds the end position |end_pos| in |segment| where the preceding width is + // no larger than |available_width_|. + size_t GetCutoffPos(const internal::LineSegment& segment) const { + DCHECK(!segment.char_range.is_empty()); + const internal::TextRunHarfBuzz& run = + *(run_list_.runs()[segment.run]).get(); + size_t end_pos = segment.char_range.start(); + SkScalar width = 0; + while (end_pos < segment.char_range.end()) { + const SkScalar char_width = + run.GetGlyphWidthForCharRange(Range(end_pos, end_pos + 1)); + if (width + char_width > available_width_) + break; + width += char_width; + end_pos++; + } + + const size_t valid_end_pos = std::max( + segment.char_range.start(), + static_cast(FindValidBoundaryBefore(text_, end_pos))); + if (end_pos != valid_end_pos) { + end_pos = valid_end_pos; + width = run.GetGlyphWidthForCharRange( + Range(segment.char_range.start(), end_pos)); + } + + // |max_width_| might be smaller than a single character. In this case we + // need to put at least one character in the line. Note that, we should + // not separate surrogate pair or combining characters. + // See RenderTextHarfBuzzTest.Multiline_MinWidth for an example. + if (width == 0 && available_width_ == max_width_) { + end_pos = std::min( + segment.char_range.end(), + static_cast(FindValidBoundaryAfter(text_, end_pos + 1))); + } + + return end_pos; + } + + // Gets the glyph width for |word_range|, and splits the |word| into different + // segments based on its runs. + SkScalar GetWordWidth(const Range& word_range, + std::vector* segments) const { + DCHECK(words_); + if (word_range.is_empty() || segments == nullptr) + return 0; + size_t run_start_index = run_list_.GetRunIndexAt(word_range.start()); + size_t run_end_index = run_list_.GetRunIndexAt(word_range.end() - 1); + SkScalar width = 0; + for (size_t i = run_start_index; i <= run_end_index; i++) { + const internal::TextRunHarfBuzz& run = *(run_list_.runs()[i]); + const Range char_range = run.range.Intersect(word_range); + DCHECK(!char_range.is_empty()); + const SkScalar char_width = run.GetGlyphWidthForCharRange(char_range); + width += char_width; + + internal::LineSegment segment; + segment.run = i; + segment.char_range = char_range; + segment.x_range = RangeF(SkScalarToFloat(text_x_ + width - char_width), + SkScalarToFloat(text_x_ + width)); + segments->push_back(segment); + } + return width; + } + + // RTL runs are broken in logical order but displayed in visual order. To find + // the text-space coordinate (where it would fall in a single-line text) + // |x_range| of RTL segments, segment widths are applied in reverse order. + // e.g. {[5, 10], [10, 40]} will become {[35, 40], [5, 35]}. + void UpdateRTLSegmentRanges() { + if (rtl_segments_.empty()) + return; + float x = SegmentFromHandle(rtl_segments_[0])->x_range.start(); + for (size_t i = rtl_segments_.size(); i > 0; --i) { + internal::LineSegment* segment = SegmentFromHandle(rtl_segments_[i - 1]); + const float segment_width = segment->width(); + segment->x_range = RangeF(x, x + segment_width); + x += segment_width; + } + rtl_segments_.clear(); + } + + const SkScalar max_width_; + const int min_baseline_; + const float min_height_; + const float glyph_height_for_test_; + const WordWrapBehavior word_wrap_behavior_; + const std::u16string& text_; + const BreakList* const words_; + const internal::TextRunList& run_list_; + + // Stores the resulting lines. + std::vector lines_; + + float max_descent_; + float max_ascent_; + + // Text space x coordinates of the next segment to be added. + SkScalar text_x_; + // Stores available width in the current line. + SkScalar available_width_; + + // Size of the multiline text, not including the currently processed line. + SizeF total_size_; + + // The current RTL run segments, to be applied by |UpdateRTLSegmentRanges()|. + std::vector rtl_segments_; +}; + +// Applies a forced text rendering direction if specified by a command-line +// switch. +void ApplyForcedDirection(UBiDiLevel* level) { + static bool has_switch = base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kForceTextDirection); + if (!has_switch) + return; + + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); + if (command_line->HasSwitch(switches::kForceTextDirection)) { + std::string force_flag = + command_line->GetSwitchValueASCII(switches::kForceTextDirection); + + if (force_flag == switches::kForceDirectionRTL) + *level = UBIDI_RTL; + if (force_flag == switches::kForceDirectionLTR) + *level = UBIDI_LTR; + } +} + +internal::TextRunHarfBuzz::FontParams CreateFontParams( + const Font& primary_font, + UBiDiLevel bidi_level, + UScriptCode script, + const internal::StyleIterator& style) { + internal::TextRunHarfBuzz::FontParams font_params(primary_font); + font_params.italic = style.style(TEXT_STYLE_ITALIC); + font_params.baseline_type = style.baseline(); + font_params.font_size = style.font_size_override(); + font_params.strike = style.style(TEXT_STYLE_STRIKE); + font_params.underline = style.style(TEXT_STYLE_UNDERLINE); + font_params.heavy_underline = style.style(TEXT_STYLE_HEAVY_UNDERLINE); + font_params.weight = style.weight(); + font_params.level = bidi_level; + font_params.script = script; + // Odd BiDi embedding levels correspond to RTL runs. + font_params.is_rtl = (font_params.level % 2) == 1; + return font_params; +} + +} // namespace + +namespace internal { + +sk_sp CreateSkiaTypeface(const Font& font, + bool italic, + Font::Weight weight) { +#if defined(OS_APPLE) + const Font::FontStyle style = italic ? Font::ITALIC : Font::NORMAL; + Font font_with_style = font.Derive(0, style, weight); + if (!font_with_style.GetNativeFont()) + return nullptr; + + return SkMakeTypefaceFromCTFont( + base::mac::NSToCFCast(font_with_style.GetNativeFont())); +#else + SkFontStyle skia_style( + static_cast(weight), SkFontStyle::kNormal_Width, + italic ? SkFontStyle::kItalic_Slant : SkFontStyle::kUpright_Slant); + return sk_sp(SkTypeface::MakeFromName( + font.GetFontName().c_str(), skia_style)); +#endif +} + +TextRunHarfBuzz::FontParams::FontParams(const Font& template_font) + : font(template_font) {} +TextRunHarfBuzz::FontParams::~FontParams() = default; +TextRunHarfBuzz::FontParams::FontParams( + const TextRunHarfBuzz::FontParams& other) = default; +TextRunHarfBuzz::FontParams& TextRunHarfBuzz::FontParams::operator=( + const TextRunHarfBuzz::FontParams& other) = default; + +bool TextRunHarfBuzz::FontParams::operator==(const FontParams& other) const { + // Empirically, |script| and |weight| are the highest entropy members. + return script == other.script && weight == other.weight && + skia_face == other.skia_face && render_params == other.render_params && + font_size == other.font_size && + baseline_offset == other.baseline_offset && + baseline_type == other.baseline_type && italic == other.italic && + strike == other.strike && underline == other.underline && + heavy_underline == other.heavy_underline && is_rtl == other.is_rtl && + level == other.level; +} + +void TextRunHarfBuzz::FontParams:: + ComputeRenderParamsFontSizeAndBaselineOffset() { + render_params = font.GetFontRenderParams(); + if (font_size == 0) + font_size = font.GetFontSize(); + baseline_offset = 0; + if (baseline_type != NORMAL_BASELINE) { + // Calculate a slightly smaller font. The ratio here is somewhat arbitrary. + // Proportions from 5/9 to 5/7 all look pretty good. + const float ratio = 5.0f / 9.0f; + font_size = base::ClampRound(font.GetFontSize() * ratio); + switch (baseline_type) { + case SUPERSCRIPT: + baseline_offset = font.GetCapHeight() - font.GetHeight(); + break; + case SUPERIOR: + baseline_offset = + base::ClampRound(font.GetCapHeight() * ratio) - font.GetCapHeight(); + break; + case SUBSCRIPT: + baseline_offset = font.GetHeight() - font.GetBaseline(); + break; + case INFERIOR: // Fall through. + default: + break; + } + } +} + +size_t TextRunHarfBuzz::FontParams::Hash::operator()( + const FontParams& key) const { + // In practice, |font|, |skia_face|, |render_params|, and |baseline_offset| + // have not yet been set when this is called. + return static_cast(key.italic) << 0 ^ + static_cast(key.strike) << 1 ^ + static_cast(key.underline) << 2 ^ + static_cast(key.heavy_underline) << 3 ^ + static_cast(key.is_rtl) << 4 ^ + static_cast(key.weight) << 8 ^ + static_cast(key.font_size) << 12 ^ + static_cast(key.baseline_type) << 16 ^ + static_cast(key.level) << 20 ^ + static_cast(key.script) << 24; +} + +bool TextRunHarfBuzz::FontParams::SetRenderParamsRematchFont( + const Font& new_font, + const FontRenderParams& new_render_params) { + // This takes the font family name from new_font, and calls + // SkTypeface::makeFromName() with that family name and the style information + // internal to this text run. So it triggers a new font match and looks for + // adjacent fonts in the family. This works for styling, e.g. styling a run in + // bold, italic or underline, but breaks font fallback in certain scenarios, + // as the fallback font may be of a different weight and style than the run's + // own, so this can lead to a failure of instantiating the correct fallback + // font. + sk_sp new_skia_face( + internal::CreateSkiaTypeface(new_font, italic, weight)); + if (!new_skia_face) + return false; + + skia_face = new_skia_face; + font = new_font; + render_params = new_render_params; + return true; +} + +bool TextRunHarfBuzz::FontParams::SetRenderParamsOverrideSkiaFaceFromFont( + const Font& fallback_font, + const FontRenderParams& new_render_params) { + PlatformFont* platform_font = fallback_font.platform_font(); + sk_sp new_skia_face = platform_font->GetNativeSkTypeface(); + + // If pass-through of the Skia native handle fails for PlatformFonts other + // than PlatformFontSkia, perform rematching. + if (!new_skia_face) + return SetRenderParamsRematchFont(fallback_font, new_render_params); + + skia_face = new_skia_face; + font = fallback_font; + render_params = new_render_params; + return true; +} + +TextRunHarfBuzz::ShapeOutput::ShapeOutput() = default; +TextRunHarfBuzz::ShapeOutput::~ShapeOutput() = default; +TextRunHarfBuzz::ShapeOutput::ShapeOutput( + const TextRunHarfBuzz::ShapeOutput& other) = default; +TextRunHarfBuzz::ShapeOutput& TextRunHarfBuzz::ShapeOutput::operator=( + const TextRunHarfBuzz::ShapeOutput& other) = default; +TextRunHarfBuzz::ShapeOutput::ShapeOutput( + TextRunHarfBuzz::ShapeOutput&& other) = default; +TextRunHarfBuzz::ShapeOutput& TextRunHarfBuzz::ShapeOutput::operator=( + TextRunHarfBuzz::ShapeOutput&& other) = default; + +TextRunHarfBuzz::TextRunHarfBuzz(const Font& template_font) + : font_params(template_font) {} + +TextRunHarfBuzz::~TextRunHarfBuzz() {} + +Range TextRunHarfBuzz::CharRangeToGlyphRange(const Range& char_range) const { + DCHECK(range.Contains(char_range)); + DCHECK(!char_range.is_reversed()); + DCHECK(!char_range.is_empty()); + + Range start_glyphs; + Range end_glyphs; + Range temp_range; + GetClusterAt(char_range.start(), &temp_range, &start_glyphs); + GetClusterAt(char_range.end() - 1, &temp_range, &end_glyphs); + + return font_params.is_rtl ? Range(end_glyphs.start(), start_glyphs.end()) + : Range(start_glyphs.start(), end_glyphs.end()); +} + +size_t TextRunHarfBuzz::CountMissingGlyphs() const { + return shape.missing_glyph_count; +} + +void TextRunHarfBuzz::GetClusterAt(size_t pos, + Range* chars, + Range* glyphs) const { + DCHECK(chars); + DCHECK(glyphs); + + bool success = true; + if (shape.glyph_count == 0 || !range.Contains(Range(pos, pos + 1))) { + *chars = range; + *glyphs = Range(); + success = false; + } + + if (font_params.is_rtl) { + success &= + GetClusterAtImpl(pos, range, shape.glyph_to_char.rbegin(), + shape.glyph_to_char.rend(), true, chars, glyphs); + } else { + success &= + GetClusterAtImpl(pos, range, shape.glyph_to_char.begin(), + shape.glyph_to_char.end(), false, chars, glyphs); + } + + if (!success) { + std::string glyph_to_char_string; + for (size_t i = 0; i < shape.glyph_count && i < shape.glyph_to_char.size(); + ++i) { + glyph_to_char_string += base::NumberToString(i) + "->" + + base::NumberToString(shape.glyph_to_char[i]) + + ", "; + } + LOG(ERROR) << " TextRunHarfBuzz error, please report at crbug.com/724880:" + << " range: " << range.ToString() + << ", rtl: " << font_params.is_rtl << "," + << " level: '" << font_params.level + << "', script: " << font_params.script << "," + << " font: '" << font_params.font.GetActualFontName() << "'," + << " glyph_count: " << shape.glyph_count << ", pos: " << pos + << "," + << " glyph_to_char: " << glyph_to_char_string; + } +} + +RangeF TextRunHarfBuzz::GetGraphemeBounds(RenderTextHarfBuzz* render_text, + size_t text_index) const { + DCHECK_LT(text_index, range.end()); + if (shape.glyph_count == 0) + return RangeF(preceding_run_widths, preceding_run_widths + shape.width); + + Range chars; + Range glyphs; + GetClusterAt(text_index, &chars, &glyphs); + const float cluster_begin_x = shape.positions[glyphs.start()].x(); + const float cluster_end_x = glyphs.end() < shape.glyph_count + ? shape.positions[glyphs.end()].x() + : SkFloatToScalar(shape.width); + DCHECK_LE(cluster_begin_x, cluster_end_x); + + // A cluster consists of a number of code points and corresponds to a number + // of glyphs that should be drawn together. A cluster can contain multiple + // graphemes. In order to place the cursor at a grapheme boundary inside the + // cluster, we simply divide the cluster width by the number of graphemes. + ptrdiff_t code_point_count = UTF16IndexToOffset(render_text->GetDisplayText(), + chars.start(), chars.end()); + if (code_point_count > 1) { + int before = 0; + int total = 0; + for (size_t i = chars.start(); i < chars.end(); ++i) { + if (render_text->IsGraphemeBoundary(i)) { + if (i < text_index) + ++before; + ++total; + } + } + // With ICU 65.1, DCHECK_GT() below fails. + // See https://crbug.com/1017047 for more details. + // + // DCHECK_GT(total, 0); + + // It's possible for |text_index| to point to a diacritical mark, at the end + // of |chars|. In this case all the grapheme boundaries come before it. Just + // provide the bounds of the last grapheme. + if (before == total) + --before; + + if (total > 1) { + if (font_params.is_rtl) + before = total - before - 1; + DCHECK_GE(before, 0); + DCHECK_LT(before, total); + const float cluster_start = preceding_run_widths + cluster_begin_x; + const float average_width = (cluster_end_x - cluster_begin_x) / total; + return RangeF(cluster_start + average_width * before, + cluster_start + average_width * (before + 1)); + } + } + + return RangeF(preceding_run_widths + cluster_begin_x, + preceding_run_widths + cluster_end_x); +} + +RangeF TextRunHarfBuzz::GetGraphemeSpanForCharRange( + RenderTextHarfBuzz* render_text, + const Range& char_range) const { + if (char_range.is_empty()) + return RangeF(); + + DCHECK(!char_range.is_reversed()); + DCHECK(range.Contains(char_range)); + size_t left_index = char_range.start(); + size_t right_index = + UTF16OffsetToIndex(render_text->GetDisplayText(), char_range.end(), -1); + DCHECK_LE(left_index, right_index); + if (font_params.is_rtl) + std::swap(left_index, right_index); + + const RangeF left_span = GetGraphemeBounds(render_text, left_index); + return left_index == right_index + ? left_span + : RangeF(left_span.start(), + GetGraphemeBounds(render_text, right_index).end()); +} + +SkScalar TextRunHarfBuzz::GetGlyphWidthForCharRange( + const Range& char_range) const { + if (char_range.is_empty()) + return 0; + + DCHECK(range.Contains(char_range)); + Range glyph_range = CharRangeToGlyphRange(char_range); + + // The |glyph_range| might be empty or invalid on Windows if a multi-character + // grapheme is divided into different runs (e.g., there are two font sizes or + // colors for a single glyph). In this case it might cause the browser crash, + // see crbug.com/526234. + if (glyph_range.start() >= glyph_range.end()) { + NOTREACHED() << "The glyph range is empty or invalid! Its char range: [" + << char_range.start() << ", " << char_range.end() + << "], and its glyph range: [" << glyph_range.start() << ", " + << glyph_range.end() << "]."; + return 0; + } + + return ((glyph_range.end() == shape.glyph_count) + ? SkFloatToScalar(shape.width) + : shape.positions[glyph_range.end()].x()) - + shape.positions[glyph_range.start()].x(); +} + +void TextRunHarfBuzz::UpdateFontParamsAndShape( + const FontParams& new_font_params, + const ShapeOutput& new_shape) { + if (new_shape.missing_glyph_count < shape.missing_glyph_count) { + font_params = new_font_params; + shape = new_shape; + // Note that |new_shape.glyph_to_char| is indexed from the beginning of + // |range|, while |shape.glyph_to_char| is indexed from the beginning of + // its embedding text. + for (size_t i = 0; i < shape.glyph_to_char.size(); ++i) + shape.glyph_to_char[i] += range.start(); + } +} + +TextRunList::TextRunList() : width_(0.0f) {} + +TextRunList::~TextRunList() {} + +void TextRunList::Reset() { + runs_.clear(); + width_ = 0.0f; +} + +void TextRunList::InitIndexMap() { + if (runs_.size() == 1) { + visual_to_logical_ = logical_to_visual_ = std::vector(1, 0); + return; + } + const size_t num_runs = runs_.size(); + std::vector levels(num_runs); + for (size_t i = 0; i < num_runs; ++i) + levels[i] = runs_[i]->font_params.level; + visual_to_logical_.resize(num_runs); + ubidi_reorderVisual(&levels[0], num_runs, &visual_to_logical_[0]); + logical_to_visual_.resize(num_runs); + ubidi_reorderLogical(&levels[0], num_runs, &logical_to_visual_[0]); +} + +void TextRunList::ComputePrecedingRunWidths() { + // Precalculate run width information. + width_ = 0.0f; + for (size_t i = 0; i < runs_.size(); ++i) { + const auto& run = runs_[visual_to_logical_[i]]; + run->preceding_run_widths = width_; + width_ += run->shape.width; + } +} + +size_t TextRunList::GetRunIndexAt(size_t position) const { + for (size_t i = 0; i < runs_.size(); ++i) { + if (runs_[i]->range.start() <= position && runs_[i]->range.end() > position) + return i; + } + return runs_.size(); +} + +namespace { + +// ShapeRunWithFont cache. Views makes repeated calls to ShapeRunWithFont +// with the same arguments in several places, and typesetting is very expensive. +// To compensate for this, encapsulate all of the input arguments to +// ShapeRunWithFont in ShapeRunWithFontInput, all of the output arguments in +// TextRunHarfBuzz::ShapeOutput, and add ShapeRunCache to map between the two. +// This is analogous to the blink::ShapeCache. +// https://crbug.com/826265 + +// Input for the stateless implementation of ShapeRunWithFont. +struct ShapeRunWithFontInput { + ShapeRunWithFontInput(const std::u16string& full_text, + const TextRunHarfBuzz::FontParams& font_params, + Range full_range, + bool obscured, + float glyph_width_for_test, + int obscured_glyph_spacing, + bool subpixel_rendering_suppressed) + : skia_face(font_params.skia_face), + render_params(font_params.render_params), + script(font_params.script), + font_size(font_params.font_size), + obscured_glyph_spacing(obscured_glyph_spacing), + glyph_width_for_test(glyph_width_for_test), + is_rtl(font_params.is_rtl), + obscured(obscured), + subpixel_rendering_suppressed(subpixel_rendering_suppressed) { + // hb_buffer_add_utf16 will read the previous and next 5 unicode characters + // (which can have a maximum length of 2 uint16_t) as "context" that is used + // only for Arabic (which is RTL). Read the previous and next 10 uint16_ts + // to ensure that we capture all of this context if we're using RTL. + size_t kContextSize = is_rtl ? 10 : 0; + size_t context_start = full_range.start() < kContextSize + ? 0 + : full_range.start() - kContextSize; + size_t context_end = + std::min(full_text.length(), full_range.end() + kContextSize); + range = Range(full_range.start() - context_start, + full_range.end() - context_start); + text = full_text.substr(context_start, context_end - context_start); + + // Pre-compute the hash to avoid having to re-hash at every comparison. + // Attempt to minimize collisions by including the typeface, script, font + // size, text and the text range. + hash = base::HashInts(hash, skia_face->uniqueID()); + hash = base::HashInts(hash, script); + hash = base::HashInts(hash, font_size); + hash = base::Hash(text); + hash = base::HashInts(hash, range.start()); + hash = base::HashInts(hash, range.length()); + } + + bool operator==(const ShapeRunWithFontInput& other) const { + return text == other.text && skia_face == other.skia_face && + render_params == other.render_params && + font_size == other.font_size && range == other.range && + script == other.script && is_rtl == other.is_rtl && + obscured == other.obscured && + glyph_width_for_test == other.glyph_width_for_test && + obscured_glyph_spacing == other.obscured_glyph_spacing && + subpixel_rendering_suppressed == other.subpixel_rendering_suppressed; + } + + struct Hash { + size_t operator()(const ShapeRunWithFontInput& key) const { + return key.hash; + } + }; + + sk_sp skia_face; + FontRenderParams render_params; + UScriptCode script; + int font_size; + int obscured_glyph_spacing; + float glyph_width_for_test; + bool is_rtl; + bool obscured; + bool subpixel_rendering_suppressed; + + // The parts of the input text that may be read by hb_buffer_add_utf16. + std::u16string text; + // The conversion of the input range to a range within |text|. + Range range; + // The hash is cached to avoid repeated calls. + size_t hash = 0; +}; + +// An MRU cache of the results from calling ShapeRunWithFont. The maximum cache +// size used in blink::ShapeCache is 10k. A Finch experiment showed that +// reducing the cache size to 1k has no performance impact. +constexpr int kShapeRunCacheSize = 1000; +using ShapeRunCacheBase = base::HashingMRUCache; +class ShapeRunCache : public ShapeRunCacheBase { + public: + ShapeRunCache() : ShapeRunCacheBase(kShapeRunCacheSize) {} +}; + +void ShapeRunWithFont(const ShapeRunWithFontInput& in, + TextRunHarfBuzz::ShapeOutput* out) { + TRACE_EVENT0("ui", "RenderTextHarfBuzz::ShapeRunWithFontInternal"); + + hb_font_t* harfbuzz_font = + CreateHarfBuzzFont(in.skia_face, SkIntToScalar(in.font_size), + in.render_params, in.subpixel_rendering_suppressed); + + // Create a HarfBuzz buffer and add the string to be shaped. The HarfBuzz + // buffer holds our text, run information to be used by the shaping engine, + // and the resulting glyph data. + hb_buffer_t* buffer = hb_buffer_create(); + // Note that the value of the |item_offset| argument (here specified as + // |in.range.start()|) does affect the result, so we will have to adjust + // the computed offsets. + hb_buffer_add_utf16( + buffer, reinterpret_cast(in.text.c_str()), + static_cast(in.text.length()), in.range.start(), in.range.length()); + hb_buffer_set_script(buffer, ICUScriptToHBScript(in.script)); + hb_buffer_set_direction(buffer, + in.is_rtl ? HB_DIRECTION_RTL : HB_DIRECTION_LTR); + // TODO(ckocagil): Should we determine the actual language? + hb_buffer_set_language(buffer, hb_language_get_default()); + + // Shape the text. + hb_shape(harfbuzz_font, buffer, NULL, 0); + + // Populate the run fields with the resulting glyph data in the buffer. + unsigned int glyph_count = 0; + hb_glyph_info_t* infos = hb_buffer_get_glyph_infos(buffer, &glyph_count); + out->glyph_count = glyph_count; + hb_glyph_position_t* hb_positions = + hb_buffer_get_glyph_positions(buffer, NULL); + out->glyphs.resize(out->glyph_count); + out->glyph_to_char.resize(out->glyph_count); + out->positions.resize(out->glyph_count); + out->width = 0.0f; + + // Font on MAC like ".SF NS Text" may have a negative x_offset. Positive + // x_offset are also found on Windows (e.g. "Segoe UI"). It requires tests + // relying on the behavior of |glyph_width_for_test_| to also be given a zero + // x_offset, otherwise expectations get thrown off + // (see: http://crbug.com/1056220). + const bool force_zero_offset = in.glyph_width_for_test > 0; + constexpr uint16_t kMissingGlyphId = 0; + + out->missing_glyph_count = 0; + for (size_t i = 0; i < out->glyph_count; ++i) { + DCHECK_LE(infos[i].codepoint, std::numeric_limits::max()); + uint16_t glyph = static_cast(infos[i].codepoint); + out->glyphs[i] = glyph; + if (glyph == kMissingGlyphId) + out->missing_glyph_count += 1; + DCHECK_GE(infos[i].cluster, in.range.start()); + out->glyph_to_char[i] = infos[i].cluster - in.range.start(); + const SkScalar x_offset = + force_zero_offset ? 0 + : HarfBuzzUnitsToSkiaScalar(hb_positions[i].x_offset); + const SkScalar y_offset = + HarfBuzzUnitsToSkiaScalar(hb_positions[i].y_offset); + out->positions[i].set(out->width + x_offset, -y_offset); + + if (in.glyph_width_for_test == 0) + out->width += HarfBuzzUnitsToFloat(hb_positions[i].x_advance); + else if (hb_positions[i].x_advance) // Leave zero-width glyphs alone. + out->width += in.glyph_width_for_test; + + if (in.obscured) + out->width += in.obscured_glyph_spacing; + + // When subpixel positioning is not enabled, glyph width is rounded to avoid + // fractional width. Disable this conversion when a glyph width is provided + // for testing. Using an integral glyph width has the same behavior as + // disabling the subpixel positioning. + const bool force_subpixel_for_test = in.glyph_width_for_test != 0; + + // Round run widths if subpixel positioning is off to match native behavior. + if (!in.render_params.subpixel_positioning && !force_subpixel_for_test) + out->width = std::round(out->width); + } + + hb_buffer_destroy(buffer); + hb_font_destroy(harfbuzz_font); +} + +std::string GetApplicationLocale() { +#if defined(OS_ANDROID) + // TODO(etienneb): Android locale should work the same way than base locale. + return base::android::GetDefaultLocaleString(); +#else + return base::i18n::GetConfiguredLocale(); +#endif +} + +} // namespace + +} // namespace internal + +RenderTextHarfBuzz::RenderTextHarfBuzz() + : RenderText(), + update_layout_run_list_(false), + update_display_run_list_(false), + update_display_text_(false), + locale_(internal::GetApplicationLocale()) { + set_truncate_length(kMaxTextLength); +} + +RenderTextHarfBuzz::~RenderTextHarfBuzz() {} + +const std::u16string& RenderTextHarfBuzz::GetDisplayText() { + // TODO(krb): Consider other elision modes for multiline. + if ((multiline() && (max_lines() == 0 || elide_behavior() != ELIDE_TAIL)) || + elide_behavior() == NO_ELIDE || elide_behavior() == FADE_TAIL) { + // Call UpdateDisplayText to clear |display_text_| and |text_elided_| + // on the RenderText class. + UpdateDisplayText(0); + update_display_text_ = false; + display_run_list_.reset(); + return GetLayoutText(); + } + + EnsureLayoutRunList(); + DCHECK(!update_display_text_); + return text_elided() ? display_text() : GetLayoutText(); +} + +SizeF RenderTextHarfBuzz::GetStringSizeF() { + EnsureLayout(); + return total_size_; +} + +SizeF RenderTextHarfBuzz::GetLineSizeF(const SelectionModel& caret) { + const internal::ShapedText* shaped_text = GetShapedText(); + const auto& caret_run = GetRunContainingCaret(caret); + for (const auto& line : shaped_text->lines()) { + for (const internal::LineSegment& segment : line.segments) { + if (segment.run == caret_run) + return line.size; + } + } + + return shaped_text->lines().back().size; +} + +std::vector RenderTextHarfBuzz::GetSubstringBounds(const Range& range) { + EnsureLayout(); + DCHECK(!update_display_run_list_); + DCHECK(range.IsBoundedBy(Range(0, text().length()))); + const Range grapheme_range = ExpandRangeToGraphemeBoundary(range); + const Range display_range(TextIndexToDisplayIndex(grapheme_range.start()), + TextIndexToDisplayIndex(grapheme_range.end())); + DCHECK(IsValidDisplayRange(display_range)); + + std::vector rects; + if (display_range.is_empty()) + return rects; + + internal::TextRunList* run_list = GetRunList(); + const internal::ShapedText* shaped_text = GetShapedText(); + for (size_t line_index = 0; line_index < shaped_text->lines().size(); + ++line_index) { + const internal::Line& line = shaped_text->lines()[line_index]; + // Only the last line can be empty. + DCHECK(!line.segments.empty() || + (line_index == shaped_text->lines().size() - 1)); + float line_start_x = + line.segments.empty() + ? 0 + : run_list->runs()[line.segments[0].run]->preceding_run_widths; + + if (line.segments.size() > 1 && IsNewlineSegment(line.segments[0])) + line_start_x += line.segments[0].width(); + + std::vector current_line_rects; + for (const internal::LineSegment& segment : line.segments) { + const Range intersection = segment.char_range.Intersect(display_range); + DCHECK(!intersection.is_reversed()); + if (!intersection.is_empty()) { + const internal::TextRunHarfBuzz& run = *run_list->runs()[segment.run]; + RangeF selected_span = + run.GetGraphemeSpanForCharRange(this, intersection); + DCHECK(!selected_span.is_reversed()); + int start_x = base::ClampFloor(selected_span.start() - line_start_x); + int end_x = base::ClampCeil(selected_span.end() - line_start_x); + Rect rect(start_x, 0, end_x - start_x, + base::ClampCeil(line.size.height())); + current_line_rects.push_back(rect + GetLineOffset(line_index)); + } + } + MergeIntersectingRects(current_line_rects); + rects.insert(rects.end(), current_line_rects.begin(), + current_line_rects.end()); + } + return rects; +} + +RangeF RenderTextHarfBuzz::GetCursorSpan(const Range& text_range) { + DCHECK(!text_range.is_reversed()); + EnsureLayout(); + const size_t index = text_range.start(); + size_t run_index = + GetRunContainingCaret(SelectionModel(index, CURSOR_FORWARD)); + internal::TextRunList* run_list = GetRunList(); + + // Return zero if the text is empty. + if (run_list->size() == 0 || text().empty()) + return RangeF(0); + + // Use the last run if the index is invalid or beyond the layout text size. + Range valid_range(text_range.start(), text_range.end()); + if (run_index >= run_list->size()) { + valid_range = Range(text().length() - 1, text().length()); + run_index = run_list->size() - 1; + } + + internal::TextRunHarfBuzz* run = run_list->runs()[run_index].get(); + + size_t next_grapheme_start = valid_range.end(); + if (!IsValidCursorIndex(next_grapheme_start)) { + next_grapheme_start = + IndexOfAdjacentGrapheme(next_grapheme_start, CURSOR_FORWARD); + } + + Range display_range(TextIndexToDisplayIndex(valid_range.start()), + TextIndexToDisplayIndex(next_grapheme_start)); + DCHECK(IsValidDisplayRange(display_range)); + + // Although highly likely, there's no guarantee that a single text run is used + // for the entire cursor span. For example, Unicode Variation Selectors are + // incorrectly placed in the next run; see crbug.com/775404. (For these, the + // variation selector has zero width, so it's safe to ignore the second run). + // TODO(tapted): Change this to a DCHECK when crbug.com/775404 is fixed. + display_range = display_range.Intersect(run->range); + + RangeF bounds = run->GetGraphemeSpanForCharRange(this, display_range); + return run->font_params.is_rtl ? RangeF(bounds.end(), bounds.start()) + : bounds; +} + +size_t RenderTextHarfBuzz::GetLineContainingCaret(const SelectionModel& caret) { + EnsureLayout(); + + if (caret.caret_pos() == 0) + return 0; + + if (!multiline()) { + DCHECK_EQ(1u, GetShapedText()->lines().size()); + return 0; + } + + size_t layout_position = TextIndexToDisplayIndex(caret.caret_pos()); + LogicalCursorDirection affinity = caret.caret_affinity(); + const internal::ShapedText* shaped_text = GetShapedText(); + for (size_t line_index = 0; line_index < shaped_text->lines().size(); + ++line_index) { + const internal::Line& line = shaped_text->lines()[line_index]; + for (const internal::LineSegment& segment : line.segments) { + if (RangeContainsCaret(segment.char_range, layout_position, affinity)) + return LineIndexForNewline(line_index, text(), segment, caret); + } + } + + return shaped_text->lines().size() - 1; +} + +SelectionModel RenderTextHarfBuzz::AdjacentCharSelectionModel( + const SelectionModel& selection, + VisualCursorDirection direction) { + DCHECK(!update_display_run_list_); + + internal::TextRunList* run_list = GetRunList(); + internal::TextRunHarfBuzz* run; + + size_t run_index = GetRunContainingCaret(selection); + if (run_index >= run_list->size()) { + // The cursor is not in any run: we're at the visual and logical edge. + SelectionModel edge = EdgeSelectionModel(direction); + if (edge.caret_pos() == selection.caret_pos()) + return edge; + int visual_index = (direction == CURSOR_RIGHT) ? 0 : run_list->size() - 1; + run = run_list->runs()[run_list->visual_to_logical(visual_index)].get(); + } else { + // If the cursor is moving within the current run, just move it by one + // grapheme in the appropriate direction. + run = run_list->runs()[run_index].get(); + size_t caret = selection.caret_pos(); + bool forward_motion = run->font_params.is_rtl == (direction == CURSOR_LEFT); + if (forward_motion) { + if (caret < DisplayIndexToTextIndex(run->range.end())) { + caret = IndexOfAdjacentGrapheme(caret, CURSOR_FORWARD); + return SelectionModel(caret, CURSOR_BACKWARD); + } + } else { + if (caret > DisplayIndexToTextIndex(run->range.start())) { + caret = IndexOfAdjacentGrapheme(caret, CURSOR_BACKWARD); + return SelectionModel(caret, CURSOR_FORWARD); + } + } + // The cursor is at the edge of a run; move to the visually adjacent run. + int visual_index = run_list->logical_to_visual(run_index); + visual_index += (direction == CURSOR_LEFT) ? -1 : 1; + if (visual_index < 0 || visual_index >= static_cast(run_list->size())) + return EdgeSelectionModel(direction); + run = run_list->runs()[run_list->visual_to_logical(visual_index)].get(); + } + bool forward_motion = run->font_params.is_rtl == (direction == CURSOR_LEFT); + return forward_motion ? FirstSelectionModelInsideRun(run) : + LastSelectionModelInsideRun(run); +} + +SelectionModel RenderTextHarfBuzz::AdjacentWordSelectionModel( + const SelectionModel& selection, + VisualCursorDirection direction) { + if (obscured()) + return EdgeSelectionModel(direction); + + base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD); + bool success = iter.Init(); + DCHECK(success); + if (!success) + return selection; + + internal::TextRunList* run_list = GetRunList(); + SelectionModel current(selection); + for (;;) { + current = AdjacentCharSelectionModel(current, direction); + size_t run = GetRunContainingCaret(current); + if (run == run_list->size()) + break; + size_t cursor = current.caret_pos(); +#if defined(OS_WIN) + // Windows generally advances to the start of a word in either direction. + // TODO: Break on the end of a word when the neighboring text is + // punctuation. + if (iter.IsStartOfWord(cursor)) + break; +#else + const bool is_forward = + run_list->runs()[run]->font_params.is_rtl == (direction == CURSOR_LEFT); + if (is_forward ? iter.IsEndOfWord(cursor) : iter.IsStartOfWord(cursor)) + break; +#endif // defined(OS_WIN) + } + return current; +} + +SelectionModel RenderTextHarfBuzz::AdjacentLineSelectionModel( + const SelectionModel& selection, + VisualCursorDirection direction) { + DCHECK(direction == CURSOR_UP || direction == CURSOR_DOWN); + + size_t line = GetLineContainingCaret(selection); + if (line == 0 && direction == CURSOR_UP) { + reset_cached_cursor_x(); + return SelectionModel(0, CURSOR_BACKWARD); + } + if (line == GetShapedText()->lines().size() - 1 && direction == CURSOR_DOWN) { + reset_cached_cursor_x(); + return SelectionModel(text().length(), CURSOR_FORWARD); + } + + direction == CURSOR_UP ? --line : ++line; + Rect bounds = GetCursorBounds(selection, true); + Point target = bounds.origin(); + if (cached_cursor_x()) + target.set_x(cached_cursor_x().value()); + else + set_cached_cursor_x(target.x()); + if (direction == CURSOR_UP) + target.Offset(0, -bounds.size().height() / 2); + else + target.Offset(0, bounds.size().height() * 3 / 2); + SelectionModel next = FindCursorPosition(target, Point()); + size_t next_line = GetLineContainingCaret(next); + + // If the |target| position is at the newline character, the caret is drawn to + // the next line. e.g., when the caret is at the beginning of the line in RTL + // text. Move the caret to the position of the previous character to move the + // caret to the previous line. + if (next_line == line + 1) + next = SelectionModel(next.caret_pos() - 1, next.caret_affinity()); + + return next; +} + +void RenderTextHarfBuzz::OnLayoutTextAttributeChanged(bool text_changed) { + RenderText::OnLayoutTextAttributeChanged(text_changed); + + update_layout_run_list_ = true; + OnDisplayTextAttributeChanged(); +} + +void RenderTextHarfBuzz::OnDisplayTextAttributeChanged() { + update_display_text_ = true; + set_shaped_text(nullptr); +} + +void RenderTextHarfBuzz::EnsureLayout() { + EnsureLayoutRunList(); + + if (update_display_run_list_) { + DCHECK(text_elided()); + const std::u16string& display_text = GetDisplayText(); + display_run_list_ = std::make_unique(); + + if (!display_text.empty()) + ItemizeAndShapeText(display_text, display_run_list_.get()); + update_display_run_list_ = false; + set_shaped_text(nullptr); + } + + if (!has_shaped_text()) { + internal::TextRunList* run_list = GetRunList(); + const int height = std::max(font_list().GetHeight(), min_line_height()); + HarfBuzzLineBreaker line_breaker( + display_rect().width(), + DetermineBaselineCenteringText(height, font_list()), height, + glyph_height_for_test_, word_wrap_behavior(), GetDisplayText(), + multiline() ? &GetLineBreaks() : nullptr, *run_list); + + if (multiline()) + line_breaker.ConstructMultiLines(); + else + line_breaker.ConstructSingleLine(); + std::vector lines; + line_breaker.FinalizeLines(&lines, &total_size_); + if (multiline() && max_lines()) { + // TODO(crbug.com/866720): no more than max_lines() should be rendered. + // Remove the IsHomogeneous() condition for the following DCHECK when the + // bug is fixed. + if (IsHomogeneous()) { + DCHECK_LE(lines.size(), max_lines()); + } + } + + set_shaped_text(std::make_unique(lines)); + } +} + +void RenderTextHarfBuzz::DrawVisualText(internal::SkiaTextRenderer* renderer, + const std::vector& selections) { + DCHECK(!update_layout_run_list_); + DCHECK(!update_display_run_list_); + DCHECK(!update_display_text_); + + const internal::ShapedText* shaped_text = GetShapedText(); + if (shaped_text->lines().empty()) + return; + + ApplyFadeEffects(renderer); + ApplyTextShadows(renderer); + + // Apply the selected text color to the [un-reversed] selection range. + BreakList colors = layout_colors(); + for (auto selection : selections) { + if (!selection.is_empty()) { + const Range grapheme_range = ExpandRangeToGraphemeBoundary(selection); + colors.ApplyValue( + selection_color(), + Range(TextIndexToDisplayIndex(grapheme_range.GetMin()), + TextIndexToDisplayIndex(grapheme_range.GetMax()))); + } + } + + internal::TextRunList* run_list = GetRunList(); + const std::u16string& display_text = GetDisplayText(); + for (size_t i = 0; i < shaped_text->lines().size(); ++i) { + const internal::Line& line = shaped_text->lines()[i]; + const Vector2d origin = GetLineOffset(i) + Vector2d(0, line.baseline); + SkScalar preceding_segment_widths = 0; + for (const internal::LineSegment& segment : line.segments) { + // Don't draw the newline glyph (crbug.com/680430). + if (IsNewlineSegment(display_text, segment)) + continue; + + const internal::TextRunHarfBuzz& run = *run_list->runs()[segment.run]; + renderer->SetTypeface(run.font_params.skia_face); + renderer->SetTextSize(SkIntToScalar(run.font_params.font_size)); + renderer->SetFontRenderParams(run.font_params.render_params, + subpixel_rendering_suppressed()); + Range glyphs_range = run.CharRangeToGlyphRange(segment.char_range); + std::vector positions(glyphs_range.length()); + SkScalar offset_x = preceding_segment_widths - + ((glyphs_range.GetMin() != 0) + ? run.shape.positions[glyphs_range.GetMin()].x() + : 0); + for (size_t j = 0; j < glyphs_range.length(); ++j) { + positions[j] = run.shape.positions[(glyphs_range.is_reversed()) + ? (glyphs_range.start() - j) + : (glyphs_range.start() + j)]; + positions[j].offset( + SkIntToScalar(origin.x()) + offset_x, + SkIntToScalar(origin.y() + run.font_params.baseline_offset)); + } + for (auto it = colors.GetBreak(segment.char_range.start()); + it != colors.breaks().end() && it->first < segment.char_range.end(); + ++it) { + const Range intersection = + colors.GetRange(it).Intersect(segment.char_range); + const Range colored_glyphs = run.CharRangeToGlyphRange(intersection); + // The range may be empty if a portion of a multi-character grapheme is + // selected, yielding two colors for a single glyph. For now, this just + // paints the glyph with a single style, but it should paint it twice, + // clipped according to selection bounds. See http://crbug.com/366786 + if (colored_glyphs.is_empty()) + continue; + + renderer->SetForegroundColor(it->second); + renderer->DrawPosText( + &positions[colored_glyphs.start() - glyphs_range.start()], + &run.shape.glyphs[colored_glyphs.start()], colored_glyphs.length()); + int start_x = SkScalarRoundToInt( + positions[colored_glyphs.start() - glyphs_range.start()].x()); + int end_x = SkScalarRoundToInt( + (colored_glyphs.end() == glyphs_range.end()) + ? (SkFloatToScalar(segment.width()) + preceding_segment_widths + + SkIntToScalar(origin.x())) + : positions[colored_glyphs.end() - glyphs_range.start()].x()); + if (run.font_params.heavy_underline) + renderer->DrawUnderline(start_x, origin.y(), end_x - start_x, 2.0); + else if (run.font_params.underline) + renderer->DrawUnderline(start_x, origin.y(), end_x - start_x); + if (run.font_params.strike) + renderer->DrawStrike(start_x, origin.y(), end_x - start_x, + strike_thickness_factor()); + } + preceding_segment_widths += SkFloatToScalar(segment.width()); + } + } +} + +size_t RenderTextHarfBuzz::GetRunContainingCaret( + const SelectionModel& caret) { + DCHECK(!update_display_run_list_); + size_t layout_position = TextIndexToDisplayIndex(caret.caret_pos()); + LogicalCursorDirection affinity = caret.caret_affinity(); + internal::TextRunList* run_list = GetRunList(); + for (size_t i = 0; i < run_list->size(); ++i) { + internal::TextRunHarfBuzz* run = run_list->runs()[i].get(); + if (RangeContainsCaret(run->range, layout_position, affinity)) + return i; + } + return run_list->size(); +} + +SelectionModel RenderTextHarfBuzz::FirstSelectionModelInsideRun( + const internal::TextRunHarfBuzz* run) { + size_t position = DisplayIndexToTextIndex(run->range.start()); + position = IndexOfAdjacentGrapheme(position, CURSOR_FORWARD); + return SelectionModel(position, CURSOR_BACKWARD); +} + +SelectionModel RenderTextHarfBuzz::LastSelectionModelInsideRun( + const internal::TextRunHarfBuzz* run) { + size_t position = DisplayIndexToTextIndex(run->range.end()); + position = IndexOfAdjacentGrapheme(position, CURSOR_BACKWARD); + return SelectionModel(position, CURSOR_FORWARD); +} + +void RenderTextHarfBuzz::ItemizeAndShapeText(const std::u16string& text, + internal::TextRunList* run_list) { + CommonizedRunsMap commonized_run_map; + ItemizeTextToRuns(text, run_list, &commonized_run_map); + + for (auto iter = commonized_run_map.begin(); iter != commonized_run_map.end(); + ++iter) { + internal::TextRunHarfBuzz::FontParams font_params = iter->first; + font_params.ComputeRenderParamsFontSizeAndBaselineOffset(); + ShapeRuns(text, font_params, std::move(iter->second)); + } + + run_list->InitIndexMap(); + run_list->ComputePrecedingRunWidths(); +} + +void RenderTextHarfBuzz::ItemizeTextToRuns( + const std::u16string& text, + internal::TextRunList* out_run_list, + CommonizedRunsMap* out_commonized_run_map) { + TRACE_EVENT1("ui", "RenderTextHarfBuzz::ItemizeTextToRuns", "text_length", + text.length()); + DCHECK(!text.empty()); + const Font& primary_font = font_list().GetPrimaryFont(); + + // If ICU fails to itemize the text, we create a run that spans the entire + // text. This is needed because leaving the runs set empty causes some clients + // to misbehave since they expect non-zero text metrics from a non-empty text. + ui::gfx::BiDiLineIterator bidi_iterator; + + if (!bidi_iterator.Open(text, GetTextDirectionForGivenText(text))) { + auto run = std::make_unique( + font_list().GetPrimaryFont()); + run->range = Range(0, text.length()); + internal::TextRunHarfBuzz::FontParams font_params(primary_font); + (*out_commonized_run_map)[font_params].push_back(run.get()); + out_run_list->Add(std::move(run)); + return; + } + + // Iterator to split ranged styles and baselines. The color attributes don't + // break text runs to keep ligature between graphemes (e.g. Arabic word). + internal::StyleIterator style = GetLayoutTextStyleIterator(); + + // Split the original text by logical runs, then each logical run by common + // script and each sequence at special characters and style boundaries. This + // invariant holds: bidi_run_start <= script_run_start <= breaking_run_start + // <= breaking_run_end <= script_run_end <= bidi_run_end + for (size_t bidi_run_start = 0; bidi_run_start < text.length();) { + // Determine the longest logical run (e.g. same bidi direction) from this + // point. + int32_t bidi_run_break = 0; + UBiDiLevel bidi_level = 0; + bidi_iterator.GetLogicalRun(bidi_run_start, &bidi_run_break, &bidi_level); + size_t bidi_run_end = static_cast(bidi_run_break); + DCHECK_LT(bidi_run_start, bidi_run_end); + + ApplyForcedDirection(&bidi_level); + + for (size_t script_run_start = bidi_run_start; + script_run_start < bidi_run_end;) { + // Find the longest sequence of characters that have at least one common + // UScriptCode value. + UScriptCode script = USCRIPT_INVALID_CODE; + size_t script_run_end = + ScriptInterval(text, script_run_start, + bidi_run_end - script_run_start, &script) + + script_run_start; + DCHECK_LT(script_run_start, script_run_end); + + for (size_t breaking_run_start = script_run_start; + breaking_run_start < script_run_end;) { + // Find the break boundary for style. The style won't break a grapheme + // since the style of the first character is applied to the whole + // grapheme. + style.IncrementToPosition(breaking_run_start); + size_t text_style_end = style.GetTextBreakingRange().end(); + + // Break runs at certain characters that need to be rendered separately + // to prevent an unusual character from forcing a fallback font on the + // entire run. After script intersection, many codepoints end up in the + // script COMMON but can't be rendered together. + size_t breaking_run_end = FindRunBreakingCharacter( + text, script, breaking_run_start, text_style_end, script_run_end); + + DCHECK_LT(breaking_run_start, breaking_run_end); + DCHECK(IsValidCodePointIndex(text, breaking_run_end)); + + // Set the font params for the current run for the current run break. + internal::TextRunHarfBuzz::FontParams font_params = + CreateFontParams(primary_font, bidi_level, script, style); + + // Create the current run from [breaking_run_start, breaking_run_end[. + auto run = std::make_unique(primary_font); + run->range = Range(breaking_run_start, breaking_run_end); + + // Add the created run to the set of runs. + (*out_commonized_run_map)[font_params].push_back(run.get()); + out_run_list->Add(std::move(run)); + + // Move to the next run. + breaking_run_start = breaking_run_end; + } + + // Move to the next script sequence. + script_run_start = script_run_end; + } + + // Move to the next direction sequence. + bidi_run_start = bidi_run_end; + } +} + +void RenderTextHarfBuzz::ShapeRuns( + const std::u16string& text, + const internal::TextRunHarfBuzz::FontParams& font_params, + std::vector runs) { + TRACE_EVENT1("ui", "RenderTextHarfBuzz::ShapeRuns", "run_count", runs.size()); + + // Runs with a single newline character should be skipped since they can't be + // rendered (see http://crbug/680430). The following code sets the runs + // shaping output to report report the missing glyph and removes the runs from + // the vector of runs to shape. The newline character doesn't have a + // glyph, which otherwise forces this function to go through the expensive + // font fallbacks before reporting a missing glyph (see http://crbug/972090). + std::vector need_shaping_runs; + for (internal::TextRunHarfBuzz*& run : runs) { + if ((run->range.length() == 1 && (text[run->range.start()] == '\r' || + text[run->range.start()] == '\n')) || + (run->range.length() == 2 && text[run->range.start()] == '\r' && + text[run->range.start() + 1] == '\n')) { + // Newline runs can't be shaped. Shape this run as if the glyph is + // missing. + run->font_params = font_params; + run->shape.missing_glyph_count = 1; + run->shape.glyph_count = 1; + run->shape.glyphs.resize(run->shape.glyph_count); + run->shape.glyph_to_char.resize(run->shape.glyph_count); + run->shape.positions.resize(run->shape.glyph_count); + // Keep width as zero since newline character doesn't have a width. + } else { + // This run needs shaping. + need_shaping_runs.push_back(run); + } + } + runs.swap(need_shaping_runs); + if (runs.empty()) { + RecordShapeRunsFallback(ShapeRunFallback::NO_FALLBACK); + return; + } + + // Keep a set of fonts already tried for shaping runs. + std::set fallback_fonts_already_tried; + std::vector fallback_font_candidates; + + // Shaping with primary configured fonts from font_list(). + for (const Font& font : font_list().GetFonts()) { + internal::TextRunHarfBuzz::FontParams test_font_params = font_params; + if (test_font_params.SetRenderParamsRematchFont( + font, font.GetFontRenderParams()) && + !FontWasAlreadyTried(test_font_params.skia_face, + &fallback_fonts_already_tried)) { + ShapeRunsWithFont(text, test_font_params, &runs); + MarkFontAsTried(test_font_params.skia_face, + &fallback_fonts_already_tried); + fallback_font_candidates.push_back(font); + } + if (runs.empty()) { + RecordShapeRunsFallback(ShapeRunFallback::NO_FALLBACK); + return; + } + } + + const Font& primary_font = font_list().GetPrimaryFont(); + + // Find fallback fonts for the remaining runs using a worklist algorithm. Try + // to shape the first run by using GetFallbackFont(...) and then try shaping + // other runs with the same font. If the first font can't be shaped, remove it + // and continue with the remaining runs until the worklist is empty. The + // fallback font returned by GetFallbackFont(...) depends on the text of the + // run and the results may differ between runs. + std::vector remaining_unshaped_runs; + while (!runs.empty()) { + Font fallback_font(primary_font); + bool fallback_found; + internal::TextRunHarfBuzz* current_run = *runs.begin(); + { + SCOPED_UMA_HISTOGRAM_LONG_TIMER("RenderTextHarfBuzz.GetFallbackFontTime"); + TRACE_EVENT1("ui", "RenderTextHarfBuzz::GetFallbackFont", "script", + TRACE_STR_COPY(uscript_getShortName(font_params.script))); + const base::StringPiece16 run_text(&text[current_run->range.start()], + current_run->range.length()); + fallback_found = + GetFallbackFont(primary_font, locale_, run_text, &fallback_font); + } + + if (fallback_found) { + internal::TextRunHarfBuzz::FontParams test_font_params = font_params; + if (test_font_params.SetRenderParamsOverrideSkiaFaceFromFont( + fallback_font, fallback_font.GetFontRenderParams()) && + !FontWasAlreadyTried(test_font_params.skia_face, + &fallback_fonts_already_tried)) { + ShapeRunsWithFont(text, test_font_params, &runs); + MarkFontAsTried(test_font_params.skia_face, + &fallback_fonts_already_tried); + } + } + + // Remove the first run if not fully shaped with its associated fallback + // font. + if (!runs.empty() && runs[0] == current_run) { + remaining_unshaped_runs.push_back(current_run); + runs.erase(runs.begin()); + } + } + runs.swap(remaining_unshaped_runs); + if (runs.empty()) { + RecordShapeRunsFallback(ShapeRunFallback::FALLBACK); + return; + } + + std::vector fallback_font_list; + { + SCOPED_UMA_HISTOGRAM_LONG_TIMER("RenderTextHarfBuzz.GetFallbackFontsTime"); + TRACE_EVENT1("ui", "RenderTextHarfBuzz::GetFallbackFonts", "script", + TRACE_STR_COPY(uscript_getShortName(font_params.script))); + fallback_font_list = GetFallbackFonts(primary_font); + +#if defined(OS_WIN) + // Append fonts in the fallback list of the fallback fonts. + // TODO(tapted): Investigate whether there's a case that benefits from this + // on Mac. + for (const auto& fallback_font : fallback_font_candidates) { + std::vector fallback_fonts = GetFallbackFonts(fallback_font); + fallback_font_list.insert(fallback_font_list.end(), + fallback_fonts.begin(), fallback_fonts.end()); + } + + // Add Segoe UI and its associated linked fonts to the fallback font list to + // ensure that the fallback list covers the basic cases. + // http://crbug.com/467459. On some Windows configurations the default font + // could be a raster font like System, which would not give us a reasonable + // fallback font list. + Font segoe("Segoe UI", 13); + if (!FontWasAlreadyTried(segoe.platform_font()->GetNativeSkTypeface(), + &fallback_fonts_already_tried)) { + std::vector default_fallback_families = GetFallbackFonts(segoe); + fallback_font_list.insert(fallback_font_list.end(), + default_fallback_families.begin(), + default_fallback_families.end()); + } +#endif + } + + // Use a set to track the fallback fonts and avoid duplicate entries. + SCOPED_UMA_HISTOGRAM_LONG_TIMER( + "RenderTextHarfBuzz.ShapeRunsWithFallbackFontsTime"); + TRACE_EVENT1("ui", "RenderTextHarfBuzz::ShapeRunsWithFallbackFonts", + "fonts_count", fallback_font_list.size()); + + // Try shaping with the fallback fonts. + for (const auto& font : fallback_font_list) { + std::string font_name = font.GetFontName(); + + FontRenderParamsQuery query; + query.families.push_back(font_name); + query.pixel_size = font_params.font_size; + query.style = font_params.italic ? Font::ITALIC : 0; + FontRenderParams fallback_render_params = GetFontRenderParams(query, NULL); + internal::TextRunHarfBuzz::FontParams test_font_params = font_params; + if (test_font_params.SetRenderParamsOverrideSkiaFaceFromFont( + font, fallback_render_params) && + !FontWasAlreadyTried(test_font_params.skia_face, + &fallback_fonts_already_tried)) { + ShapeRunsWithFont(text, test_font_params, &runs); + MarkFontAsTried(test_font_params.skia_face, + &fallback_fonts_already_tried); + } + if (runs.empty()) { + TRACE_EVENT_INSTANT2("ui", "RenderTextHarfBuzz::FallbackFont", + TRACE_EVENT_SCOPE_THREAD, "font_name", + TRACE_STR_COPY(font_name.c_str()), + "primary_font_name", primary_font.GetFontName()); + RecordShapeRunsFallback(ShapeRunFallback::FALLBACKS); + return; + } + } + + for (internal::TextRunHarfBuzz*& run : runs) { + if (run->shape.missing_glyph_count == std::numeric_limits::max()) { + run->shape.glyph_count = 0; + run->shape.width = 0.0f; + } + } + + RecordShapeRunsFallback(ShapeRunFallback::FAILED); +} + +void RenderTextHarfBuzz::ShapeRunsWithFont( + const std::u16string& text, + const internal::TextRunHarfBuzz::FontParams& font_params, + std::vector* in_out_runs) { + // ShapeRunWithFont can be extremely slow, so use cached results if possible. + // Only do this on the UI thread, to avoid synchronization overhead (and + // because almost all calls are on the UI thread. Also avoid caching long + // strings, to avoid blowing up the cache size. + constexpr size_t kMaxRunLengthToCache = 25; + static base::NoDestructor cache; + + std::vector runs_with_missing_glyphs; + for (internal::TextRunHarfBuzz*& run : *in_out_runs) { + // First do a cache lookup. + bool can_use_cache = base::CurrentUIThread::IsSet() && + run->range.length() <= kMaxRunLengthToCache; + bool found_in_cache = false; + const internal::ShapeRunWithFontInput cache_key( + text, font_params, run->range, obscured(), glyph_width_for_test_, + obscured_glyph_spacing(), subpixel_rendering_suppressed()); + if (can_use_cache) { + auto found = cache.get()->Get(cache_key); + if (found != cache.get()->end()) { + run->UpdateFontParamsAndShape(font_params, found->second); + found_in_cache = true; + } + } + + // If that fails, compute the shape of the run, and add the result to the + // cache. + // TODO(ccameron): Coalesce calls to ShapeRunsWithFont when possible. + if (!found_in_cache) { + internal::TextRunHarfBuzz::ShapeOutput output; + ShapeRunWithFont(cache_key, &output); + run->UpdateFontParamsAndShape(font_params, output); + if (can_use_cache) + cache.get()->Put(cache_key, output); + } + + // Check to see if we still have missing glyphs. + if (run->shape.missing_glyph_count) + runs_with_missing_glyphs.push_back(run); + } + in_out_runs->swap(runs_with_missing_glyphs); +} + +void RenderTextHarfBuzz::EnsureLayoutRunList() { + // Update layout run list if the device scale factor has changed since the + // layout run list was last updated, as changes in device scale factor change + // subpixel positioning, at least on Linux and Chrome OS. + const float device_scale_factor = GetFontRenderParamsDeviceScaleFactor(); + + if (update_layout_run_list_ || device_scale_factor_ != device_scale_factor) { + device_scale_factor_ = device_scale_factor; + layout_run_list_.Reset(); + + const std::u16string& text = GetLayoutText(); + if (!text.empty()) + ItemizeAndShapeText(text, &layout_run_list_); + + display_run_list_.reset(); + update_display_text_ = true; + update_layout_run_list_ = false; + } + if (update_display_text_) { + set_shaped_text(nullptr); + UpdateDisplayText(multiline() ? 0 : layout_run_list_.width()); + update_display_text_ = false; + update_display_run_list_ = text_elided(); + } +} + +// Returns the current run list, |display_run_list_| if the text is elided, or +// |layout_run_list_| otherwise. +internal::TextRunList* RenderTextHarfBuzz::GetRunList() { + DCHECK(!update_layout_run_list_); + DCHECK(!update_display_run_list_); + return text_elided() ? display_run_list_.get() : &layout_run_list_; +} + +const internal::TextRunList* RenderTextHarfBuzz::GetRunList() const { + return const_cast(this)->GetRunList(); +} + +bool RenderTextHarfBuzz::IsValidDisplayRange(Range display_range) { + // The |display_text_| is an elided version of |layout_text_|. Removing + // codepoints from the text may break the conversion for codepoint offsets + // between text to display_text offset. For elding behaviors that truncate + // codepoint at the end, the conversion will work just fine. But for eliding + // behavior that truncate at the beginning of middle of the text, the offsets + // are completely wrong and should not be used. + // TODO(http://crbug.com/1085014): Fix eliding for the broken cases. + switch (elide_behavior()) { + case NO_ELIDE: + case FADE_TAIL: + return display_range.IsBoundedBy(Range(0, GetDisplayText().length())); + case TRUNCATE: + case ELIDE_TAIL: + return display_range.IsBoundedBy(Range(0, GetLayoutText().length())); + case ELIDE_HEAD: + case ELIDE_MIDDLE: + case ELIDE_EMAIL: + return !text_elided(); + } +} + +bool RenderTextHarfBuzz::GetDecoratedTextForRange( + const Range& range, + DecoratedText* decorated_text) { + if (obscured()) + return false; + + EnsureLayout(); + + decorated_text->attributes.clear(); + decorated_text->text = GetTextFromRange(range); + + const internal::TextRunList* run_list = GetRunList(); + for (size_t i = 0; i < run_list->size(); i++) { + const internal::TextRunHarfBuzz& run = *run_list->runs()[i]; + + const Range intersection = range.Intersect(run.range); + DCHECK(!intersection.is_reversed()); + + if (!intersection.is_empty()) { + int style = Font::NORMAL; + if (run.font_params.italic) + style |= Font::ITALIC; + if (run.font_params.underline || run.font_params.heavy_underline) + style |= Font::UNDERLINE; + + // Get range relative to the decorated text. + DecoratedText::RangedAttribute attribute( + Range(intersection.start() - range.GetMin(), + intersection.end() - range.GetMin()), + run.font_params.font.Derive(0, style, run.font_params.weight)); + + attribute.strike = run.font_params.strike; + decorated_text->attributes.push_back(attribute); + } + } + return true; +} + +} // namespace gfx diff --git a/render_text_harfbuzz.h b/render_text_harfbuzz.h new file mode 100644 index 000000000000..64a823dd32a4 --- /dev/null +++ b/render_text_harfbuzz.h @@ -0,0 +1,329 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_RENDER_TEXT_HARFBUZZ_H_ +#define UI_GFX_RENDER_TEXT_HARFBUZZ_H_ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "base/macros.h" +#include "third_party/icu/source/common/unicode/ubidi.h" +#include "third_party/icu/source/common/unicode/uscript.h" +#include "ui/gfx/render_text.h" + +namespace gfx { + +class Range; +class RangeF; +class RenderTextHarfBuzz; + +namespace internal { + +struct GFX_EXPORT TextRunHarfBuzz { + // Construct the run with |template_font| since determining the details of a + // default-constructed gfx::Font is expensive, but it will always be replaced. + explicit TextRunHarfBuzz(const Font& template_font); + + TextRunHarfBuzz(const TextRunHarfBuzz&) = delete; + TextRunHarfBuzz& operator=(const TextRunHarfBuzz&) = delete; + + ~TextRunHarfBuzz(); + + // Returns the corresponding glyph range of the given character range. + // |range| is in text-space (0 corresponds to |GetDisplayText()[0]|). Returned + // value is in run-space (0 corresponds to the first glyph in the run). + Range CharRangeToGlyphRange(const Range& range) const; + + // Returns the number of missing glyphs in the shaped text run. + size_t CountMissingGlyphs() const; + + // Writes the character and glyph ranges of the cluster containing |pos|. + void GetClusterAt(size_t pos, Range* chars, Range* glyphs) const; + + // Returns the grapheme bounds at |text_index|. Handles multi-grapheme glyphs. + // Returned value is the horizontal pixel span in text-space (assumes all runs + // are on the same line). The returned range is never reversed. + RangeF GetGraphemeBounds(RenderTextHarfBuzz* render_text, + size_t text_index) const; + + // Returns the horizontal span of the given |char_range| handling grapheme + // boundaries within glyphs. This is a wrapper around one or more calls to + // GetGraphemeBounds(), returning a range in the same coordinate space. + RangeF GetGraphemeSpanForCharRange(RenderTextHarfBuzz* render_text, + const Range& char_range) const; + + // Returns the glyph width for the given character range. |char_range| is in + // text-space (0 corresponds to |GetDisplayText()[0]|). + SkScalar GetGlyphWidthForCharRange(const Range& char_range) const; + + // Font parameters that may be common to multiple text runs within a text run + // list. + struct GFX_EXPORT FontParams { + // The default constructor for Font is expensive, so always require that a + // Font be provided. + explicit FontParams(const Font& template_font); + ~FontParams(); + FontParams(const FontParams& other); + FontParams& operator=(const FontParams& other); + bool operator==(const FontParams& other) const; + + // Populates |render_params|, |font_size| and |baseline_offset| based on + // |font|. + void ComputeRenderParamsFontSizeAndBaselineOffset(); + + // Populates |font|, |skia_face|, and |render_params|. Returns false if + // |skia_face| is nullptr. Takes |font|'s family name and rematches this + // family and the run's weight and style properties to find a new font. + bool SetRenderParamsRematchFont(const Font& font, + const FontRenderParams& render_params); + + // Populates |font|, |skia_face|, and |render_params|. Returns false if + // |skia_face| is nullptr. Does not perform rematching but extracts an + // SkTypeface from the underlying PlatformFont of font. Use this method when + // configuring the |TextRunHarfBuzz| for shaping with fallback fonts, where + // it is important to keep the underlying font handle of platform font and + // not perform rematching as in |SetRenderParamsRematchFont|. + bool SetRenderParamsOverrideSkiaFaceFromFont( + const Font& font, + const FontRenderParams& render_params); + + struct Hash { + size_t operator()(const FontParams& key) const; + }; + + Font font; + sk_sp skia_face; + FontRenderParams render_params; + Font::Weight weight = Font::Weight::NORMAL; + int font_size = 0; + int baseline_offset = 0; + int baseline_type = 0; + bool italic = false; + bool strike = false; + bool underline = false; + bool heavy_underline = false; + bool is_rtl = false; + UBiDiLevel level = 0; + UScriptCode script = USCRIPT_INVALID_CODE; + }; + + // Parameters that are set by ShapeRunWithFont. + struct GFX_EXPORT ShapeOutput { + ShapeOutput(); + ~ShapeOutput(); + ShapeOutput(const ShapeOutput& other); + ShapeOutput& operator=(const ShapeOutput& other); + ShapeOutput(ShapeOutput&& other); + ShapeOutput& operator=(ShapeOutput&& other); + + float width = 0.0; + std::vector glyphs; + std::vector positions; + // Note that in the context of TextRunHarfBuzz, |glyph_to_char| is indexed + // based off of the full string (so it is in the same domain as + // TextRunHarfBuzz::range). + std::vector glyph_to_char; + size_t glyph_count = 0; + size_t missing_glyph_count = std::numeric_limits::max(); + }; + + // If |new_shape.missing_glyph_count| is less than that of |shape|, set + // |font_params| and |shape| to the specified values. + void UpdateFontParamsAndShape(const FontParams& new_font_params, + const ShapeOutput& new_shape); + + Range range; + FontParams font_params; + ShapeOutput shape; + float preceding_run_widths = 0.0; +}; + +// Manages the list of TextRunHarfBuzz and its logical <-> visual index mapping. +class TextRunList { + public: + TextRunList(); + + TextRunList(const TextRunList&) = delete; + TextRunList& operator=(const TextRunList&) = delete; + + ~TextRunList(); + + size_t size() const { return runs_.size(); } + + // Converts the index between logical and visual index. + size_t visual_to_logical(size_t index) const { + return visual_to_logical_[index]; + } + size_t logical_to_visual(size_t index) const { + return logical_to_visual_[index]; + } + + const std::vector>& runs() const { + return runs_; + } + + // Adds the new |run| to the run list. + void Add(std::unique_ptr run) { + runs_.push_back(std::move(run)); + } + + // Reset the run list. + void Reset(); + + // Initialize the index mapping. + void InitIndexMap(); + + // Precomputes the offsets for all runs. + void ComputePrecedingRunWidths(); + + // Get the total width of runs, as if they were shown on one line. + // Do not use this when multiline is enabled. + float width() const { return width_; } + + // Get the run index applicable to |position| (at or preceeding |position|). + size_t GetRunIndexAt(size_t position) const; + + private: + // Text runs in logical order. + std::vector> runs_; + + // Maps visual run indices to logical run indices and vice versa. + std::vector visual_to_logical_; + std::vector logical_to_visual_; + + float width_; +}; + +} // namespace internal + +class GFX_EXPORT RenderTextHarfBuzz : public RenderText { + public: + RenderTextHarfBuzz(); + + RenderTextHarfBuzz(const RenderTextHarfBuzz&) = delete; + RenderTextHarfBuzz& operator=(const RenderTextHarfBuzz&) = delete; + + ~RenderTextHarfBuzz() override; + + // RenderText: + const std::u16string& GetDisplayText() override; + SizeF GetStringSizeF() override; + SizeF GetLineSizeF(const SelectionModel& caret) override; + std::vector GetSubstringBounds(const Range& range) override; + RangeF GetCursorSpan(const Range& text_range) override; + size_t GetLineContainingCaret(const SelectionModel& caret) override; + + protected: + // RenderText: + SelectionModel AdjacentCharSelectionModel( + const SelectionModel& selection, + VisualCursorDirection direction) override; + SelectionModel AdjacentWordSelectionModel( + const SelectionModel& selection, + VisualCursorDirection direction) override; + SelectionModel AdjacentLineSelectionModel( + const SelectionModel& selection, + VisualCursorDirection direction) override; + void OnLayoutTextAttributeChanged(bool text_changed) override; + void OnDisplayTextAttributeChanged() override; + void EnsureLayout() override; + void DrawVisualText(internal::SkiaTextRenderer* renderer, + const std::vector& selections) override; + + private: + friend class test::RenderTextTestApi; + friend class RenderTextTest; + + // Return the run index that contains the argument; or the length of the + // |runs_| vector if argument exceeds the text length or width. + size_t GetRunContainingCaret(const SelectionModel& caret); + + // Given a |run|, returns the SelectionModel that contains the logical first + // or last caret position inside (not at a boundary of) the run. + // The returned value represents a cursor/caret position without a selection. + SelectionModel FirstSelectionModelInsideRun( + const internal::TextRunHarfBuzz* run); + SelectionModel LastSelectionModelInsideRun( + const internal::TextRunHarfBuzz* run); + + using CommonizedRunsMap = + std::unordered_map, + internal::TextRunHarfBuzz::FontParams::Hash>; + + // Break the text into logical runs in |out_run_list|. Populate + // |out_commonized_run_map| such that each run is present in the vector + // corresponding to its FontParams. + void ItemizeTextToRuns(const std::u16string& string, + internal::TextRunList* out_run_list, + CommonizedRunsMap* out_commonized_run_map); + + // Shape the glyphs needed for each run in |runs| within |text|. This method + // will apply a number of fonts to |base_font_params| and assign to each + // run's FontParams and ShapeOutput the parameters and resulting shape that + // had the smallest number of missing glyphs. + void ShapeRuns(const std::u16string& text, + const internal::TextRunHarfBuzz::FontParams& base_font_params, + std::vector runs); + + // Shape the glyphs for |in_out_runs| within |text| using the parameters + // specified by |font_params|. If, for any run in |*in_out_runs|, the + // resulting shaping has fewer missing glyphs than the existing shape, then + // write |font_params| and the resulting ShapeOutput to that run. Remove all + // runs with no missing glyphs from |in_out_runs| (the caller, ShapeRuns, will + // terminate when no runs with missing glyphs remain). + void ShapeRunsWithFont( + const std::u16string& text, + const internal::TextRunHarfBuzz::FontParams& font_params, + std::vector* in_out_runs); + + // Itemize |text| into runs in |out_run_list|, shape the runs, and populate + // |out_run_list|'s visual <-> logical maps. + void ItemizeAndShapeText(const std::u16string& text, + internal::TextRunList* out_run_list); + + // Makes sure that text runs for layout text are shaped. + void EnsureLayoutRunList(); + + // Returns whether the display range is still a valid range after the eliding + // pass. + bool IsValidDisplayRange(Range display_range); + + // RenderText: + internal::TextRunList* GetRunList() override; + const internal::TextRunList* GetRunList() const override; + bool GetDecoratedTextForRange(const Range& range, + DecoratedText* decorated_text) override; + + // Text run list for |layout_text_| and |display_text_|. + // |display_run_list_| is created only when the text is elided. + internal::TextRunList layout_run_list_; + std::unique_ptr display_run_list_; + + bool update_layout_run_list_ : 1; + bool update_display_run_list_ : 1; + bool update_display_text_ : 1; + + // The device scale factor for which the text was laid out. + float device_scale_factor_ = 1.0f; + + // The total size of the layouted text. + SizeF total_size_; + + // The process application locale used to configure text rendering. + std::string locale_; +}; + +} // namespace gfx + +#endif // UI_GFX_RENDER_TEXT_HARFBUZZ_H_ diff --git a/render_text_test_api.h b/render_text_test_api.h new file mode 100644 index 000000000000..e7a7b33fb844 --- /dev/null +++ b/render_text_test_api.h @@ -0,0 +1,129 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_RENDER_TEXT_TEST_API_H_ +#define UI_GFX_RENDER_TEXT_TEST_API_H_ + +#include "base/macros.h" +#include "ui/gfx/break_list.h" +#include "ui/gfx/geometry/vector2d.h" +#include "ui/gfx/render_text.h" +#include "ui/gfx/selection_model.h" + +namespace gfx { +namespace test { + +class RenderTextTestApi { + public: + RenderTextTestApi(RenderText* render_text) : render_text_(render_text) {} + + RenderTextTestApi(const RenderTextTestApi&) = delete; + RenderTextTestApi& operator=(const RenderTextTestApi&) = delete; + + static const cc::PaintFlags& GetRendererPaint( + internal::SkiaTextRenderer* renderer) { + return renderer->flags_; + } + + static const SkFont& GetRendererFont(internal::SkiaTextRenderer* renderer) { + return renderer->font_; + } + + // Callers must ensure that the associated RenderText object is a + // RenderTextHarfBuzz instance. + internal::TextRunList* GetHarfBuzzRunList() { + return render_text_->GetRunList(); + } + + void DrawVisualText(internal::SkiaTextRenderer* renderer, + const std::vector selection) { + render_text_->DrawVisualText(renderer, selection); + } + + void Draw(Canvas* canvas, bool select_all = false) { + render_text_->Draw(canvas, select_all); + } + + const std::u16string& GetLayoutText() { + return render_text_->GetLayoutText(); + } + + const BreakList& colors() const { return render_text_->colors(); } + + const BreakList& baselines() const { + return render_text_->baselines(); + } + + const BreakList& font_size_overrides() const { + return render_text_->font_size_overrides(); + } + + const BreakList& weights() const { + return render_text_->weights(); + } + + const internal::StyleArray& styles() const { return render_text_->styles(); } + + const std::vector& lines() const { + return render_text_->GetShapedText()->lines(); + } + + const Vector2d& display_offset() const { + return render_text_->display_offset_; + } + + SelectionModel EdgeSelectionModel(VisualCursorDirection direction) { + return render_text_->EdgeSelectionModel(direction); + } + + size_t TextIndexToDisplayIndex(size_t index) { + return render_text_->TextIndexToDisplayIndex(index); + } + + size_t DisplayIndexToTextIndex(size_t index) { + return render_text_->DisplayIndexToTextIndex(index); + } + + void EnsureLayout() { render_text_->EnsureLayout(); } + + Vector2d GetAlignmentOffset(size_t line_number) { + return render_text_->GetAlignmentOffset(line_number); + } + + int GetDisplayTextBaseline() { + return render_text_->GetDisplayTextBaseline(); + } + + void SetGlyphWidth(float test_width) { + render_text_->set_glyph_width_for_test(test_width); + } + + void SetGlyphHeight(float test_height) { + render_text_->set_glyph_height_for_test(test_height); + } + + static gfx::Rect ExpandToBeVerticallySymmetric( + const gfx::Rect& rect, + const gfx::Rect& display_rect) { + return RenderText::ExpandToBeVerticallySymmetric(rect, display_rect); + } + + static void MergeIntersectingRects(std::vector& rects) { + RenderText::MergeIntersectingRects(rects); + } + + void reset_cached_cursor_x() { render_text_->reset_cached_cursor_x(); } + + int GetLineContainingYCoord(float text_y) { + return render_text_->GetLineContainingYCoord(text_y); + } + + private: + RenderText* render_text_; +}; + +} // namespace test +} // namespace gfx + +#endif // UI_GFX_RENDER_TEXT_TEST_API_H_ diff --git a/render_text_unittest.cc b/render_text_unittest.cc new file mode 100644 index 000000000000..42f346c5a9f6 --- /dev/null +++ b/render_text_unittest.cc @@ -0,0 +1,8562 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/render_text.h" + +#include +#include +#include + +#include +#include +#include + +#include "base/cxx17_backports.h" +#include "base/format_macros.h" +#include "base/i18n/break_iterator.h" +#include "base/i18n/char_iterator.h" +#include "base/logging.h" +#include "base/run_loop.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "base/test/task_environment.h" +#include "build/build_config.h" +#include "cc/paint/paint_record.h" +#include "cc/paint/paint_recorder.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "third_party/skia/include/core/SkColor.h" +#include "third_party/skia/include/core/SkFontStyle.h" +#include "third_party/skia/include/core/SkSurface.h" +#include "third_party/skia/include/core/SkTextBlob.h" +#include "third_party/skia/include/core/SkTypeface.h" +#include "ui/gfx/break_list.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/color_palette.h" +#include "ui/gfx/color_utils.h" +#include "ui/gfx/decorated_text.h" +#include "ui/gfx/font.h" +#include "ui/gfx/font_names_testing.h" +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/point_conversions.h" +#include "ui/gfx/geometry/point_f.h" +#include "ui/gfx/range/range.h" +#include "ui/gfx/range/range_f.h" +#include "ui/gfx/render_text_harfbuzz.h" +#include "ui/gfx/render_text_test_api.h" +#include "ui/gfx/switches.h" +#include "ui/gfx/text_elider.h" +#include "ui/gfx/text_utils.h" + +#if defined(OS_WIN) +#include + +#include "base/win/windows_version.h" +#endif + +#if defined(OS_APPLE) +#include "base/mac/mac_util.h" +#endif + +namespace gfx { + +namespace { + +// Various weak, LTR, RTL, and Bidi string cases with three characters each. +const char16_t kWeak[] = u" . "; +const char16_t kLtr[] = u"abc"; +const char16_t kRtl[] = u"אבג"; +const char16_t kLtrRtl[] = u"aאב"; +const char16_t kLtrRtlLtr[] = u"aבb"; +const char16_t kRtlLtr[] = u"אבa"; +const char16_t kRtlLtrRtl[] = u"אaב"; + +constexpr bool kUseWordWrap = true; +constexpr bool kUseObscuredText = true; + +// Bitmasks based on gfx::TextStyle. +enum { + ITALIC_MASK = 1 << TEXT_STYLE_ITALIC, + STRIKE_MASK = 1 << TEXT_STYLE_STRIKE, + UNDERLINE_MASK = 1 << TEXT_STYLE_UNDERLINE, +}; + +using FontSpan = std::pair; + +bool IsFontsSmoothingEnabled() { +#if defined(OS_WIN) + BOOL antialiasing = TRUE; + BOOL result = SystemParametersInfo(SPI_GETFONTSMOOTHING, 0, &antialiasing, 0); + if (result == FALSE) { + ADD_FAILURE() << "Failed to retrieve font aliasing configuration."; + } + return antialiasing; +#else + return true; +#endif +} + +// Checks whether |range| contains |index|. This is not the same as calling +// range.Contains(Range(index)), which returns true if |index| == |range.end()|. +bool IndexInRange(const Range& range, size_t index) { + return index >= range.start() && index < range.end(); +} + +std::u16string GetSelectedText(RenderText* render_text) { + return render_text->text().substr(render_text->selection().GetMin(), + render_text->selection().length()); +} + +// A test utility function to set the application default text direction. +void SetRTL(bool rtl) { + // Override the current locale/direction. + base::i18n::SetICUDefaultLocale(rtl ? "he" : "en"); + EXPECT_EQ(rtl, base::i18n::IsRTL()); +} + +// Execute MoveCursor on the given |render_text| instance for the given +// arguments and verify the selected range matches |expected|. Also, clears the +// expectations. +void RunMoveCursorTestAndClearExpectations(RenderText* render_text, + BreakType break_type, + VisualCursorDirection direction, + SelectionBehavior selection_behavior, + std::vector* expected) { + for (size_t i = 0; i < expected->size(); ++i) { + SCOPED_TRACE(base::StringPrintf( + "BreakType-%d VisualCursorDirection-%d SelectionBehavior-%d Case-%d.", + break_type, direction, selection_behavior, static_cast(i))); + + render_text->MoveCursor(break_type, direction, selection_behavior); + EXPECT_EQ(expected->at(i), render_text->selection()); + } + expected->clear(); +} + +// Execute MoveCursor on the given |render_text| instance for the given +// arguments and verify the line matches |expected|. Also, clears the +// expectations. +void RunMoveCursorTestAndClearExpectations(RenderText* render_text, + BreakType break_type, + VisualCursorDirection direction, + SelectionBehavior selection_behavior, + std::vector* expected) { + int case_index = 0; + for (auto expected_line : *expected) { + SCOPED_TRACE(testing::Message() + << "Text: " << render_text->text() << " BreakType: " + << break_type << " VisualCursorDirection: " << direction + << " SelectionBehavior: " << selection_behavior + << " Case: " << case_index++); + render_text->MoveCursor(break_type, direction, selection_behavior); + EXPECT_EQ(expected_line, render_text->GetLineContainingCaret( + render_text->selection_model())); + } + expected->clear(); +} + +// Ensure cursor movement in the specified |direction| yields |expected| values. +void RunMoveCursorLeftRightTest(RenderText* render_text, + const std::vector& expected, + VisualCursorDirection direction) { + for (size_t i = 0; i < expected.size(); ++i) { + SCOPED_TRACE(base::StringPrintf("Going %s; expected value index %d.", + direction == CURSOR_LEFT ? "left" : "right", static_cast(i))); + EXPECT_EQ(expected[i], render_text->selection_model()); + render_text->MoveCursor(CHARACTER_BREAK, direction, SELECTION_NONE); + } + // Check that cursoring is clamped at the line edge. + EXPECT_EQ(expected.back(), render_text->selection_model()); + // Check that it is the line edge. + render_text->MoveCursor(LINE_BREAK, direction, SELECTION_NONE); + EXPECT_EQ(expected.back(), render_text->selection_model()); +} + +// Creates a RangedAttribute instance for a single character range at the +// given |index| with the given |weight| and |style_mask|. |index| is the +// index of the character in the DecoratedText instance and |font_index| is +// used to retrieve the font used from |font_spans|. +DecoratedText::RangedAttribute CreateRangedAttribute( + const std::vector& font_spans, + int index, + int font_index, + Font::Weight weight, + int style_mask) { + const auto iter = std::find_if(font_spans.cbegin(), font_spans.cend(), + [font_index](const FontSpan& span) { + return IndexInRange(span.second, font_index); + }); + DCHECK(font_spans.end() != iter); + const Font& font = iter->first; + + int font_style = Font::NORMAL; + if (style_mask & ITALIC_MASK) + font_style |= Font::ITALIC; + if (style_mask & UNDERLINE_MASK) + font_style |= Font::UNDERLINE; + + const Font font_with_style = font.Derive(0, font_style, weight); + DecoratedText::RangedAttribute attributes(Range(index, index + 1), + font_with_style); + attributes.strike = style_mask & STRIKE_MASK; + return attributes; +} + +// Verifies the given DecoratedText instances are equal by comparing the +// respective strings and attributes for each index. Note, corresponding +// ranged attributes from |expected| and |actual| can't be compared since the +// partition of |actual| into RangedAttributes will depend on the text runs +// generated. +void VerifyDecoratedWordsAreEqual(const DecoratedText& expected, + const DecoratedText& actual) { + ASSERT_EQ(expected.text, actual.text); + + // Compare attributes for each index. + for (size_t i = 0; i < expected.text.length(); i++) { + SCOPED_TRACE(base::StringPrintf("Comparing index[%" PRIuS "]", i)); + auto find_attribute_func = [i](const DecoratedText::RangedAttribute& attr) { + return IndexInRange(attr.range, i); + }; + const auto expected_attr = + std::find_if(expected.attributes.begin(), expected.attributes.end(), + find_attribute_func); + const auto actual_attr = + std::find_if(actual.attributes.begin(), actual.attributes.end(), + find_attribute_func); + ASSERT_NE(expected.attributes.end(), expected_attr); + ASSERT_NE(actual.attributes.end(), actual_attr); + + EXPECT_EQ(expected_attr->strike, actual_attr->strike); + EXPECT_EQ(expected_attr->font.GetFontName(), + actual_attr->font.GetFontName()); + EXPECT_EQ(expected_attr->font.GetFontSize(), + actual_attr->font.GetFontSize()); + EXPECT_EQ(expected_attr->font.GetWeight(), actual_attr->font.GetWeight()); + EXPECT_EQ(expected_attr->font.GetStyle(), actual_attr->font.GetStyle()); + } +} + +// Helper method to return an obscured string of the given |length|, with the +// |reveal_index| filled with |reveal_char|. +std::u16string GetObscuredString(size_t length, + size_t reveal_index, + char16_t reveal_char) { + std::vector arr(length, RenderText::kPasswordReplacementChar); + arr[reveal_index] = reveal_char; + return std::u16string(arr.begin(), arr.end()); +} + +// Helper method to return an obscured string of the given |length|. +std::u16string GetObscuredString(size_t length) { + return std::u16string(length, RenderText::kPasswordReplacementChar); +} + +// Returns the combined character range from all text runs on |line|. +Range LineCharRange(const internal::Line& line) { + if (line.segments.empty()) + return Range(); + Range ltr(line.segments.front().char_range.start(), + line.segments.back().char_range.end()); + if (ltr.end() > ltr.start()) + return ltr; + + // For RTL, the order of segments is reversed, but the ranges are not. + return Range(line.segments.back().char_range.start(), + line.segments.front().char_range.end()); +} + +struct GlyphCountAndColor { + size_t glyph_count = 0; + SkColor color = kPlaceholderColor; +}; + +class TextLog { + public: + TextLog(PointF origin, std::vector glyphs, SkColor color) + : origin_(origin), glyphs_(glyphs), color_(color) {} + TextLog(const TextLog&) = default; + + PointF origin() const { return origin_; } + SkColor color() const { return color_; } + const std::vector& glyphs() const { return glyphs_; } + + private: + const PointF origin_; + const std::vector glyphs_; + const SkColor color_ = SK_ColorTRANSPARENT; +}; + +// The class which records the drawing operations so that the test case can +// verify where exactly the glyphs are drawn. +class TestSkiaTextRenderer : public internal::SkiaTextRenderer { + public: + explicit TestSkiaTextRenderer(Canvas* canvas) + : internal::SkiaTextRenderer(canvas) {} + + TestSkiaTextRenderer(const TestSkiaTextRenderer&) = delete; + TestSkiaTextRenderer& operator=(const TestSkiaTextRenderer&) = delete; + + ~TestSkiaTextRenderer() override {} + + void GetTextLogAndReset(std::vector* text_log) { + text_log_.swap(*text_log); + text_log_.clear(); + } + + private: + // internal::SkiaTextRenderer overrides: + void DrawPosText(const SkPoint* pos, + const uint16_t* glyphs, + size_t glyph_count) override { + if (glyph_count) { + PointF origin = + PointF(SkScalarToFloat(pos[0].x()), SkScalarToFloat(pos[0].y())); + for (size_t i = 1U; i < glyph_count; ++i) { + origin.SetToMin( + PointF(SkScalarToFloat(pos[i].x()), SkScalarToFloat(pos[i].y()))); + } + std::vector run_glyphs(glyphs, glyphs + glyph_count); + SkColor color = + test::RenderTextTestApi::GetRendererPaint(this).getColor(); + text_log_.push_back(TextLog(origin, std::move(run_glyphs), color)); + } + + internal::SkiaTextRenderer::DrawPosText(pos, glyphs, glyph_count); + } + + std::vector text_log_; +}; + +class TestRenderTextCanvas : public SkCanvas { + public: + TestRenderTextCanvas(int width, int height) : SkCanvas(width, height) {} + + // SkCanvas overrides: + void onDrawTextBlob(const SkTextBlob* blob, + SkScalar x, + SkScalar y, + const SkPaint& paint) override { + PointF origin = PointF(SkScalarToFloat(x), SkScalarToFloat(y)); + std::vector glyphs; + if (blob) { + SkTextBlob::Iter::Run run; + for (SkTextBlob::Iter it(*blob); it.next(&run);) { + auto run_glyphs = + base::span(run.fGlyphIndices, run.fGlyphCount); + glyphs.insert(glyphs.end(), run_glyphs.begin(), run_glyphs.end()); + } + } + text_log_.push_back(TextLog(origin, std::move(glyphs), paint.getColor())); + + SkCanvas::onDrawTextBlob(blob, x, y, paint); + } + + void GetTextLogAndReset(std::vector* text_log) { + text_log_.swap(*text_log); + text_log_.clear(); + } + + const std::vector& text_log() const { return text_log_; } + + private: + std::vector text_log_; +}; + +// Given a buffer to test against, this can be used to test various areas of the +// rectangular buffer against a specific color value. +class TestRectangleBuffer { + public: + TestRectangleBuffer(const char* string, + const SkColor* buffer, + uint32_t stride, + uint32_t row_count) + : string_(string), + buffer_(buffer), + stride_(stride), + row_count_(row_count) {} + + TestRectangleBuffer(const TestRectangleBuffer&) = delete; + TestRectangleBuffer& operator=(const TestRectangleBuffer&) = delete; + + // Test if any values in the rectangular area are anything other than |color|. + void EnsureSolidRect(SkColor color, + int left, + int top, + int width, + int height) const { + ASSERT_LT(top, row_count_) << string_; + ASSERT_LE(top + height, row_count_) << string_; + ASSERT_LT(left, stride_) << string_; + ASSERT_LE(left + width, stride_) << string_ << ", left " << left + << ", width " << width << ", stride_ " + << stride_; + for (int y = top; y < top + height; ++y) { + for (int x = left; x < left + width; ++x) { + SkColor buffer_color = buffer_[x + y * stride_]; + EXPECT_EQ(color, buffer_color) << string_ << " at " << x << ", " << y; + } + } + } + + // Test that the rect defined by |left|, |top|, |width| and |height| is filled + // with the same color. + void EnsureRectIsAllSameColor(int left, + int top, + int width, + int height) const { + SkColor buffer_color = buffer_[left + top * stride_]; + EnsureSolidRect(buffer_color, left, top, width, height); + } + + private: + const char* string_; + const SkColor* buffer_; + int stride_; + int row_count_; +}; + +} // namespace + +// Test fixture class used to run parameterized tests for all RenderText +// implementations. +class RenderTextTest : public testing::Test { + public: + RenderTextTest() + : task_environment_( + base::test::SingleThreadTaskEnvironment::MainThreadType::UI), + render_text_(std::make_unique()), + test_api_(new test::RenderTextTestApi(render_text_.get())), + renderer_(canvas()) {} + + RenderTextTest(const RenderTextTest&) = delete; + RenderTextTest& operator=(const RenderTextTest&) = delete; + + protected: + const cc::PaintFlags& GetRendererPaint() { + return test::RenderTextTestApi::GetRendererPaint(renderer()); + } + + const SkFont& GetRendererFont() { + return test::RenderTextTestApi::GetRendererFont(renderer()); + } + + void DrawVisualText(const std::vector selections = {}) { + test_api()->EnsureLayout(); + test_api()->DrawVisualText(renderer(), selections); + renderer()->GetTextLogAndReset(&text_log_); + } + + void Draw(bool select_all = false) { + constexpr int kCanvasWidth = 1200; + constexpr int kCanvasHeight = 400; + + cc::PaintRecorder recorder; + Canvas canvas(recorder.beginRecording(kCanvasWidth, kCanvasHeight), 1.0f); + test_api_->Draw(&canvas, select_all); + sk_sp record = recorder.finishRecordingAsPicture(); + + TestRenderTextCanvas test_canvas(kCanvasWidth, kCanvasHeight); + record->Playback(&test_canvas); + + test_canvas.GetTextLogAndReset(&text_log_); + } + + internal::TextRunList* GetHarfBuzzRunList() { + test_api()->EnsureLayout(); + return test_api()->GetHarfBuzzRunList(); + } + + // For testing purposes, returns which fonts were chosen for which parts of + // the text by returning a vector of Font and Range pairs, where each range + // specifies the character range for which the corresponding font has been + // chosen. + std::vector GetFontSpans() { + test_api()->EnsureLayout(); + + const internal::TextRunList* run_list = GetHarfBuzzRunList(); + std::vector spans; + std::transform( + run_list->runs().begin(), run_list->runs().end(), + std::back_inserter(spans), [this](const auto& run) { + return FontSpan( + run->font_params.font, + Range(test_api()->DisplayIndexToTextIndex(run->range.start()), + test_api()->DisplayIndexToTextIndex(run->range.end()))); + }); + + return spans; + } + + // Converts the current run list into a human-readable string. Can be used in + // test assertions for a readable expectation and failure message. + // + // The string shows the runs in visual order. Each run is enclosed in square + // brackets, and shows the begin and end inclusive logical character position, + // with an arrow indicating the direction of the run. Single-character runs + // just show the character position. + // + // For example, the the logical bidirectional string "abc+\u05d0\u05d1\u05d2" + // (visual string: "abc+אבג") yields "[0->2][3][6<-4]". + std::string GetRunListStructureString() { + test_api()->EnsureLayout(); + + const internal::TextRunList* run_list = GetHarfBuzzRunList(); + std::string result; + for (size_t i = 0; i < run_list->size(); ++i) { + size_t logical_index = run_list->visual_to_logical(i); + const internal::TextRunHarfBuzz& run = *run_list->runs()[logical_index]; + if (run.range.length() == 1) { + result.append(base::StringPrintf("[%d]", run.range.start())); + } else if (run.font_params.is_rtl) { + result.append(base::StringPrintf("[%d<-%d]", run.range.end() - 1, + run.range.start())); + } else { + result.append(base::StringPrintf("[%d->%d]", run.range.start(), + run.range.end() - 1)); + } + } + return result; + } + + // Returns a vector of text fragments corresponding to the current list of + // text runs. + std::vector GetRunListStrings() { + std::vector runs_as_text; + for (const auto& span : GetFontSpans()) { + runs_as_text.push_back(render_text_->text().substr(span.second.GetMin(), + span.second.length())); + } + return runs_as_text; + } + + // Sets the text to |text|, then returns GetRunListStrings(). + std::vector RunsFor(const std::u16string& text) { + render_text_->SetText(text); + test_api()->EnsureLayout(); + return GetRunListStrings(); + } + + void ResetRenderTextInstance() { + render_text_ = std::make_unique(); + test_api_ = std::make_unique(GetRenderText()); + } + + void ResetCursorX() { test_api()->reset_cached_cursor_x(); } + + int GetLineContainingYCoord(float text_y) { + return test_api()->GetLineContainingYCoord(text_y); + } + + RenderTextHarfBuzz* GetRenderText() { return render_text_.get(); } + + Rect GetSubstringBoundsUnion(const Range& range) { + const std::vector bounds = render_text_->GetSubstringBounds(range); + return std::accumulate( + bounds.begin(), bounds.end(), Rect(), + [](const Rect& a, const Rect& b) { return UnionRects(a, b); }); + } + + Rect GetSelectionBoundsUnion() { + return GetSubstringBoundsUnion(render_text_->selection()); + } + + // Checks left-to-right text, ensuring that the caret moves to the right as + // the cursor position increments through the logical text. Also ensures that + // each glyph is to the right of the prior glyph. RenderText automatically + // updates invalid cursor positions (eg. between a surrogate pair) to a valid + // neighbor, so the positions may be unchanged for some iterations. Invoking + // this in a test gives coverage of the sanity checks in functions such as + // TextRunHarfBuzz::GetGraphemeBounds() which rely on sensible glyph positions + // being provided by installed typefaces. + void CheckBoundsForCursorPositions() { + ASSERT_FALSE(render_text_->text().empty()); + + // Use a wide display rect to avoid scrolling. + render_text_->SetDisplayRect(gfx::Rect(0, 0, 1000, 50)); + EXPECT_LT(render_text_->GetContentWidthF(), + render_text_->display_rect().width()); + + // Assume LTR for now. + int max_cursor_x = 0; + int max_glyph_x = 0, max_glyph_right = 0; + + for (size_t i = 0; i <= render_text_->text().size(); ++i) { + render_text_->SetCursorPosition(i); + + SCOPED_TRACE(testing::Message() + << "Cursor position: " << i + << " selection: " << render_text_->selection().ToString()); + + const gfx::Rect cursor_bounds = render_text_->GetUpdatedCursorBounds(); + + // The cursor should always be one pixel wide. + EXPECT_EQ(1, cursor_bounds.width()); + EXPECT_LE(max_cursor_x, cursor_bounds.x()); + max_cursor_x = cursor_bounds.x(); + + const gfx::Rect glyph_bounds = + render_text_->GetCursorBounds(render_text_->selection_model(), false); + EXPECT_LE(max_glyph_x, glyph_bounds.x()); + EXPECT_LE(max_glyph_right, glyph_bounds.right()); + max_glyph_x = glyph_bounds.x(); + max_glyph_right = glyph_bounds.right(); + } + } + + void SetGlyphWidth(float test_width) { + test_api()->SetGlyphWidth(test_width); + } + + void SetGlyphHeight(float test_height) { + test_api()->SetGlyphHeight(test_height); + } + + bool ShapeRunWithFont(const std::u16string& text, + const Font& font, + const FontRenderParams& render_params, + internal::TextRunHarfBuzz* run) { + internal::TextRunHarfBuzz::FontParams font_params = run->font_params; + font_params.ComputeRenderParamsFontSizeAndBaselineOffset(); + font_params.SetRenderParamsRematchFont(font, render_params); + run->shape.missing_glyph_count = static_cast(-1); + std::vector runs = {run}; + GetRenderText()->ShapeRunsWithFont(text, font_params, &runs); + return runs.empty(); + } + + int GetCursorYForTesting(int line_num = 0) { + return GetRenderText()->GetLineOffset(line_num).y() + 1; + } + + size_t GetLineContainingCaret() { + return GetRenderText()->GetLineContainingCaret( + GetRenderText()->selection_model()); + } + + Canvas* canvas() { return &canvas_; } + TestSkiaTextRenderer* renderer() { return &renderer_; } + test::RenderTextTestApi* test_api() { return test_api_.get(); } + const test::RenderTextTestApi* test_api() const { return test_api_.get(); } + const std::vector& text_log() const { return text_log_; } + + void ExpectTextLog(std::vector runs) { + EXPECT_EQ(runs.size(), text_log_.size()); + const size_t min_size = std::min(runs.size(), text_log_.size()); + for (size_t i = 0; i < min_size; ++i) { + SCOPED_TRACE(testing::Message() + << "ExpectTextLog, run " << i << " of " << runs.size()); + EXPECT_EQ(runs[i].color, text_log_[i].color()); + EXPECT_EQ(runs[i].glyph_count, text_log_[i].glyphs().size()); + } + } + + private: + // Needed to bypass DCHECK in GetFallbackFont. + base::test::SingleThreadTaskEnvironment task_environment_; + + std::unique_ptr render_text_; + std::unique_ptr test_api_; + std::vector text_log_; + Canvas canvas_; + TestSkiaTextRenderer renderer_; +}; + +TEST_F(RenderTextTest, DefaultStyles) { + // Check the default styles applied to new instances and adjusted text. + RenderText* render_text = GetRenderText(); + EXPECT_TRUE(render_text->text().empty()); + const char16_t* const cases[] = {kWeak, kLtr, u"Hello", kRtl, u"", u""}; + for (size_t i = 0; i < base::size(cases); ++i) { + EXPECT_TRUE(test_api()->colors().EqualsValueForTesting(kPlaceholderColor)); + EXPECT_TRUE(test_api()->baselines().EqualsValueForTesting(NORMAL_BASELINE)); + EXPECT_TRUE(test_api()->font_size_overrides().EqualsValueForTesting(0)); + for (size_t style = 0; style < static_cast(TEXT_STYLE_COUNT); ++style) + EXPECT_TRUE(test_api()->styles()[style].EqualsValueForTesting(false)); + render_text->SetText(cases[i]); + } +} + +TEST_F(RenderTextTest, SetStyles) { + // Ensure custom default styles persist across setting and clearing text. + RenderText* render_text = GetRenderText(); + const SkColor color = SK_ColorGREEN; + render_text->SetColor(color); + render_text->SetBaselineStyle(SUPERSCRIPT); + render_text->SetWeight(Font::Weight::BOLD); + render_text->SetStyle(TEXT_STYLE_UNDERLINE, false); + const char16_t* const cases[] = {kWeak, kLtr, u"Hello", kRtl, u"", u""}; + for (size_t i = 0; i < base::size(cases); ++i) { + EXPECT_TRUE(test_api()->colors().EqualsValueForTesting(color)); + EXPECT_TRUE(test_api()->baselines().EqualsValueForTesting(SUPERSCRIPT)); + EXPECT_TRUE( + test_api()->weights().EqualsValueForTesting(Font::Weight::BOLD)); + EXPECT_TRUE( + test_api()->styles()[TEXT_STYLE_UNDERLINE].EqualsValueForTesting( + false)); + render_text->SetText(cases[i]); + + // Ensure custom default styles can be applied after text has been set. + if (i == 1) + render_text->SetStyle(TEXT_STYLE_STRIKE, true); + if (i >= 1) + EXPECT_TRUE( + test_api()->styles()[TEXT_STYLE_STRIKE].EqualsValueForTesting(true)); + } +} + +TEST_F(RenderTextTest, ApplyStyles) { + RenderText* render_text = GetRenderText(); + render_text->SetText(u"012345678"); + + constexpr int kTestFontSizeOverride = 20; + + // Apply a ranged color and style and check the resulting breaks. + render_text->ApplyColor(SK_ColorGREEN, Range(1, 4)); + render_text->ApplyBaselineStyle(SUPERIOR, Range(2, 4)); + render_text->ApplyWeight(Font::Weight::BOLD, Range(2, 5)); + render_text->ApplyFontSizeOverride(kTestFontSizeOverride, Range(5, 7)); + + EXPECT_TRUE(test_api()->colors().EqualsForTesting( + {{0, kPlaceholderColor}, {1, SK_ColorGREEN}, {4, kPlaceholderColor}})); + + EXPECT_TRUE(test_api()->baselines().EqualsForTesting( + {{0, NORMAL_BASELINE}, {2, SUPERIOR}, {4, NORMAL_BASELINE}})); + + EXPECT_TRUE(test_api()->font_size_overrides().EqualsForTesting( + {{0, 0}, {5, kTestFontSizeOverride}, {7, 0}})); + + EXPECT_TRUE( + test_api()->weights().EqualsForTesting({{0, Font::Weight::NORMAL}, + {2, Font::Weight::BOLD}, + {5, Font::Weight::NORMAL}})); + + // Ensure that setting a value overrides the ranged values. + render_text->SetColor(SK_ColorBLUE); + EXPECT_TRUE(test_api()->colors().EqualsValueForTesting(SK_ColorBLUE)); + render_text->SetBaselineStyle(SUBSCRIPT); + EXPECT_TRUE(test_api()->baselines().EqualsValueForTesting(SUBSCRIPT)); + render_text->SetWeight(Font::Weight::NORMAL); + EXPECT_TRUE( + test_api()->weights().EqualsValueForTesting(Font::Weight::NORMAL)); + + // Apply a value over the text end and check the resulting breaks (INT_MAX + // should be used instead of the text length for the range end) + const size_t text_length = render_text->text().length(); + render_text->ApplyColor(SK_ColorGREEN, Range(0, text_length)); + render_text->ApplyBaselineStyle(SUPERIOR, Range(0, text_length)); + render_text->ApplyWeight(Font::Weight::BOLD, Range(2, text_length)); + + EXPECT_TRUE(test_api()->colors().EqualsForTesting({{0, SK_ColorGREEN}})); + EXPECT_TRUE(test_api()->baselines().EqualsForTesting({{0, SUPERIOR}})); + EXPECT_TRUE(test_api()->weights().EqualsForTesting( + {{0, Font::Weight::NORMAL}, {2, Font::Weight::BOLD}})); + + // Ensure ranged values adjust to accommodate text length changes. + render_text->ApplyStyle(TEXT_STYLE_ITALIC, true, Range(0, 2)); + render_text->ApplyStyle(TEXT_STYLE_ITALIC, true, Range(3, 6)); + render_text->ApplyStyle(TEXT_STYLE_ITALIC, true, Range(7, text_length)); + std::vector> expected_italic = { + {0, true}, {2, false}, {3, true}, {6, false}, {7, true}}; + EXPECT_TRUE(test_api()->styles()[TEXT_STYLE_ITALIC].EqualsForTesting( + expected_italic)); + + // Changing the text should clear any breaks except for the first one. + render_text->SetText(u"0123456"); + expected_italic.resize(1); + EXPECT_TRUE(test_api()->styles()[TEXT_STYLE_ITALIC].EqualsForTesting( + expected_italic)); + render_text->ApplyStyle(TEXT_STYLE_ITALIC, false, Range(2, 4)); + render_text->SetText(u"012345678"); + EXPECT_TRUE(test_api()->styles()[TEXT_STYLE_ITALIC].EqualsForTesting( + expected_italic)); + render_text->ApplyStyle(TEXT_STYLE_ITALIC, false, Range(0, 1)); + render_text->SetText(u"0123456"); + expected_italic.begin()->second = false; + EXPECT_TRUE(test_api()->styles()[TEXT_STYLE_ITALIC].EqualsForTesting( + expected_italic)); + render_text->ApplyStyle(TEXT_STYLE_ITALIC, true, Range(2, 4)); + render_text->SetText(u"012345678"); + EXPECT_TRUE(test_api()->styles()[TEXT_STYLE_ITALIC].EqualsForTesting( + expected_italic)); + + // Styles mid-grapheme should work. Style of first character of the grapheme + // is used. + render_text->SetText(u"0\u0915\u093f1\u0915\u093f2"); + render_text->ApplyStyle(TEXT_STYLE_UNDERLINE, true, Range(2, 5)); + EXPECT_TRUE(test_api()->styles()[TEXT_STYLE_UNDERLINE].EqualsForTesting( + {{0, false}, {2, true}, {5, false}})); +} + +TEST_F(RenderTextTest, ApplyStyleSurrogatePair) { + RenderText* render_text = GetRenderText(); + render_text->SetText(u"x\U0001F601x"); + // Apply the style in the middle of a surrogate pair. The style should be + // applied to the whole range of the codepoint. + gfx::Range range(2, 3); + render_text->ApplyWeight(gfx::Font::Weight::BOLD, range); + render_text->ApplyStyle(TEXT_STYLE_ITALIC, true, range); + render_text->ApplyColor(SK_ColorGREEN, range); + render_text->Draw(canvas()); + + EXPECT_TRUE(test_api()->styles()[TEXT_STYLE_ITALIC].EqualsForTesting( + {{0, false}, {2, true}, {3, false}})); + EXPECT_TRUE(test_api()->colors().EqualsForTesting( + {{0, kPlaceholderColor}, {2, SK_ColorGREEN}, {3, kPlaceholderColor}})); + EXPECT_TRUE( + test_api()->weights().EqualsForTesting({{0, Font::Weight::NORMAL}, + {2, Font::Weight::BOLD}, + {3, Font::Weight::NORMAL}})); +} + +TEST_F(RenderTextTest, ApplyStyleGrapheme) { + RenderText* render_text = GetRenderText(); + render_text->SetText(u"e\u0301"); + render_text->ApplyStyle(TEXT_STYLE_ITALIC, true, gfx::Range(1, 2)); + render_text->ApplyStyle(TEXT_STYLE_UNDERLINE, true, gfx::Range(0, 1)); + Draw(); + + // Ensures that the whole grapheme is drawn with the same style. + ExpectTextLog({{1}}); +} + +TEST_F(RenderTextTest, ApplyStyleMultipleGraphemes) { + RenderText* render_text = GetRenderText(); + render_text->SetText(u"xxe\u0301x"); + // Apply the style in the middle of a grapheme. + gfx::Range range(1, 3); + render_text->ApplyStyle(TEXT_STYLE_ITALIC, true, range); + Draw(); + + EXPECT_TRUE(test_api()->styles()[TEXT_STYLE_ITALIC].EqualsForTesting( + {{0, false}, {1, true}, {3, false}})); + + // Ensures that the style of the grapheme is the style at its first character. + ExpectTextLog({{1}, {2}, {1}}); +} + +TEST_F(RenderTextTest, ApplyColorSurrogatePair) { + RenderText* render_text = GetRenderText(); + render_text->SetText(u"x\U0001F601x"); + render_text->ApplyColor(SK_ColorGREEN, Range(2, 3)); + Draw(); + + // Ensures that the color is not applied since it is in the middle of a + // surrogate pair. + // There is three runs since the codepoints are not in the same script. + ExpectTextLog({{1}, {1}, {1}}); + + // Obscure the text should renders characters with the same colors. + render_text->SetObscured(true); + Draw(); + ExpectTextLog({{3}}); +} + +TEST_F(RenderTextTest, ApplyColorLongEmoji) { + // A long emoji sequence. + static const char16_t kLongEmoji[] = u"\U0001F468\u200D\u2708\uFE0F"; + + RenderText* render_text = GetRenderText(); + render_text->SetText(kLongEmoji); + render_text->AppendText(kLongEmoji); + render_text->AppendText(kLongEmoji); + + render_text->ApplyColor(SK_ColorGREEN, Range(0, 2)); + render_text->ApplyColor(SK_ColorBLUE, Range(8, 13)); + Draw(); + + // Ensures that the color of the emoji is the color at its first character. + ASSERT_EQ(3u, text_log().size()); + EXPECT_EQ(SK_ColorGREEN, text_log()[0].color()); + EXPECT_EQ(kPlaceholderColor, text_log()[1].color()); + EXPECT_EQ(SK_ColorBLUE, text_log()[2].color()); + + // Reset the color. + render_text->SetColor(SK_ColorBLACK); + Draw(); + + // The amount of glyphs depend on the font. If the font supports the emoji, + // the amount of glyph is 1, otherwise it vary. + ASSERT_EQ(1u, text_log().size()); + EXPECT_EQ(SK_ColorBLACK, text_log()[0].color()); +} + +TEST_F(RenderTextTest, ApplyColorObscuredEmoji) { + RenderText* render_text = GetRenderText(); + render_text->SetText(u"\U0001F628\U0001F628\U0001F628"); + render_text->ApplyColor(SK_ColorGREEN, Range(0, 2)); + render_text->ApplyColor(SK_ColorBLUE, Range(4, 5)); + + const std::vector kExpectedTextLog = { + {1, SK_ColorGREEN}, {1, kPlaceholderColor}, {1, SK_ColorBLUE}}; + + // Ensures that colors are applied. + Draw(); + ExpectTextLog(kExpectedTextLog); + + // Obscure the text. + render_text->SetObscured(true); + + // The obscured text (layout text) no longer contains surrogate pairs. + EXPECT_EQ(render_text->text().size(), 2 * test_api()->GetLayoutText().size()); + + // Obscured text should give the same colors. + Draw(); + ExpectTextLog(kExpectedTextLog); + + for (size_t i = 0; i < render_text->text().size(); ++i) { + render_text->RenderText::SetObscuredRevealIndex(i); + + // Revealed codepoints should give the same colors. + Draw(); + ExpectTextLog(kExpectedTextLog); + } +} + +TEST_F(RenderTextTest, ApplyColorArabicDiacritics) { + // Render an Arabic character with two diacritics. The color should be taken + // from the base character. + RenderText* render_text = GetRenderText(); + render_text->SetText(u"\u0628\u0651\u0650"); + render_text->ApplyColor(SK_ColorGREEN, Range(0, 1)); + render_text->ApplyColor(SK_ColorBLACK, Range(1, 2)); + render_text->ApplyColor(SK_ColorBLUE, Range(2, 3)); + Draw(); + ASSERT_EQ(1u, text_log().size()); + EXPECT_EQ(SK_ColorGREEN, text_log()[0].color()); +} + +TEST_F(RenderTextTest, ApplyColorArabicLigature) { + // In Arabic, letters of each word join together whenever possible. During + // the shaping pass of the font, characters will take their joining form: + // Isolated, Initial, Medial or Final. + + // Render the isolated form of the first glyph. + RenderText* render_text = GetRenderText(); + render_text->SetText(u"ب"); + Draw(); + ASSERT_EQ(1u, text_log().size()); + ASSERT_EQ(1u, text_log()[0].glyphs().size()); + uint16_t isolated_first_glyph = text_log()[0].glyphs()[0]; + + // Render a pair of glyphs (initial form and final form). + render_text->SetText(u"بم"); + Draw(); + ASSERT_EQ(1u, text_log().size()); + ASSERT_LE(2u, text_log()[0].glyphs().size()); + uint16_t initial_first_glyph = text_log()[0].glyphs()[0]; + uint16_t final_second_glyph = text_log()[0].glyphs()[1]; + + // A ligature is applied between glyphs and the two glyphs (isolated and + // initial form) are displayed differently. + EXPECT_NE(isolated_first_glyph, initial_first_glyph); + + // Ensures that both characters didn't merge in a single glyph. + EXPECT_NE(initial_first_glyph, final_second_glyph); + + // Applying color should not break the ligature. + // see: https://w3c.github.io/alreq/#h_styling_individual_letters + render_text->ApplyColor(SK_ColorGREEN, Range(0, 1)); + render_text->ApplyColor(SK_ColorBLACK, Range(1, 2)); + Draw(); + ASSERT_EQ(2u, text_log().size()); + ASSERT_LE(1u, text_log()[0].glyphs().size()); + ASSERT_EQ(1u, text_log()[1].glyphs().size()); + uint16_t colored_first_glyph = text_log()[1].glyphs()[0]; + uint16_t colored_second_glyph = text_log()[0].glyphs()[0]; + + // Glyphs should be the same with and without color. + EXPECT_EQ(initial_first_glyph, colored_first_glyph); + EXPECT_EQ(final_second_glyph, colored_second_glyph); + + // Colors should be applied. + EXPECT_EQ(SK_ColorGREEN, text_log()[0].color()); + EXPECT_EQ(SK_ColorBLACK, text_log()[1].color()); +} + +TEST_F(RenderTextTest, AppendTextKeepsStyles) { + RenderText* render_text = GetRenderText(); + // Setup basic functionality. + render_text->SetText(u"abcd"); + render_text->ApplyColor(SK_ColorGREEN, Range(0, 1)); + render_text->ApplyBaselineStyle(SUPERSCRIPT, Range(1, 2)); + render_text->ApplyStyle(TEXT_STYLE_UNDERLINE, true, Range(2, 3)); + render_text->ApplyFontSizeOverride(20, Range(3, 4)); + // Verify basic functionality. + const std::vector> expected_color = { + {0, SK_ColorGREEN}, {1, kPlaceholderColor}}; + EXPECT_TRUE(test_api()->colors().EqualsForTesting(expected_color)); + const std::vector> expected_baseline = { + {0, NORMAL_BASELINE}, {1, SUPERSCRIPT}, {2, NORMAL_BASELINE}}; + EXPECT_TRUE(test_api()->baselines().EqualsForTesting(expected_baseline)); + const std::vector> expected_style = { + {0, false}, {2, true}, {3, false}}; + EXPECT_TRUE(test_api()->styles()[TEXT_STYLE_UNDERLINE].EqualsForTesting( + expected_style)); + const std::vector> expected_font_size = {{0, 0}, + {3, 20}}; + EXPECT_TRUE( + test_api()->font_size_overrides().EqualsForTesting(expected_font_size)); + + // Ensure AppendText maintains current text styles. + render_text->AppendText(u"efg"); + EXPECT_EQ(render_text->GetDisplayText(), u"abcdefg"); + EXPECT_TRUE(test_api()->colors().EqualsForTesting(expected_color)); + EXPECT_TRUE(test_api()->baselines().EqualsForTesting(expected_baseline)); + EXPECT_TRUE(test_api()->styles()[TEXT_STYLE_UNDERLINE].EqualsForTesting( + expected_style)); + EXPECT_TRUE( + test_api()->font_size_overrides().EqualsForTesting(expected_font_size)); +} + +TEST_F(RenderTextTest, SetSelection) { + RenderText* render_text = GetRenderText(); + render_text->set_selection_color(SK_ColorGREEN); + render_text->SetText(u"abcdef"); + render_text->set_focused(true); + + // Single selection + render_text->SetSelection( + {{{4, 100}}, LogicalCursorDirection::CURSOR_FORWARD}); + Draw(); + ExpectTextLog({{4}, {2, SK_ColorGREEN}}); + + // Multiple selections + render_text->SetSelection( + {{{0, 1}, {4, 100}}, LogicalCursorDirection::CURSOR_FORWARD}); + Draw(); + ExpectTextLog({{1, SK_ColorGREEN}, {3}, {2, SK_ColorGREEN}}); + + render_text->ClearSelection(); + Draw(); + ExpectTextLog({{6}}); +} + +TEST_F(RenderTextTest, SelectRangeColored) { + RenderText* render_text = GetRenderText(); + render_text->SetText(u"abcdef"); + render_text->SetColor(SK_ColorBLACK); + render_text->set_selection_color(SK_ColorGREEN); + render_text->set_focused(true); + + render_text->SelectRange(Range(0, 1)); + Draw(); + ExpectTextLog({{1, SK_ColorGREEN}, {5, SK_ColorBLACK}}); + + render_text->SelectRange(Range(1, 3)); + Draw(); + ExpectTextLog({{1, SK_ColorBLACK}, {2, SK_ColorGREEN}, {3, SK_ColorBLACK}}); + + render_text->ClearSelection(); + Draw(); + ExpectTextLog({{6, SK_ColorBLACK}}); +} + +// Tests that when a selection is made and the selection background is +// translucent, the selection renders properly. See crbug.com/1134440. +TEST_F(RenderTextTest, SelectWithTranslucentBackground) { + constexpr float kGlyphWidth = 5.5f; + constexpr Size kCanvasSize(300, 50); + constexpr SkColor kTranslucentBlue = SkColorSetARGB(0x7F, 0x00, 0x00, 0xFF); + const char* const kTestString{"A B C D"}; + const char16_t* const kTestString16{u"A B C D"}; + + SkBitmap bitmap; + bitmap.allocPixels( + SkImageInfo::MakeN32Premul(kCanvasSize.width(), kCanvasSize.height())); + cc::SkiaPaintCanvas paint_canvas(bitmap); + Canvas canvas(&paint_canvas, 1.0f); + paint_canvas.clear(SK_ColorWHITE); + + SetGlyphWidth(kGlyphWidth); + RenderText* render_text = GetRenderText(); + render_text->set_selection_background_focused_color(kTranslucentBlue); + render_text->set_focused(true); + + render_text->SetText(kTestString16); + render_text->SelectRange(Range(0, 7)); + const Rect text_rect = Rect(render_text->GetStringSize()); + render_text->SetDisplayRect(text_rect); + render_text->Draw(&canvas); + const uint32_t* buffer = static_cast(bitmap.getPixels()); + ASSERT_NE(nullptr, buffer); + TestRectangleBuffer rect_buffer(kTestString, buffer, kCanvasSize.width(), + kCanvasSize.height()); + + // This whole slice should be the same color and opacity. + rect_buffer.EnsureRectIsAllSameColor(0, 0, text_rect.width() - 1, 1); +} + +TEST_F(RenderTextTest, SelectRangeColoredGrapheme) { + RenderText* render_text = GetRenderText(); + render_text->SetText(u"xe\u0301y"); + render_text->SetColor(SK_ColorBLACK); + render_text->set_selection_color(SK_ColorGREEN); + render_text->set_focused(true); + + render_text->SelectRange(Range(0, 1)); + Draw(); + ExpectTextLog({{1, SK_ColorGREEN}, {2, SK_ColorBLACK}}); + + render_text->SelectRange(Range(1, 2)); + Draw(); + ExpectTextLog({{1, SK_ColorBLACK}, {1, SK_ColorGREEN}, {1, SK_ColorBLACK}}); + + render_text->SelectRange(Range(2, 3)); + Draw(); + ExpectTextLog({{1, SK_ColorBLACK}, {1, SK_ColorGREEN}, {1, SK_ColorBLACK}}); + + render_text->SelectRange(Range(2, 4)); + Draw(); + ExpectTextLog({{1, SK_ColorBLACK}, {2, SK_ColorGREEN}}); +} + +TEST_F(RenderTextTest, SelectRangeMultiple) { + RenderText* render_text = GetRenderText(); + render_text->set_selection_color(SK_ColorGREEN); + render_text->SetText(u"abcdef"); + render_text->set_focused(true); + + // Multiple selections + render_text->SelectRange(Range(0, 1)); + render_text->SelectRange(Range(4, 2), false); + Draw(); + ExpectTextLog({{1, SK_ColorGREEN}, {1}, {2, SK_ColorGREEN}, {2}}); + + // Setting a primary selection should override secondary selections + render_text->SelectRange(Range(5, 6)); + Draw(); + ExpectTextLog({{5}, {1, SK_ColorGREEN}}); + + render_text->ClearSelection(); + Draw(); + ExpectTextLog({{6}}); +} + +TEST_F(RenderTextTest, SetCompositionRangeColored) { + RenderText* render_text = GetRenderText(); + render_text->SetText(u"abcdef"); + + render_text->SetCompositionRange(Range(0, 1)); + Draw(); + ExpectTextLog({{1}, {5}}); + + render_text->SetCompositionRange(Range(1, 3)); + Draw(); + ExpectTextLog({{1}, {2}, {3}}); + + render_text->SetCompositionRange(Range::InvalidRange()); + Draw(); + ExpectTextLog({{6}}); +} + +TEST_F(RenderTextTest, SetCompositionRangeColoredGrapheme) { + RenderText* render_text = GetRenderText(); + render_text->SetText(u"xe\u0301y"); + + render_text->SetCompositionRange(Range(0, 1)); + Draw(); + ExpectTextLog({{1}, {2}}); + + render_text->SetCompositionRange(Range(2, 3)); + Draw(); + ExpectTextLog({{3}}); + + render_text->SetCompositionRange(Range(2, 4)); + Draw(); + ExpectTextLog({{2}, {1}}); +} + +void TestVisualCursorMotionInObscuredField( + RenderText* render_text, + const std::u16string& text, + SelectionBehavior selection_behavior) { + const bool select = selection_behavior != SELECTION_NONE; + ASSERT_TRUE(render_text->obscured()); + render_text->SetText(text); + int len = text.length(); + render_text->MoveCursor(LINE_BREAK, CURSOR_RIGHT, selection_behavior); + EXPECT_EQ(SelectionModel(Range(select ? 0 : len, len), CURSOR_FORWARD), + render_text->selection_model()); + render_text->MoveCursor(LINE_BREAK, CURSOR_LEFT, selection_behavior); + EXPECT_EQ(SelectionModel(0, CURSOR_BACKWARD), render_text->selection_model()); + for (int j = 1; j <= len; ++j) { + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, selection_behavior); + EXPECT_EQ(SelectionModel(Range(select ? 0 : j, j), CURSOR_BACKWARD), + render_text->selection_model()); + } + for (int j = len - 1; j >= 0; --j) { + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_LEFT, selection_behavior); + EXPECT_EQ(SelectionModel(Range(select ? 0 : j, j), CURSOR_FORWARD), + render_text->selection_model()); + } + render_text->MoveCursor(WORD_BREAK, CURSOR_RIGHT, selection_behavior); + EXPECT_EQ(SelectionModel(Range(select ? 0 : len, len), CURSOR_FORWARD), + render_text->selection_model()); + render_text->MoveCursor(WORD_BREAK, CURSOR_LEFT, selection_behavior); + EXPECT_EQ(SelectionModel(0, CURSOR_BACKWARD), render_text->selection_model()); +} + +TEST_F(RenderTextTest, ObscuredText) { + const std::u16string seuss = u"hop on pop"; + const std::u16string no_seuss = GetObscuredString(seuss.length()); + RenderText* render_text = GetRenderText(); + + // GetDisplayText() returns a string filled with + // RenderText::kPasswordReplacementChar when the obscured bit is set. + render_text->SetText(seuss); + render_text->SetObscured(true); + EXPECT_EQ(seuss, render_text->text()); + EXPECT_EQ(no_seuss, render_text->GetDisplayText()); + render_text->SetObscured(false); + EXPECT_EQ(seuss, render_text->text()); + EXPECT_EQ(seuss, render_text->GetDisplayText()); + + render_text->SetObscured(true); + + // Surrogate pairs are counted as one code point. + const char16_t invalid_surrogates[] = {0xDC00, 0xD800, 0}; + render_text->SetText(invalid_surrogates); + EXPECT_EQ(GetObscuredString(2), render_text->GetDisplayText()); + const char16_t valid_surrogates[] = {0xD800, 0xDC00, 0}; + render_text->SetText(valid_surrogates); + EXPECT_EQ(GetObscuredString(1), render_text->GetDisplayText()); + EXPECT_EQ(0U, render_text->cursor_position()); + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(2U, render_text->cursor_position()); + + // Test index conversion and cursor validity with a valid surrogate pair. + // Text contains "u+D800 u+DC00" and display text contains "u+2022". + EXPECT_EQ(0U, test_api()->TextIndexToDisplayIndex(0U)); + EXPECT_EQ(0U, test_api()->TextIndexToDisplayIndex(1U)); + EXPECT_EQ(1U, test_api()->TextIndexToDisplayIndex(2U)); + EXPECT_EQ(0U, test_api()->DisplayIndexToTextIndex(0U)); + EXPECT_EQ(2U, test_api()->DisplayIndexToTextIndex(1U)); + EXPECT_TRUE(render_text->IsValidCursorIndex(0U)); + EXPECT_FALSE(render_text->IsValidCursorIndex(1U)); + EXPECT_TRUE(render_text->IsValidCursorIndex(2U)); + + // FindCursorPosition() should not return positions between a surrogate pair. + render_text->SetDisplayRect(Rect(0, 0, 20, 20)); + const int cursor_y = GetCursorYForTesting(); + EXPECT_EQ(render_text->FindCursorPosition(Point(0, cursor_y)).caret_pos(), + 0U); + EXPECT_EQ(render_text->FindCursorPosition(Point(20, cursor_y)).caret_pos(), + 2U); + for (int x = -1; x <= 20; ++x) { + SelectionModel selection = + render_text->FindCursorPosition(Point(x, cursor_y)); + EXPECT_TRUE(selection.caret_pos() == 0U || selection.caret_pos() == 2U); + } + + // GetCursorSpan() should yield the entire string bounds for text index 0. + EXPECT_EQ(render_text->GetStringSize().width(), + std::ceil(render_text->GetCursorSpan({0, 2}).length())); + + // Cursoring is independent of underlying characters when text is obscured. + const char16_t* const texts[] = { + kWeak, kLtr, kLtrRtl, kLtrRtlLtr, kRtl, kRtlLtr, kRtlLtrRtl, + u"hop on pop", // Check LTR word boundaries. + u"אב אג בג", // Check RTL word boundaries. + }; + for (size_t i = 0; i < base::size(texts); ++i) { + TestVisualCursorMotionInObscuredField(render_text, texts[i], + SELECTION_NONE); + TestVisualCursorMotionInObscuredField(render_text, texts[i], + SELECTION_RETAIN); + } +} + +TEST_F(RenderTextTest, ObscuredTextMultiline) { + const std::u16string test = u"a\nbc\ndef"; + RenderText* render_text = GetRenderText(); + render_text->SetText(test); + render_text->SetObscured(true); + render_text->SetMultiline(true); + + // Newlines should be kept in multiline mode. + std::u16string display_text = render_text->GetDisplayText(); + EXPECT_EQ(display_text[1], '\n'); + EXPECT_EQ(display_text[4], '\n'); +} + +TEST_F(RenderTextTest, ObscuredTextMultilineNewline) { + const std::u16string test = u"\r\r\n"; + RenderText* render_text = GetRenderText(); + render_text->SetText(test); + render_text->SetObscured(true); + render_text->SetMultiline(true); + + // Newlines should be kept in multiline mode. + std::u16string display_text = render_text->GetDisplayText(); + EXPECT_EQ(display_text[0], '\r'); + EXPECT_EQ(display_text[1], '\r'); + EXPECT_EQ(display_text[2], '\n'); +} + +TEST_F(RenderTextTest, RevealObscuredText) { + const std::u16string seuss = u"hop on pop"; + const std::u16string no_seuss = GetObscuredString(seuss.length()); + RenderText* render_text = GetRenderText(); + + render_text->SetText(seuss); + render_text->SetObscured(true); + EXPECT_EQ(seuss, render_text->text()); + EXPECT_EQ(no_seuss, render_text->GetDisplayText()); + + // Valid reveal index and new revealed index clears previous one. + render_text->RenderText::SetObscuredRevealIndex(0); + EXPECT_EQ(GetObscuredString(seuss.length(), 0, 'h'), + render_text->GetDisplayText()); + render_text->RenderText::SetObscuredRevealIndex(1); + EXPECT_EQ(GetObscuredString(seuss.length(), 1, 'o'), + render_text->GetDisplayText()); + render_text->RenderText::SetObscuredRevealIndex(2); + EXPECT_EQ(GetObscuredString(seuss.length(), 2, 'p'), + render_text->GetDisplayText()); + + // Invalid reveal index. + render_text->RenderText::SetObscuredRevealIndex(-1); + EXPECT_EQ(no_seuss, render_text->GetDisplayText()); + render_text->RenderText::SetObscuredRevealIndex(seuss.length() + 1); + EXPECT_EQ(no_seuss, render_text->GetDisplayText()); + + // SetObscured clears the revealed index. + render_text->RenderText::SetObscuredRevealIndex(0); + EXPECT_EQ(GetObscuredString(seuss.length(), 0, 'h'), + render_text->GetDisplayText()); + render_text->SetObscured(false); + EXPECT_EQ(seuss, render_text->GetDisplayText()); + render_text->SetObscured(true); + EXPECT_EQ(no_seuss, render_text->GetDisplayText()); + + // SetText clears the revealed index. + render_text->SetText(u"new"); + EXPECT_EQ(GetObscuredString(3), render_text->GetDisplayText()); + render_text->RenderText::SetObscuredRevealIndex(2); + EXPECT_EQ(GetObscuredString(3, 2, 'w'), render_text->GetDisplayText()); + render_text->SetText(u"new longer"); + EXPECT_EQ(GetObscuredString(10), render_text->GetDisplayText()); + + // Text with invalid surrogates (surrogates low 0xDC00 and high 0xD800). + // Invalid surrogates are replaced by replacement character (e.g. 0xFFFD). + render_text->SetText(u"\xDC00\xD800hop"); + EXPECT_EQ(GetObscuredString(5), render_text->GetDisplayText()); + render_text->RenderText::SetObscuredRevealIndex(0); + EXPECT_EQ(GetObscuredString(5, 0, 0xFFFD), render_text->GetDisplayText()); + render_text->RenderText::SetObscuredRevealIndex(1); + EXPECT_EQ(GetObscuredString(5, 1, 0xFFFD), render_text->GetDisplayText()); + render_text->RenderText::SetObscuredRevealIndex(2); + EXPECT_EQ(GetObscuredString(5, 2, 'h'), render_text->GetDisplayText()); + + // Text with valid surrogates before and after the reveal index. + render_text->SetText(u"\xD800\xDC00hop\xD800\xDC00"); + EXPECT_EQ(GetObscuredString(5), render_text->GetDisplayText()); + render_text->RenderText::SetObscuredRevealIndex(0); + const char16_t valid_expect_0_and_1[] = {0xD800, + 0xDC00, + RenderText::kPasswordReplacementChar, + RenderText::kPasswordReplacementChar, + RenderText::kPasswordReplacementChar, + RenderText::kPasswordReplacementChar, + 0}; + EXPECT_EQ(valid_expect_0_and_1, render_text->GetDisplayText()); + render_text->RenderText::SetObscuredRevealIndex(1); + EXPECT_EQ(valid_expect_0_and_1, render_text->GetDisplayText()); + render_text->RenderText::SetObscuredRevealIndex(2); + EXPECT_EQ(GetObscuredString(5, 1, 'h'), render_text->GetDisplayText()); + render_text->RenderText::SetObscuredRevealIndex(5); + const char16_t valid_expect_5_and_6[] = {RenderText::kPasswordReplacementChar, + RenderText::kPasswordReplacementChar, + RenderText::kPasswordReplacementChar, + RenderText::kPasswordReplacementChar, + 0xD800, + 0xDC00, + 0}; + EXPECT_EQ(valid_expect_5_and_6, render_text->GetDisplayText()); + render_text->RenderText::SetObscuredRevealIndex(6); + EXPECT_EQ(valid_expect_5_and_6, render_text->GetDisplayText()); +} + +TEST_F(RenderTextTest, ObscuredEmoji) { + // Ensures text itemization doesn't crash on obscured multi-char glyphs. + RenderText* render_text = GetRenderText(); + render_text->SetObscured(true); + // Test U+1F601 😁 "Grinning face with smiling eyes", followed by 'y'. + render_text->SetText(u"😁y"); + render_text->Draw(canvas()); + + // Emoji codepoints are replaced by bullets. + EXPECT_EQ(u"••", render_text->GetDisplayText()); + EXPECT_EQ(0U, test_api()->TextIndexToDisplayIndex(0U)); + EXPECT_EQ(0U, test_api()->TextIndexToDisplayIndex(1U)); + EXPECT_EQ(1U, test_api()->TextIndexToDisplayIndex(2U)); + + EXPECT_EQ(0U, test_api()->DisplayIndexToTextIndex(0U)); + EXPECT_EQ(2U, test_api()->DisplayIndexToTextIndex(1U)); + + // Out of bound accesses. + EXPECT_EQ(2U, test_api()->TextIndexToDisplayIndex(3U)); + EXPECT_EQ(3U, test_api()->DisplayIndexToTextIndex(2U)); + + // Test two U+1F4F7 📷 "Camera" characters in a row. + render_text->SetText(u"📷📷"); + render_text->Draw(canvas()); + + // Emoji codepoints are replaced by bullets. + EXPECT_EQ(u"••", render_text->GetDisplayText()); + EXPECT_EQ(0U, test_api()->TextIndexToDisplayIndex(0U)); + EXPECT_EQ(0U, test_api()->TextIndexToDisplayIndex(1U)); + EXPECT_EQ(1U, test_api()->TextIndexToDisplayIndex(2U)); + EXPECT_EQ(1U, test_api()->TextIndexToDisplayIndex(3U)); + + EXPECT_EQ(0U, test_api()->DisplayIndexToTextIndex(0U)); + EXPECT_EQ(2U, test_api()->DisplayIndexToTextIndex(1U)); + + // Reveal the first emoji. + render_text->SetObscuredRevealIndex(0); + render_text->Draw(canvas()); + + EXPECT_EQ(u"📷•", render_text->GetDisplayText()); + EXPECT_EQ(0U, test_api()->TextIndexToDisplayIndex(0U)); + EXPECT_EQ(0U, test_api()->TextIndexToDisplayIndex(1U)); + EXPECT_EQ(2U, test_api()->TextIndexToDisplayIndex(2U)); + EXPECT_EQ(2U, test_api()->TextIndexToDisplayIndex(3U)); + + EXPECT_EQ(0U, test_api()->DisplayIndexToTextIndex(0U)); + EXPECT_EQ(0U, test_api()->DisplayIndexToTextIndex(1U)); + EXPECT_EQ(2U, test_api()->DisplayIndexToTextIndex(2U)); +} + +TEST_F(RenderTextTest, ObscuredEmojiRevealed) { + RenderText* render_text = GetRenderText(); + + std::u16string text = u"123📷📷x😁-"; + for (size_t i = 0; i < text.length(); ++i) { + render_text->SetText(text); + render_text->SetObscured(true); + render_text->SetObscuredRevealIndex(i); + render_text->Draw(canvas()); + } +} + +struct TextIndexConversionCase { + const char* test_name; + const char16_t* text; +}; + +using TextIndexConversionParam = + std::tuple; + +class RenderTextTestWithTextIndexConversionCase + : public RenderTextTest, + public ::testing::WithParamInterface { + public: + static std::string ParamInfoToString( + ::testing::TestParamInfo param_info) { + TextIndexConversionCase param = std::get<0>(param_info.param); + bool obscured = std::get<1>(param_info.param); + size_t reveal_index = std::get<2>(param_info.param); + return base::StringPrintf("%s%s%zu", param.test_name, + (obscured ? "Obscured" : ""), reveal_index); + } +}; + +TEST_P(RenderTextTestWithTextIndexConversionCase, TextIndexConversion) { + TextIndexConversionCase param = std::get<0>(GetParam()); + bool obscured = std::get<1>(GetParam()); + size_t reveal_index = std::get<2>(GetParam()); + + RenderText* render_text = GetRenderText(); + render_text->SetText(param.text); + render_text->SetObscured(obscured); + render_text->SetObscuredRevealIndex(reveal_index); + render_text->Draw(canvas()); + + std::u16string text = render_text->text(); + std::u16string display_text = render_text->GetDisplayText(); + + // Adjust reveal_index to point to the beginning of the surrogate pair, if + // needed. + U16_SET_CP_START(text.c_str(), 0, reveal_index); + + // Validate that codepoints still match. + for (base::i18n::UTF16CharIterator iter(render_text->text()); !iter.end(); + iter.Advance()) { + size_t text_index = iter.array_pos(); + size_t display_index = test_api()->TextIndexToDisplayIndex(text_index); + EXPECT_EQ(text_index, test_api()->DisplayIndexToTextIndex(display_index)); + if (obscured && reveal_index != text_index) { + EXPECT_EQ(display_text[display_index], + RenderText::kPasswordReplacementChar); + } else { + EXPECT_EQ(display_text[display_index], text[text_index]); + } + } +} + +const TextIndexConversionCase kTextIndexConversionCases[] = { + {"simple", u"abc"}, + {"simple_obscured1", u"abc"}, + {"simple_obscured2", u"abc"}, + {"emoji_asc", u"😨1234"}, + {"emoji_asc_obscured0", u"😨1234"}, + {"emoji_asc_obscured2", u"😨1234"}, + {"picto_title", u"xx☛"}, + {"simple_mixed", u"aaڭڭcc"}, + {"simple_rtl", u"أسكي"}, +}; + +// Validate that conversion text and between display text indexes are consistent +// even when text obscured and reveal character features are used. +INSTANTIATE_TEST_SUITE_P( + ItemizeTextToRunsConversion, + RenderTextTestWithTextIndexConversionCase, + ::testing::Combine(::testing::ValuesIn(kTextIndexConversionCases), + testing::Values(false, true), + testing::Values(0, 1, 3)), + RenderTextTestWithTextIndexConversionCase::ParamInfoToString); + +struct RunListCase { + const char* test_name; + const char16_t* text; + const char* expected; + const bool multiline = false; +}; + +class RenderTextTestWithRunListCase + : public RenderTextTest, + public ::testing::WithParamInterface { + public: + static std::string ParamInfoToString( + ::testing::TestParamInfo param_info) { + return param_info.param.test_name; + } +}; + +TEST_P(RenderTextTestWithRunListCase, ItemizeTextToRuns) { + RunListCase param = GetParam(); + RenderTextHarfBuzz* render_text = GetRenderText(); + render_text->SetMultiline(param.multiline); + render_text->SetText(param.text); + EXPECT_EQ(param.expected, GetRunListStructureString()); +} + +const RunListCase kBasicsRunListCases[] = { + {"simpleLTR", u"abc", "[0->2]"}, + {"simpleRTL", u"ښڛڜ", "[2<-0]"}, + {"asc_arb", u"abcښڛڜdef", "[0->2][5<-3][6->8]"}, + {"asc_dev_asc", u"abcऔकखdefڜ", "[0->2][3->5][6->8][9]"}, + {"phone", u"1-(800)-xxx-xxxx", "[0][1][2][3->5][6][7][8->10][11][12->15]"}, + {"dev_ZWS", u"क\u200Bख", "[0][1][2]"}, + {"numeric", u"1 2 3 4", "[0][1][2][3][4][5][6]"}, + {"joiners1", u"1\u200C2\u200C3\u200C4", "[0->6]"}, + {"joiners2", u"؏\u200C؏", "[0->2]"}, + {"combining_accents1", u"àé", "[0->3]"}, + {"combining_accents2", u"ëё", "[0->1][2->3]"}, + {"picto_title", u"☞☛test☚☜", "[0->1][2->5][6->7]"}, + {"picto_LTR", u"☺☺☺!", "[0->2][3]"}, + {"picto_RTL", u"☺☺☺ښ", "[3][2<-0]"}, + {"paren_picto", u"(☾☹☽)", "[0][1][2][3][4]"}, + {"emoji_asc", u"😨1234", "[0->1][2->5]"}, // http://crbug.com/530021 + {"emoji_title", u"▶Feel goods", + "[0][1->4][5][6->10]"}, // http://crbug.com/278913 + {"jap_paren1", u"ぬ「シ」ほ", + "[0][1][2][3][4]"}, // http://crbug.com/396776 + {"jap_paren2", u"國哲(c)1", + "[0->1][2][3][4][5]"}, // http://crbug.com/125792 + {"newline1", u"\n\n", "[0->1]"}, + {"newline2", u"\r\n\r\n", "[0->3]"}, + {"newline3", u"\r\r\n", "[0->2]"}, + {"multiline_newline1", u"\n\n", "[0][1]", true}, + {"multiline_newline2", u"\r\n\r\n", "[0->1][2->3]", true}, + {"multiline_newline3", u"\r\r\n", "[0][1->2]", true}, + {"multiline_newline4", u"x\r\r", "[0][1][2]", true}, + {"multiline_newline5", u"x\n\r\r", "[0][1][2][3]", true}, + {"multiline_newline6", u"x\ny\rz\r\n", "[0][1][2][3][4][5->6]", true}, +}; + +INSTANTIATE_TEST_SUITE_P(ItemizeTextToRunsBasics, + RenderTextTestWithRunListCase, + ::testing::ValuesIn(kBasicsRunListCases), + RenderTextTestWithRunListCase::ParamInfoToString); + +// see 'Unicode Bidirectional Algorithm': http://unicode.org/reports/tr9/ +const RunListCase kBidiRunListCases[] = { + {"simple_ltr", u"ascii", "[0->4]"}, + {"simple_rtl", u"أسكي", "[3<-0]"}, + {"simple_mixed", u"aaڭڭcc", "[0->1][3<-2][4->5]"}, + {"simple_mixed_LRE", u"\u202Aaaڭڭcc\u202C", "[0][1->2][4<-3][5->6][7]"}, + {"simple_mixed_RLE", u"\u202Baaڭڭcc\u202C", "[7][5->6][4<-3][0][1->2]"}, + {"sequence_RLE", u"\u202Baa\u202C\u202Bbb\u202C", + "[7][0][1->2][3->4][5->6]"}, + {"simple_mixed_LRI", u"\u2066aaڭڭcc\u2069", "[0][1->2][4<-3][5->6][7]"}, + {"simple_mixed_RLI", u"\u2067aaڭڭcc\u2069", "[0][5->6][4<-3][1->2][7]"}, + {"sequence_RLI", u"\u2067aa\u2069\u2067bb\u2069", + "[0][1->2][3->4][5->6][7]"}, + {"override_ltr_RLO", u"\u202Eaaa\u202C", "[4][3<-1][0]"}, + {"override_rtl_LRO", u"\u202Dڭڭڭ\u202C", "[0][1->3][4]"}, + {"neutral_strong_ltr", u"a!!a", "[0][1->2][3]"}, + {"neutral_strong_rtl", u"ڭ!!ڭ", "[3][2<-1][0]"}, + {"neutral_strong_both", u"a a ڭ ڭ", "[0][1][2][3][6][5][4]"}, + {"neutral_strong_both_RLE", u"\u202Ba a ڭ ڭ\u202C", + "[8][7][6][5][4][0][1][2][3]"}, + {"weak_numbers", u"one ڭ222ڭ", "[0->2][3][8][5->7][4]"}, + {"not_weak_letters", u"one ڭabcڭ", "[0->2][3][4][5->7][8]"}, + {"weak_arabic_numbers", u"one ڭ١٢٣ڭ", "[0->2][3][8][5->7][4]"}, + {"neutral_LRM_pre", u"\u200E……", "[0->2]"}, + {"neutral_LRM_post", u"……\u200E", "[0->2]"}, + {"neutral_RLM_pre", u"\u200F……", "[2<-0]"}, + {"neutral_RLM_post", u"……\u200F", "[2<-0]"}, + {"brackets_ltr", u"aa(ڭڭ)……", "[0->1][2][4<-3][5][6->7]"}, + {"brackets_rtl", u"ڭڭ(aa)……", "[7<-6][5][3->4][2][1<-0]"}, + {"mixed_with_punct", u"aa \"ڭڭ!\", aa", + "[0->1][2][3][5<-4][6->8][9][10->11]"}, + {"mixed_with_punct_RLI", u"aa \"\u2067ڭڭ!\u2069\", aa", + "[0->1][2][3][4][7][6<-5][8][9->10][11][12->13]"}, + {"mixed_with_punct_RLM", u"aa \"ڭڭ!\u200F\", aa", + "[0->1][2][3][7][6][5<-4][8->9][10][11->12]"}, +}; + +INSTANTIATE_TEST_SUITE_P(ItemizeTextToRunsBidi, + RenderTextTestWithRunListCase, + ::testing::ValuesIn(kBidiRunListCases), + RenderTextTestWithRunListCase::ParamInfoToString); + +const RunListCase kBracketsRunListCases[] = { + {"matched_parens", u"(a)", "[0][1][2]"}, + {"double_matched_parens", u"((a))", "[0->1][2][3->4]"}, + {"double_matched_parens2", u"((aaa))", "[0->1][2->4][5->6]"}, + {"square_brackets", u"[...]x", "[0][1->3][4][5]"}, + {"curly_brackets", u"{}x{}", "[0->1][2][3->4]"}, + {"style_brackets", u"「...」x", "[0][1->3][4][5]"}, + {"tibetan_brackets", u"༺༻༠༠༼༽", "[0->1][2->3][4->5]"}, + {"angle_brackets", u"〈〇〇〉", "[0][1->2][3]"}, + {"double_angle_brackets", u"《〇〇》", "[0][1->2][3]"}, + {"corner_angle_brackets", u"「〇〇」", "[0][1->2][3]"}, + {"fullwidth_parens", u"(!)", "[0][1][2]"}, +}; + +INSTANTIATE_TEST_SUITE_P(ItemizeTextToRunsBrackets, + RenderTextTestWithRunListCase, + ::testing::ValuesIn(kBracketsRunListCases), + RenderTextTestWithRunListCase::ParamInfoToString); + +// Test cases to ensure the extraction of script extensions are taken into +// account while performing the text itemization. +// See table 7 from http://www.unicode.org/reports/tr24/tr24-29.html +const RunListCase kScriptExtensionRunListCases[] = { + {"implicit_com_inherited", u"a\u0301", "[0->1]"}, + {"explicit_lat", u"\u0061d", "[0->1]"}, + {"explicit_inherited_lat", u"x\u0363d", "[0->2]"}, + {"explicit_inherited_dev", u"क\u1CD1क", "[0->2]"}, + {"multi_explicit_hira", u"は\u30FCz", "[0->1][2]"}, + {"multi_explicit_kana", u"ハ\u30FCz", "[0->1][2]"}, + {"multi_explicit_lat", u"a\u30FCz", "[0][1][2]"}, + {"multi_explicit_impl_dev", u"क\u1CD0z", "[0->1][2]"}, + {"multi_explicit_expl_dev", u"क\u096Fz", "[0->1][2]"}, +}; + +INSTANTIATE_TEST_SUITE_P(ItemizeTextToRunsScriptExtension, + RenderTextTestWithRunListCase, + ::testing::ValuesIn(kScriptExtensionRunListCases), + RenderTextTestWithRunListCase::ParamInfoToString); + +// Test cases to ensure ItemizeTextToRuns is splitting text based on unicode +// script (intersection of script extensions). +// See ScriptExtensions.txt and Scripts.txt from +// http://www.unicode.org/reports/tr24/tr24-29.html +const RunListCase kScriptsRunListCases[] = { + {"lat", u"abc", "[0->2]"}, + {"lat_diac", u"e\u0308f", "[0->2]"}, + // Indic Fraction codepoints have large set of script extensions. + {"indic_fraction", u"\uA830\uA832\uA834\uA835", "[0->3]"}, + // Devanagari Danda codepoints have large set of script extensions. + {"dev_danda", u"\u0964\u0965", "[0->1]"}, + // Combining Diacritical Marks (inherited) should only merge with preceding. + {"diac_lat", u"\u0308fg", "[0][1->2]"}, + {"diac_dev", u"क\u0308f", "[0->1][2]"}, + // ZWJW has the inherited script. + {"lat_ZWNJ", u"ab\u200Ccd", "[0->4]"}, + {"dev_ZWNJ", u"क\u200Cक", "[0->2]"}, + {"lat_dev_ZWNJ", u"a\u200Cक", "[0->1][2]"}, + // Invalid codepoints. + {"invalid_cp", u"\uFFFE", "[0]"}, + {"invalid_cps", u"\uFFFE\uFFFF", "[0->1]"}, + {"unknown", u"a\u243Fb", "[0][1][2]"}, + + // Codepoints from different code block should be in same run when part of + // the same script. + {"blocks_lat", u"aɒɠƉĚÑ", "[0->5]"}, + {"blocks_lat_paren", u"([_!_])", "[0->1][2->4][5->6]"}, + {"blocks_lat_sub", u"ₐₑaeꬱ", "[0->4]"}, + {"blocks_lat_smallcap", u"ꟺM", "[0->1]"}, + {"blocks_lat_small_letter", u"ᶓᶍᶓᴔᴟ", "[0->4]"}, + {"blocks_lat_acc", u"eéěĕȩɇḕẻếⱻꞫ", "[0->10]"}, + {"blocks_com", u"⟦ℳ¥¾⟾⁸⟧Ⓔ", "[0][1][2->3][4][5][6][7]"}, + + // Latin script. + {"latin_numbers", u"a1b2c3", "[0][1][2][3][4][5]"}, + {"latin_puncts1", u"a,b,c.", "[0][1][2][3][4][5]"}, + {"latin_puncts2", u"aa,bb,cc!!", "[0->1][2][3->4][5][6->7][8->9]"}, + {"latin_diac_multi", u"a\u0300e\u0352i", "[0->4]"}, + + // Common script. + {"common_tm", u"•bug™", "[0][1->3][4]"}, + {"common_copyright", u"chromium©", "[0->7][8]"}, + {"common_math1", u"ℳ: ¬ƒ(x)=½×¾", "[0][1][2][3][4][5][6][7][8][9->11]"}, + {"common_math2", u"𝟏×𝟑", "[0->1][2][3->4]"}, + {"common_numbers", u"🄀𝟭𝟐⒓¹²", "[0->1][2->5][6][7->8]"}, + {"common_puncts", u",.\u0083!", "[0->1][2][3]"}, + + // Arabic script. + {"arabic", u"\u0633\u069b\u0763\u077f\u08A2\uFB53", "[5<-0]"}, + {"arabic_lat", u"\u0633\u069b\u0763\u077f\u08A2\uFB53abc", "[6->8][5<-0]"}, + {"arabic_word_ligatures", u"\uFDFD\uFDF3", "[1<-0]"}, + {"arabic_diac", u"\u069D\u0300", "[1<-0]"}, + {"arabic_diac_lat", u"\u069D\u0300abc", "[2->4][1<-0]"}, + {"arabic_diac_lat2", u"abc\u069D\u0300abc", "[0->2][4<-3][5->7]"}, + {"arabic_lyd", u"\U00010935\U00010930\u06B0\u06B1", "[5<-4][3<-0]"}, + {"arabic_numbers", u"12\u06D034", "[3->4][2][0->1]"}, + {"arabic_letters", u"ab\u06D0cd", "[0->1][2][3->4]"}, + {"arabic_mixed", u"a1\u06D02d", "[0][1][3][2][4]"}, + {"arabic_coptic1", u"\u06D0\U000102E2\u2CB2", "[1->3][0]"}, + {"arabic_coptic2", u"\u2CB2\U000102E2\u06D0", "[0->2][3]"}, + + // Devanagari script. + {"devanagari1", u"ञटठडढणतथ", "[0->7]"}, + {"devanagari2", u"ढ꣸ꣴ", "[0->2]"}, + {"devanagari_vowels", u"\u0915\u093F\u0915\u094C", "[0->3]"}, + {"devanagari_consonants", u"\u0915\u094D\u0937", "[0->2]"}, + + // Ethiopic script. + {"ethiopic", u"መጩጪᎅⶹⶼꬣꬦ", "[0->7]"}, + {"ethiopic_numbers", u"1ቨቤ2", "[0][1->2][3]"}, + {"ethiopic_mixed1", u"abቨቤ12", "[0->1][2->3][4->5]"}, + {"ethiopic_mixed2", u"a1ቨቤb2", "[0][1][2->3][4][5]"}, + + // Georgian script. + {"georgian1", u"ႼႽႾႿჀჁჂჳჴჵ", "[0->9]"}, + {"georgian2", u"ლⴊⴅ", "[0->2]"}, + {"georgian_numbers", u"1ლⴊⴅ2", "[0][1->3][4]"}, + {"georgian_mixed", u"a1ლⴊⴅb2", "[0][1][2->4][5][6]"}, + + // Telugu script. + {"telugu_lat", u"aaఉయ!", "[0->1][2->3][4]"}, + {"telugu_numbers", u"123౦౧౨456౩౪౫", "[0->2][3->5][6->8][9->11]"}, + {"telugu_puncts", u"కురుచ, చిఱుత, చేరువ, చెఱువు!", + "[0->4][5][6][7->11][12][13][14->18][19][20][21->26][27]"}, + + // Control Pictures. + {"control_pictures", u"␑␒␓␔␕␖␗␘␙␚␛", "[0->10]"}, + {"control_pictures_rewrite", u"␑\t␛", "[0->2]"}, + + // Unicode art. + {"unicode_emoticon1", u"(▀̿ĺ̯▀̿ ̿)", "[0][1->2][3->4][5->6][7->8][9]"}, + {"unicode_emoticon2", u"▀̿̿Ĺ̯̿▀̿ ", "[0->2][3->5][6->7][8]"}, + {"unicode_emoticon3", u"( ͡° ͜ʖ ͡°)", "[0][1->2][3][4->5][6][7->8][9][10]"}, + {"unicode_emoticon4", u"✩·͙*̩̩͙˚̩̥̩̥( ͡ᵔ ͜ʖ ͡ᵔ )*̩̩͙✩·͙˚̩̥̩̥.", + "[0][1->2][3->6][7->11][12][13->14][15][16->17][18][19->20][21][22][23][" + "24->27][28][29->30][31->35][36]"}, + {"unicode_emoticon5", u"ヽ(͡◕ ͜ʖ ͡◕)ノ", + "[0][1->2][3][4->5][6][7->8][9][10][11]"}, + {"unicode_art1", u"꧁༒✧ Great ✧༒꧂", "[0][1][2][3][4->8][9][10][11][12]"}, + {"unicode_art2", u"t͎e͎s͎t͎", "[0->7]"}, + + // Combining diacritical sequences. + {"unicode_diac1", u"\u2123\u0336", "[0->1]"}, + {"unicode_diac2", u"\u273c\u0325", "[0->1]"}, + {"unicode_diac3", u"\u2580\u033f", "[0->1]"}, + {"unicode_diac4", u"\u2022\u0325\u0329", "[0->2]"}, + {"unicode_diac5", u"\u2022\u0325", "[0->1]"}, + {"unicode_diac6", u"\u00b7\u0359\u0325", "[0->2]"}, + {"unicode_diac7", u"\u2027\u0329\u0325", "[0->2]"}, + {"unicode_diac8", u"\u0332\u0305\u03c1", "[0->1][2]"}, +}; + +INSTANTIATE_TEST_SUITE_P(ItemizeTextToRunsScripts, + RenderTextTestWithRunListCase, + ::testing::ValuesIn(kScriptsRunListCases), + RenderTextTestWithRunListCase::ParamInfoToString); + +// Test cases to ensure ItemizeTextToRuns is splitting emoji correctly. +// see: http://www.unicode.org/reports/tr51 +// see: http://www.unicode.org/emoji/charts/full-emoji-list.html +const RunListCase kEmojiRunListCases[] = { + // Samples from + // https://www.unicode.org/Public/emoji/12.0/emoji-data.txt + {"number_sign", u"\u0023", "[0]"}, + {"keyboard", u"\u2328", "[0]"}, + {"aries", u"\u2648", "[0]"}, + {"candle", u"\U0001F56F", "[0->1]"}, + {"anchor", u"\u2693", "[0]"}, + {"grinning_face", u"\U0001F600", "[0->1]"}, + {"face_with_monocle", u"\U0001F9D0", "[0->1]"}, + {"light_skin_tone", u"\U0001F3FB", "[0->1]"}, + {"index_pointing_up", u"\u261D", "[0]"}, + {"horse_racing", u"\U0001F3C7", "[0->1]"}, + {"kiss", u"\U0001F48F", "[0->1]"}, + {"couple_with_heart", u"\U0001F491", "[0->1]"}, + {"people_wrestling", u"\U0001F93C", "[0->1]"}, + {"eject_button", u"\u23CF", "[0]"}, + + // Samples from + // https://unicode.org/Public/emoji/12.0/emoji-sequences.txt + {"watch", u"\u231A", "[0]"}, + {"cross_mark", u"\u274C", "[0]"}, + {"copyright", u"\u00A9\uFE0F", "[0->1]"}, + {"stop_button", u"\u23F9\uFE0F", "[0->1]"}, + {"passenger_ship", u"\U0001F6F3\uFE0F", "[0->2]"}, + {"keycap_star", u"\u002A\uFE0F\u20E3", "[0->2]"}, + {"keycap_6", u"\u0036\uFE0F\u20E3", "[0->2]"}, + {"flag_andorra", u"\U0001F1E6\U0001F1E9", "[0->3]"}, + {"flag_egypt", u"\U0001F1EA\U0001F1EC", "[0->3]"}, + {"flag_england", + u"\U0001F3F4\U000E0067\U000E0062\U000E0065\U000E006E\U000E0067\U000E007F", + "[0->13]"}, + {"index_up_light", u"\u261D\U0001F3FB", "[0->2]"}, + {"person_bouncing_ball_light", u"\u26F9\U0001F3FC", "[0->2]"}, + {"victory_hand_med_light", u"\u270C\U0001F3FC", "[0->2]"}, + {"horse_racing_med_dark", u"\U0001F3C7\U0001F3FE", "[0->3]"}, + {"woman_man_hands_light", u"\U0001F46B\U0001F3FB", "[0->3]"}, + {"person_haircut_med_light", u"\U0001F487\U0001F3FC", "[0->3]"}, + {"pinching_hand_light", u"\U0001F90F\U0001F3FB", "[0->3]"}, + {"love_you_light", u"\U0001F91F\U0001F3FB", "[0->3]"}, + {"leg_dark", u"\U0001F9B5\U0001F3FF", "[0->3]"}, + + // Samples from + // https://unicode.org/Public/emoji/12.0/emoji-variation-sequences.txt + {"number_sign_text", u"\u0023\uFE0E", "[0->1]"}, + {"number_sign_emoji", u"\u0023\uFE0F", "[0->1]"}, + {"digit_eight_text", u"\u0038\uFE0E", "[0->1]"}, + {"digit_eight_emoji", u"\u0038\uFE0F", "[0->1]"}, + {"up_down_arrow_text", u"\u2195\uFE0E", "[0->1]"}, + {"up_down_arrow_emoji", u"\u2195\uFE0F", "[0->1]"}, + {"stopwatch_text", u"\u23F1\uFE0E", "[0->1]"}, + {"stopwatch_emoji", u"\u23F1\uFE0F", "[0->1]"}, + {"thermometer_text", u"\U0001F321\uFE0E", "[0->2]"}, + {"thermometer_emoji", u"\U0001F321\uFE0F", "[0->2]"}, + {"thumbs_up_text", u"\U0001F44D\uFE0E", "[0->2]"}, + {"thumbs_up_emoji", u"\U0001F44D\uFE0F", "[0->2]"}, + {"hole_text", u"\U0001F573\uFE0E", "[0->2]"}, + {"hole_emoji", u"\U0001F573\uFE0F", "[0->2]"}, + + // Samples from + // https://unicode.org/Public/emoji/12.0/emoji-zwj-sequences.txt + {"couple_man_man", u"\U0001F468\u200D\u2764\uFE0F\u200D\U0001F468", + "[0->7]"}, + {"kiss_man_man", + u"\U0001F468\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468", + "[0->10]"}, + {"family_man_woman_girl_boy", + u"\U0001F468\u200D\U0001F469\u200D\U0001F467\u200D\U0001F466", "[0->10]"}, + {"men_hands_dark_medium", + u"\U0001F468\U0001F3FF\u200D\U0001F91D\u200D\U0001F468\U0001F3FD", + "[0->11]"}, + {"people_hands_dark", + u"\U0001F9D1\U0001F3FF\u200D\U0001F91D\u200D\U0001F9D1\U0001F3FF", + "[0->11]"}, + {"man_pilot", u"\U0001F468\u200D\u2708\uFE0F", "[0->4]"}, + {"man_scientist", u"\U0001F468\u200D\U0001F52C", "[0->4]"}, + {"man_mechanic_light", u"\U0001F468\U0001F3FB\u200D\U0001F527", "[0->6]"}, + {"man_judge_medium", u"\U0001F468\U0001F3FD\u200D\u2696\uFE0F", "[0->6]"}, + {"woman_cane_dark", u"\U0001F469\U0001F3FF\u200D\U0001F9AF", "[0->6]"}, + {"woman_ball_light", u"\u26F9\U0001F3FB\u200D\u2640\uFE0F", "[0->5]"}, + {"woman_running", u"\U0001F3C3\u200D\u2640\uFE0F", "[0->4]"}, + {"woman_running_dark", u"\U0001F3C3\U0001F3FF\u200D\u2640\uFE0F", "[0->6]"}, + {"woman_turban", u"\U0001F473\u200D\u2640\uFE0F", "[0->4]"}, + {"woman_detective", u"\U0001F575\uFE0F\u200D\u2640\uFE0F", "[0->5]"}, + {"man_facepalming", u"\U0001F926\u200D\u2642\uFE0F", "[0->4]"}, + {"man_red_hair", u"\U0001F468\u200D\U0001F9B0", "[0->4]"}, + {"man_medium_curly", u"\U0001F468\U0001F3FD\u200D\U0001F9B1", "[0->6]"}, + {"woman_dark_white_hair", u"\U0001F469\U0001F3FF\u200D\U0001F9B3", + "[0->6]"}, + {"rainbow_flag", u"\U0001F3F3\uFE0F\u200D\U0001F308", "[0->5]"}, + {"pirate_flag", u"\U0001F3F4\u200D\u2620\uFE0F", "[0->4]"}, + {"service_dog", u"\U0001F415\u200D\U0001F9BA", "[0->4]"}, + {"eye_bubble", u"\U0001F441\uFE0F\u200D\U0001F5E8\uFE0F", "[0->6]"}, +}; + +INSTANTIATE_TEST_SUITE_P(ItemizeTextToRunsEmoji, + RenderTextTestWithRunListCase, + ::testing::ValuesIn(kEmojiRunListCases), + RenderTextTestWithRunListCase::ParamInfoToString); + +struct ElideTextTestOptions { + const ElideBehavior elide_behavior; +}; + +const bool kForceNoWhitespaceElision = false; +const bool kForceWhitespaceElision = true; + +struct ElideTextCase { + const char* test_name; + const char16_t* text; + const char16_t* display_text; + // The available width, specified as a number of fixed-width glyphs. If no + // value is specified, the width of the resulting |display_text| is used. This + // helps test available widths larger than the resulting test; e.g. "a b" + // should yield "a…" even if 3 glyph widths are available, when + // whitespace elision is enabled. + const absl::optional available_width_as_glyph_count = absl::nullopt; + const absl::optional whitespace_elision = absl::nullopt; +}; + +using ElideTextCaseParam = std::tuple; + +class RenderTextTestWithElideTextCase + : public RenderTextTest, + public ::testing::WithParamInterface { + public: + static std::string ParamInfoToString( + ::testing::TestParamInfo param_info) { + return std::get<1>(param_info.param).test_name; + } +}; + +TEST_P(RenderTextTestWithElideTextCase, ElideText) { + // This test requires glyphs to be the same width. + constexpr int kGlyphWidth = 10; + SetGlyphWidth(kGlyphWidth); + + const ElideTextTestOptions options = std::get<0>(GetParam()); + const ElideTextCase param = std::get<1>(GetParam()); + const std::u16string text = param.text; + const std::u16string display_text = param.display_text; + + // Retrieve the display_text width without eliding. + RenderTextHarfBuzz* render_text = GetRenderText(); + render_text->SetText(display_text); + const int expected_width = render_text->GetContentWidth(); + + // Set the text and the eliding behavior. + render_text->SetText(text); + render_text->SetElideBehavior(options.elide_behavior); + + // If specified, set the whitespace elision. Otherwise, keep the eliding + // behavior default value. + if (param.whitespace_elision.has_value()) + render_text->SetWhitespaceElision(param.whitespace_elision.value()); + + // Set the display width to trigger the eliding. + if (param.available_width_as_glyph_count.has_value()) { + render_text->SetDisplayRect( + Rect(0, 0, + param.available_width_as_glyph_count.value() * kGlyphWidth + + kGlyphWidth / 2, + 100)); + } else { + render_text->SetDisplayRect( + Rect(0, 0, expected_width + kGlyphWidth / 2, 100)); + } + + const int elided_width = render_text->GetContentWidth(); + + EXPECT_EQ(text, render_text->text()); + EXPECT_EQ(display_text, render_text->GetDisplayText()); + EXPECT_EQ(elided_width, expected_width); +} + +const ElideTextCase kElideHeadTextCases[] = { + {"empty", u"", u""}, + {"letter_m_tail0", u"M", u""}, + {"letter_m_tail1", u"M", u"M"}, + {"no_eliding", u"012ab", u"012ab"}, + {"ltr_3", u"abc", u"abc"}, + {"ltr_2", u"abc", u"…c"}, + {"ltr_1", u"abc", u"…"}, + {"ltr_0", u"abc", u""}, + {"rtl_3", u"אבג", u"אבג"}, + {"rtl_2", u"אבג", u"…ג"}, + {"rtl_1", u"אבג", u"…"}, + {"rtl_0", u"אבג", u""}, + {"ltr_rtl_5", u"abcאבג", u"…cאבג"}, + {"ltr_rtl_4", u"abcאבג", u"…אבג"}, + {"ltr_rtl_3", u"abcאבג", u"…בג"}, + {"rtl_ltr_5", u"אבגabc", u"…גabc"}, + {"rtl_ltr_4", u"אבגabc", u"…abc"}, + {"rtl_ltr_3", u"אבגabc", u"…bc"}, + {"bidi_1", u"aבbבc012", u"…bבc012"}, + {"bidi_2", u"aבbבc012", u"…בc012"}, + {"bidi_3", u"aבbבc012", u"…c012"}, + // Test surrogate pairs. No surrogate pair should be partially elided. + {"surrogate1", u"abc\U0001D11E\U0001D122x", u"…\U0001D11E\U0001D122x"}, + {"surrogate2", u"abc\U0001D11E\U0001D122x", u"…\U0001D122x"}, + {"surrogate3", u"abc\U0001D11E\U0001D122x", u"…x"}, + // Test combining character sequences. U+0915 U+093F forms a compound + // glyph, as does U+0915 U+0942. No combining sequence should be partially + // elided. + {"combining1", u"0123\u0915\u093f\u0915\u0942456", u"…\u0915\u0942456"}, + {"combining2", u"0123\u0915\u093f\u0915\u0942456", u"…456"}, + // 𝄞 (U+1D11E, MUSICAL SYMBOL G CLEF) should be fully elided. + {"emoji1", u"012\U0001D11Ex", u"…\U0001D11Ex"}, + {"emoji2", u"012\U0001D11Ex", u"…x"}, + + // Whitespace elision tests. + {"empty_no_elision", u"", u"", 0, kForceNoWhitespaceElision}, + {"empty_elision", u"", u"", 0, kForceWhitespaceElision}, + {"xyz_no_elision", u" x xyz", u"… xyz", 5, kForceNoWhitespaceElision}, + {"xyz_elision", u" x xyz", u"…xyz", 5, kForceWhitespaceElision}, + {"ltr_rtl_elision3", u"x ב y ג", u"…ג", 3, kForceWhitespaceElision}, + {"ltr_rtl_elision6", u"x ב y ג", u"…ג", 6, kForceWhitespaceElision}, + {"ltr_rtl_elision7", u"x ב y ג", u"…y ג", 7, + kForceWhitespaceElision}, + {"ltr_rtl_elision10", u"x ב y ג", u"…ב y ג", 10, + kForceWhitespaceElision}, + {"ltr_rtl_elision11", u"x ב y ג", u"…ב y ג", 11, + kForceWhitespaceElision}, + // Emoji U+1F601 and emoji U+1F321 U+FE0E are graphemes that result in + // one glyph each. Eliding a glyph must remove the whole grapheme. It is + // invalid to break a grapheme in pieces. + {"graphemes_elision3", u" \U0001F601 \U0001F321\uFE0E ", u"…", 3, + kForceWhitespaceElision}, + {"graphemes_elision4", u" \U0001F601 \U0001F321\uFE0E ", + u"…\U0001F321\uFE0E ", 4, kForceWhitespaceElision}, + {"graphemes_elision6", u" \U0001F601 \U0001F321\uFE0E ", + u"…\U0001F321\uFE0E ", 6, kForceWhitespaceElision}, + {"graphemes_elision7", u" \U0001F601 \U0001F321\uFE0E ", + u"…\U0001F601 \U0001F321\uFE0E ", 7, kForceWhitespaceElision}, +}; + +INSTANTIATE_TEST_SUITE_P( + ElideHead, + RenderTextTestWithElideTextCase, + testing::Combine(testing::Values(ElideTextTestOptions{ELIDE_HEAD}), + testing::ValuesIn(kElideHeadTextCases)), + RenderTextTestWithElideTextCase::ParamInfoToString); + +const ElideTextCase kElideTailTextCases[] = { + {"empty", u"", u""}, + {"letter_m_tail0", u"M", u""}, + {"letter_m_tail1", u"M", u"M"}, + {"letter_weak_3", u" . ", u" . "}, + {"letter_weak_2", u" . ", u"…"}, + {"no_eliding", u"012ab", u"012ab"}, + {"ltr_3", u"abc", u"abc"}, + {"ltr_2", u"abc", u"a…"}, + {"ltr_1", u"abc", u"…"}, + {"ltr_0", u"abc", u""}, + {"rtl_3", u"אבג", u"אבג"}, + {"rtl_2", u"אבג", u"א…"}, + {"rtl_1", u"אבג", u"…"}, + {"rtl_0", u"אבג", u""}, + {"ltr_rtl_5", u"abcאבג", u"abcא…\u200F"}, + {"ltr_rtl_4", u"abcאבג", u"abc…"}, + {"ltr_rtl_3", u"abcאבג", u"ab…"}, + {"rtl_ltr_5", u"אבגabc", u"אבגa…\u200E"}, + {"rtl_ltr_4", u"אבגabc", u"אבג…"}, + {"rtl_ltr_3", u"אבגabc", u"אב…"}, + {"bidi_1", u"012aבbבc", u"012a…"}, + {"bidi_2", u"012aבbבc", u"012aב…\u200F"}, + {"bidi_3", u"012aבbבc", u"012aבb…"}, + // No RLM marker added as digits (012) have weak directionality. + {"no_rlm", u"01אבג", u"01א…"}, + // RLM marker added as "ab" have strong LTR directionality. + {"with_rlm", u"abאבגcd", u"abאב…\u200f"}, + // Test surrogate pairs. The first pair 𝄞 'MUSICAL SYMBOL G CLEF' U+1D11E + // should be kept, and the second pair 𝄢 'MUSICAL SYMBOL F CLEF' U+1D122 + // should be removed. No surrogate pair should be partially elided. + {"surrogate", u"0123\U0001D11E\U0001D122x", u"0123\U0001D11E…"}, + // Test combining character sequences. U+0915 U+093F forms a compound + // glyph, as does U+0915 U+0942. The first should be kept; the second + // removed. No combining sequence should be partially elided. + {"combining", u"0123\u0915\u093f\u0915\u0942456", u"0123\u0915\u093f…"}, + // U+05E9 U+05BC U+05C1 U+05B8 forms a four-character compound glyph. + // It should be either fully elided, or not elided at all. If completely + // elided, an LTR Mark (U+200E) should be added. + {"grapheme1", u"0\u05e9\u05bc\u05c1\u05b8", u"0\u05e9\u05bc\u05c1\u05b8"}, + {"grapheme2", u"0\u05e9\u05bc\u05c1\u05b8abc", u"0…\u200E"}, + {"grapheme3", u"01\u05e9\u05bc\u05c1\u05b8abc", u"01…\u200E"}, + {"grapheme4", u"012\u05e9\u05bc\u05c1\u05b8abc", u"012…\u200E"}, + // 𝄞 (U+1D11E, MUSICAL SYMBOL G CLEF) should be fully elided. + {"emoji", u"012\U0001D11Ex", u"012…"}, + + // Whitespace elision tests. + {"empty_no_elision", u"", u"", 0, kForceNoWhitespaceElision}, + {"empty_elision", u"", u"", 0, kForceWhitespaceElision}, + {"letter_weak_2_no_elision", u" . ", u" …", 2, kForceNoWhitespaceElision}, + {"xyz_no_elision", u" x xyz", u" x …", 5, kForceNoWhitespaceElision}, + {"xyz_elision", u" x xyz", u" x…", 5, kForceWhitespaceElision}, + {"ltr_rtl_elision4", u"x ב y ג", u"x…", 4, kForceWhitespaceElision}, + {"ltr_rtl_elision5", u"x ב y ג", u"x ב…\u200F", 5, + kForceWhitespaceElision}, + {"ltr_rtl_elision9", u"x ב y ג", u"x ב y…", 9, + kForceWhitespaceElision}, + // Emoji U+1F601 and emoji U+1F321 U+FE0E are graphemes that result in + // one glyph each. Eliding a glyph must remove the whole grapheme. It is + // invalid to break a grapheme in pieces. + {"graphemes_elision3", u" \U0001F601 \U0001F321\uFE0E ", u"…", 3, + kForceWhitespaceElision}, + {"graphemes_elision6", u" \U0001F601 \U0001F321\uFE0E ", + u" \U0001F601…", 6, kForceWhitespaceElision}, + {"graphemes_elision7", u" \U0001F601 \U0001F321\uFE0E ", + u" \U0001F601 \U0001F321\uFE0E…", 7, kForceWhitespaceElision}, +}; + +INSTANTIATE_TEST_SUITE_P( + ElideTail, + RenderTextTestWithElideTextCase, + testing::Combine(testing::Values(ElideTextTestOptions{ELIDE_TAIL}), + testing::ValuesIn(kElideTailTextCases)), + RenderTextTestWithElideTextCase::ParamInfoToString); + +const ElideTextCase kElideTruncateTextCases[] = { + {"empty", u"", u""}, + {"letter_m_tail0", u"M", u""}, + {"letter_m_tail1", u"M", u"M"}, + {"no_eliding", u"012ab", u"012ab"}, + {"ltr_3", u"abc", u"abc"}, + {"ltr_2", u"abc", u"ab"}, + {"ltr_1", u"abc", u"a"}, + {"ltr_0", u"abc", u""}, + {"rtl_3", u"אבג", u"אבג"}, + {"rtl_2", u"אבג", u"אב"}, + {"rtl_1", u"אבג", u"א"}, + {"rtl_0", u"אבג", u""}, + {"ltr_rtl_5", u"abcאבג", u"abcאב"}, + {"ltr_rtl_4", u"abcאבג", u"abcא"}, + {"ltr_rtl_3", u"abcאבג", u"abc"}, + {"ltr_rtl_2", u"abcאבג", u"ab"}, + {"rtl_ltr_5", u"אבגabc", u"אבגab"}, + {"rtl_ltr_4", u"אבגabc", u"אבגa"}, + {"rtl_ltr_3", u"אבגabc", u"אבג"}, + {"rtl_ltr_2", u"אבגabc", u"אב"}, + {"bidi_1", u"012aבbבc", u"012aבbב"}, + {"bidi_2", u"012aבbבc", u"012aבb"}, + {"bidi_3", u"012aבbבc", u"012aב"}, + {"bidi_4", u"012aבbבc", u"012aב"}, + // Test surrogate pairs. The first pair 𝄞 'MUSICAL SYMBOL G CLEF' U+1D11E + // should be kept, and the second pair 𝄢 'MUSICAL SYMBOL F CLEF' U+1D122 + // should be removed. No surrogate pair should be partially elided. + {"surrogate1", u"0123\U0001D11E\U0001D122x", u"0123\U0001D11E\U0001D122"}, + {"surrogate2", u"0123\U0001D11E\U0001D122x", u"0123\U0001D11E"}, + {"surrogate3", u"0123\U0001D11E\U0001D122x", u"0123"}, + // Test combining character sequences. U+0915 U+093F forms a compound + // glyph, as does U+0915 U+0942. The first should be kept; the second + // removed. No combining sequence should be partially elided. + {"combining", u"0123\u0915\u093f\u0915\u0942456", u"0123\u0915\u093f"}, + // 𝄞 (U+1D11E, MUSICAL SYMBOL G CLEF) should be fully elided. + {"emoji1", u"012\U0001D11Ex", u"012\U0001D11E"}, + {"emoji2", u"012\U0001D11Ex", u"012"}, + + // Whitespace elision tests. + {"empty_no_elision", u"", u"", 0, kForceNoWhitespaceElision}, + {"empty_elision", u"", u"", 0, kForceWhitespaceElision}, + {"xyz_no_elision", u" x xyz", u" x ", 5, kForceNoWhitespaceElision}, + {"xyz_elision", u" x xyz", u" x", 5, kForceWhitespaceElision}, + {"ltr_rtl_elision3", u"x ב y ג", u"x", 3, kForceWhitespaceElision}, + {"ltr_rtl_elision4", u"x ב y ג", u"x ב", 4, kForceWhitespaceElision}, + {"ltr_rtl_elision5", u"x ב y ג", u"x ב", 5, kForceWhitespaceElision}, + {"ltr_rtl_elision9", u"x ב y ג", u"x ב y", 9, + kForceWhitespaceElision}, + // Emoji U+1F601 and emoji U+1F321 U+FE0E are graphemes that result in + // one glyph each. Eliding a glyph must remove the whole grapheme. It is + // invalid to break a grapheme in pieces. + {"graphemes_elision2", u" \U0001F601 \U0001F321\uFE0E ", u"", 2, + kForceWhitespaceElision}, + {"graphemes_elision3", u" \U0001F601 \U0001F321\uFE0E ", u" \U0001F601", + 3, kForceWhitespaceElision}, + {"graphemes_elision5", u" \U0001F601 \U0001F321\uFE0E ", u" \U0001F601", + 5, kForceWhitespaceElision}, + {"graphemes_elision6", u" \U0001F601 \U0001F321\uFE0E ", + u" \U0001F601 \U0001F321\uFE0E", 6, kForceWhitespaceElision}, + {"graphemes_elision7", u" \U0001F601 \U0001F321\uFE0E ", + u" \U0001F601 \U0001F321\uFE0E", 7, kForceWhitespaceElision}, +}; + +INSTANTIATE_TEST_SUITE_P( + ElideTruncate, + RenderTextTestWithElideTextCase, + testing::Combine(testing::Values(ElideTextTestOptions{TRUNCATE}), + testing::ValuesIn(kElideTruncateTextCases)), + RenderTextTestWithElideTextCase::ParamInfoToString); + +const ElideTextCase kElideEmailTextCases[] = { + // Invalid email text. + {"empty", u"", u""}, + {"invalid_char1", u"x", u""}, + {"invalid_char3", u"xyz", u"x…"}, + {"invalid_amp", u"@", u""}, + {"invalid_no_prefix0", u"@y", u""}, + {"invalid_no_prefix1", u"@y", u"…"}, + {"invalid_no_prefix2", u"@xyz", u"@x…"}, + {"invalid_no_suffix0", u"x@", u""}, + {"invalid_no_suffix1", u"x@", u"…"}, + {"invalid_no_suffix2", u"xyz@", u"x…@"}, + + {"at1", u"@", u"@"}, + {"at2", u"@@", u"…", 1}, + {"at3", u"@@@", u"…", 2}, + {"at4", u"@@@@", u"@…@", 3}, + + {"small1", u"a@b", u"…", 1}, + {"small2", u"a@b", u"…", 2}, + {"small3", u"a@b", u"a@b", 3}, + {"small_username3", u"xyz@b", u"…", 3}, + {"small_username4", u"xyz@b", u"x…@b", 4}, + {"small_username5", u"xyz@b", u"xyz@b", 5}, + {"small_domain3", u"a@xyz", u"…", 3}, + {"small_domain4", u"a@xyz", u"a@x…", 4}, + {"small_domain5", u"a@xyz", u"a@xyz", 5}, + + // Valid email. + {"email_small", u"a@b.com", u"…"}, + {"email_nobody3", u"nobody@gmail.com", u"…", 3}, + {"email_nobody4", u"nobody@gmail.com", u"…", 4}, + {"email_nobody5", u"nobody@gmail.com", u"n…@g…", 5}, + {"email_nobody6", u"nobody@gmail.com", u"no…@g…", 6}, + {"email_nobody7", u"nobody@gmail.com", u"no…@g…m", 7}, + {"email_nobody8", u"nobody@gmail.com", u"nob…@g…m", 8}, + {"email_nobody9", u"nobody@gmail.com", u"nob…@gm…m", 9}, + {"email_nobody10", u"nobody@gmail.com", u"nobo…@gm…m", 10}, + {"email_root", u"root@localhost", u"r…@l…", 5}, + {"email_myself", u"myself@127.0.0.1", u"my…@1…", 6}, +}; + +INSTANTIATE_TEST_SUITE_P( + ElideEmail, + RenderTextTestWithElideTextCase, + testing::Combine(testing::Values(ElideTextTestOptions{ELIDE_EMAIL}), + testing::ValuesIn(kElideEmailTextCases)), + RenderTextTestWithElideTextCase::ParamInfoToString); + +TEST_F(RenderTextTest, ElidedText_NoTrimWhitespace) { + // This test requires glyphs to be the same width. + constexpr int kGlyphWidth = 10; + SetGlyphWidth(kGlyphWidth); + + RenderText* render_text = GetRenderText(); + gfx::test::RenderTextTestApi render_text_test_api(render_text); + render_text->SetElideBehavior(ELIDE_TAIL); + render_text->SetWhitespaceElision(false); + + // Pick a sufficiently long string that's mostly whitespace. + // Tail-eliding this with whitespace elision turned off should look like: + // [ ...] + // and not like: + // [... ] + constexpr char16_t kInputString[] = u" foo"; + render_text->SetText(kInputString); + + // Choose a width based on being able to display 12 characters (one of which + // will be the trailing ellipsis). + constexpr int kDesiredChars = 12; + constexpr int kRequiredWidth = (kDesiredChars + 0.5f) * kGlyphWidth; + render_text->SetDisplayRect(Rect(0, 0, kRequiredWidth, 100)); + + // Verify this doesn't change the full text. + EXPECT_EQ(kInputString, render_text->text()); + + // Verify that the string is truncated to |kDesiredChars| with the ellipsis. + const std::u16string result = render_text->GetDisplayText(); + const std::u16string expected = + std::u16string(kInputString).substr(0, kDesiredChars - 1) + + kEllipsisUTF16[0]; + EXPECT_EQ(expected, result); +} + +TEST_F(RenderTextTest, SetElideBehavior) { + // This test requires glyphs to be the same width. + constexpr int kGlyphWidth = 10; + SetGlyphWidth(kGlyphWidth); + + RenderText* render_text = GetRenderText(); + render_text->SetText(u"abcdef"); + render_text->SetCursorEnabled(false); + render_text->SetDisplayRect(Rect(0, 0, 3 * kGlyphWidth, 100)); + render_text->SetElideBehavior(ELIDE_TAIL); + EXPECT_EQ(u"ab…", render_text->GetDisplayText()); + + // Setting a different eliding behavior must trigger a relayout. + render_text->SetElideBehavior(ELIDE_HEAD); + EXPECT_EQ(u"…ef", render_text->GetDisplayText()); +} + +TEST_F(RenderTextTest, SetWhitespaceElision) { + // This test requires glyphs to be the same width. + constexpr int kGlyphWidth = 10; + SetGlyphWidth(kGlyphWidth); + + RenderText* render_text = GetRenderText(); + render_text->SetText(u"a b c d"); + render_text->SetCursorEnabled(false); + render_text->SetDisplayRect(Rect(0, 0, 3 * kGlyphWidth, 100)); + render_text->SetElideBehavior(ELIDE_TAIL); + render_text->SetWhitespaceElision(false); + EXPECT_EQ(u"a …", render_text->GetDisplayText()); + + // Setting a different whitespace elision must trigger a relayout. + render_text->SetWhitespaceElision(true); + EXPECT_EQ(u"a…", render_text->GetDisplayText()); +} + +TEST_F(RenderTextTest, ElidedObscuredText) { + auto expected_render_text = std::make_unique(); + expected_render_text->SetDisplayRect(Rect(0, 0, 9999, 100)); + const char16_t elided_obscured_text[] = {RenderText::kPasswordReplacementChar, + RenderText::kPasswordReplacementChar, + kEllipsisUTF16[0], 0}; + expected_render_text->SetText(elided_obscured_text); + + RenderText* render_text = GetRenderText(); + render_text->SetElideBehavior(ELIDE_TAIL); + render_text->SetDisplayRect( + Rect(0, 0, expected_render_text->GetContentWidth(), 100)); + render_text->SetObscured(true); + render_text->SetText(u"abcdef"); + EXPECT_EQ(u"abcdef", render_text->text()); + EXPECT_EQ(elided_obscured_text, render_text->GetDisplayText()); +} + +TEST_F(RenderTextTest, MultilineElide) { + RenderText* render_text = GetRenderText(); + std::u16string input_text; + // Aim for 3 lines of text. + for (int i = 0; i < 20; ++i) + input_text.append(u"hello world "); + render_text->SetText(input_text); + // Apply a style that tweaks the layout to make sure elision is calculated + // with these styles. This can expose a behavior in layout where text is + // slightly different width. This must be done after |SetText()|. + render_text->ApplyWeight(Font::Weight::BOLD, Range(1, 20)); + render_text->ApplyStyle(TEXT_STYLE_ITALIC, true, Range(1, 20)); + render_text->SetMultiline(true); + render_text->SetElideBehavior(ELIDE_TAIL); + render_text->SetMaxLines(3); + const Size size = render_text->GetStringSize(); + // Fit in 3 lines. (If we knew the width of a word, we could + // anticipate word wrap better.) + render_text->SetDisplayRect(Rect((size.width() + 96) / 3, 0)); + // Trigger rendering. + render_text->GetStringSize(); + EXPECT_EQ(input_text, render_text->GetDisplayText()); + + std::u16string actual_text; + // Try widening the space gradually, one pixel at a time, trying + // to trigger a failure in layout. There was an issue where, right at + // the edge of a word getting truncated, the estimate would be wrong + // and it would wrap instead. + for (int i = (size.width() - 12) / 3; i < (size.width() + 30) / 3; ++i) { + render_text->SetDisplayRect(Rect(i, 0)); + // Trigger rendering. + render_text->GetStringSize(); + actual_text = render_text->GetDisplayText(); + EXPECT_LT(actual_text.size(), input_text.size()); + EXPECT_EQ(actual_text, input_text.substr(0, actual_text.size() - 1) + + std::u16string(kEllipsisUTF16)); + EXPECT_EQ(3U, render_text->GetNumLines()); + } + // Now remove line restriction. + render_text->SetMaxLines(0); + render_text->GetStringSize(); + EXPECT_EQ(input_text, render_text->GetDisplayText()); + + // And put it back. + render_text->SetMaxLines(3); + render_text->GetStringSize(); + EXPECT_LT(actual_text.size(), input_text.size()); + EXPECT_EQ(actual_text, input_text.substr(0, actual_text.size() - 1) + + std::u16string(kEllipsisUTF16)); +} + +TEST_F(RenderTextTest, MultilineElideWrap) { + RenderText* render_text = GetRenderText(); + std::u16string input_text; + for (int i = 0; i < 20; ++i) + input_text.append(u"hello world "); + render_text->SetText(input_text); + render_text->SetMultiline(true); + render_text->SetMaxLines(3); + render_text->SetElideBehavior(ELIDE_TAIL); + render_text->SetDisplayRect(Rect(30, 0)); + + // ELIDE_LONG_WORDS doesn't make sense in multiline, and triggers assertion + // failure. + const WordWrapBehavior wrap_behaviors[] = { + IGNORE_LONG_WORDS, TRUNCATE_LONG_WORDS, WRAP_LONG_WORDS}; + for (auto wrap_behavior : wrap_behaviors) { + render_text->SetWordWrapBehavior(wrap_behavior); + render_text->GetStringSize(); + std::u16string actual_text = render_text->GetDisplayText(); + EXPECT_LE(actual_text.size(), input_text.size()); + EXPECT_EQ(actual_text, input_text.substr(0, actual_text.size() - 1) + + std::u16string(kEllipsisUTF16)); + EXPECT_LE(render_text->GetNumLines(), 3U); + } +} + +// TODO(crbug.com/866720): The current implementation of eliding is not aware +// of text styles. The elide text algorithm doesn't take into account the style +// properties when eliding the text. This lead to incorrect text size when the +// styles are applied. +TEST_F(RenderTextTest, DISABLED_MultilineElideWrapWithStyle) { + RenderText* render_text = GetRenderText(); + std::u16string input_text; + for (int i = 0; i < 20; ++i) + input_text.append(u"hello world "); + render_text->SetText(input_text); + render_text->ApplyWeight(Font::Weight::BOLD, Range(1, 20)); + render_text->ApplyStyle(TEXT_STYLE_ITALIC, true, Range(1, 20)); + render_text->SetMultiline(true); + render_text->SetMaxLines(3); + render_text->SetElideBehavior(ELIDE_TAIL); + render_text->SetDisplayRect(Rect(30, 0)); + + // ELIDE_LONG_WORDS doesn't make sense in multiline, and triggers assertion + // failure. + const WordWrapBehavior wrap_behaviors[] = { + IGNORE_LONG_WORDS, TRUNCATE_LONG_WORDS, WRAP_LONG_WORDS}; + for (auto wrap_behavior : wrap_behaviors) { + render_text->SetWordWrapBehavior(wrap_behavior); + render_text->GetStringSize(); + std::u16string actual_text = render_text->GetDisplayText(); + EXPECT_LE(actual_text.size(), input_text.size()); + EXPECT_EQ(actual_text, input_text.substr(0, actual_text.size() - 1) + + std::u16string(kEllipsisUTF16)); + EXPECT_LE(render_text->GetNumLines(), 3U); + } +} + +TEST_F(RenderTextTest, MultilineElideWrapStress) { + RenderText* render_text = GetRenderText(); + std::u16string input_text; + for (int i = 0; i < 20; ++i) + input_text.append(u"hello world "); + render_text->SetText(input_text); + render_text->SetMultiline(true); + render_text->SetMaxLines(3); + render_text->SetElideBehavior(ELIDE_TAIL); + + // ELIDE_LONG_WORDS doesn't make sense in multiline, and triggers assertion + // failure. + const WordWrapBehavior wrap_behaviors[] = { + IGNORE_LONG_WORDS, TRUNCATE_LONG_WORDS, WRAP_LONG_WORDS}; + for (auto wrap_behavior : wrap_behaviors) { + for (int i = 1; i < 60; ++i) { + SCOPED_TRACE(base::StringPrintf( + "MultilineElideWrapStress wrap_behavior = %d, width = %d", + wrap_behavior, i)); + + render_text->SetDisplayRect(Rect(i, 0)); + render_text->SetWordWrapBehavior(wrap_behavior); + render_text->GetStringSize(); + std::u16string actual_text = render_text->GetDisplayText(); + EXPECT_LE(actual_text.size(), input_text.size()); + EXPECT_LE(render_text->GetNumLines(), 3U); + } + } +} + +// TODO(crbug.com/866720): The current implementation of eliding is not aware +// of text styles. The elide text algorithm doesn't take into account the style +// properties when eliding the text. This lead to incorrect text size when the +// styles are applied. +TEST_F(RenderTextTest, DISABLED_MultilineElideWrapStressWithStyle) { + RenderText* render_text = GetRenderText(); + std::u16string input_text; + for (int i = 0; i < 20; ++i) + input_text.append(u"hello world "); + render_text->SetText(input_text); + render_text->ApplyWeight(Font::Weight::BOLD, Range(1, 20)); + render_text->SetMultiline(true); + render_text->SetMaxLines(3); + render_text->SetElideBehavior(ELIDE_TAIL); + + // ELIDE_LONG_WORDS doesn't make sense in multiline, and triggers assertion + // failure. + const WordWrapBehavior wrap_behaviors[] = { + IGNORE_LONG_WORDS, TRUNCATE_LONG_WORDS, WRAP_LONG_WORDS}; + for (auto wrap_behavior : wrap_behaviors) { + for (int i = 1; i < 60; ++i) { + SCOPED_TRACE(base::StringPrintf( + "MultilineElideWrapStress wrap_behavior = %d, width = %d", + wrap_behavior, i)); + + render_text->SetDisplayRect(Rect(i, 0)); + render_text->SetWordWrapBehavior(wrap_behavior); + render_text->GetStringSize(); + std::u16string actual_text = render_text->GetDisplayText(); + EXPECT_LE(actual_text.size(), input_text.size()); + EXPECT_LE(render_text->GetNumLines(), 3U); + } + } +} + +TEST_F(RenderTextTest, MultilineElideRTL) { + RenderText* render_text = GetRenderText(); + SetGlyphWidth(5); + + std::u16string input_text(u"זהו המסר של ההודעה"); + render_text->SetText(input_text); + render_text->SetCursorEnabled(false); + render_text->SetMultiline(true); + render_text->SetMaxLines(1); + render_text->SetElideBehavior(ELIDE_TAIL); + render_text->SetDisplayRect(Rect(45, 0)); + render_text->GetStringSize(); + + EXPECT_EQ(render_text->GetDisplayText(), + input_text.substr(0, 8) + std::u16string(kEllipsisUTF16)); + EXPECT_EQ(render_text->GetNumLines(), 1U); +} + +TEST_F(RenderTextTest, MultilineElideBiDi) { + RenderText* render_text = GetRenderText(); + SetGlyphWidth(5); + + std::u16string input_text(u"אa\nbcdבגדהefg\nhו"); + render_text->SetText(input_text); + render_text->SetCursorEnabled(false); + render_text->SetMultiline(true); + render_text->SetMaxLines(2); + render_text->SetElideBehavior(ELIDE_TAIL); + render_text->SetDisplayRect(Rect(30, 0)); + test_api()->EnsureLayout(); + + EXPECT_EQ(render_text->GetDisplayText(), + u"אa\nbcdבג" + std::u16string(kEllipsisUTF16)); + EXPECT_EQ(render_text->GetNumLines(), 2U); +} + +TEST_F(RenderTextTest, MultilineElideLinebreak) { + RenderText* render_text = GetRenderText(); + SetGlyphWidth(5); + + std::u16string input_text(u"hello\nworld"); + render_text->SetText(input_text); + render_text->SetCursorEnabled(false); + render_text->SetMultiline(true); + render_text->SetMaxLines(1); + render_text->SetElideBehavior(ELIDE_TAIL); + render_text->SetDisplayRect(Rect(100, 0)); + render_text->GetStringSize(); + + EXPECT_EQ(render_text->GetDisplayText(), + input_text.substr(0, 5) + std::u16string(kEllipsisUTF16)); + EXPECT_EQ(render_text->GetNumLines(), 1U); +} + +TEST_F(RenderTextTest, ElidedStyledTextRtl) { + static const char16_t* kInputTexts[] = { + u"http://ar.wikipedia.com/فحص", + u"testحص,", + u"حص,test", + u"…", + u"…test", + u"test…", + u"حص,test…", + u"ٱ", + u"\uFEFF", // BOM: Byte Order Marker + u"…\u200F", // Right to left marker. + }; + + for (const auto* raw_text : kInputTexts) { + std::u16string input_text(raw_text); + SCOPED_TRACE(u"ElidedStyledTextRtl text = " + input_text); + + RenderText* render_text = GetRenderText(); + render_text->SetText(input_text); + render_text->SetElideBehavior(ELIDE_TAIL); + render_text->SetStyle(TEXT_STYLE_STRIKE, true); + render_text->SetDirectionalityMode(DIRECTIONALITY_FORCE_LTR); + + constexpr int kMaxContentWidth = 2000; + for (int i = 0; i < kMaxContentWidth; ++i) { + SCOPED_TRACE(base::StringPrintf("ElidedStyledTextRtl width = %d", i)); + render_text->SetDisplayRect(Rect(i, 20)); + render_text->GetStringSize(); + std::u16string display_text = render_text->GetDisplayText(); + EXPECT_LE(display_text.size(), input_text.size()); + + // Every size of content width was tried. + if (display_text == input_text) + break; + } + } +} + +TEST_F(RenderTextTest, ElidedEmail) { + RenderText* render_text = GetRenderText(); + render_text->SetText(u"test@example.com"); + const Size size = render_text->GetStringSize(); + + const std::u16string long_email = u"longemailaddresstest@example.com"; + render_text->SetText(long_email); + render_text->SetElideBehavior(ELIDE_EMAIL); + render_text->SetDisplayRect(Rect(size)); + EXPECT_GE(size.width(), render_text->GetStringSize().width()); + EXPECT_GT(long_email.size(), render_text->GetDisplayText().size()); +} + +TEST_F(RenderTextTest, TruncatedText) { + struct { + const char16_t* text; + const char16_t* display_text; + } cases[] = { + // Strings shorter than the truncation length should be laid out in full. + {u"", u""}, + {u" . ", u" . "}, // a wide kWeak + {u"abc", u"abc"}, // a wide kLtr + {u"אבג", u"אבג"}, // a wide kRtl + {u"aאב", u"aאב"}, // a wide kLtrRtl + {u"aבb", u"aבb"}, // a wide kLtrRtlLtr + {u"אבa", u"אבa"}, // a wide kRtlLtr + {u"אaב", u"אaב"}, // a wide kRtlLtrRtl + {u"01234", u"01234"}, + // Long strings should be truncated with an ellipsis appended at the end. + {u"012345", u"0123…"}, + {u"012 . ", u"012 …"}, + {u"012abc", u"012a…"}, + {u"012aאב", u"012a…"}, + {u"012aבb", u"012a…"}, + {u"012אבג", u"012א…"}, + {u"012אבa", u"012א…"}, + {u"012אaב", u"012א…"}, + // Surrogate pairs should be truncated reasonably enough. + {u"0123\u0915\u093f", u"0123…"}, + {u"\u05e9\u05bc\u05c1\u05b8", u"\u05e9\u05bc\u05c1\u05b8"}, + {u"0\u05e9\u05bc\u05c1\u05b8", u"0\u05e9\u05bc\u05c1\u05b8"}, + {u"01\u05e9\u05bc\u05c1\u05b8", u"01…"}, + {u"012\u05e9\u05bc\u05c1\u05b8", u"012…"}, + // Codepoint U+0001D11E is using 2x 16-bit characters. + {u"0\U0001D11Eaaa", u"0\U0001D11Ea…"}, + {u"01\U0001D11Eaaa", u"01\U0001D11E…"}, + {u"012\U0001D11Eaaa", u"012…"}, + {u"0123\U0001D11Eaaa", u"0123…"}, + {u"01234\U0001D11Eaaa", u"0123…"}, + // Combining codepoint should stay together. + // (Letter 'e' U+0065 and acute accent U+0301). + {u"0e\u0301aaa", u"0e\u0301a…"}, + {u"01e\u0301aaa", u"01e\u0301…"}, + {u"012e\u0301aaa", u"012…"}, + // Emoji 'keycap letter 6'. + {u"\u0036\uFE0F\u20E3aaa", u"\u0036\uFE0F\u20E3a…"}, + {u"0\u0036\uFE0F\u20E3aaa", u"0\u0036\uFE0F\u20E3…"}, + {u"01\u0036\uFE0F\u20E3aaa", u"01…"}, + // Emoji 'pilot'. + {u"\U0001F468\u200D\u2708\uFE0F", u"\U0001F468\u200D\u2708\uFE0F"}, + {u"\U0001F468\u200D\u2708\uFE0F0", u"…"}, + {u"0\U0001F468\u200D\u2708\uFE0F", u"0…"}, + }; + + RenderText* render_text = GetRenderText(); + render_text->set_truncate_length(5); + for (size_t i = 0; i < base::size(cases); i++) { + render_text->SetText(cases[i].text); + EXPECT_EQ(cases[i].text, render_text->text()); + EXPECT_EQ(cases[i].display_text, render_text->GetDisplayText()) + << "For case " << i << ": " << cases[i].text; + } +} + +TEST_F(RenderTextTest, TruncatedObscuredText) { + RenderText* render_text = GetRenderText(); + render_text->set_truncate_length(3); + render_text->SetObscured(true); + render_text->SetText(u"abcdef"); + EXPECT_EQ(u"abcdef", render_text->text()); + EXPECT_EQ(GetObscuredString(3, 2, kEllipsisUTF16[0]), + render_text->GetDisplayText()); +} + +TEST_F(RenderTextTest, TruncatedObscuredTextWithGraphemes) { + RenderText* render_text = GetRenderText(); + render_text->set_truncate_length(3); + render_text->SetText(u"e\u0301\U0001F468\u200D\u2708\uFE0F\U0001D11E"); + render_text->SetObscured(true); + EXPECT_EQ(GetObscuredString(3), render_text->GetDisplayText()); + + render_text->SetObscuredRevealIndex(0); + EXPECT_EQ(u"e\u0301…", render_text->GetDisplayText()); + + render_text->SetObscuredRevealIndex(2); + EXPECT_EQ(u"\u2022…", render_text->GetDisplayText()); + + render_text->SetObscuredRevealIndex(7); + EXPECT_EQ(u"\u2022\u2022…", render_text->GetDisplayText()); +} + +TEST_F(RenderTextTest, TruncatedCursorMovementLTR) { + RenderText* render_text = GetRenderText(); + render_text->set_truncate_length(2); + render_text->SetText(u"abcd"); + + EXPECT_EQ(SelectionModel(0, CURSOR_BACKWARD), render_text->selection_model()); + render_text->MoveCursor(LINE_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(SelectionModel(4, CURSOR_FORWARD), render_text->selection_model()); + render_text->MoveCursor(LINE_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(SelectionModel(0, CURSOR_BACKWARD), render_text->selection_model()); + + std::vector expected; + expected.push_back(SelectionModel(0, CURSOR_BACKWARD)); + expected.push_back(SelectionModel(1, CURSOR_BACKWARD)); + // The cursor hops over the ellipsis and elided text to the line end. + expected.push_back(SelectionModel(4, CURSOR_BACKWARD)); + expected.push_back(SelectionModel(4, CURSOR_FORWARD)); + RunMoveCursorLeftRightTest(render_text, expected, CURSOR_RIGHT); + + expected.clear(); + expected.push_back(SelectionModel(4, CURSOR_FORWARD)); + // The cursor hops over the elided text to preceeding text. + expected.push_back(SelectionModel(1, CURSOR_FORWARD)); + expected.push_back(SelectionModel(0, CURSOR_FORWARD)); + expected.push_back(SelectionModel(0, CURSOR_BACKWARD)); + RunMoveCursorLeftRightTest(render_text, expected, CURSOR_LEFT); +} + +TEST_F(RenderTextTest, TruncatedCursorMovementRTL) { + RenderText* render_text = GetRenderText(); + render_text->set_truncate_length(2); + render_text->SetText(u"אבגד"); + + EXPECT_EQ(SelectionModel(0, CURSOR_BACKWARD), render_text->selection_model()); + render_text->MoveCursor(LINE_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(SelectionModel(4, CURSOR_FORWARD), render_text->selection_model()); + render_text->MoveCursor(LINE_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(SelectionModel(0, CURSOR_BACKWARD), render_text->selection_model()); + + std::vector expected; + expected.push_back(SelectionModel(0, CURSOR_BACKWARD)); + expected.push_back(SelectionModel(1, CURSOR_BACKWARD)); + // The cursor hops over the ellipsis and elided text to the line end. + expected.push_back(SelectionModel(4, CURSOR_BACKWARD)); + expected.push_back(SelectionModel(4, CURSOR_FORWARD)); + RunMoveCursorLeftRightTest(render_text, expected, CURSOR_LEFT); + + expected.clear(); + expected.push_back(SelectionModel(4, CURSOR_FORWARD)); + // The cursor hops over the elided text to preceeding text. + expected.push_back(SelectionModel(1, CURSOR_FORWARD)); + expected.push_back(SelectionModel(0, CURSOR_FORWARD)); + expected.push_back(SelectionModel(0, CURSOR_BACKWARD)); + RunMoveCursorLeftRightTest(render_text, expected, CURSOR_RIGHT); +} + +TEST_F(RenderTextTest, MoveCursor_Character) { + RenderText* render_text = GetRenderText(); + render_text->SetText(u"123 456 789"); + std::vector expected; + + // SELECTION_NONE. + render_text->SelectRange(Range(6)); + + // Move right twice. + expected.push_back(Range(7)); + expected.push_back(Range(8)); + RunMoveCursorTestAndClearExpectations( + render_text, CHARACTER_BREAK, CURSOR_RIGHT, SELECTION_NONE, &expected); + + // Move left twice. + expected.push_back(Range(7)); + expected.push_back(Range(6)); + RunMoveCursorTestAndClearExpectations(render_text, CHARACTER_BREAK, + CURSOR_LEFT, SELECTION_NONE, &expected); + + // SELECTION_CARET. + render_text->SelectRange(Range(6)); + expected.push_back(Range(6, 7)); + + // Move right. + RunMoveCursorTestAndClearExpectations( + render_text, CHARACTER_BREAK, CURSOR_RIGHT, SELECTION_CARET, &expected); + + // Move left twice. + expected.push_back(Range(6)); + expected.push_back(Range(6, 5)); + RunMoveCursorTestAndClearExpectations( + render_text, CHARACTER_BREAK, CURSOR_LEFT, SELECTION_CARET, &expected); + + // SELECTION_RETAIN. + render_text->SelectRange(Range(6)); + + // Move right. + expected.push_back(Range(6, 7)); + RunMoveCursorTestAndClearExpectations( + render_text, CHARACTER_BREAK, CURSOR_RIGHT, SELECTION_RETAIN, &expected); + + // Move left twice. + expected.push_back(Range(6)); + expected.push_back(Range(6, 5)); + RunMoveCursorTestAndClearExpectations( + render_text, CHARACTER_BREAK, CURSOR_LEFT, SELECTION_RETAIN, &expected); + + // SELECTION_EXTEND. + render_text->SelectRange(Range(6)); + + // Move right. + expected.push_back(Range(6, 7)); + RunMoveCursorTestAndClearExpectations( + render_text, CHARACTER_BREAK, CURSOR_RIGHT, SELECTION_EXTEND, &expected); + + // Move left twice. + expected.push_back(Range(7, 6)); + expected.push_back(Range(7, 5)); + RunMoveCursorTestAndClearExpectations( + render_text, CHARACTER_BREAK, CURSOR_LEFT, SELECTION_EXTEND, &expected); +} + +TEST_F(RenderTextTest, MoveCursor_Word) { + RenderText* render_text = GetRenderText(); + render_text->SetText(u"123 456 789"); + std::vector expected; + + // SELECTION_NONE. + render_text->SelectRange(Range(6)); + + // Move left twice. + expected.push_back(Range(4)); + expected.push_back(Range(0)); + RunMoveCursorTestAndClearExpectations(render_text, WORD_BREAK, CURSOR_LEFT, + SELECTION_NONE, &expected); + + // Move right twice. +#if defined(OS_WIN) // Move word right includes space/punctuation. + expected.push_back(Range(4)); + expected.push_back(Range(8)); +#else // Non-Windows: move word right does NOT include space/punctuation. + expected.push_back(Range(3)); + expected.push_back(Range(7)); +#endif + RunMoveCursorTestAndClearExpectations(render_text, WORD_BREAK, CURSOR_RIGHT, + SELECTION_NONE, &expected); + + // SELECTION_CARET. + render_text->SelectRange(Range(6)); + + // Move left. + expected.push_back(Range(6, 4)); + RunMoveCursorTestAndClearExpectations(render_text, WORD_BREAK, CURSOR_LEFT, + SELECTION_CARET, &expected); + + // Move right twice. + expected.push_back(Range(6)); +#if defined(OS_WIN) // Select word right includes space/punctuation. + expected.push_back(Range(6, 8)); +#else // Non-Windows: select word right does NOT include space/punctuation. + expected.push_back(Range(6, 7)); +#endif + RunMoveCursorTestAndClearExpectations(render_text, WORD_BREAK, CURSOR_RIGHT, + SELECTION_CARET, &expected); + + // Move left. + expected.push_back(Range(6)); + RunMoveCursorTestAndClearExpectations(render_text, WORD_BREAK, CURSOR_LEFT, + SELECTION_CARET, &expected); + + // SELECTION_RETAIN. + render_text->SelectRange(Range(6)); + + // Move left. + expected.push_back(Range(6, 4)); + RunMoveCursorTestAndClearExpectations(render_text, WORD_BREAK, CURSOR_LEFT, + SELECTION_RETAIN, &expected); + + // Move right twice. +#if defined(OS_WIN) // Select word right includes space/punctuation. + expected.push_back(Range(6, 8)); +#else // Non-Windows: select word right does NOT include space/punctuation. + expected.push_back(Range(6, 7)); +#endif + expected.push_back(Range(6, 11)); + RunMoveCursorTestAndClearExpectations(render_text, WORD_BREAK, CURSOR_RIGHT, + SELECTION_RETAIN, &expected); + + // Move left. + expected.push_back(Range(6, 8)); + RunMoveCursorTestAndClearExpectations(render_text, WORD_BREAK, CURSOR_LEFT, + SELECTION_RETAIN, &expected); + + // SELECTION_EXTEND. + render_text->SelectRange(Range(6)); + + // Move left. + expected.push_back(Range(6, 4)); + RunMoveCursorTestAndClearExpectations(render_text, WORD_BREAK, CURSOR_LEFT, + SELECTION_EXTEND, &expected); + + // Move right twice. +#if defined(OS_WIN) // Select word right includes space/punctuation. + expected.push_back(Range(4, 8)); +#else // Non-Windows: select word right does NOT include space/punctuation. + expected.push_back(Range(4, 7)); +#endif + expected.push_back(Range(4, 11)); + RunMoveCursorTestAndClearExpectations(render_text, WORD_BREAK, CURSOR_RIGHT, + SELECTION_EXTEND, &expected); + + // Move left. + expected.push_back(Range(4, 8)); + RunMoveCursorTestAndClearExpectations(render_text, WORD_BREAK, CURSOR_LEFT, + SELECTION_EXTEND, &expected); +} + +TEST_F(RenderTextTest, MoveCursor_Word_RTL) { + RenderText* render_text = GetRenderText(); + render_text->SetText(u"אבג דהו זחט"); + std::vector expected; + + // SELECTION_NONE. + render_text->SelectRange(Range(6)); + + // Move right twice. + expected.push_back(Range(4)); + expected.push_back(Range(0)); + RunMoveCursorTestAndClearExpectations(render_text, WORD_BREAK, CURSOR_RIGHT, + SELECTION_NONE, &expected); + + // Move left twice. +#if defined(OS_WIN) // Move word left includes space/punctuation. + expected.push_back(Range(4)); + expected.push_back(Range(8)); +#else // Non-Windows: move word left does NOT include space/punctuation. + expected.push_back(Range(3)); + expected.push_back(Range(7)); +#endif + RunMoveCursorTestAndClearExpectations(render_text, WORD_BREAK, CURSOR_LEFT, + SELECTION_NONE, &expected); + + // SELECTION_CARET. + render_text->SelectRange(Range(6)); + + // Move right. + expected.push_back(Range(6, 4)); + RunMoveCursorTestAndClearExpectations(render_text, WORD_BREAK, CURSOR_RIGHT, + SELECTION_CARET, &expected); + + // Move left twice. + expected.push_back(Range(6)); +#if defined(OS_WIN) // Select word left includes space/punctuation. + expected.push_back(Range(6, 8)); +#else // Non-Windows: select word left does NOT include space/punctuation. + expected.push_back(Range(6, 7)); +#endif + RunMoveCursorTestAndClearExpectations(render_text, WORD_BREAK, CURSOR_LEFT, + SELECTION_CARET, &expected); + + // Move right. + expected.push_back(Range(6)); + RunMoveCursorTestAndClearExpectations(render_text, WORD_BREAK, CURSOR_RIGHT, + SELECTION_CARET, &expected); + + // SELECTION_RETAIN. + render_text->SelectRange(Range(6)); + + // Move right. + expected.push_back(Range(6, 4)); + RunMoveCursorTestAndClearExpectations(render_text, WORD_BREAK, CURSOR_RIGHT, + SELECTION_RETAIN, &expected); + + // Move left twice. +#if defined(OS_WIN) // Select word left includes space/punctuation. + expected.push_back(Range(6, 8)); +#else // Non-Windows: select word left does NOT include space/punctuation. + expected.push_back(Range(6, 7)); +#endif + expected.push_back(Range(6, 11)); + RunMoveCursorTestAndClearExpectations(render_text, WORD_BREAK, CURSOR_LEFT, + SELECTION_RETAIN, &expected); + + // Move right. + expected.push_back(Range(6, 8)); + RunMoveCursorTestAndClearExpectations(render_text, WORD_BREAK, CURSOR_RIGHT, + SELECTION_RETAIN, &expected); + + // SELECTION_EXTEND. + render_text->SelectRange(Range(6)); + + // Move right. + expected.push_back(Range(6, 4)); + RunMoveCursorTestAndClearExpectations(render_text, WORD_BREAK, CURSOR_RIGHT, + SELECTION_EXTEND, &expected); + + // Move left twice. +#if defined(OS_WIN) // Select word left includes space/punctuation. + expected.push_back(Range(4, 8)); +#else // Non-Windows: select word left does NOT include space/punctuation. + expected.push_back(Range(4, 7)); +#endif + expected.push_back(Range(4, 11)); + RunMoveCursorTestAndClearExpectations(render_text, WORD_BREAK, CURSOR_LEFT, + SELECTION_EXTEND, &expected); + + // Move right. + expected.push_back(Range(4, 8)); + RunMoveCursorTestAndClearExpectations(render_text, WORD_BREAK, CURSOR_RIGHT, + SELECTION_EXTEND, &expected); +} + +TEST_F(RenderTextTest, MoveCursor_Line) { + RenderText* render_text = GetRenderText(); + render_text->SetText(u"123 456 789"); + std::vector expected; + + for (auto break_type : {LINE_BREAK, FIELD_BREAK}) { + // SELECTION_NONE. + render_text->SelectRange(Range(6)); + + // Move right twice. + expected.push_back(Range(11)); + expected.push_back(Range(11)); + RunMoveCursorTestAndClearExpectations(render_text, break_type, CURSOR_RIGHT, + SELECTION_NONE, &expected); + + // Move left twice. + expected.push_back(Range(0)); + expected.push_back(Range(0)); + RunMoveCursorTestAndClearExpectations(render_text, break_type, CURSOR_LEFT, + SELECTION_NONE, &expected); + + // SELECTION_CARET. + render_text->SelectRange(Range(6)); + + // Move right. + expected.push_back(Range(6, 11)); + RunMoveCursorTestAndClearExpectations(render_text, break_type, CURSOR_RIGHT, + SELECTION_CARET, &expected); + + // Move left twice. + expected.push_back(Range(6)); + expected.push_back(Range(6, 0)); + RunMoveCursorTestAndClearExpectations(render_text, break_type, CURSOR_LEFT, + SELECTION_CARET, &expected); + + // Move right. + expected.push_back(Range(6)); + RunMoveCursorTestAndClearExpectations(render_text, break_type, CURSOR_RIGHT, + SELECTION_CARET, &expected); + + // SELECTION_RETAIN. + render_text->SelectRange(Range(6)); + + // Move right. + expected.push_back(Range(6, 11)); + RunMoveCursorTestAndClearExpectations(render_text, break_type, CURSOR_RIGHT, + SELECTION_RETAIN, &expected); + + // Move left twice. + expected.push_back(Range(6, 0)); + expected.push_back(Range(6, 0)); + RunMoveCursorTestAndClearExpectations(render_text, break_type, CURSOR_LEFT, + SELECTION_RETAIN, &expected); + + // Move right. + expected.push_back(Range(6, 11)); + RunMoveCursorTestAndClearExpectations(render_text, break_type, CURSOR_RIGHT, + SELECTION_RETAIN, &expected); + + // SELECTION_EXTEND. + render_text->SelectRange(Range(6)); + + // Move right. + expected.push_back(Range(6, 11)); + RunMoveCursorTestAndClearExpectations(render_text, break_type, CURSOR_RIGHT, + SELECTION_EXTEND, &expected); + + // Move left twice. + expected.push_back(Range(11, 0)); + expected.push_back(Range(11, 0)); + RunMoveCursorTestAndClearExpectations(render_text, break_type, CURSOR_LEFT, + SELECTION_EXTEND, &expected); + + // Move right. + expected.push_back(Range(0, 11)); + RunMoveCursorTestAndClearExpectations(render_text, break_type, CURSOR_RIGHT, + SELECTION_EXTEND, &expected); + } +} + +TEST_F(RenderTextTest, MoveCursor_UpDown) { + SetGlyphWidth(5); + RenderText* render_text = GetRenderText(); + render_text->SetDisplayRect(Rect(45, 1000)); + render_text->SetMultiline(true); + + std::vector expected_lines; + std::vector expected_range; + for (auto* text : {u"123 456 123 456 ", u"אבג דהו זחט זחט "}) { + render_text->SetText(text); + EXPECT_EQ(2U, render_text->GetNumLines()); + + // SELECTION_NONE. + render_text->SelectRange(Range(0)); + ResetCursorX(); + + // Move down twice. + expected_lines.push_back(1); + expected_lines.push_back(1); + RunMoveCursorTestAndClearExpectations(render_text, CHARACTER_BREAK, + CURSOR_DOWN, SELECTION_NONE, + &expected_lines); + + // Move up twice. + expected_lines.push_back(0); + expected_lines.push_back(0); + RunMoveCursorTestAndClearExpectations(render_text, CHARACTER_BREAK, + CURSOR_UP, SELECTION_NONE, + &expected_lines); + + // SELECTION_CARET. + render_text->SelectRange(Range(0)); + ResetCursorX(); + + // Move down twice. + expected_range.push_back(Range(0, 8)); + expected_range.push_back(Range(0, 16)); + RunMoveCursorTestAndClearExpectations(render_text, CHARACTER_BREAK, + CURSOR_DOWN, SELECTION_CARET, + &expected_range); + + // Move up twice. + expected_range.push_back(Range(0, 8)); + expected_range.push_back(Range(0)); + RunMoveCursorTestAndClearExpectations(render_text, CHARACTER_BREAK, + CURSOR_UP, SELECTION_CARET, + &expected_range); + } +} + +TEST_F(RenderTextTest, MoveCursor_UpDown_Newline) { + SetGlyphWidth(5); + RenderText* render_text = GetRenderText(); + render_text->SetText(u"123 456\n123 456 "); + render_text->SetDisplayRect(Rect(100, 1000)); + render_text->SetMultiline(true); + EXPECT_EQ(2U, render_text->GetNumLines()); + + std::vector expected_lines; + std::vector expected_range; + + // SELECTION_NONE. + render_text->SelectRange(Range(0)); + ResetCursorX(); + + // Move down twice. + expected_lines.push_back(1); + expected_lines.push_back(1); + RunMoveCursorTestAndClearExpectations(render_text, CHARACTER_BREAK, + CURSOR_DOWN, SELECTION_NONE, + &expected_lines); + + // Move up twice. + expected_lines.push_back(0); + expected_lines.push_back(0); + RunMoveCursorTestAndClearExpectations(render_text, CHARACTER_BREAK, CURSOR_UP, + SELECTION_NONE, &expected_lines); + + // SELECTION_CARET. + render_text->SelectRange(Range(0)); + ResetCursorX(); + + // Move down twice. + expected_range.push_back(Range(0, 8)); + expected_range.push_back(Range(0, 16)); + RunMoveCursorTestAndClearExpectations(render_text, CHARACTER_BREAK, + CURSOR_DOWN, SELECTION_CARET, + &expected_range); + + // Move up twice. + expected_range.push_back(Range(0, 7)); + expected_range.push_back(Range(0)); + RunMoveCursorTestAndClearExpectations(render_text, CHARACTER_BREAK, CURSOR_UP, + SELECTION_CARET, &expected_range); +} + +TEST_F(RenderTextTest, MoveCursor_UpDown_EmptyLines) { + SetGlyphWidth(5); + RenderText* render_text = GetRenderText(); + render_text->SetText(u"one\n\ntwo three"); + render_text->SetDisplayRect(Rect(45, 1000)); + render_text->SetMultiline(true); + EXPECT_EQ(3U, render_text->GetNumLines()); + + std::vector expected_range; + + // Test up/down cursor movement at the start of the line. + render_text->SelectRange(Range(0)); + expected_range.push_back(Range(4)); + expected_range.push_back(Range(5)); + expected_range.push_back(Range(14)); + RunMoveCursorTestAndClearExpectations(render_text, CHARACTER_BREAK, + CURSOR_DOWN, SELECTION_NONE, + &expected_range); + + render_text->SelectRange(Range(5)); + expected_range.push_back(Range(4)); + expected_range.push_back(Range(0)); + expected_range.push_back(Range(0)); + RunMoveCursorTestAndClearExpectations(render_text, CHARACTER_BREAK, CURSOR_UP, + SELECTION_NONE, &expected_range); + + // Test up/down movement at the end of the line. + render_text->SelectRange(Range(3)); + expected_range.push_back(Range(4)); + expected_range.push_back(Range(8)); + expected_range.push_back(Range(14)); + RunMoveCursorTestAndClearExpectations(render_text, CHARACTER_BREAK, + CURSOR_DOWN, SELECTION_NONE, + &expected_range); + + render_text->SelectRange(Range(14)); + expected_range.push_back(Range(4)); + expected_range.push_back(Range(3)); + expected_range.push_back(Range(0)); + RunMoveCursorTestAndClearExpectations(render_text, CHARACTER_BREAK, CURSOR_UP, + SELECTION_NONE, &expected_range); + + // Test up/down movement somewhere in the middle of the line. + render_text->SelectRange(Range(2)); + expected_range.push_back(Range(4)); + expected_range.push_back(Range(7)); + expected_range.push_back(Range(14)); + RunMoveCursorTestAndClearExpectations(render_text, CHARACTER_BREAK, + CURSOR_DOWN, SELECTION_NONE, + &expected_range); + + render_text->SelectRange(Range(7)); + expected_range.push_back(Range(4)); + expected_range.push_back(Range(2)); + expected_range.push_back(Range(0)); + RunMoveCursorTestAndClearExpectations(render_text, CHARACTER_BREAK, CURSOR_UP, + SELECTION_NONE, &expected_range); +} + +TEST_F(RenderTextTest, MoveCursor_UpDown_Cache) { + SetGlyphWidth(5); + RenderText* render_text = GetRenderText(); + render_text->SetText(u"123 456\n\n123 456"); + render_text->SetDisplayRect(Rect(45, 1000)); + render_text->SetMultiline(true); + EXPECT_EQ(3U, render_text->GetNumLines()); + + std::vector expected_lines; + std::vector expected_range; + + // SELECTION_NONE. + render_text->SelectRange(Range(2)); + ResetCursorX(); + + // Move down twice. + expected_range.push_back(Range(8)); + expected_range.push_back(Range(11)); + RunMoveCursorTestAndClearExpectations(render_text, CHARACTER_BREAK, + CURSOR_DOWN, SELECTION_NONE, + &expected_range); + + // Move up twice. + expected_range.push_back(Range(8)); + expected_range.push_back(Range(2)); + RunMoveCursorTestAndClearExpectations(render_text, CHARACTER_BREAK, CURSOR_UP, + SELECTION_NONE, &expected_range); + + // Move left. + expected_range.push_back(Range(1)); + RunMoveCursorTestAndClearExpectations(render_text, CHARACTER_BREAK, + CURSOR_LEFT, SELECTION_NONE, + &expected_range); + + // Move down twice again. + expected_range.push_back(Range(8)); + expected_range.push_back(Range(10)); + RunMoveCursorTestAndClearExpectations(render_text, CHARACTER_BREAK, + CURSOR_DOWN, SELECTION_NONE, + &expected_range); +} + +TEST_F(RenderTextTest, MoveCursorWithNewline) { + RenderText* render_text = GetRenderText(); + render_text->SetText(u"a\r\nb"); + render_text->SetMultiline(false); + EXPECT_EQ(1U, render_text->GetNumLines()); + + EXPECT_EQ(SelectionModel(0, CURSOR_BACKWARD), render_text->selection_model()); + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(SelectionModel(1, CURSOR_BACKWARD), render_text->selection_model()); + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(SelectionModel(3, CURSOR_BACKWARD), render_text->selection_model()); + + render_text->MoveCursor(LINE_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(SelectionModel(4, CURSOR_FORWARD), render_text->selection_model()); +} + +TEST_F(RenderTextTest, GetTextDirectionInvalidation) { + RenderText* render_text = GetRenderText(); + ASSERT_EQ(render_text->directionality_mode(), DIRECTIONALITY_FROM_TEXT); + + const base::i18n::TextDirection original_text_direction = + render_text->GetTextDirection(); + + render_text->SetText(u"a"); + EXPECT_EQ(base::i18n::LEFT_TO_RIGHT, render_text->GetTextDirection()); + + render_text->SetText(u"א"); + EXPECT_EQ(base::i18n::RIGHT_TO_LEFT, render_text->GetTextDirection()); + + // The codepoints u+2026 (ellipsis) has no strong direction. + render_text->SetText(u"…"); + EXPECT_EQ(original_text_direction, render_text->GetTextDirection()); + render_text->AppendText(u"a"); + EXPECT_EQ(base::i18n::LEFT_TO_RIGHT, render_text->GetTextDirection()); + + render_text->SetText(u"…"); + EXPECT_EQ(original_text_direction, render_text->GetTextDirection()); + render_text->AppendText(u"א"); + EXPECT_EQ(base::i18n::RIGHT_TO_LEFT, render_text->GetTextDirection()); +} + +TEST_F(RenderTextTest, GetDisplayTextDirectionInvalidation) { + RenderText* render_text = GetRenderText(); + ASSERT_EQ(render_text->directionality_mode(), DIRECTIONALITY_FROM_TEXT); + + const base::i18n::TextDirection original_text_direction = + render_text->GetDisplayTextDirection(); + + render_text->SetText(u"a"); + EXPECT_EQ(base::i18n::LEFT_TO_RIGHT, render_text->GetDisplayTextDirection()); + + render_text->SetText(u"א"); + EXPECT_EQ(base::i18n::RIGHT_TO_LEFT, render_text->GetDisplayTextDirection()); + + // The codepoints u+2026 (ellipsis) has no strong direction. + render_text->SetText(u"…"); + EXPECT_EQ(original_text_direction, render_text->GetDisplayTextDirection()); + render_text->AppendText(u"a"); + EXPECT_EQ(base::i18n::LEFT_TO_RIGHT, render_text->GetDisplayTextDirection()); + + render_text->SetText(u"…"); + EXPECT_EQ(original_text_direction, render_text->GetDisplayTextDirection()); + render_text->AppendText(u"א"); + EXPECT_EQ(base::i18n::RIGHT_TO_LEFT, render_text->GetDisplayTextDirection()); +} + +TEST_F(RenderTextTest, GetTextDirectionWithDifferentDirection) { + SetGlyphWidth(10); + RenderText* render_text = GetRenderText(); + ASSERT_EQ(render_text->directionality_mode(), DIRECTIONALITY_FROM_TEXT); + render_text->SetWhitespaceElision(false); + render_text->SetText(u"123\u0638xyz"); + render_text->SetElideBehavior(ELIDE_HEAD); + render_text->SetDisplayRect(Rect(25, 100)); + + // The elided text is an ellipsis with neutral directionality, and a 'z' with + // a strong LTR directionality. + EXPECT_EQ(u"…z", render_text->GetDisplayText()); + EXPECT_EQ(base::i18n::RIGHT_TO_LEFT, render_text->GetTextDirection()); + EXPECT_EQ(base::i18n::LEFT_TO_RIGHT, render_text->GetDisplayTextDirection()); +} + +TEST_F(RenderTextTest, DirectionalityInvalidation) { + RenderText* render_text = GetRenderText(); + ASSERT_EQ(render_text->directionality_mode(), DIRECTIONALITY_FROM_TEXT); + + // The codepoints u+2026 (ellipsis) has weak directionality. + render_text->SetText(u"…"); + const base::i18n::TextDirection original_text_direction = + render_text->GetTextDirection(); + + render_text->SetDirectionalityMode(DIRECTIONALITY_FORCE_LTR); + EXPECT_EQ(base::i18n::LEFT_TO_RIGHT, render_text->GetTextDirection()); + EXPECT_EQ(base::i18n::LEFT_TO_RIGHT, render_text->GetDisplayTextDirection()); + + render_text->SetDirectionalityMode(DIRECTIONALITY_FORCE_RTL); + EXPECT_EQ(base::i18n::RIGHT_TO_LEFT, render_text->GetTextDirection()); + EXPECT_EQ(base::i18n::RIGHT_TO_LEFT, render_text->GetDisplayTextDirection()); + + render_text->SetDirectionalityMode(DIRECTIONALITY_FROM_TEXT); + EXPECT_EQ(original_text_direction, render_text->GetTextDirection()); + EXPECT_EQ(original_text_direction, render_text->GetDisplayTextDirection()); +} + +TEST_F(RenderTextTest, MoveCursor_UpDown_Scroll) { + RenderText* render_text = GetRenderText(); + render_text->SetDisplayRect(Rect(100, 30)); + render_text->SetMultiline(true); + render_text->SetVerticalAlignment(ALIGN_TOP); + + const size_t kLineSize = 50; + std::u16string text; + for (size_t i = 0; i < kLineSize - 1; ++i) + text += u"a\n"; + + render_text->SetText(text); + EXPECT_EQ(kLineSize, render_text->GetNumLines()); + + // Move cursor down with scroll. + render_text->SelectRange(Range(0)); + // |line_height| is the distance from the top. + float line_height = + render_text->GetLineSizeF(render_text->selection_model()).height(); + for (size_t i = 1; i < kLineSize; ++i) { + SCOPED_TRACE(base::StringPrintf("Testing line [%" PRIuS "]", i)); + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_DOWN, SELECTION_NONE); + ASSERT_EQ(Range(i * 2), render_text->selection()); + ASSERT_TRUE(render_text->display_rect().Contains( + render_text->GetUpdatedCursorBounds())); + line_height += + render_text->GetLineSizeF(render_text->selection_model()).height(); + ASSERT_FLOAT_EQ(test_api()->display_offset().y(), + std::min(0.0f, 30.0f - line_height)); + } + + // Move cursor up with scroll. + // |line_height| is the distance from the bottom. + line_height = + render_text->GetLineSizeF(render_text->selection_model()).height(); + int offset_y = test_api()->display_offset().y(); + for (size_t i = kLineSize - 2; i != static_cast(-1); --i) { + SCOPED_TRACE(base::StringPrintf("Testing line [%" PRIuS "]", i)); + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_UP, SELECTION_NONE); + ASSERT_EQ(Range(i * 2), render_text->selection()); + ASSERT_TRUE(render_text->display_rect().Contains( + render_text->GetUpdatedCursorBounds())); + line_height += + render_text->GetLineSizeF(render_text->selection_model()).height(); + ASSERT_FLOAT_EQ(test_api()->display_offset().y(), + offset_y + std::max(0.0f, line_height - 30.0f)); + } + EXPECT_EQ(0, test_api()->display_offset().y()); +} + +TEST_F(RenderTextTest, GetDisplayTextDirection) { + struct { + const char16_t* text; + const base::i18n::TextDirection text_direction; + } cases[] = { + // Blank strings and those with no/weak directionality default to LTR. + {u"", base::i18n::LEFT_TO_RIGHT}, + {kWeak, base::i18n::LEFT_TO_RIGHT}, + // Strings that begin with strong LTR characters. + {kLtr, base::i18n::LEFT_TO_RIGHT}, + {kLtrRtl, base::i18n::LEFT_TO_RIGHT}, + {kLtrRtlLtr, base::i18n::LEFT_TO_RIGHT}, + // Strings that begin with strong RTL characters. + {kRtl, base::i18n::RIGHT_TO_LEFT}, + {kRtlLtr, base::i18n::RIGHT_TO_LEFT}, + {kRtlLtrRtl, base::i18n::RIGHT_TO_LEFT}, + }; + + RenderText* render_text = GetRenderText(); + const bool was_rtl = base::i18n::IsRTL(); + + for (size_t i = 0; i < 2; ++i) { + // Toggle the application default text direction (to try each direction). + SetRTL(!base::i18n::IsRTL()); + const base::i18n::TextDirection ui_direction = base::i18n::IsRTL() ? + base::i18n::RIGHT_TO_LEFT : base::i18n::LEFT_TO_RIGHT; + + // Ensure that directionality modes yield the correct text directions. + for (size_t j = 0; j < base::size(cases); j++) { + render_text->SetText(cases[j].text); + render_text->SetDirectionalityMode(DIRECTIONALITY_FROM_TEXT); + EXPECT_EQ(render_text->GetDisplayTextDirection(),cases[j].text_direction); + render_text->SetDirectionalityMode(DIRECTIONALITY_FROM_UI); + EXPECT_EQ(render_text->GetDisplayTextDirection(), ui_direction); + render_text->SetDirectionalityMode(DIRECTIONALITY_FORCE_LTR); + EXPECT_EQ(render_text->GetDisplayTextDirection(), + base::i18n::LEFT_TO_RIGHT); + render_text->SetDirectionalityMode(DIRECTIONALITY_FORCE_RTL); + EXPECT_EQ(render_text->GetDisplayTextDirection(), + base::i18n::RIGHT_TO_LEFT); + render_text->SetDirectionalityMode(DIRECTIONALITY_AS_URL); + EXPECT_EQ(render_text->GetDisplayTextDirection(), + base::i18n::LEFT_TO_RIGHT); + } + } + + EXPECT_EQ(was_rtl, base::i18n::IsRTL()); + + // Ensure that text changes update the direction for DIRECTIONALITY_FROM_TEXT. + render_text->SetDirectionalityMode(DIRECTIONALITY_FROM_TEXT); + render_text->SetText(kLtr); + EXPECT_EQ(render_text->GetDisplayTextDirection(), base::i18n::LEFT_TO_RIGHT); + render_text->SetText(kRtl); + EXPECT_EQ(render_text->GetDisplayTextDirection(), base::i18n::RIGHT_TO_LEFT); +} + +struct GetTextIndexOfLineCase { + const char* test_name; + const char16_t* const text; + const std::vector line_breaks; + const bool set_word_wrap = false; + const bool set_obscured = false; +}; + +class RenderTextTestWithGetTextIndexOfLineCase + : public RenderTextTest, + public ::testing::WithParamInterface { + public: + static std::string ParamInfoToString( + ::testing::TestParamInfo param_info) { + return param_info.param.test_name; + } +}; + +TEST_P(RenderTextTestWithGetTextIndexOfLineCase, GetTextIndexOfLine) { + GetTextIndexOfLineCase param = GetParam(); + RenderText* render_text = GetRenderText(); + render_text->SetMultiline(true); + SetGlyphWidth(10); + if (param.set_word_wrap) { + render_text->SetDisplayRect(Rect(1, 1000)); + render_text->SetWordWrapBehavior(WRAP_LONG_WORDS); + } + render_text->SetObscured(param.set_obscured); + render_text->SetText(param.text); + for (size_t i = 0; i < param.line_breaks.size(); ++i) { + EXPECT_EQ(param.line_breaks[i], render_text->GetTextIndexOfLine(i)); + } +} + +const GetTextIndexOfLineCase kGetTextIndexOfLineCases[] = { + {"emptyString", u"", {0}}, + // The following test strings are three character strings. + // The word wrap makes each character fall on a new line. + {"kWeak_minWidth", u" . ", {0, 1, 2}, kUseWordWrap}, + {"kLtr_minWidth", u"abc", {0, 1, 2}, kUseWordWrap}, + {"kLtrRtl_minWidth", u"aאב", {0, 1, 2}, kUseWordWrap}, + {"kLtrRtlLtr_minWidth", u"aבb", {0, 1, 2}, kUseWordWrap}, + {"kRtl_minWidth", u"אבג", {0, 1, 2}, kUseWordWrap}, + {"kRtlLtr_minWidth", u"אבa", {0, 1, 2}, kUseWordWrap}, + {"kRtlLtrRtl_minWidth", u"אaב", {0, 1, 2}, kUseWordWrap}, + // The following test strings have 2 graphemes separated by a newline. + // The obscured text replace each grapheme by a single codepoint. + {"grapheme_unobscured", + u"\U0001F601\n\U0001F468\u200D\u2708\uFE0F\nx", + {0, 3, 9}}, + {"grapheme_obscured", + u"\U0001F601\n\U0001F468\u200D\u2708\uFE0F\nx", + {0, 3, 9}, + !kUseWordWrap, + kUseObscuredText}, + // The following test strings have a new line character. + {"basic_newLine", u"abc\ndef", {0, 4}}, + {"basic_newLineWindows", u"abc\r\ndef", {0, 5}}, + {"spaces_newLine", u"a \n b ", {0, 3}}, + {"spaces_newLineWindows", u"a \r\n b ", {0, 4}}, + {"double_newLine", u"a\n\nb", {0, 2, 3}}, + {"double_newLineWindows", u"a\r\n\r\nb", {0, 3, 5}}, + {"start_newLine", u"\nab", {0, 1}}, + {"start_newLineWindows", u"\r\nab", {0, 2}}, + {"end_newLine", u"ab\n", {0}}, + {"end_newLineWindows", u"ab\r\n", {0}}, + {"isolated_newLine", u"\n", {0}}, + {"isolated_newLineWindows", u"\r\n", {0}}, + {"isolatedDouble_newLine", u"\n\n", {0, 1}}, + {"isolatedDouble_newLineWindows", u"\r\n\r\n", {0, 2}}, + // The following test strings have unicode characters. + {"playSymbol_unicode", u"x\n\u25B6\ny", {0, 2, 4}}, + {"emoji_unicode", u"x\n\U0001F601\ny\n\u2728\nz", {0, 2, 5, 7, 9}}, + {"flag_unicode", u"🇬🇧\n🇯🇵", {0, 5}, false, false}, + // The following cases test that GetTextIndexOfLine returns the length of + // the text when passed a line index larger than the number of lines. + {"basic_outsideRange", u"abc", {0, 1, 2, 3, 3}, kUseWordWrap}, + {"emptyString_outsideRange", u"", {0, 0, 0}}, + {"newLine_outsideRange", u"\n", {0, 1, 1}}, + {"newLineWindows_outsideRange", u"\r\n", {0, 2, 2, 2}}, + {"doubleNewLine_outsideRange", u"\n\n", {0, 1, 2, 2}}, + {"doubleNewLineWindows_outsideRange", u"\r\n\r\n", {0, 2, 4, 4}}, +}; + +INSTANTIATE_TEST_SUITE_P( + GetTextIndexOfLine, + RenderTextTestWithGetTextIndexOfLineCase, + ::testing::ValuesIn(kGetTextIndexOfLineCases), + RenderTextTestWithGetTextIndexOfLineCase::ParamInfoToString); + +TEST_F(RenderTextTest, MoveCursorLeftRightInLtr) { + RenderText* render_text = GetRenderText(); + // Pure LTR. + render_text->SetText(u"abc"); + // |expected| saves the expected SelectionModel when moving cursor from left + // to right. + std::vector expected; + expected.push_back(SelectionModel(0, CURSOR_BACKWARD)); + expected.push_back(SelectionModel(1, CURSOR_BACKWARD)); + expected.push_back(SelectionModel(2, CURSOR_BACKWARD)); + expected.push_back(SelectionModel(3, CURSOR_BACKWARD)); + expected.push_back(SelectionModel(3, CURSOR_FORWARD)); + RunMoveCursorLeftRightTest(render_text, expected, CURSOR_RIGHT); + + expected.clear(); + expected.push_back(SelectionModel(3, CURSOR_FORWARD)); + expected.push_back(SelectionModel(2, CURSOR_FORWARD)); + expected.push_back(SelectionModel(1, CURSOR_FORWARD)); + expected.push_back(SelectionModel(0, CURSOR_FORWARD)); + expected.push_back(SelectionModel(0, CURSOR_BACKWARD)); + RunMoveCursorLeftRightTest(render_text, expected, CURSOR_LEFT); +} + +TEST_F(RenderTextTest, MoveCursorLeftRightInLtrRtl) { + RenderText* render_text = GetRenderText(); + // LTR-RTL + render_text->SetText(u"abcאבג"); + // The last one is the expected END position. + std::vector expected; + expected.push_back(SelectionModel(0, CURSOR_BACKWARD)); + expected.push_back(SelectionModel(1, CURSOR_BACKWARD)); + expected.push_back(SelectionModel(2, CURSOR_BACKWARD)); + expected.push_back(SelectionModel(3, CURSOR_BACKWARD)); + expected.push_back(SelectionModel(5, CURSOR_FORWARD)); + expected.push_back(SelectionModel(4, CURSOR_FORWARD)); + expected.push_back(SelectionModel(3, CURSOR_FORWARD)); + expected.push_back(SelectionModel(6, CURSOR_FORWARD)); + RunMoveCursorLeftRightTest(render_text, expected, CURSOR_RIGHT); + + expected.clear(); + expected.push_back(SelectionModel(6, CURSOR_FORWARD)); + expected.push_back(SelectionModel(4, CURSOR_BACKWARD)); + expected.push_back(SelectionModel(5, CURSOR_BACKWARD)); + expected.push_back(SelectionModel(6, CURSOR_BACKWARD)); + expected.push_back(SelectionModel(2, CURSOR_FORWARD)); + expected.push_back(SelectionModel(1, CURSOR_FORWARD)); + expected.push_back(SelectionModel(0, CURSOR_FORWARD)); + expected.push_back(SelectionModel(0, CURSOR_BACKWARD)); + RunMoveCursorLeftRightTest(render_text, expected, CURSOR_LEFT); +} + +TEST_F(RenderTextTest, MoveCursorLeftRightInLtrRtlLtr) { + RenderText* render_text = GetRenderText(); + // LTR-RTL-LTR. + render_text->SetText(u"aבb"); + std::vector expected; + expected.push_back(SelectionModel(0, CURSOR_BACKWARD)); + expected.push_back(SelectionModel(1, CURSOR_BACKWARD)); + expected.push_back(SelectionModel(1, CURSOR_FORWARD)); + expected.push_back(SelectionModel(3, CURSOR_BACKWARD)); + expected.push_back(SelectionModel(3, CURSOR_FORWARD)); + RunMoveCursorLeftRightTest(render_text, expected, CURSOR_RIGHT); + + expected.clear(); + expected.push_back(SelectionModel(3, CURSOR_FORWARD)); + expected.push_back(SelectionModel(2, CURSOR_FORWARD)); + expected.push_back(SelectionModel(2, CURSOR_BACKWARD)); + expected.push_back(SelectionModel(0, CURSOR_FORWARD)); + expected.push_back(SelectionModel(0, CURSOR_BACKWARD)); + RunMoveCursorLeftRightTest(render_text, expected, CURSOR_LEFT); +} + +TEST_F(RenderTextTest, MoveCursorLeftRightInRtl) { + RenderText* render_text = GetRenderText(); + // Pure RTL. + render_text->SetText(u"אבג"); + render_text->MoveCursor(LINE_BREAK, CURSOR_RIGHT, SELECTION_NONE); + std::vector expected; + + expected.push_back(SelectionModel(0, CURSOR_BACKWARD)); + expected.push_back(SelectionModel(1, CURSOR_BACKWARD)); + expected.push_back(SelectionModel(2, CURSOR_BACKWARD)); + expected.push_back(SelectionModel(3, CURSOR_BACKWARD)); + expected.push_back(SelectionModel(3, CURSOR_FORWARD)); + RunMoveCursorLeftRightTest(render_text, expected, CURSOR_LEFT); + + expected.clear(); + + expected.push_back(SelectionModel(3, CURSOR_FORWARD)); + expected.push_back(SelectionModel(2, CURSOR_FORWARD)); + expected.push_back(SelectionModel(1, CURSOR_FORWARD)); + expected.push_back(SelectionModel(0, CURSOR_FORWARD)); + expected.push_back(SelectionModel(0, CURSOR_BACKWARD)); + RunMoveCursorLeftRightTest(render_text, expected, CURSOR_RIGHT); +} + +TEST_F(RenderTextTest, MoveCursorLeftRightInRtlLtr) { + RenderText* render_text = GetRenderText(); + // RTL-LTR + render_text->SetText(u"אבגabc"); + render_text->MoveCursor(LINE_BREAK, CURSOR_RIGHT, SELECTION_NONE); + std::vector expected; + expected.push_back(SelectionModel(0, CURSOR_BACKWARD)); + expected.push_back(SelectionModel(1, CURSOR_BACKWARD)); + expected.push_back(SelectionModel(2, CURSOR_BACKWARD)); + expected.push_back(SelectionModel(3, CURSOR_BACKWARD)); + expected.push_back(SelectionModel(5, CURSOR_FORWARD)); + expected.push_back(SelectionModel(4, CURSOR_FORWARD)); + expected.push_back(SelectionModel(3, CURSOR_FORWARD)); + expected.push_back(SelectionModel(6, CURSOR_FORWARD)); + RunMoveCursorLeftRightTest(render_text, expected, CURSOR_LEFT); + + expected.clear(); + expected.push_back(SelectionModel(6, CURSOR_FORWARD)); + expected.push_back(SelectionModel(4, CURSOR_BACKWARD)); + expected.push_back(SelectionModel(5, CURSOR_BACKWARD)); + expected.push_back(SelectionModel(6, CURSOR_BACKWARD)); + expected.push_back(SelectionModel(2, CURSOR_FORWARD)); + expected.push_back(SelectionModel(1, CURSOR_FORWARD)); + expected.push_back(SelectionModel(0, CURSOR_FORWARD)); + expected.push_back(SelectionModel(0, CURSOR_BACKWARD)); + RunMoveCursorLeftRightTest(render_text, expected, CURSOR_RIGHT); +} + +TEST_F(RenderTextTest, MoveCursorLeftRightInRtlLtrRtl) { + RenderText* render_text = GetRenderText(); + // RTL-LTR-RTL. + render_text->SetText(u"אaב"); + render_text->MoveCursor(LINE_BREAK, CURSOR_RIGHT, SELECTION_NONE); + std::vector expected; + expected.push_back(SelectionModel(0, CURSOR_BACKWARD)); + expected.push_back(SelectionModel(1, CURSOR_BACKWARD)); + expected.push_back(SelectionModel(1, CURSOR_FORWARD)); + expected.push_back(SelectionModel(3, CURSOR_BACKWARD)); + expected.push_back(SelectionModel(3, CURSOR_FORWARD)); + RunMoveCursorLeftRightTest(render_text, expected, CURSOR_LEFT); + + expected.clear(); + expected.push_back(SelectionModel(3, CURSOR_FORWARD)); + expected.push_back(SelectionModel(2, CURSOR_FORWARD)); + expected.push_back(SelectionModel(2, CURSOR_BACKWARD)); + expected.push_back(SelectionModel(0, CURSOR_FORWARD)); + expected.push_back(SelectionModel(0, CURSOR_BACKWARD)); + RunMoveCursorLeftRightTest(render_text, expected, CURSOR_RIGHT); +} + +TEST_F(RenderTextTest, MoveCursorLeftRight_ComplexScript) { + RenderText* render_text = GetRenderText(); + render_text->SetText(u"\u0915\u093f\u0915\u094d\u0915"); + EXPECT_EQ(0U, render_text->cursor_position()); + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(2U, render_text->cursor_position()); + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(5U, render_text->cursor_position()); + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(5U, render_text->cursor_position()); + + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(2U, render_text->cursor_position()); + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(0U, render_text->cursor_position()); + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(0U, render_text->cursor_position()); +} + +TEST_F(RenderTextTest, MoveCursorLeftRight_MeiryoUILigatures) { + RenderText* render_text = GetRenderText(); + // Meiryo UI uses single-glyph ligatures for 'ff' and 'ffi', but each letter + // (code point) has unique bounds, so mid-glyph cursoring should be possible. + render_text->SetFontList(FontList("Meiryo UI, 12px")); + render_text->SetText(u"ff ffi"); + render_text->SetDisplayRect(gfx::Rect(100, 100)); + EXPECT_EQ(0U, render_text->cursor_position()); + + gfx::Rect last_selection_bounds = GetSelectionBoundsUnion(); + for (size_t i = 0; i < render_text->text().length(); ++i) { + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, SELECTION_RETAIN); + EXPECT_EQ(i + 1, render_text->cursor_position()); + + // Verify the selection bounds also increase and that the correct bounds are + // returned even when the grapheme boundary lies within a glyph. + const gfx::Rect selection_bounds = GetSelectionBoundsUnion(); + EXPECT_GT(selection_bounds.right(), last_selection_bounds.right()); + EXPECT_EQ(selection_bounds.x(), last_selection_bounds.x()); + last_selection_bounds = selection_bounds; + } + EXPECT_EQ(6U, render_text->cursor_position()); +} + +TEST_F(RenderTextTest, GraphemeIterator) { + RenderText* render_text = GetRenderText(); + render_text->SetText(u"a\u0065\u0301b"); + + internal::GraphemeIterator iterator = + render_text->GetGraphemeIteratorAtTextIndex(0); + + EXPECT_EQ(0U, render_text->GetTextIndex(iterator)); + EXPECT_EQ(0U, render_text->GetDisplayTextIndex(iterator)); + ++iterator; + EXPECT_EQ(1U, render_text->GetTextIndex(iterator)); + EXPECT_EQ(1U, render_text->GetDisplayTextIndex(iterator)); + ++iterator; + EXPECT_EQ(3U, render_text->GetTextIndex(iterator)); + EXPECT_EQ(3U, render_text->GetDisplayTextIndex(iterator)); + ++iterator; + EXPECT_EQ(4U, render_text->GetTextIndex(iterator)); + EXPECT_EQ(4U, render_text->GetDisplayTextIndex(iterator)); + + --iterator; + EXPECT_EQ(3U, render_text->GetTextIndex(iterator)); + EXPECT_EQ(3U, render_text->GetDisplayTextIndex(iterator)); + --iterator; + EXPECT_EQ(1U, render_text->GetTextIndex(iterator)); + EXPECT_EQ(1U, render_text->GetDisplayTextIndex(iterator)); + --iterator; + EXPECT_EQ(0U, render_text->GetTextIndex(iterator)); + EXPECT_EQ(0U, render_text->GetDisplayTextIndex(iterator)); + + iterator = render_text->GetGraphemeIteratorAtTextIndex(0); + EXPECT_EQ(0U, render_text->GetTextIndex(iterator)); + iterator = render_text->GetGraphemeIteratorAtTextIndex(1); + EXPECT_EQ(1U, render_text->GetTextIndex(iterator)); + iterator = render_text->GetGraphemeIteratorAtTextIndex(2); + EXPECT_EQ(1U, render_text->GetTextIndex(iterator)); + iterator = render_text->GetGraphemeIteratorAtTextIndex(3); + EXPECT_EQ(3U, render_text->GetTextIndex(iterator)); + + iterator = render_text->GetGraphemeIteratorAtDisplayTextIndex(0); + EXPECT_EQ(0U, render_text->GetDisplayTextIndex(iterator)); + iterator = render_text->GetGraphemeIteratorAtDisplayTextIndex(1); + EXPECT_EQ(1U, render_text->GetDisplayTextIndex(iterator)); + iterator = render_text->GetGraphemeIteratorAtDisplayTextIndex(2); + EXPECT_EQ(1U, render_text->GetDisplayTextIndex(iterator)); + iterator = render_text->GetGraphemeIteratorAtDisplayTextIndex(3); + EXPECT_EQ(3U, render_text->GetDisplayTextIndex(iterator)); + + render_text->SetText(u"e\u0301b"); + render_text->SetObscured(true); + iterator = render_text->GetGraphemeIteratorAtDisplayTextIndex(0); + EXPECT_EQ(0U, render_text->GetTextIndex(iterator)); + iterator = render_text->GetGraphemeIteratorAtDisplayTextIndex(1); + EXPECT_EQ(2U, render_text->GetTextIndex(iterator)); + render_text->SetObscured(false); + iterator = render_text->GetGraphemeIteratorAtDisplayTextIndex(0); + EXPECT_EQ(0U, render_text->GetTextIndex(iterator)); + iterator = render_text->GetGraphemeIteratorAtDisplayTextIndex(1); + EXPECT_EQ(0U, render_text->GetTextIndex(iterator)); + iterator = render_text->GetGraphemeIteratorAtDisplayTextIndex(2); + EXPECT_EQ(2U, render_text->GetTextIndex(iterator)); + + render_text->SetText(u"a\U0001F601b"); + render_text->SetObscured(true); + iterator = render_text->GetGraphemeIteratorAtDisplayTextIndex(0); + EXPECT_EQ(0U, render_text->GetTextIndex(iterator)); + EXPECT_EQ(0U, render_text->GetDisplayTextIndex(iterator)); + iterator = render_text->GetGraphemeIteratorAtDisplayTextIndex(1); + EXPECT_EQ(1U, render_text->GetTextIndex(iterator)); + EXPECT_EQ(1U, render_text->GetDisplayTextIndex(iterator)); + iterator = render_text->GetGraphemeIteratorAtDisplayTextIndex(2); + EXPECT_EQ(3U, render_text->GetTextIndex(iterator)); + EXPECT_EQ(2U, render_text->GetDisplayTextIndex(iterator)); + + render_text->SetText(u"\U0001F468\u200D\u2708\uFE0Fx"); + render_text->SetObscured(true); + iterator = render_text->GetGraphemeIteratorAtDisplayTextIndex(0); + EXPECT_EQ(0U, render_text->GetTextIndex(iterator)); + EXPECT_EQ(0U, render_text->GetDisplayTextIndex(iterator)); + iterator = render_text->GetGraphemeIteratorAtDisplayTextIndex(1); + EXPECT_EQ(5U, render_text->GetTextIndex(iterator)); + EXPECT_EQ(1U, render_text->GetDisplayTextIndex(iterator)); + render_text->SetObscured(false); + iterator = render_text->GetGraphemeIteratorAtDisplayTextIndex(0); + EXPECT_EQ(0U, render_text->GetTextIndex(iterator)); + EXPECT_EQ(0U, render_text->GetDisplayTextIndex(iterator)); + iterator = render_text->GetGraphemeIteratorAtDisplayTextIndex(1); + EXPECT_EQ(0U, render_text->GetTextIndex(iterator)); + EXPECT_EQ(0U, render_text->GetDisplayTextIndex(iterator)); + iterator = render_text->GetGraphemeIteratorAtDisplayTextIndex(5); + EXPECT_EQ(5U, render_text->GetTextIndex(iterator)); + EXPECT_EQ(5U, render_text->GetDisplayTextIndex(iterator)); +} + +TEST_F(RenderTextTest, GraphemeBoundaries) { + static const char16_t text[] = + u"\u0065\u0301" // Letter 'e' U+0065 and acute accent U+0301 + u"\u0036\uFE0F\u20E3" // Emoji 'keycap letter 6' + u"\U0001F468\u200D\u2708\uFE0F"; // Emoji 'pilot'. + + RenderText* render_text = GetRenderText(); + render_text->SetText(text); + + EXPECT_TRUE(render_text->IsGraphemeBoundary(0)); + EXPECT_FALSE(render_text->IsGraphemeBoundary(1)); + EXPECT_TRUE(render_text->IsGraphemeBoundary(2)); + EXPECT_FALSE(render_text->IsGraphemeBoundary(3)); + EXPECT_FALSE(render_text->IsGraphemeBoundary(4)); + EXPECT_TRUE(render_text->IsGraphemeBoundary(5)); + EXPECT_FALSE(render_text->IsGraphemeBoundary(6)); + EXPECT_FALSE(render_text->IsGraphemeBoundary(7)); + EXPECT_FALSE(render_text->IsGraphemeBoundary(8)); + EXPECT_FALSE(render_text->IsGraphemeBoundary(9)); + EXPECT_TRUE(render_text->IsGraphemeBoundary(10)); + + EXPECT_EQ(2U, render_text->IndexOfAdjacentGrapheme(0, CURSOR_FORWARD)); + EXPECT_EQ(2U, render_text->IndexOfAdjacentGrapheme(1, CURSOR_FORWARD)); + EXPECT_EQ(5U, render_text->IndexOfAdjacentGrapheme(2, CURSOR_FORWARD)); + EXPECT_EQ(5U, render_text->IndexOfAdjacentGrapheme(3, CURSOR_FORWARD)); + EXPECT_EQ(5U, render_text->IndexOfAdjacentGrapheme(4, CURSOR_FORWARD)); + EXPECT_EQ(10U, render_text->IndexOfAdjacentGrapheme(5, CURSOR_FORWARD)); + EXPECT_EQ(10U, render_text->IndexOfAdjacentGrapheme(6, CURSOR_FORWARD)); + EXPECT_EQ(10U, render_text->IndexOfAdjacentGrapheme(7, CURSOR_FORWARD)); + EXPECT_EQ(10U, render_text->IndexOfAdjacentGrapheme(8, CURSOR_FORWARD)); + EXPECT_EQ(10U, render_text->IndexOfAdjacentGrapheme(9, CURSOR_FORWARD)); + EXPECT_EQ(10U, render_text->IndexOfAdjacentGrapheme(10, CURSOR_FORWARD)); + + EXPECT_EQ(0U, render_text->IndexOfAdjacentGrapheme(0, CURSOR_BACKWARD)); + EXPECT_EQ(0U, render_text->IndexOfAdjacentGrapheme(1, CURSOR_BACKWARD)); + EXPECT_EQ(0U, render_text->IndexOfAdjacentGrapheme(2, CURSOR_BACKWARD)); + EXPECT_EQ(2U, render_text->IndexOfAdjacentGrapheme(3, CURSOR_BACKWARD)); + EXPECT_EQ(2U, render_text->IndexOfAdjacentGrapheme(4, CURSOR_BACKWARD)); + EXPECT_EQ(2U, render_text->IndexOfAdjacentGrapheme(5, CURSOR_BACKWARD)); + EXPECT_EQ(5U, render_text->IndexOfAdjacentGrapheme(6, CURSOR_BACKWARD)); + EXPECT_EQ(5U, render_text->IndexOfAdjacentGrapheme(7, CURSOR_BACKWARD)); + EXPECT_EQ(5U, render_text->IndexOfAdjacentGrapheme(8, CURSOR_BACKWARD)); + EXPECT_EQ(5U, render_text->IndexOfAdjacentGrapheme(9, CURSOR_BACKWARD)); + EXPECT_EQ(5U, render_text->IndexOfAdjacentGrapheme(10, CURSOR_BACKWARD)); +} + +TEST_F(RenderTextTest, GraphemePositions) { + // LTR कि (DEVANAGARI KA with VOWEL I) (2-char grapheme), LTR abc, and LTR कि. + const std::u16string kText1 = u"\u0915\u093fabc\u0915\u093f"; + + // LTR ab, LTR कि (DEVANAGARI KA with VOWEL I) (2-char grapheme), LTR cd. + const std::u16string kText2 = u"ab\u0915\u093fcd"; + + // LTR ab, 𝄞 'MUSICAL SYMBOL G CLEF' U+1D11E (surrogate pair), LTR cd. + // Windows requires wide strings for \Unnnnnnnn universal character names. + const std::u16string kText3 = u"ab\U0001D11Ecd"; + + struct { + std::u16string text; + size_t index; + size_t expected_previous; + size_t expected_next; + } cases[] = { + {std::u16string(), 0, 0, 0}, + {std::u16string(), 1, 0, 0}, + {std::u16string(), 50, 0, 0}, + {kText1, 0, 0, 2}, + {kText1, 1, 0, 2}, + {kText1, 2, 0, 3}, + {kText1, 3, 2, 4}, + {kText1, 4, 3, 5}, + {kText1, 5, 4, 7}, + {kText1, 6, 5, 7}, + {kText1, 7, 5, 7}, + {kText1, 8, 7, 7}, + {kText1, 50, 7, 7}, + {kText2, 0, 0, 1}, + {kText2, 1, 0, 2}, + {kText2, 2, 1, 4}, + {kText2, 3, 2, 4}, + {kText2, 4, 2, 5}, + {kText2, 5, 4, 6}, + {kText2, 6, 5, 6}, + {kText2, 7, 6, 6}, + {kText2, 50, 6, 6}, + {kText3, 0, 0, 1}, + {kText3, 1, 0, 2}, + {kText3, 2, 1, 4}, + {kText3, 3, 2, 4}, + {kText3, 4, 2, 5}, + {kText3, 5, 4, 6}, + {kText3, 6, 5, 6}, + {kText3, 7, 6, 6}, + {kText3, 50, 6, 6}, + }; + + RenderText* render_text = GetRenderText(); + for (size_t i = 0; i < base::size(cases); i++) { + SCOPED_TRACE(base::StringPrintf("Testing cases[%" PRIuS "]", i)); + render_text->SetText(cases[i].text); + + size_t next = render_text->IndexOfAdjacentGrapheme(cases[i].index, + CURSOR_FORWARD); + EXPECT_EQ(cases[i].expected_next, next); + EXPECT_TRUE(render_text->IsValidCursorIndex(next)); + + size_t previous = render_text->IndexOfAdjacentGrapheme(cases[i].index, + CURSOR_BACKWARD); + EXPECT_EQ(cases[i].expected_previous, previous); + EXPECT_TRUE(render_text->IsValidCursorIndex(previous)); + } +} + +TEST_F(RenderTextTest, MidGraphemeSelectionBounds) { + // Test that selection bounds may be set amid multi-character graphemes. + const std::u16string kHindi = u"\u0915\u093f"; + const std::u16string kThai = u"\u0e08\u0e33"; + const std::u16string cases[] = {kHindi, kThai}; + + RenderText* render_text = GetRenderText(); + render_text->SetDisplayRect(Rect(100, 1000)); + for (size_t i = 0; i < base::size(cases); i++) { + SCOPED_TRACE(base::StringPrintf("Testing cases[%" PRIuS "]", i)); + render_text->SetText(cases[i]); + EXPECT_TRUE(render_text->IsValidLogicalIndex(1)); + EXPECT_FALSE(render_text->IsValidCursorIndex(1)); + EXPECT_TRUE(render_text->SelectRange(Range(2, 1))); + EXPECT_EQ(Range(2, 1), render_text->selection()); + EXPECT_EQ(1U, render_text->cursor_position()); + + // Verify that the selection bounds extend over the entire grapheme, even if + // the selection is set amid the grapheme. + const gfx::Rect mid_grapheme_bounds = GetSelectionBoundsUnion(); + render_text->SelectAll(false); + EXPECT_EQ(GetSelectionBoundsUnion(), mid_grapheme_bounds); + + // Although selection bounds may be set within a multi-character grapheme, + // cursor movement (e.g. via arrow key) should avoid those indices. + EXPECT_TRUE(render_text->SelectRange(Range(2, 1))); + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(0U, render_text->cursor_position()); + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(2U, render_text->cursor_position()); + } +} + +TEST_F(RenderTextTest, FindCursorPosition) { + const char16_t* kTestStrings[] = {kLtrRtl, kLtrRtlLtr, kRtlLtr, kRtlLtrRtl}; + RenderText* render_text = GetRenderText(); + render_text->SetDisplayRect(Rect(0, 0, 100, 20)); + for (size_t i = 0; i < base::size(kTestStrings); ++i) { + SCOPED_TRACE(base::StringPrintf("Testing case[%" PRIuS "]", i)); + render_text->SetText(kTestStrings[i]); + for (size_t j = 0; j < render_text->text().length(); ++j) { + gfx::RangeF cursor_span = render_text->GetCursorSpan(Range(j, j + 1)); + // Test a point just inside the leading edge of the glyph bounds. + float x = cursor_span.start() + (cursor_span.is_reversed() ? -1 : 1); + Point point = gfx::ToCeiledPoint(PointF(x, GetCursorYForTesting())); + EXPECT_EQ(j, render_text->FindCursorPosition(point).caret_pos()); + } + } +} + +TEST_F(RenderTextTest, GetCursorBoundsMultilineLTR) { + const int kGlyphWidth = 5; + const int kGlyphHeight = 6; + SetGlyphWidth(kGlyphWidth); + SetGlyphHeight(kGlyphHeight); + RenderText* render_text = GetRenderText(); + render_text->SetCursorEnabled(true); + render_text->SetDisplayRect(Rect(45, 100)); + render_text->SetMultiline(true); + render_text->SetText(u"one\n\ntwo three"); + + const auto& text = render_text->GetDisplayText(); + int expected_x = 0; + int current_line = 0; + for (size_t i = 0; i < text.size(); ++i) { + EXPECT_EQ( + expected_x, + render_text->GetCursorBounds(SelectionModel(i, CURSOR_FORWARD), true) + .x()); + EXPECT_EQ( + render_text->GetLineOffset(current_line).y(), + render_text->GetCursorBounds(SelectionModel(i, CURSOR_FORWARD), true) + .y()); + + EXPECT_EQ( + expected_x, + render_text->GetCursorBounds(SelectionModel(i, CURSOR_BACKWARD), true) + .x()); + EXPECT_EQ( + render_text->GetLineOffset(current_line).y(), + render_text->GetCursorBounds(SelectionModel(i, CURSOR_BACKWARD), true) + .y()); + + if (text[i] == '\n') { + expected_x = 0; + ++current_line; + } else { + expected_x += kGlyphWidth; + } + } +} + +TEST_F(RenderTextTest, GetCursorBoundsMultilineCarriageReturn) { + const int kGlyphWidth = 5; + const int kGlyphHeight = 6; + SetGlyphWidth(kGlyphWidth); + SetGlyphHeight(kGlyphHeight); + RenderText* render_text = GetRenderText(); + render_text->SetCursorEnabled(true); + render_text->SetDisplayRect(Rect(45, 100)); + render_text->SetMultiline(true); + render_text->SetText(u"abc\n\n\ndef\r\n\r\n"); + + const auto& text = render_text->GetDisplayText(); + int expected_x = 0; + int current_line = 0; + for (size_t i = 0; i < text.size(); ++i) { + EXPECT_EQ( + expected_x, + render_text->GetCursorBounds(SelectionModel(i, CURSOR_FORWARD), true) + .x()); + EXPECT_EQ( + render_text->GetLineOffset(current_line).y(), + render_text->GetCursorBounds(SelectionModel(i, CURSOR_FORWARD), true) + .y()); + + if (text[i] == '\n') { + expected_x = 0; + ++current_line; + } else if (text[i] != '\r') { + // \r isn't a valid cursor position, since \r\n is a single 0-width glyph, + // everything else 5px + expected_x += kGlyphWidth; + } + } +} + +TEST_F(RenderTextTest, GetCursorBoundsMultilineRTL) { + const int kGlyphWidth = 5; + const int kGlyphHeight = 6; + SetGlyphWidth(kGlyphWidth); + SetGlyphHeight(kGlyphHeight); + RenderText* render_text = GetRenderText(); + render_text->SetCursorEnabled(true); + render_text->SetDisplayRect(Rect(45, 100)); + render_text->SetMultiline(true); + render_text->SetText(u"אבג\n\nאבג אבגא"); + + // Line 1 is 3 codepoints, 5px wide each. Line 2 has 0 codepoints, and Line 3 + // has 8 codepoints, 5px wide each. + const int kExpectedFirstGlyphInLineX[] = {15, 0, 40}; + const auto& text = render_text->GetDisplayText(); + int expected_x_offset = 0; + int current_line = 0; + for (size_t i = 0; i < text.size(); ++i) { + EXPECT_EQ( + kExpectedFirstGlyphInLineX[current_line] - expected_x_offset, + render_text->GetCursorBounds(SelectionModel(i, CURSOR_FORWARD), true) + .x()); + EXPECT_EQ( + render_text->GetLineOffset(current_line).y(), + render_text->GetCursorBounds(SelectionModel(i, CURSOR_FORWARD), true) + .y()); + + EXPECT_EQ( + kExpectedFirstGlyphInLineX[current_line] - expected_x_offset, + render_text->GetCursorBounds(SelectionModel(i, CURSOR_BACKWARD), true) + .x()); + EXPECT_EQ( + render_text->GetLineOffset(current_line).y(), + render_text->GetCursorBounds(SelectionModel(i, CURSOR_BACKWARD), true) + .y()); + + if (text[i] == '\n') { + expected_x_offset = 0; + ++current_line; + } else { + expected_x_offset += kGlyphWidth; + } + } +} + +// Tests that FindCursorPosition behaves correctly for multi-line text. +TEST_F(RenderTextTest, FindCursorPositionMultiline) { + const char16_t* kTestStrings[] = {u"abc def", u"אבג דהו"}; + + SetGlyphWidth(5); + RenderText* render_text = GetRenderText(); + render_text->SetDisplayRect(Rect(25, 1000)); + render_text->SetMultiline(true); + + for (size_t i = 0; i < base::size(kTestStrings); i++) { + render_text->SetText(kTestStrings[i]); + EXPECT_EQ(2u, render_text->GetNumLines()); + + const bool is_ltr = + render_text->GetDisplayTextDirection() == base::i18n::LEFT_TO_RIGHT; + for (size_t j = 0; j < render_text->text().length(); ++j) { + SCOPED_TRACE(base::StringPrintf( + "Testing index %" PRIuS " for case %" PRIuS "", j, i)); + render_text->SelectRange(Range(j, j + 1)); + + // Test a point inside the leading edge of the glyph bounds. + const Rect bounds = GetSelectionBoundsUnion(); + const Point cursor_position(is_ltr ? bounds.x() + 1 : bounds.right() - 1, + bounds.y() + 1); + + const SelectionModel model = + render_text->FindCursorPosition(cursor_position); + EXPECT_EQ(j, model.caret_pos()); + EXPECT_EQ(CURSOR_FORWARD, model.caret_affinity()); + } + } +} + +// Ensure FindCursorPosition returns positions only at valid grapheme +// boundaries. +TEST_F(RenderTextTest, FindCursorPosition_GraphemeBoundaries) { + struct { + std::u16string text; + std::set expected_cursor_positions; + } cases[] = { + // LTR कि (DEVANAGARI KA with VOWEL I) (2-char grapheme), LTR abc, LTR कि. + {u"\u0915\u093fabc\u0915\u093f", {0, 2, 3, 4, 5, 7}}, + // LTR ab, LTR कि (DEVANAGARI KA with VOWEL I) (2-char grapheme), LTR cd. + {u"ab\u0915\u093fcd", {0, 1, 2, 4, 5, 6}}, + // LTR ab, surrogate pair composed of two 16 bit characters, LTR cd. + // Windows requires wide strings for \Unnnnnnnn universal character names. + {u"ab\U0001D11Ecd", {0, 1, 2, 4, 5, 6}}}; + + RenderText* render_text = GetRenderText(); + render_text->SetDisplayRect(gfx::Rect(100, 30)); + for (size_t i = 0; i < base::size(cases); i++) { + SCOPED_TRACE(base::StringPrintf("Testing case %" PRIuS "", i)); + render_text->SetText(cases[i].text); + std::set obtained_cursor_positions; + size_t cursor_y = GetCursorYForTesting(); + for (int x = -5; x < 105; x++) + obtained_cursor_positions.insert( + render_text->FindCursorPosition(gfx::Point(x, cursor_y)).caret_pos()); + EXPECT_EQ(cases[i].expected_cursor_positions, obtained_cursor_positions); + } +} + +TEST_F(RenderTextTest, EdgeSelectionModels) { + // Simple Latin text. + const std::u16string kLatin = u"abc"; + // LTR कि (DEVANAGARI KA with VOWEL I). + const std::u16string kLTRGrapheme = u"\u0915\u093f"; + // LTR कि (DEVANAGARI KA with VOWEL I), LTR a, LTR कि. + const std::u16string kHindiLatin = u"\u0915\u093fa\u0915\u093f"; + // RTL נָ (Hebrew letter NUN and point QAMATS). + const std::u16string kRTLGrapheme = u"\u05e0\u05b8"; + // RTL נָ (Hebrew letter NUN and point QAMATS), LTR a, RTL נָ. + const std::u16string kHebrewLatin = u"\u05e0\u05b8a\u05e0\u05b8"; + + struct { + std::u16string text; + base::i18n::TextDirection expected_text_direction; + } cases[] = { + {std::u16string(), base::i18n::LEFT_TO_RIGHT}, + {kLatin, base::i18n::LEFT_TO_RIGHT}, + {kLTRGrapheme, base::i18n::LEFT_TO_RIGHT}, + {kHindiLatin, base::i18n::LEFT_TO_RIGHT}, + {kRTLGrapheme, base::i18n::RIGHT_TO_LEFT}, + {kHebrewLatin, base::i18n::RIGHT_TO_LEFT}, + }; + + RenderText* render_text = GetRenderText(); + for (size_t i = 0; i < base::size(cases); i++) { + render_text->SetText(cases[i].text); + bool ltr = (cases[i].expected_text_direction == base::i18n::LEFT_TO_RIGHT); + + SelectionModel start_edge = + test_api()->EdgeSelectionModel(ltr ? CURSOR_LEFT : CURSOR_RIGHT); + EXPECT_EQ(start_edge, SelectionModel(0, CURSOR_BACKWARD)); + + SelectionModel end_edge = + test_api()->EdgeSelectionModel(ltr ? CURSOR_RIGHT : CURSOR_LEFT); + EXPECT_EQ(end_edge, SelectionModel(cases[i].text.length(), CURSOR_FORWARD)); + } +} + +TEST_F(RenderTextTest, SelectAll) { + const char16_t* const cases[] = {kWeak, kLtr, kLtrRtl, kLtrRtlLtr, + kRtl, kRtlLtr, kRtlLtrRtl}; + + // Ensure that SelectAll respects the |reversed| argument regardless of + // application locale and text content directionality. + RenderText* render_text = GetRenderText(); + const SelectionModel expected_reversed(Range(3, 0), CURSOR_FORWARD); + const SelectionModel expected_forwards(Range(0, 3), CURSOR_BACKWARD); + const bool was_rtl = base::i18n::IsRTL(); + + for (size_t i = 0; i < 2; ++i) { + SetRTL(!base::i18n::IsRTL()); + // Test that an empty string produces an empty selection model. + render_text->SetText(std::u16string()); + EXPECT_EQ(render_text->selection_model(), SelectionModel()); + + // Test the weak, LTR, RTL, and Bidi string cases. + for (size_t j = 0; j < base::size(cases); j++) { + render_text->SetText(cases[j]); + render_text->SelectAll(false); + EXPECT_EQ(render_text->selection_model(), expected_forwards); + render_text->SelectAll(true); + EXPECT_EQ(render_text->selection_model(), expected_reversed); + } + } + + EXPECT_EQ(was_rtl, base::i18n::IsRTL()); +} + +TEST_F(RenderTextTest, MoveCursorLeftRightWithSelection) { + RenderText* render_text = GetRenderText(); + render_text->SetText(u"abcאבג"); + // Left arrow on select ranging (6, 4). + render_text->MoveCursor(LINE_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(Range(6), render_text->selection()); + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(Range(4), render_text->selection()); + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(Range(5), render_text->selection()); + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(Range(6), render_text->selection()); + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, SELECTION_RETAIN); + EXPECT_EQ(Range(6, 5), render_text->selection()); + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, SELECTION_RETAIN); + EXPECT_EQ(Range(6, 4), render_text->selection()); + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(Range(6), render_text->selection()); + + // Right arrow on select ranging (4, 6). + render_text->MoveCursor(LINE_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(Range(0), render_text->selection()); + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(Range(1), render_text->selection()); + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(Range(2), render_text->selection()); + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(Range(3), render_text->selection()); + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(Range(5), render_text->selection()); + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(Range(4), render_text->selection()); + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_LEFT, SELECTION_RETAIN); + EXPECT_EQ(Range(4, 5), render_text->selection()); + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_LEFT, SELECTION_RETAIN); + EXPECT_EQ(Range(4, 6), render_text->selection()); + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(Range(4), render_text->selection()); +} + +TEST_F(RenderTextTest, MoveCursorLeftRightWithSelection_Multiline) { + SetGlyphWidth(5); + RenderText* render_text = GetRenderText(); + render_text->SetMultiline(true); + render_text->SetDisplayRect(Rect(20, 1000)); + render_text->SetText(u"012 456\n\n90"); + EXPECT_EQ(4U, render_text->GetNumLines()); + + // Move cursor right to the end of the text. + render_text->MoveCursor(LINE_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(Range(4), render_text->selection()); + EXPECT_EQ(0U, GetLineContainingCaret()); + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(Range(5), render_text->selection()); + EXPECT_EQ(1U, GetLineContainingCaret()); + render_text->MoveCursor(LINE_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(Range(7), render_text->selection()); + EXPECT_EQ(1U, GetLineContainingCaret()); + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(Range(8), render_text->selection()); + EXPECT_EQ(2U, GetLineContainingCaret()); + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(Range(9), render_text->selection()); + EXPECT_EQ(3U, GetLineContainingCaret()); + render_text->MoveCursor(LINE_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(Range(11), render_text->selection()); + EXPECT_EQ(3U, GetLineContainingCaret()); + + // Move cursor left to the beginning of the text. + render_text->MoveCursor(LINE_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(Range(9), render_text->selection()); + EXPECT_EQ(3U, GetLineContainingCaret()); + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(Range(8), render_text->selection()); + EXPECT_EQ(2U, GetLineContainingCaret()); + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(Range(7), render_text->selection()); + EXPECT_EQ(1U, GetLineContainingCaret()); + render_text->MoveCursor(LINE_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(Range(4), render_text->selection()); + EXPECT_EQ(1U, GetLineContainingCaret()); + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(Range(3), render_text->selection()); + EXPECT_EQ(0U, GetLineContainingCaret()); + render_text->MoveCursor(LINE_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(Range(0), render_text->selection()); + EXPECT_EQ(0U, GetLineContainingCaret()); + + // Move cursor right with WORD_BREAK. + render_text->MoveCursor(WORD_BREAK, CURSOR_RIGHT, SELECTION_NONE); +#if defined(OS_WIN) + EXPECT_EQ(Range(4), render_text->selection()); +#else + EXPECT_EQ(Range(3), render_text->selection()); +#endif + EXPECT_EQ(0U, GetLineContainingCaret()); + render_text->MoveCursor(WORD_BREAK, CURSOR_RIGHT, SELECTION_NONE); +#if defined(OS_WIN) + EXPECT_EQ(Range(9), render_text->selection()); + EXPECT_EQ(3U, GetLineContainingCaret()); +#else + EXPECT_EQ(Range(7), render_text->selection()); + EXPECT_EQ(1U, GetLineContainingCaret()); +#endif + render_text->MoveCursor(WORD_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(Range(11), render_text->selection()); + EXPECT_EQ(3U, GetLineContainingCaret()); + + // Move cursor left with WORD_BREAK. + render_text->MoveCursor(WORD_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(Range(9), render_text->selection()); + EXPECT_EQ(3U, GetLineContainingCaret()); + render_text->MoveCursor(WORD_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(Range(4), render_text->selection()); + EXPECT_EQ(1U, GetLineContainingCaret()); + render_text->MoveCursor(WORD_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(Range(0), render_text->selection()); + EXPECT_EQ(0U, GetLineContainingCaret()); + + // Move cursor right with FIELD_BREAK. + render_text->MoveCursor(FIELD_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(Range(11), render_text->selection()); + EXPECT_EQ(3U, GetLineContainingCaret()); + + // Move cursor left with FIELD_BREAK. + render_text->MoveCursor(FIELD_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(Range(0), render_text->selection()); + EXPECT_EQ(0U, GetLineContainingCaret()); +} + +TEST_F(RenderTextTest, CenteredDisplayOffset) { + RenderText* render_text = GetRenderText(); + render_text->SetText(u"abcdefghij"); + render_text->SetHorizontalAlignment(ALIGN_CENTER); + + const int kEnlargement = 10; + const int content_width = render_text->GetContentWidth(); + Rect display_rect(0, 0, content_width / 2, + render_text->font_list().GetHeight()); + render_text->SetDisplayRect(display_rect); + + // Move the cursor to the beginning of the text and, by checking the cursor + // bounds, make sure no empty space is to the left of the text. + render_text->SetCursorPosition(0); + EXPECT_EQ(display_rect.x(), render_text->GetUpdatedCursorBounds().x()); + + // Widen the display rect and, by checking the cursor bounds, make sure no + // empty space is introduced to the left of the text. + display_rect.Inset(0, 0, -kEnlargement, 0); + render_text->SetDisplayRect(display_rect); + EXPECT_EQ(display_rect.x(), render_text->GetUpdatedCursorBounds().x()); + + // Move the cursor to the end of the text and, by checking the cursor + // bounds, make sure no empty space is to the right of the text. + render_text->SetCursorPosition(render_text->text().length()); + EXPECT_EQ(display_rect.right(), + render_text->GetUpdatedCursorBounds().right()); + + // Widen the display rect and, by checking the cursor bounds, make sure no + // empty space is introduced to the right of the text. + display_rect.Inset(0, 0, -kEnlargement, 0); + render_text->SetDisplayRect(display_rect); + EXPECT_EQ(display_rect.right(), + render_text->GetUpdatedCursorBounds().right()); +} + +void MoveLeftRightByWordVerifier(RenderText* render_text, const char16_t* str) { + SCOPED_TRACE(str); + const std::u16string str16(str); + render_text->SetText(str16); + + // Test moving by word from left to right. + render_text->MoveCursor(LINE_BREAK, CURSOR_LEFT, SELECTION_NONE); + const size_t num_words = (str16.length() + 1) / 4; + for (size_t i = 0; i < num_words; ++i) { + // First, test moving by word from a word break position, such as from + // "|abc def" to "abc| def". + const SelectionModel start = render_text->selection_model(); + render_text->MoveCursor(WORD_BREAK, CURSOR_RIGHT, SELECTION_NONE); + const SelectionModel end = render_text->selection_model(); + + // For testing simplicity, each word is a 3-character word. +#if defined(OS_WIN) + // Windows moves from "|abc def" to "abc |def" instead of "abc| def", so + // traverse 4 characters on all but the last word instead of all but the + // first. + const int num_character_moves = (i == num_words - 1) ? 3 : 4; +#else + const int num_character_moves = (i == 0) ? 3 : 4; +#endif + render_text->SetSelection(start); + for (int j = 0; j < num_character_moves; ++j) + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(end, render_text->selection_model()); + + // Then, test moving by word from positions inside the word, such as from + // "a|bc def" to "abc| def", and from "ab|c def" to "abc| def". + for (int j = 1; j < num_character_moves; ++j) { + render_text->SetSelection(start); + for (int k = 0; k < j; ++k) + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, SELECTION_NONE); + render_text->MoveCursor(WORD_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(end, render_text->selection_model()); + } + } + + // Test moving by word from right to left. + render_text->MoveCursor(LINE_BREAK, CURSOR_RIGHT, SELECTION_NONE); + for (size_t i = 0; i < num_words; ++i) { + const SelectionModel start = render_text->selection_model(); + render_text->MoveCursor(WORD_BREAK, CURSOR_LEFT, SELECTION_NONE); + const SelectionModel end = render_text->selection_model(); + + const int num_character_moves = (i == 0) ? 3 : 4; + render_text->SetSelection(start); + for (int j = 0; j < num_character_moves; ++j) + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(end, render_text->selection_model()); + + for (int j = 1; j < num_character_moves; ++j) { + render_text->SetSelection(start); + for (int k = 0; k < j; ++k) + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_LEFT, SELECTION_NONE); + render_text->MoveCursor(WORD_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(end, render_text->selection_model()); + } + } +} + +#if defined(OS_WIN) +// TODO(aleventhal): https://crbug.com/906308 Fix bugs, update verifier code +// above, and enable for Windows. +#define MAYBE_MoveLeftRightByWordInBidiText \ + DISABLED_MoveLeftRightByWordInBidiText +#else +#define MAYBE_MoveLeftRightByWordInBidiText MoveLeftRightByWordInBidiText +#endif +TEST_F(RenderTextTest, MAYBE_MoveLeftRightByWordInBidiText) { + RenderText* render_text = GetRenderText(); + // For testing simplicity, each word is a 3-character word. + std::vector test; + test.push_back(u"abc"); + test.push_back(u"abc def"); + test.push_back(u"\u05E1\u05E2\u05E3"); + test.push_back(u"\u05E1\u05E2\u05E3 \u05E4\u05E5\u05E6"); + test.push_back(u"abc \u05E1\u05E2\u05E3"); + test.push_back(u"abc def \u05E1\u05E2\u05E3 \u05E4\u05E5\u05E6"); + test.push_back( + u"abc def hij \u05E1\u05E2\u05E3 \u05E4\u05E5\u05E6" + u" \u05E7\u05E8\u05E9"); + + test.push_back(u"abc \u05E1\u05E2\u05E3 hij"); + test.push_back(u"abc def \u05E1\u05E2\u05E3 \u05E4\u05E5\u05E6 hij opq"); + test.push_back( + u"abc def hij \u05E1\u05E2\u05E3 \u05E4\u05E5\u05E6" + u" \u05E7\u05E8\u05E9 opq rst uvw"); + + test.push_back(u"\u05E1\u05E2\u05E3 abc"); + test.push_back(u"\u05E1\u05E2\u05E3 \u05E4\u05E5\u05E6 abc def"); + test.push_back( + u"\u05E1\u05E2\u05E3 \u05E4\u05E5\u05E6 \u05E7\u05E8\u05E9" + u" abc def hij"); + + test.push_back(u"בגד abc \u05E1\u05E2\u05E3"); + test.push_back( + u"בגד הוז abc def" + u" \u05E1\u05E2\u05E3 \u05E4\u05E5\u05E6"); + test.push_back( + u"בגד הוז חטי" + u" abc def hij \u05E1\u05E2\u05E3 \u05E4\u05E5\u05E6" + u" \u05E7\u05E8\u05E9"); + + for (size_t i = 0; i < test.size(); ++i) + MoveLeftRightByWordVerifier(render_text, test[i]); +} + +TEST_F(RenderTextTest, MoveLeftRightByWordInBidiText_TestEndOfText) { + RenderText* render_text = GetRenderText(); + + render_text->SetText(u"ab\u05E1"); + // Moving the cursor by word from "abC|" to the left should return "|abC". + // But since end of text is always treated as a word break, it returns + // position "ab|C". + // TODO(xji): Need to make it work as expected. + render_text->MoveCursor(LINE_BREAK, CURSOR_RIGHT, SELECTION_NONE); + render_text->MoveCursor(WORD_BREAK, CURSOR_LEFT, SELECTION_NONE); + // EXPECT_EQ(SelectionModel(), render_text->selection_model()); + + // Moving the cursor by word from "|abC" to the right returns "abC|". + render_text->MoveCursor(LINE_BREAK, CURSOR_LEFT, SELECTION_NONE); + render_text->MoveCursor(WORD_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(SelectionModel(3, CURSOR_FORWARD), render_text->selection_model()); + + render_text->SetText(u"\u05E1\u05E2a"); + // For logical text "BCa", moving the cursor by word from "aCB|" to the left + // returns "|aCB". + render_text->MoveCursor(LINE_BREAK, CURSOR_RIGHT, SELECTION_NONE); + render_text->MoveCursor(WORD_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(SelectionModel(3, CURSOR_FORWARD), render_text->selection_model()); + + // Moving the cursor by word from "|aCB" to the right should return "aCB|". + // But since end of text is always treated as a word break, it returns + // position "a|CB". + // TODO(xji): Need to make it work as expected. + render_text->MoveCursor(LINE_BREAK, CURSOR_LEFT, SELECTION_NONE); + render_text->MoveCursor(WORD_BREAK, CURSOR_RIGHT, SELECTION_NONE); + // EXPECT_EQ(SelectionModel(), render_text->selection_model()); +} + +TEST_F(RenderTextTest, MoveLeftRightByWordInTextWithMultiSpaces) { + RenderText* render_text = GetRenderText(); + render_text->SetText(u"abc def"); + render_text->SetCursorPosition(5); + render_text->MoveCursor(WORD_BREAK, CURSOR_RIGHT, SELECTION_NONE); +#if defined(OS_WIN) + EXPECT_EQ(8U, render_text->cursor_position()); +#else + EXPECT_EQ(11U, render_text->cursor_position()); +#endif + + render_text->SetCursorPosition(5); + render_text->MoveCursor(WORD_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(0U, render_text->cursor_position()); +} + +TEST_F(RenderTextTest, MoveLeftRightByWordInThaiText) { + RenderText* render_text = GetRenderText(); + // เรียกดูรวดเร็ว is broken to เรียก|ดู|รวดเร็ว. + render_text->SetText(u"เรียกดูรวดเร็ว"); + render_text->MoveCursor(LINE_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(0U, render_text->cursor_position()); + render_text->MoveCursor(WORD_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(5U, render_text->cursor_position()); + render_text->MoveCursor(WORD_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(7U, render_text->cursor_position()); + render_text->MoveCursor(WORD_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(14U, render_text->cursor_position()); + render_text->MoveCursor(WORD_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(14U, render_text->cursor_position()); + + render_text->MoveCursor(WORD_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(7U, render_text->cursor_position()); + render_text->MoveCursor(WORD_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(5U, render_text->cursor_position()); + render_text->MoveCursor(WORD_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(0U, render_text->cursor_position()); + render_text->MoveCursor(WORD_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(0U, render_text->cursor_position()); +} + +// TODO(crbug.com/865527): Chinese and Japanese tokenization doesn't work on +// mobile. +#if !defined(OS_ANDROID) +TEST_F(RenderTextTest, MoveLeftRightByWordInChineseText) { + RenderText* render_text = GetRenderText(); + // zh-Hans-CN: 我们去公园玩, broken to 我们|去|公园|玩. + render_text->SetText(u"\u6211\u4EEC\u53BB\u516C\u56ED\u73A9"); + render_text->MoveCursor(LINE_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(0U, render_text->cursor_position()); + render_text->MoveCursor(WORD_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(2U, render_text->cursor_position()); + render_text->MoveCursor(WORD_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(3U, render_text->cursor_position()); + render_text->MoveCursor(WORD_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(5U, render_text->cursor_position()); + render_text->MoveCursor(WORD_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(6U, render_text->cursor_position()); + render_text->MoveCursor(WORD_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(6U, render_text->cursor_position()); + + render_text->MoveCursor(WORD_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(5U, render_text->cursor_position()); + render_text->MoveCursor(WORD_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(3U, render_text->cursor_position()); + render_text->MoveCursor(WORD_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(2U, render_text->cursor_position()); + render_text->MoveCursor(WORD_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(0U, render_text->cursor_position()); + render_text->MoveCursor(WORD_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(0U, render_text->cursor_position()); +} +#endif + +// Test the correct behavior of undirected selections: selections where the +// "end" of the selection that holds the cursor is only determined after the +// first cursor movement. +TEST_F(RenderTextTest, DirectedSelections) { + RenderText* render_text = GetRenderText(); + + auto ResultAfter = [&](VisualCursorDirection direction) -> std::u16string { + render_text->MoveCursor(CHARACTER_BREAK, direction, SELECTION_RETAIN); + return GetSelectedText(render_text); + }; + + render_text->SetText(u"01234"); + + // Test Right, then Left. LTR. + // Undirected, or forward when kSelectionIsAlwaysDirected. + render_text->SelectRange({2, 4}); + EXPECT_EQ(u"23", GetSelectedText(render_text)); + EXPECT_EQ(u"234", ResultAfter(CURSOR_RIGHT)); + EXPECT_EQ(u"23", ResultAfter(CURSOR_LEFT)); + + // Test collapsing the selection. This always ignores any existing direction. + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(Range(2, 2), render_text->selection()); // Collapse left. + + // Undirected, or backward when kSelectionIsAlwaysDirected. + render_text->SelectRange({4, 2}); + EXPECT_EQ(u"23", GetSelectedText(render_text)); + if (RenderText::kSelectionIsAlwaysDirected) + EXPECT_EQ(u"3", ResultAfter(CURSOR_RIGHT)); // Keep left. + else + EXPECT_EQ(u"234", ResultAfter(CURSOR_RIGHT)); // Pick right. + EXPECT_EQ(u"23", ResultAfter(CURSOR_LEFT)); + + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(Range(2, 2), render_text->selection()); // Collapse left. + + // Test Left, then Right. LTR. + // Undirected, or forward when kSelectionIsAlwaysDirected. + render_text->SelectRange({2, 4}); + EXPECT_EQ(u"23", GetSelectedText(render_text)); // Sanity check, + + if (RenderText::kSelectionIsAlwaysDirected) + EXPECT_EQ(u"2", ResultAfter(CURSOR_LEFT)); // Keep right. + else + EXPECT_EQ(u"123", ResultAfter(CURSOR_LEFT)); // Pick left. + EXPECT_EQ(u"23", ResultAfter(CURSOR_RIGHT)); + + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(Range(4, 4), render_text->selection()); // Collapse right. + + // Undirected, or backward when kSelectionIsAlwaysDirected. + render_text->SelectRange({4, 2}); + EXPECT_EQ(u"23", GetSelectedText(render_text)); + EXPECT_EQ(u"123", ResultAfter(CURSOR_LEFT)); + EXPECT_EQ(u"23", ResultAfter(CURSOR_RIGHT)); + + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(Range(4, 4), render_text->selection()); // Collapse right. + + auto ToHebrew = [](const char* digits) -> std::u16string { + const std::u16string hebrew = u"אבגדח"; // Roughly "abcde". + DCHECK_EQ(5u, hebrew.size()); + std::u16string result; + for (const char* d = digits; *d; d++) + result += hebrew[*d - '0']; + return result; + }; + render_text->SetText(ToHebrew("01234")); + + // Test Left, then Right. RTL. + // Undirected, or forward (to the left) when kSelectionIsAlwaysDirected. + render_text->SelectRange({2, 4}); + EXPECT_EQ(ToHebrew("23"), GetSelectedText(render_text)); + EXPECT_EQ(ToHebrew("234"), ResultAfter(CURSOR_LEFT)); + EXPECT_EQ(ToHebrew("23"), ResultAfter(CURSOR_RIGHT)); + + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(Range(4, 4), render_text->selection()); // Collapse left. + + // Undirected, or backward (to the right) when kSelectionIsAlwaysDirected. + render_text->SelectRange({4, 2}); + EXPECT_EQ(ToHebrew("23"), GetSelectedText(render_text)); + if (RenderText::kSelectionIsAlwaysDirected) + EXPECT_EQ(ToHebrew("3"), ResultAfter(CURSOR_LEFT)); // Keep right. + else + EXPECT_EQ(ToHebrew("234"), ResultAfter(CURSOR_LEFT)); // Pick left. + EXPECT_EQ(ToHebrew("23"), ResultAfter(CURSOR_RIGHT)); + + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(Range(4, 4), render_text->selection()); // Collapse left. + + // Test Right, then Left. RTL. + // Undirected, or forward (to the left) when kSelectionIsAlwaysDirected. + render_text->SelectRange({2, 4}); + EXPECT_EQ(ToHebrew("23"), GetSelectedText(render_text)); + if (RenderText::kSelectionIsAlwaysDirected) + EXPECT_EQ(ToHebrew("2"), ResultAfter(CURSOR_RIGHT)); // Keep left. + else + EXPECT_EQ(ToHebrew("123"), ResultAfter(CURSOR_RIGHT)); // Pick right. + EXPECT_EQ(ToHebrew("23"), ResultAfter(CURSOR_LEFT)); + + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(Range(2, 2), render_text->selection()); // Collapse right. + + // Undirected, or backward (to the right) when kSelectionIsAlwaysDirected. + render_text->SelectRange({4, 2}); + EXPECT_EQ(ToHebrew("23"), GetSelectedText(render_text)); + EXPECT_EQ(ToHebrew("123"), ResultAfter(CURSOR_RIGHT)); + EXPECT_EQ(ToHebrew("23"), ResultAfter(CURSOR_LEFT)); + + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(Range(2, 2), render_text->selection()); // Collapse right. +} + +TEST_F(RenderTextTest, DirectedSelections_Multiline) { + SetGlyphWidth(5); + RenderText* render_text = GetRenderText(); + + auto ResultAfter = [&](VisualCursorDirection direction) { + render_text->MoveCursor(CHARACTER_BREAK, direction, SELECTION_RETAIN); + return GetSelectedText(render_text); + }; + + render_text->SetText(u"01234\n56789\nabcde"); + render_text->SetMultiline(true); + render_text->SetDisplayRect(Rect(500, 500)); + ResetCursorX(); + + // Test Down, then Up. LTR. + // Undirected, or forward when kSelectionIsAlwaysDirected. + render_text->SelectRange({2, 4}); + EXPECT_EQ(u"23", GetSelectedText(render_text)); + EXPECT_EQ(u"234\n5678", ResultAfter(CURSOR_DOWN)); + EXPECT_EQ(u"23", ResultAfter(CURSOR_UP)); + + // Test collapsing the selection. This always ignores any existing direction. + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(Range(2, 2), render_text->selection()); // Collapse left. + + // Undirected, or backward when kSelectionIsAlwaysDirected. + ResetCursorX(); // Reset cached cursor x position. + render_text->SelectRange({4, 2}); + EXPECT_EQ(u"23", GetSelectedText(render_text)); + if (RenderText::kSelectionIsAlwaysDirected) { + EXPECT_EQ(u"4\n56", ResultAfter(CURSOR_DOWN)); // Keep left. + } else { + EXPECT_EQ(u"234\n5678", + ResultAfter(CURSOR_DOWN)); // Pick right. + } + EXPECT_EQ(u"23", ResultAfter(CURSOR_UP)); + + // Test with multi-line selection. + // Undirected, or forward when kSelectionIsAlwaysDirected. + ResetCursorX(); + render_text->SelectRange({2, 7}); // Select multi-line. + EXPECT_EQ(u"234\n5", GetSelectedText(render_text)); + EXPECT_EQ(u"234\n56789\na", ResultAfter(CURSOR_DOWN)); + EXPECT_EQ(u"234\n5", ResultAfter(CURSOR_UP)); + + // Undirected, or backward when kSelectionIsAlwaysDirected. + ResetCursorX(); + render_text->SelectRange({7, 2}); // Select multi-line. + EXPECT_EQ(u"234\n5", GetSelectedText(render_text)); + + if (RenderText::kSelectionIsAlwaysDirected) { + EXPECT_EQ(u"6", ResultAfter(CURSOR_DOWN)); // Keep left. + } else { + EXPECT_EQ(u"234\n56789\na", + ResultAfter(CURSOR_DOWN)); // Pick right. + } + EXPECT_EQ(u"234\n5", ResultAfter(CURSOR_UP)); +} + +TEST_F(RenderTextTest, StringSizeSanity) { + RenderText* render_text = GetRenderText(); + render_text->SetText(u"Hello World"); + const Size string_size = render_text->GetStringSize(); + EXPECT_GT(string_size.width(), 0); + EXPECT_GT(string_size.height(), 0); +} + +TEST_F(RenderTextTest, StringSizeLongStrings) { + RenderText* render_text = GetRenderText(); + // Remove the default 100000 characters limit. + render_text->set_truncate_length(0); + Size previous_string_size; + for (size_t length = 10; length < 1000000; length *= 10) { + render_text->SetText(std::u16string(length, 'a')); + const Size string_size = render_text->GetStringSize(); + EXPECT_GT(string_size.width(), previous_string_size.width()); + EXPECT_GT(string_size.height(), 0); + previous_string_size = string_size; + } +} + +TEST_F(RenderTextTest, StringSizeEmptyString) { + // Ascent and descent of Arial and Symbol are different on most platforms. + const FontList font_list( + base::StringPrintf("Arial,%s, 16px", kSymbolFontName)); + RenderText* render_text = GetRenderText(); + render_text->SetFontList(font_list); + render_text->SetDisplayRect(Rect(0, 0, 0, font_list.GetHeight())); + + // The empty string respects FontList metrics for non-zero height + // and baseline. + render_text->SetText(std::u16string()); + EXPECT_EQ(font_list.GetHeight(), render_text->GetStringSize().height()); + EXPECT_EQ(0, render_text->GetStringSize().width()); + EXPECT_EQ(font_list.GetBaseline(), render_text->GetBaseline()); + + render_text->SetText(u" "); + EXPECT_EQ(font_list.GetHeight(), render_text->GetStringSize().height()); + EXPECT_EQ(font_list.GetBaseline(), render_text->GetBaseline()); +} + +TEST_F(RenderTextTest, StringSizeRespectsFontListMetrics) { + // NOTE: On most platforms, kCJKFontName has different metrics than + // kTestFontName, but on Android it does not. + Font test_font(kTestFontName, 16); + ASSERT_EQ(base::ToLowerASCII(kTestFontName), + base::ToLowerASCII(test_font.GetActualFontName())); + Font cjk_font(kCJKFontName, 16); + ASSERT_EQ(base::ToLowerASCII(kCJKFontName), + base::ToLowerASCII(cjk_font.GetActualFontName())); + Font smaller_font = test_font; + Font larger_font = cjk_font; + // "a" should be rendered with the test font, not with the CJK font. + const char16_t* smaller_font_text = u"a"; + // "円" (U+5168 Han character YEN) should render with the CJK font, not + // the test font. + const char16_t* larger_font_text = u"\u5168"; + if (cjk_font.GetHeight() < test_font.GetHeight() && + cjk_font.GetBaseline() < test_font.GetBaseline()) { + std::swap(smaller_font, larger_font); + std::swap(smaller_font_text, larger_font_text); + } + ASSERT_LE(smaller_font.GetHeight(), larger_font.GetHeight()); + ASSERT_LE(smaller_font.GetBaseline(), larger_font.GetBaseline()); + + // Check |smaller_font_text| is rendered with the smaller font. + RenderText* render_text = GetRenderText(); + render_text->SetText(smaller_font_text); + render_text->SetFontList(FontList(smaller_font)); + render_text->SetDisplayRect(Rect(0, 0, 0, + render_text->font_list().GetHeight())); + EXPECT_EQ(smaller_font.GetHeight(), render_text->GetStringSize().height()); + EXPECT_EQ(smaller_font.GetBaseline(), render_text->GetBaseline()); + EXPECT_EQ(GetFontSpans()[0].first.GetFontName(), smaller_font.GetFontName()); + + // Layout the same text with mixed fonts. The text should be rendered with + // the smaller font, but the height and baseline are determined with the + // metrics of the font list, which is equal to the larger font. + std::vector fonts; + fonts.push_back(smaller_font); // The primary font is the smaller font. + fonts.push_back(larger_font); + const FontList font_list(fonts); + render_text->SetFontList(font_list); + render_text->SetDisplayRect(Rect(0, 0, 0, + render_text->font_list().GetHeight())); + EXPECT_EQ(GetFontSpans()[0].first.GetFontName(), smaller_font.GetFontName()); + EXPECT_LE(smaller_font.GetHeight(), render_text->GetStringSize().height()); + EXPECT_LE(smaller_font.GetBaseline(), render_text->GetBaseline()); + EXPECT_EQ(font_list.GetHeight(), render_text->GetStringSize().height()); + EXPECT_EQ(font_list.GetBaseline(), render_text->GetBaseline()); +} + +TEST_F(RenderTextTest, StringSizeMultiline) { + SetGlyphWidth(5); + RenderText* render_text = GetRenderText(); + render_text->SetText(u"Hello\nWorld"); + const Size string_size = render_text->GetStringSize(); + EXPECT_EQ(55, string_size.width()); + + render_text->SetDisplayRect(Rect(30, 1000)); + render_text->SetMultiline(true); + // The newline glyph has width 0. + EXPECT_EQ(50, render_text->TotalLineWidth()); + + // The newline glyph has width 0. + EXPECT_FLOAT_EQ( + 25, render_text->GetLineSizeF(SelectionModel(0, CURSOR_FORWARD)).width()); + EXPECT_FLOAT_EQ( + 25, render_text->GetLineSizeF(SelectionModel(6, CURSOR_FORWARD)).width()); + // |GetStringSize()| of multi-line text does not include newline character. + EXPECT_EQ(25, render_text->GetStringSize().width()); + // Expect height to be 2 times the font height. This assumes simple strings + // that do not have special metrics. + int font_height = render_text->font_list().GetHeight(); + EXPECT_EQ(font_height * 2, render_text->GetStringSize().height()); +} + +TEST_F(RenderTextTest, MinLineHeight) { + RenderText* render_text = GetRenderText(); + render_text->SetText(u"Hello!"); + SizeF default_size = render_text->GetStringSizeF(); + ASSERT_NE(0, default_size.height()); + ASSERT_NE(0, default_size.width()); + + render_text->SetMinLineHeight(default_size.height() / 2); + EXPECT_EQ(default_size.ToString(), render_text->GetStringSizeF().ToString()); + + render_text->SetMinLineHeight(default_size.height() * 2); + SizeF taller_size = render_text->GetStringSizeF(); + EXPECT_EQ(default_size.height() * 2, taller_size.height()); + EXPECT_EQ(default_size.width(), taller_size.width()); +} + +// Check that, for Latin characters, typesetting text in the default fonts and +// sizes does not discover any glyphs that would exceed the line spacing +// recommended by gfx::Font. +TEST_F(RenderTextTest, DefaultLineHeights) { + RenderText* render_text = GetRenderText(); + render_text->SetText(u"A quick brown fox jumped over the lazy dog!"); + +#if defined(OS_APPLE) + const FontList body2_font = FontList().DeriveWithSizeDelta(-1); +#else + const FontList body2_font; +#endif + + const FontList headline_font = body2_font.DeriveWithSizeDelta(8); + const FontList title_font = body2_font.DeriveWithSizeDelta(3); + const FontList body1_font = body2_font.DeriveWithSizeDelta(1); +#if defined(OS_WIN) + const FontList button_font = + body2_font.DeriveWithWeight(gfx::Font::Weight::BOLD); +#else + const FontList button_font = + body2_font.DeriveWithWeight(gfx::Font::Weight::MEDIUM); +#endif + + EXPECT_EQ(12, body2_font.GetFontSize()); + EXPECT_EQ(20, headline_font.GetFontSize()); + EXPECT_EQ(15, title_font.GetFontSize()); + EXPECT_EQ(13, body1_font.GetFontSize()); + EXPECT_EQ(12, button_font.GetFontSize()); + + for (const auto& font : + {headline_font, title_font, body1_font, body2_font, button_font}) { + render_text->SetFontList(font); + EXPECT_EQ(font.GetHeight(), render_text->GetStringSizeF().height()); + } +} + +TEST_F(RenderTextTest, TextSize) { + // Set a fractional glyph size to trigger floating rounding logic. + const float kGlyphWidth = 1.2; + const float kGlyphHeight = 9.2; + SetGlyphWidth(kGlyphWidth); + SetGlyphHeight(kGlyphHeight); + + RenderText* render_text = GetRenderText(); + for (size_t text_length = 0; text_length < 10; ++text_length) { + render_text->SetText(std::u16string(text_length, u'x')); + + // Ensures that conversion from float to integer ceils the values. + const float expected_width = text_length * kGlyphWidth; + const float expected_height = kGlyphHeight; + const int expected_ceiled_width = std::ceil(expected_width); + const int expected_ceiled_height = std::ceil(expected_height); + + EXPECT_FLOAT_EQ(expected_width, render_text->GetStringSizeF().width()); + EXPECT_FLOAT_EQ(expected_height, render_text->GetStringSizeF().height()); + EXPECT_EQ(expected_ceiled_width, render_text->GetStringSize().width()); + EXPECT_EQ(expected_ceiled_height, render_text->GetStringSize().height()); + + EXPECT_FLOAT_EQ(expected_width, render_text->TotalLineWidth()); + + // With cursor disabled, the content width is the same as string width. + render_text->SetCursorEnabled(false); + EXPECT_FLOAT_EQ(expected_width, render_text->GetContentWidthF()); + EXPECT_EQ(expected_ceiled_width, render_text->GetContentWidth()); + + render_text->SetCursorEnabled(true); + // The cursor is drawn one pixel beyond the int-enclosing text bounds. + EXPECT_FLOAT_EQ(expected_ceiled_width + 1, render_text->GetContentWidthF()); + EXPECT_EQ(expected_ceiled_width + 1, render_text->GetContentWidth()); + } +} + +TEST_F(RenderTextTest, TextSizeMultiline) { + // Set a fractional glyph size to trigger floating rounding logic. + const float kGlyphWidth = 1.2; + const float kGlyphHeight = 9.2; + SetGlyphWidth(kGlyphWidth); + SetGlyphHeight(kGlyphHeight); + + RenderText* render_text = GetRenderText(); + render_text->SetMultiline(true); + + for (size_t line = 0; line < 10; ++line) { + if (line != 0) + render_text->AppendText(u"\n"); + const int text_length = line; + render_text->AppendText(std::u16string(text_length, u'x')); + + // Ensures that conversion from float to integer ceils the values. + const float expected_width = text_length * kGlyphWidth; + const float expected_height = (line + 1) * kGlyphHeight; + const int expected_ceiled_width = std::ceil(expected_width); + const int expected_ceiled_height = std::ceil(expected_height); + + EXPECT_FLOAT_EQ(expected_width, render_text->GetStringSizeF().width()); + EXPECT_FLOAT_EQ(expected_height, render_text->GetStringSizeF().height()); + EXPECT_EQ(expected_ceiled_width, render_text->GetStringSize().width()); + EXPECT_EQ(expected_ceiled_height, render_text->GetStringSize().height()); + + const int total_glyphs = render_text->text().length(); + // |total_glyphs| includes one \n glyph per line, which has width 0. + EXPECT_FLOAT_EQ((total_glyphs - line) * kGlyphWidth, + render_text->TotalLineWidth()); + + // With cursor disabled, the content width is the same as string width. + render_text->SetCursorEnabled(false); + EXPECT_FLOAT_EQ(expected_width, render_text->GetContentWidthF()); + EXPECT_EQ(expected_ceiled_width, render_text->GetContentWidth()); + + render_text->SetCursorEnabled(true); + // The cursor is drawn one pixel beyond the int-enclosing text bounds. + EXPECT_FLOAT_EQ(expected_ceiled_width + 1, render_text->GetContentWidthF()); + EXPECT_EQ(expected_ceiled_width + 1, render_text->GetContentWidth()); + } +} + +TEST_F(RenderTextTest, LineSizeMultiline) { + // Set a fractional glyph size to trigger floating rounding logic. + const float kGlyphWidth = 1.2; + SetGlyphWidth(kGlyphWidth); + + RenderText* render_text = GetRenderText(); + render_text->SetMultiline(true); + render_text->SetText(u"xx\nxxx\nxxxxx"); + + // \n doesn't have a width, so the expected sizes are only the visible + // characters + const float expected_line1_size = 2 * kGlyphWidth; + const float expected_line2_size = 3 * kGlyphWidth; + const float expected_line3_size = 5 * kGlyphWidth; + + EXPECT_FLOAT_EQ( + expected_line1_size, + render_text->GetLineSizeF(SelectionModel(1, CURSOR_FORWARD)).width()); + EXPECT_FLOAT_EQ( + expected_line2_size, + render_text->GetLineSizeF(SelectionModel(4, CURSOR_FORWARD)).width()); + EXPECT_FLOAT_EQ( + expected_line3_size, + render_text->GetLineSizeF(SelectionModel(10, CURSOR_FORWARD)).width()); +} + +TEST_F(RenderTextTest, TextPosition) { + // Set a fractional glyph size to trigger floating rounding logic. + // This test sets a fixed fractional width and height for glyphs to ensure + // that computations are fixed (i.e. not font or system dependent). + const float kGlyphWidth = 5.4; + const float kGlyphHeight = 9.2; + SetGlyphWidth(kGlyphWidth); + SetGlyphHeight(kGlyphHeight); + + const int kGlyphCount = 3; + + RenderText* render_text = GetRenderText(); + render_text->SetText(std::u16string(kGlyphCount, u'x')); + render_text->SetDisplayRect(Rect(1, 1, 25, 12)); + render_text->SetCursorEnabled(false); + render_text->SetVerticalAlignment(ALIGN_TOP); + + // Content width is 16.2px. Extra space inside display rect is 8.8px + // (i.e. 25px - 16.2px) which is used for alignment. + const float expected_content_width = kGlyphCount * kGlyphWidth; + EXPECT_FLOAT_EQ(expected_content_width, render_text->GetContentWidthF()); + EXPECT_FLOAT_EQ(expected_content_width, render_text->TotalLineWidth()); + + render_text->SetHorizontalAlignment(ALIGN_LEFT); + EXPECT_EQ(1, render_text->GetLineOffset(0).x()); + + EXPECT_EQ(Rect(1, 1, 6, 10), GetSubstringBoundsUnion(Range(0, 1))); + EXPECT_EQ(Rect(6, 1, 6, 10), GetSubstringBoundsUnion(Range(1, 2))); + EXPECT_EQ(Rect(11, 1, 7, 10), GetSubstringBoundsUnion(Range(2, 3))); + + render_text->SetHorizontalAlignment(ALIGN_CENTER); + EXPECT_EQ(5, render_text->GetLineOffset(0).x()); + EXPECT_EQ(Rect(5, 1, 6, 10), GetSubstringBoundsUnion(Range(0, 1))); + EXPECT_EQ(Rect(10, 1, 6, 10), GetSubstringBoundsUnion(Range(1, 2))); + EXPECT_EQ(Rect(15, 1, 7, 10), GetSubstringBoundsUnion(Range(2, 3))); + + render_text->SetHorizontalAlignment(ALIGN_RIGHT); + EXPECT_EQ(9, render_text->GetLineOffset(0).x()); + EXPECT_EQ(Rect(9, 1, 6, 10), GetSubstringBoundsUnion(Range(0, 1))); + EXPECT_EQ(Rect(14, 1, 6, 10), GetSubstringBoundsUnion(Range(1, 2))); + EXPECT_EQ(Rect(19, 1, 7, 10), GetSubstringBoundsUnion(Range(2, 3))); +} + +TEST_F(RenderTextTest, SetFontList) { + RenderText* render_text = GetRenderText(); + render_text->SetFontList( + FontList(base::StringPrintf("Arial,%s, 13px", kSymbolFontName))); + const std::vector& fonts = render_text->font_list().GetFonts(); + ASSERT_EQ(2U, fonts.size()); + EXPECT_EQ("Arial", fonts[0].GetFontName()); + EXPECT_EQ(kSymbolFontName, fonts[1].GetFontName()); + EXPECT_EQ(13, render_text->font_list().GetFontSize()); +} + +TEST_F(RenderTextTest, StringSizeBoldWidth) { + // TODO(mboc): Add some unittests for other weights (currently not + // implemented because of test system font configuration). + RenderText* render_text = GetRenderText(); + +#if defined(OS_FUCHSIA) + // Increase font size to ensure that bold and regular styles differ in width. + render_text->SetFontList(FontList("Arial, 20px")); +#endif // defined(OS_FUCHSIA) + + render_text->SetText(u"Hello World"); + + const int plain_width = render_text->GetStringSize().width(); + EXPECT_GT(plain_width, 0); + + // Apply a bold style and check that the new width is greater. + render_text->SetWeight(Font::Weight::BOLD); + const int bold_width = render_text->GetStringSize().width(); + EXPECT_GT(bold_width, plain_width); + +#if defined(OS_WIN) + render_text->SetWeight(Font::Weight::SEMIBOLD); + const int semibold_width = render_text->GetStringSize().width(); + EXPECT_GT(bold_width, semibold_width); +#endif + + // Now, apply a plain style over the first word only. + render_text->ApplyWeight(Font::Weight::NORMAL, Range(0, 5)); + const int plain_bold_width = render_text->GetStringSize().width(); + EXPECT_GT(plain_bold_width, plain_width); + EXPECT_LT(plain_bold_width, bold_width); +} + +TEST_F(RenderTextTest, StringSizeHeight) { + std::u16string cases[] = { + u"Hello World!", // English + u"\u6328\u62f6", // Japanese 挨拶 (characters press & near) + u"\u0915\u093f", // Hindi कि (letter KA with vowel I) + u"\u05e0\u05b8", // Hebrew נָ (letter NUN and point QAMATS) + }; + + const FontList default_font_list; + const FontList& larger_font_list = default_font_list.DeriveWithSizeDelta(24); + EXPECT_GT(larger_font_list.GetHeight(), default_font_list.GetHeight()); + + for (size_t i = 0; i < base::size(cases); i++) { + ResetRenderTextInstance(); + RenderText* render_text = GetRenderText(); + render_text->SetFontList(default_font_list); + render_text->SetText(cases[i]); + + const int height1 = render_text->GetStringSize().height(); + EXPECT_GT(height1, 0); + + // Check that setting the larger font increases the height. + render_text->SetFontList(larger_font_list); + const int height2 = render_text->GetStringSize().height(); + EXPECT_GT(height2, height1); + } +} + +TEST_F(RenderTextTest, GetBaselineSanity) { + RenderText* render_text = GetRenderText(); + render_text->SetText(u"Hello World"); + const int baseline = render_text->GetBaseline(); + EXPECT_GT(baseline, 0); +} + +TEST_F(RenderTextTest, GetCursorBoundsInReplacementMode) { + RenderText* render_text = GetRenderText(); + render_text->SetText(u"abcdefg"); + render_text->SetDisplayRect(Rect(100, 17)); + SelectionModel sel_b(1, CURSOR_FORWARD); + SelectionModel sel_c(2, CURSOR_FORWARD); + Rect cursor_around_b = render_text->GetCursorBounds(sel_b, false); + Rect cursor_before_b = render_text->GetCursorBounds(sel_b, true); + Rect cursor_before_c = render_text->GetCursorBounds(sel_c, true); + EXPECT_EQ(cursor_around_b.x(), cursor_before_b.x()); + EXPECT_EQ(cursor_around_b.right(), cursor_before_c.x()); +} + +TEST_F(RenderTextTest, GetCursorBoundsWithGraphemes) { + constexpr int kGlyphWidth = 10; + SetGlyphWidth(kGlyphWidth); + constexpr int kGlyphHeight = 12; + SetGlyphHeight(kGlyphHeight); + + RenderText* render_text = GetRenderText(); + render_text->SetText(u"a\u0300e\u0301\U0001F601x\U0001F573\uFE0F"); + render_text->SetDisplayRect(Rect(100, 20)); + render_text->SetVerticalAlignment(ALIGN_TOP); + + static const size_t kGraphemeBoundaries[] = {0, 2, 4, 6, 7}; + for (size_t i = 0; i < base::size(kGraphemeBoundaries); ++i) { + const size_t text_offset = kGraphemeBoundaries[i]; + EXPECT_EQ(render_text->GetCursorBounds( + SelectionModel(text_offset, CURSOR_FORWARD), true), + Rect(i * kGlyphWidth, 0, 1, kGlyphHeight)); + EXPECT_EQ(render_text->GetCursorBounds( + SelectionModel(text_offset, CURSOR_FORWARD), false), + Rect(i * kGlyphWidth, 0, kGlyphWidth, kGlyphHeight)); + } + + // Check cursor bounds at end of text. + EXPECT_EQ( + render_text->GetCursorBounds(SelectionModel(10, CURSOR_FORWARD), true), + Rect(50, 0, 1, kGlyphHeight)); + EXPECT_EQ( + render_text->GetCursorBounds(SelectionModel(10, CURSOR_FORWARD), false), + Rect(50, 0, 1, kGlyphHeight)); +} + +TEST_F(RenderTextTest, GetTextOffset) { + // The default horizontal text offset differs for LTR and RTL, and is only set + // when the RenderText object is created. This test will check the default in + // LTR mode, and the next test will check the RTL default. + const bool was_rtl = base::i18n::IsRTL(); + SetRTL(false); + + // Reset the render text instance since the locale was changed. + ResetRenderTextInstance(); + RenderText* render_text = GetRenderText(); + + render_text->SetText(u"abcdefg"); + render_text->SetFontList(FontList("Arial, 13px")); + + // Set display area's size equal to the font size. + const Size font_size(render_text->GetContentWidth(), + render_text->font_list().GetHeight()); + Rect display_rect(font_size); + render_text->SetDisplayRect(display_rect); + + Vector2d offset = render_text->GetLineOffset(0); + EXPECT_TRUE(offset.IsZero()); + + const int kEnlargementX = 2; + display_rect.Inset(0, 0, -kEnlargementX, 0); + render_text->SetDisplayRect(display_rect); + + // Check the default horizontal alignment. + offset = render_text->GetLineOffset(0); + EXPECT_EQ(0, offset.x()); + + // Check explicitly setting the horizontal alignment. + render_text->SetHorizontalAlignment(ALIGN_LEFT); + offset = render_text->GetLineOffset(0); + EXPECT_EQ(0, offset.x()); + render_text->SetHorizontalAlignment(ALIGN_CENTER); + offset = render_text->GetLineOffset(0); + EXPECT_EQ(kEnlargementX / 2, offset.x()); + render_text->SetHorizontalAlignment(ALIGN_RIGHT); + offset = render_text->GetLineOffset(0); + EXPECT_EQ(kEnlargementX, offset.x()); + + // Check that text is vertically centered within taller display rects. + const int kEnlargementY = display_rect.height(); + display_rect.Inset(0, 0, 0, -kEnlargementY); + render_text->SetDisplayRect(display_rect); + const Vector2d prev_offset = render_text->GetLineOffset(0); + display_rect.Inset(0, 0, 0, -2 * kEnlargementY); + render_text->SetDisplayRect(display_rect); + offset = render_text->GetLineOffset(0); + EXPECT_EQ(prev_offset.y() + kEnlargementY, offset.y()); + + SetRTL(was_rtl); +} + +TEST_F(RenderTextTest, GetTextOffsetHorizontalDefaultInRTL) { + // This only checks the default horizontal alignment in RTL mode; all other + // GetLineOffset(0) attributes are checked by the test above. + const bool was_rtl = base::i18n::IsRTL(); + SetRTL(true); + + // Reset the render text instance since the locale was changed. + ResetRenderTextInstance(); + RenderText* render_text = GetRenderText(); + + render_text->SetText(u"abcdefg"); + render_text->SetFontList(FontList("Arial, 13px")); + const int kEnlargement = 2; + const Size font_size(render_text->GetContentWidth() + kEnlargement, + render_text->GetStringSize().height()); + Rect display_rect(font_size); + render_text->SetDisplayRect(display_rect); + Vector2d offset = render_text->GetLineOffset(0); + EXPECT_EQ(kEnlargement, offset.x()); + SetRTL(was_rtl); +} + +TEST_F(RenderTextTest, GetTextOffsetVerticalAlignment) { + RenderText* render_text = GetRenderText(); + render_text->SetText(u"abcdefg"); + render_text->SetFontList(FontList("Arial, 13px")); + + // Set display area's size equal to the font size. + const Size font_size(render_text->GetContentWidth(), + render_text->font_list().GetHeight()); + Rect display_rect(font_size); + render_text->SetDisplayRect(display_rect); + + Vector2d offset = render_text->GetLineOffset(0); + EXPECT_TRUE(offset.IsZero()); + + const int kEnlargementY = 10; + display_rect.Inset(0, 0, 0, -kEnlargementY); + render_text->SetDisplayRect(display_rect); + + // Check the default vertical alignment (ALIGN_MIDDLE). + offset = render_text->GetLineOffset(0); + // Because the line height may be odd, and because of the way this calculation + // is done, we will accept a result within 1 DIP of half: + EXPECT_NEAR(kEnlargementY / 2, offset.y(), 1); + + // Check explicitly setting the vertical alignment. + render_text->SetVerticalAlignment(ALIGN_TOP); + offset = render_text->GetLineOffset(0); + EXPECT_EQ(0, offset.y()); + render_text->SetVerticalAlignment(ALIGN_MIDDLE); + offset = render_text->GetLineOffset(0); + EXPECT_NEAR(kEnlargementY / 2, offset.y(), 1); + render_text->SetVerticalAlignment(ALIGN_BOTTOM); + offset = render_text->GetLineOffset(0); + EXPECT_EQ(kEnlargementY, offset.y()); +} + +TEST_F(RenderTextTest, GetTextOffsetVerticalAlignment_Multiline) { + RenderText* render_text = GetRenderText(); + render_text->SetMultiline(true); + render_text->SetMaxLines(2); + render_text->SetText(u"abcdefg hijklmn"); + render_text->SetFontList(FontList("Arial, 13px")); + + // Set display area's size equal to the font size. + const Size font_size(render_text->GetContentWidth(), + render_text->font_list().GetHeight()); + + // Force text onto two lines. + Rect display_rect(Size(font_size.width() * 2 / 3, font_size.height() * 2)); + render_text->SetDisplayRect(display_rect); + + Vector2d offset = render_text->GetLineOffset(0); + EXPECT_TRUE(offset.IsZero()); + + const int kEnlargementY = 10; + display_rect.Inset(0, 0, 0, -kEnlargementY); + render_text->SetDisplayRect(display_rect); + + // Check the default vertical alignment (ALIGN_MIDDLE). + offset = render_text->GetLineOffset(0); + EXPECT_EQ(kEnlargementY / 2, offset.y()); + + // Check explicitly setting the vertical alignment. + render_text->SetVerticalAlignment(ALIGN_TOP); + offset = render_text->GetLineOffset(0); + EXPECT_EQ(0, offset.y()); + render_text->SetVerticalAlignment(ALIGN_MIDDLE); + offset = render_text->GetLineOffset(0); + EXPECT_EQ(kEnlargementY / 2, offset.y()); + render_text->SetVerticalAlignment(ALIGN_BOTTOM); + offset = render_text->GetLineOffset(0); + EXPECT_EQ(kEnlargementY, offset.y()); +} + +TEST_F(RenderTextTest, SetDisplayOffset) { + RenderText* render_text = GetRenderText(); + render_text->SetText(u"abcdefg"); + render_text->SetFontList(FontList("Arial, 13px")); + + const Size font_size(render_text->GetContentWidth(), + render_text->font_list().GetHeight()); + const int kEnlargement = 10; + + // Set display width |kEnlargement| pixels greater than content width and test + // different possible situations. In this case the only possible display + // offset is zero. + Rect display_rect(font_size); + display_rect.Inset(0, 0, -kEnlargement, 0); + render_text->SetDisplayRect(display_rect); + + struct { + HorizontalAlignment alignment; + int offset; + } small_content_cases[] = { + { ALIGN_LEFT, -kEnlargement }, + { ALIGN_LEFT, 0 }, + { ALIGN_LEFT, kEnlargement }, + { ALIGN_RIGHT, -kEnlargement }, + { ALIGN_RIGHT, 0 }, + { ALIGN_RIGHT, kEnlargement }, + { ALIGN_CENTER, -kEnlargement }, + { ALIGN_CENTER, 0 }, + { ALIGN_CENTER, kEnlargement }, + }; + + for (size_t i = 0; i < base::size(small_content_cases); i++) { + render_text->SetHorizontalAlignment(small_content_cases[i].alignment); + render_text->SetDisplayOffset(small_content_cases[i].offset); + EXPECT_EQ(0, render_text->GetUpdatedDisplayOffset().x()); + } + + // Set display width |kEnlargement| pixels less than content width and test + // different possible situations. + display_rect = Rect(font_size); + display_rect.Inset(0, 0, kEnlargement, 0); + render_text->SetDisplayRect(display_rect); + + struct { + HorizontalAlignment alignment; + int offset; + int expected_offset; + } large_content_cases[] = { + // When text is left-aligned, display offset can be in range + // [-kEnlargement, 0]. + { ALIGN_LEFT, -2 * kEnlargement, -kEnlargement }, + { ALIGN_LEFT, -kEnlargement / 2, -kEnlargement / 2 }, + { ALIGN_LEFT, kEnlargement, 0 }, + // When text is right-aligned, display offset can be in range + // [0, kEnlargement]. + { ALIGN_RIGHT, -kEnlargement, 0 }, + { ALIGN_RIGHT, kEnlargement / 2, kEnlargement / 2 }, + { ALIGN_RIGHT, 2 * kEnlargement, kEnlargement }, + // When text is center-aligned, display offset can be in range + // [-kEnlargement / 2 - 1, (kEnlargement - 1) / 2]. + { ALIGN_CENTER, -kEnlargement, -kEnlargement / 2 - 1 }, + { ALIGN_CENTER, -kEnlargement / 4, -kEnlargement / 4 }, + { ALIGN_CENTER, kEnlargement / 4, kEnlargement / 4 }, + { ALIGN_CENTER, kEnlargement, (kEnlargement - 1) / 2 }, + }; + + for (size_t i = 0; i < base::size(large_content_cases); i++) { + render_text->SetHorizontalAlignment(large_content_cases[i].alignment); + render_text->SetDisplayOffset(large_content_cases[i].offset); + EXPECT_EQ(large_content_cases[i].expected_offset, + render_text->GetUpdatedDisplayOffset().x()); + } +} + +TEST_F(RenderTextTest, SameFontForParentheses) { + struct { + const char16_t left_char; + const char16_t right_char; + } punctuation_pairs[] = { + { '(', ')' }, + { '{', '}' }, + { '<', '>' }, + }; + struct { + std::u16string text; + } cases[] = { + // English(English) + {u"Hello World(a)"}, + // English(English)English + {u"Hello World(a)Hello World"}, + + // Japanese(English) + {u"\u6328\u62f6(a)"}, + // Japanese(English)Japanese + {u"\u6328\u62f6(a)\u6328\u62f6"}, + // English(Japanese)English + {u"Hello World(\u6328\u62f6)Hello World"}, + + // Hindi(English) + {u"\u0915\u093f(a)"}, + // Hindi(English)Hindi + {u"\u0915\u093f(a)\u0915\u093f"}, + // English(Hindi)English + {u"Hello World(\u0915\u093f)Hello World"}, + + // Hebrew(English) + {u"\u05e0\u05b8(a)"}, + // Hebrew(English)Hebrew + {u"\u05e0\u05b8(a)\u05e0\u05b8"}, + // English(Hebrew)English + {u"Hello World(\u05e0\u05b8)Hello World"}, + }; + + RenderText* render_text = GetRenderText(); + for (size_t i = 0; i < base::size(cases); ++i) { + std::u16string text = cases[i].text; + const size_t start_paren_char_index = text.find('('); + ASSERT_NE(std::u16string::npos, start_paren_char_index); + const size_t end_paren_char_index = text.find(')'); + ASSERT_NE(std::u16string::npos, end_paren_char_index); + + for (size_t j = 0; j < base::size(punctuation_pairs); ++j) { + text[start_paren_char_index] = punctuation_pairs[j].left_char; + text[end_paren_char_index] = punctuation_pairs[j].right_char; + render_text->SetText(text); + + const std::vector spans = GetFontSpans(); + + int start_paren_span_index = -1; + int end_paren_span_index = -1; + for (size_t k = 0; k < spans.size(); ++k) { + if (IndexInRange(spans[k].second, start_paren_char_index)) + start_paren_span_index = k; + if (IndexInRange(spans[k].second, end_paren_char_index)) + end_paren_span_index = k; + } + ASSERT_NE(-1, start_paren_span_index); + ASSERT_NE(-1, end_paren_span_index); + + const Font& start_font = spans[start_paren_span_index].first; + const Font& end_font = spans[end_paren_span_index].first; + EXPECT_EQ(start_font.GetFontName(), end_font.GetFontName()); + EXPECT_EQ(start_font.GetFontSize(), end_font.GetFontSize()); + EXPECT_EQ(start_font.GetStyle(), end_font.GetStyle()); + } + } +} + +// Make sure the caret width is always >=1 so that the correct +// caret is drawn at high DPI. crbug.com/164100. +TEST_F(RenderTextTest, CaretWidth) { + RenderText* render_text = GetRenderText(); + render_text->SetText(u"abcdefg"); + EXPECT_GE(render_text->GetUpdatedCursorBounds().width(), 1); +} + +TEST_F(RenderTextTest, SelectWord) { + RenderText* render_text = GetRenderText(); + render_text->SetText(u" foo a.bc.d bar"); + + struct { + size_t cursor; + size_t selection_start; + size_t selection_end; + } cases[] = { + { 0, 0, 1 }, + { 1, 1, 4 }, + { 2, 1, 4 }, + { 3, 1, 4 }, + { 4, 4, 6 }, + { 5, 4, 6 }, + { 6, 6, 7 }, + { 7, 7, 8 }, + { 8, 8, 10 }, + { 9, 8, 10 }, + { 10, 10, 11 }, + { 11, 11, 12 }, + { 12, 12, 13 }, + { 13, 13, 16 }, + { 14, 13, 16 }, + { 15, 13, 16 }, + { 16, 13, 16 }, + }; + + for (size_t i = 0; i < base::size(cases); ++i) { + render_text->SetCursorPosition(cases[i].cursor); + render_text->SelectWord(); + EXPECT_EQ(Range(cases[i].selection_start, cases[i].selection_end), + render_text->selection()); + } +} + +// Make sure the last word is selected when the cursor is at text.length(). +TEST_F(RenderTextTest, LastWordSelected) { + const std::u16string kTestURL1 = u"http://www.google.com"; + const std::u16string kTestURL2 = u"http://www.google.com/something/"; + + RenderText* render_text = GetRenderText(); + + render_text->SetText(kTestURL1); + render_text->SetCursorPosition(kTestURL1.length()); + render_text->SelectWord(); + EXPECT_EQ(u"com", GetSelectedText(render_text)); + EXPECT_FALSE(render_text->selection().is_reversed()); + + render_text->SetText(kTestURL2); + render_text->SetCursorPosition(kTestURL2.length()); + render_text->SelectWord(); + EXPECT_EQ(u"/", GetSelectedText(render_text)); + EXPECT_FALSE(render_text->selection().is_reversed()); +} + +// When given a non-empty selection, SelectWord should expand the selection to +// nearest word boundaries. +TEST_F(RenderTextTest, SelectMultipleWords) { + const std::u16string kTestURL = u"http://www.google.com"; + + RenderText* render_text = GetRenderText(); + + render_text->SetText(kTestURL); + render_text->SelectRange(Range(16, 20)); + render_text->SelectWord(); + EXPECT_EQ(u"google.com", GetSelectedText(render_text)); + EXPECT_FALSE(render_text->selection().is_reversed()); + + // SelectWord should preserve the selection direction. + render_text->SelectRange(Range(20, 16)); + render_text->SelectWord(); + EXPECT_EQ(u"google.com", GetSelectedText(render_text)); + EXPECT_TRUE(render_text->selection().is_reversed()); +} + +TEST_F(RenderTextTest, DisplayRectShowsCursorLTR) { + ASSERT_FALSE(base::i18n::IsRTL()); + ASSERT_FALSE(base::i18n::ICUIsRTL()); + + RenderText* render_text = GetRenderText(); + render_text->SetText(u"abcdefghijklmnopqrstuvwxzyabcdefg"); + render_text->SetCursorPosition(render_text->text().length()); + int width = render_text->GetStringSize().width(); + ASSERT_GT(width, 10); + + // Ensure that the cursor is placed at the width of its preceding text. + render_text->SetDisplayRect(Rect(width + 10, 1)); + EXPECT_EQ(width, render_text->GetUpdatedCursorBounds().x()); + + // Ensure that shrinking the display rectangle keeps the cursor in view. + render_text->SetDisplayRect(Rect(width - 10, 1)); + EXPECT_EQ(render_text->display_rect().width(), + render_text->GetUpdatedCursorBounds().right()); + + // Ensure that the text will pan to fill its expanding display rectangle. + render_text->SetDisplayRect(Rect(width - 5, 1)); + EXPECT_EQ(render_text->display_rect().width(), + render_text->GetUpdatedCursorBounds().right()); + + // Ensure that a sufficiently large display rectangle shows all the text. + render_text->SetDisplayRect(Rect(width + 10, 1)); + EXPECT_EQ(width, render_text->GetUpdatedCursorBounds().x()); + + // Repeat the test with RTL text. + render_text->SetText(u"אבגדהוזחטיךכלםמן"); + render_text->SetCursorPosition(0); + width = render_text->GetStringSize().width(); + ASSERT_GT(width, 10); + + // Ensure that the cursor is placed at the width of its preceding text. + render_text->SetDisplayRect(Rect(width + 10, 1)); + EXPECT_EQ(width, render_text->GetUpdatedCursorBounds().x()); + + // Ensure that shrinking the display rectangle keeps the cursor in view. + render_text->SetDisplayRect(Rect(width - 10, 1)); + EXPECT_EQ(render_text->display_rect().width(), + render_text->GetUpdatedCursorBounds().right()); + + // Ensure that the text will pan to fill its expanding display rectangle. + render_text->SetDisplayRect(Rect(width - 5, 1)); + EXPECT_EQ(render_text->display_rect().width(), + render_text->GetUpdatedCursorBounds().right()); + + // Ensure that a sufficiently large display rectangle shows all the text. + render_text->SetDisplayRect(Rect(width + 10, 1)); + EXPECT_EQ(width, render_text->GetUpdatedCursorBounds().x()); +} + +TEST_F(RenderTextTest, DisplayRectShowsCursorRTL) { + // Set the application default text direction to RTL. + const bool was_rtl = base::i18n::IsRTL(); + SetRTL(true); + + // Reset the render text instance since the locale was changed. + ResetRenderTextInstance(); + RenderText* render_text = GetRenderText(); + render_text->SetText(u"abcdefghijklmnopqrstuvwxzyabcdefg"); + render_text->SetCursorPosition(0); + int width = render_text->GetStringSize().width(); + ASSERT_GT(width, 10); + + // Ensure that the cursor is placed at the width of its preceding text. + render_text->SetDisplayRect(Rect(width + 10, 1)); + EXPECT_EQ(render_text->display_rect().width() - width - 1, + render_text->GetUpdatedCursorBounds().x()); + + // Ensure that shrinking the display rectangle keeps the cursor in view. + render_text->SetDisplayRect(Rect(width - 10, 1)); + EXPECT_EQ(0, render_text->GetUpdatedCursorBounds().x()); + + // Ensure that the text will pan to fill its expanding display rectangle. + render_text->SetDisplayRect(Rect(width - 5, 1)); + EXPECT_EQ(0, render_text->GetUpdatedCursorBounds().x()); + + // Ensure that a sufficiently large display rectangle shows all the text. + render_text->SetDisplayRect(Rect(width + 10, 1)); + EXPECT_EQ(render_text->display_rect().width() - width - 1, + render_text->GetUpdatedCursorBounds().x()); + + // Repeat the test with RTL text. + render_text->SetText(u"אבגדהוזחטיךכלםמן"); + render_text->SetCursorPosition(render_text->text().length()); + width = render_text->GetStringSize().width(); + ASSERT_GT(width, 10); + + // Ensure that the cursor is placed at the width of its preceding text. + render_text->SetDisplayRect(Rect(width + 10, 1)); + EXPECT_EQ(render_text->display_rect().width() - width - 1, + render_text->GetUpdatedCursorBounds().x()); + + // Ensure that shrinking the display rectangle keeps the cursor in view. + render_text->SetDisplayRect(Rect(width - 10, 1)); + EXPECT_EQ(0, render_text->GetUpdatedCursorBounds().x()); + + // Ensure that the text will pan to fill its expanding display rectangle. + render_text->SetDisplayRect(Rect(width - 5, 1)); + EXPECT_EQ(0, render_text->GetUpdatedCursorBounds().x()); + + // Ensure that a sufficiently large display rectangle shows all the text. + render_text->SetDisplayRect(Rect(width + 10, 1)); + EXPECT_EQ(render_text->display_rect().width() - width - 1, + render_text->GetUpdatedCursorBounds().x()); + + // Reset the application default text direction to LTR. + SetRTL(was_rtl); + EXPECT_EQ(was_rtl, base::i18n::IsRTL()); +} + +// Changing colors between or inside ligated glyphs should not break shaping. +TEST_F(RenderTextTest, SelectionKeepsLigatures) { + const char16_t* const kTestStrings[] = {u"\u0644\u0623", u"\u0633\u0627"}; + RenderText* render_text = GetRenderText(); + render_text->set_selection_color(SK_ColorGREEN); + + for (size_t i = 0; i < base::size(kTestStrings); ++i) { + render_text->SetText(kTestStrings[i]); + const int expected_width = render_text->GetStringSize().width(); + render_text->SelectRange({0, 1}); + EXPECT_EQ(expected_width, render_text->GetStringSize().width()); + // Drawing the text should not DCHECK or crash; see http://crbug.com/262119 + render_text->Draw(canvas()); + render_text->SetCursorPosition(0); + } +} + +// Test that characters commonly used in the context of several scripts do not +// cause text runs to break. For example the Japanese "long sound symbol" -- +// normally only used in a Katakana script, is also used on occasion when in +// Hiragana scripts. It shouldn't cause a Hiragana text run break since that +// could upset kerning. +TEST_F(RenderTextTest, ScriptExtensionsDoNotBreak) { + // Apparently ramen restaurants prefer "らーめん" over "らあめん". The "dash" + // is the long sound symbol and usually just appears in Katakana writing. + const std::u16string ramen_hiragana = u"らーめん"; + const std::u16string ramen_katakana = u"ラーメン"; + const std::u16string ramen_mixed = u"らあメン"; + + EXPECT_EQ(std::vector({ramen_hiragana}), + RunsFor(ramen_hiragana)); + EXPECT_EQ(std::vector({ramen_katakana}), + RunsFor(ramen_katakana)); + + EXPECT_EQ(std::vector({u"らあ", u"メン"}), + RunsFor(ramen_mixed)); +} + +// Test that whitespace breaks runs of text. E.g. this can permit better fonts +// to be chosen by the fallback mechanism when a font does not provide +// whitespace glyphs for all scripts. See http://crbug.com/731563. +TEST_F(RenderTextTest, WhitespaceDoesBreak) { + // Title of the Wikipedia page for "bit". ASCII spaces. In Hebrew and English. + // Note that the hyphens that Wikipedia uses are different. English uses + // ASCII (U+002D) "hyphen minus", Hebrew uses the U+2013 "EN Dash". + const std::u16string ascii_space_he = u"סיבית – ויקיפדיה"; + const std::u16string ascii_space_en = u"Bit - Wikipedia"; + + // This says "thank you very much" with a full-width non-ascii space (U+3000). + const std::u16string full_width_space = u"ども ありがと"; + + EXPECT_EQ( + std::vector({u"סיבית", u" ", u"–", u" ", u"ויקיפדיה"}), + RunsFor(ascii_space_he)); + EXPECT_EQ( + std::vector({u"Bit", u" ", u"-", u" ", u"Wikipedia"}), + RunsFor(ascii_space_en)); + EXPECT_EQ(std::vector({u"ども", u" ", u"ありがと"}), + RunsFor(full_width_space)); +} + +// Ensure strings wrap onto multiple lines for a small available width. +TEST_F(RenderTextTest, Multiline_MinWidth) { + const char16_t* kTestStrings[] = {kWeak, kLtr, kLtrRtl, kLtrRtlLtr, + kRtl, kRtlLtr, kRtlLtrRtl}; + + RenderText* render_text = GetRenderText(); + render_text->SetDisplayRect(Rect(1, 1000)); + render_text->SetMultiline(true); + render_text->SetWordWrapBehavior(WRAP_LONG_WORDS); + + for (size_t i = 0; i < base::size(kTestStrings); ++i) { + SCOPED_TRACE(base::StringPrintf("kTestStrings[%" PRIuS "]", i)); + render_text->SetText(kTestStrings[i]); + render_text->Draw(canvas()); + EXPECT_GT(test_api()->lines().size(), 1U); + } +} + +// Ensure strings wrap onto multiple lines for a normal available width. +TEST_F(RenderTextTest, Multiline_NormalWidth) { + // Should RenderText suppress drawing whitespace at the end of a line? + // Currently it does not. + const struct { + const char16_t* const text; + const Range first_line_char_range; + const Range second_line_char_range; + + // Lengths of each text run. Runs break at whitespace. + std::vector run_lengths; + + // The index of the text run that should start the second line. + int second_line_run_index; + + bool is_ltr; + } kTestStrings[] = { + {u"abc defg hijkl", Range(0, 9), Range(9, 14), {3, 1, 4, 1, 5}, 4, true}, + {u"qwertyzxcvbn", Range(0, 10), Range(10, 12), {10, 2}, 1, true}, + // RTL: should render left-to-right as "43210 \n cba9876". + // Note this used to say "Arabic language", in Arabic, but the last + // character in the string (\u0629) got fancy in an updated Mac font, so + // now the penultimate character repeats. (See "NOTE" below). + {u"اللغة العربيي", + Range(0, 6), + Range(6, 13), + {1 /* space first */, 5, 7}, + 2, + false}, + // RTL: should render left-to-right as "3210 \n cba98765". + {u"تفاح תפוזיךכם", + Range(0, 5), + Range(5, 13), + {1 /* space first */, 5, 8}, + 2, + false}}; + + RenderTextHarfBuzz* render_text = GetRenderText(); + + // Specify the fixed width for characters to suppress the possible variations + // of linebreak results. + SetGlyphWidth(5); + render_text->SetDisplayRect(Rect(50, 1000)); + render_text->SetMultiline(true); + render_text->SetWordWrapBehavior(WRAP_LONG_WORDS); + render_text->SetHorizontalAlignment(ALIGN_TO_HEAD); + + for (size_t i = 0; i < base::size(kTestStrings); ++i) { + SCOPED_TRACE(base::StringPrintf("kTestStrings[%" PRIuS "]", i)); + render_text->SetText(kTestStrings[i].text); + DrawVisualText(); + + ASSERT_EQ(2U, test_api()->lines().size()); + EXPECT_EQ(kTestStrings[i].first_line_char_range, + LineCharRange(test_api()->lines()[0])); + EXPECT_EQ(kTestStrings[i].second_line_char_range, + LineCharRange(test_api()->lines()[1])); + + ASSERT_EQ(kTestStrings[i].run_lengths.size(), text_log().size()); + + // NOTE: this expectation compares the character length and glyph counts, + // which isn't always equal. This is okay only because all the test + // strings are simple (like, no compound characters nor UTF16-surrogate + // pairs). Be careful in case more complicated test strings are added. + EXPECT_EQ(kTestStrings[i].run_lengths[0], text_log()[0].glyphs().size()); + const int second_line_start = kTestStrings[i].second_line_run_index; + EXPECT_EQ(kTestStrings[i].run_lengths[second_line_start], + text_log()[second_line_start].glyphs().size()); + EXPECT_LT(text_log()[0].origin().y(), + text_log()[second_line_start].origin().y()); + if (kTestStrings[i].is_ltr) { + EXPECT_EQ(0, text_log()[0].origin().x()); + EXPECT_EQ(0, text_log()[second_line_start].origin().x()); + } else { + EXPECT_LT(0, text_log()[0].origin().x()); + EXPECT_LT(0, text_log()[second_line_start].origin().x()); + } + } +} + +// Ensure strings don't wrap onto multiple lines for a sufficient available +// width. +TEST_F(RenderTextTest, Multiline_SufficientWidth) { + const char16_t* kTestStrings[] = {u"", + u" ", + u".", + u" . ", + u"abc", + u"a b c", + u"\u062E\u0628\u0632", + u"\u062E \u0628 \u0632"}; + + RenderText* render_text = GetRenderText(); + render_text->SetDisplayRect(Rect(1000, 1000)); + render_text->SetMultiline(true); + + for (size_t i = 0; i < base::size(kTestStrings); ++i) { + SCOPED_TRACE(base::StringPrintf("kTestStrings[%" PRIuS "]", i)); + render_text->SetText(kTestStrings[i]); + render_text->Draw(canvas()); + EXPECT_EQ(1U, test_api()->lines().size()); + } +} + +TEST_F(RenderTextTest, Multiline_Newline) { + const struct { + const char16_t* const text; + const size_t lines_count; + // Ranges of the characters on each line. + const Range line_char_ranges[3]; + } kTestStrings[] = { + {u"abc\ndef", 2ul, {Range(0, 4), Range(4, 7), Range::InvalidRange()}}, + {u"a \n b ", 2ul, {Range(0, 3), Range(3, 6), Range::InvalidRange()}}, + {u"ab\n", 2ul, {Range(0, 3), Range(), Range::InvalidRange()}}, + {u"a\n\nb", 3ul, {Range(0, 2), Range(2, 3), Range(3, 4)}}, + {u"\nab", 2ul, {Range(0, 1), Range(1, 3), Range::InvalidRange()}}, + {u"\n", 2ul, {Range(0, 1), Range(), Range::InvalidRange()}}, + }; + + RenderText* render_text = GetRenderText(); + render_text->SetDisplayRect(Rect(200, 1000)); + render_text->SetMultiline(true); + + for (size_t i = 0; i < base::size(kTestStrings); ++i) { + SCOPED_TRACE(base::StringPrintf("kTestStrings[%" PRIuS "]", i)); + render_text->SetText(kTestStrings[i].text); + render_text->Draw(canvas()); + EXPECT_EQ(kTestStrings[i].lines_count, test_api()->lines().size()); + if (kTestStrings[i].lines_count != test_api()->lines().size()) + continue; + + for (size_t j = 0; j < kTestStrings[i].lines_count; ++j) { + SCOPED_TRACE(base::StringPrintf("Line %" PRIuS "", j)); + // There might be multiple segments in one line. Merge all the segments + // ranges in the same line. + const size_t segment_size = test_api()->lines()[j].segments.size(); + Range line_range; + if (segment_size > 0) + line_range = Range( + test_api()->lines()[j].segments[0].char_range.start(), + test_api()->lines()[j].segments[segment_size - 1].char_range.end()); + EXPECT_EQ(kTestStrings[i].line_char_ranges[j], line_range); + } + } +} + +// Make sure that multiline mode ignores elide behavior. +TEST_F(RenderTextTest, Multiline_IgnoreElide) { + const char16_t kTestString[] = + u"very very very long string xxxxxxxxxxxxxxxxxxxxxxxxxx"; + + RenderText* render_text = GetRenderText(); + render_text->SetElideBehavior(ELIDE_TAIL); + render_text->SetDisplayRect(Rect(20, 1000)); + render_text->SetText(kTestString); + EXPECT_NE(std::u16string::npos, render_text->GetDisplayText().find(u"…")); + + render_text->SetMultiline(true); + EXPECT_EQ(std::u16string::npos, render_text->GetDisplayText().find(u"…")); +} + +TEST_F(RenderTextTest, Multiline_NewlineCharacterReplacement) { + const char16_t* kTestStrings[] = { + u"abc\ndef", u"a \n b ", u"ab\n", u"a\n\nb", u"\nab", u"\n", + }; + + for (size_t i = 0; i < base::size(kTestStrings); ++i) { + SCOPED_TRACE(base::StringPrintf("kTestStrings[%" PRIuS "]", i)); + ResetRenderTextInstance(); + RenderText* render_text = GetRenderText(); + render_text->SetDisplayRect(Rect(200, 1000)); + render_text->SetText(kTestStrings[i]); + + std::u16string display_text = render_text->GetDisplayText(); + // If RenderText is not multiline, the newline characters are replaced + // by symbols, therefore the character should be changed. + EXPECT_NE(kTestStrings[i], render_text->GetDisplayText()); + + // Setting multiline will fix this, the newline characters will be back + // to the original text. + render_text->SetMultiline(true); + EXPECT_EQ(kTestStrings[i], render_text->GetDisplayText()); + } +} + +// Ensure horizontal alignment works in multiline mode. +TEST_F(RenderTextTest, Multiline_HorizontalAlignment) { + constexpr struct { + const char16_t* const text; + const HorizontalAlignment alignment; + const base::i18n::TextDirection display_text_direction; + } kTestStrings[] = { + {u"abcdefghi\nhijk", ALIGN_LEFT, base::i18n::LEFT_TO_RIGHT}, + {u"nhij\nabcdefghi", ALIGN_LEFT, base::i18n::LEFT_TO_RIGHT}, + // Hebrew, 2nd line shorter + {u"אבגדהוזח\n" + u"אבגד", + ALIGN_RIGHT, base::i18n::RIGHT_TO_LEFT}, + // Hebrew, 2nd line longer + {u"אבגד\n" + u"אבגדהוזח", + ALIGN_RIGHT, base::i18n::RIGHT_TO_LEFT}, + // Arabic, 2nd line shorter. + {u"\u0627\u0627\u0627\u0627\u0627\u0627\u0627\u0627\n" + u"\u0627\u0644\u0644\u063A", + ALIGN_RIGHT, base::i18n::RIGHT_TO_LEFT}, + // Arabic, 2nd line longer. + {u"\u0627\u0644\u0644\u063A\n" + u"\u0627\u0627\u0627\u0627\u0627\u0627\u0627\u0627", + ALIGN_RIGHT, base::i18n::RIGHT_TO_LEFT}, + }; + const int kGlyphSize = 5; + RenderTextHarfBuzz* render_text = GetRenderText(); + render_text->SetHorizontalAlignment(ALIGN_TO_HEAD); + SetGlyphWidth(kGlyphSize); + render_text->SetDisplayRect(Rect(100, 1000)); + render_text->SetMultiline(true); + + for (size_t i = 0; i < base::size(kTestStrings); ++i) { + SCOPED_TRACE(testing::Message("kTestStrings[") + << i << "] = " << kTestStrings[i].text); + render_text->SetText(kTestStrings[i].text); + EXPECT_EQ(kTestStrings[i].display_text_direction, + render_text->GetDisplayTextDirection()); + render_text->Draw(canvas()); + ASSERT_LE(2u, test_api()->lines().size()); + if (kTestStrings[i].alignment == ALIGN_LEFT) { + EXPECT_EQ(0, test_api()->GetAlignmentOffset(0).x()); + EXPECT_EQ(0, test_api()->GetAlignmentOffset(1).x()); + } else { + std::vector lines = + base::SplitString(kTestStrings[i].text, std::u16string(1, '\n'), + base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); + ASSERT_EQ(2u, lines.size()); + + // Sanity check the input string lengths match the glyph lengths. + EXPECT_EQ(4u, std::min(lines[0].length(), lines[1].length())); + EXPECT_EQ(8u, std::max(lines[0].length(), lines[1].length())); + const internal::TextRunList* run_list = GetHarfBuzzRunList(); + ASSERT_EQ(3U, run_list->runs().size()); + EXPECT_EQ(lines[0].length(), run_list->runs()[0]->shape.glyph_count); + EXPECT_EQ(1u, run_list->runs()[1]->shape.glyph_count); // \n. + EXPECT_EQ(lines[1].length(), run_list->runs()[2]->shape.glyph_count); + + // The newline glyph itself has width 0. + int difference = (lines[0].length() - lines[1].length()) * kGlyphSize; + EXPECT_EQ(test_api()->GetAlignmentOffset(0).x() + difference, + test_api()->GetAlignmentOffset(1).x()); + } + } +} + +TEST_F(RenderTextTest, Multiline_WordWrapBehavior) { + const int kGlyphSize = 5; + const struct { + const WordWrapBehavior behavior; + const size_t num_lines; + const Range char_ranges[4]; + } kTestScenarios[] = { + { IGNORE_LONG_WORDS, 3u, + { Range(0, 4), Range(4, 11), Range(11, 14), Range::InvalidRange() } }, + { TRUNCATE_LONG_WORDS, 3u, + { Range(0, 4), Range(4, 8), Range(11, 14), Range::InvalidRange() } }, + { WRAP_LONG_WORDS, 4u, + { Range(0, 4), Range(4, 8), Range(8, 11), Range(11, 14) } }, + // TODO(mukai): implement ELIDE_LONG_WORDS. It's not used right now. + }; + RenderTextHarfBuzz* render_text = GetRenderText(); + render_text->SetMultiline(true); + render_text->SetText(u"foo fooooo foo"); + SetGlyphWidth(kGlyphSize); + render_text->SetDisplayRect(Rect(0, 0, kGlyphSize * 4, 0)); + + for (size_t i = 0; i < base::size(kTestScenarios); ++i) { + SCOPED_TRACE(base::StringPrintf( + "kTestScenarios[%" PRIuS "] %d", i, kTestScenarios[i].behavior)); + render_text->SetWordWrapBehavior(kTestScenarios[i].behavior); + render_text->Draw(canvas()); + + ASSERT_EQ(kTestScenarios[i].num_lines, test_api()->lines().size()); + for (size_t j = 0; j < test_api()->lines().size(); ++j) { + SCOPED_TRACE(base::StringPrintf("%" PRIuS "-th line", j)); + EXPECT_EQ(kTestScenarios[i].char_ranges[j], + LineCharRange(test_api()->lines()[j])); + EXPECT_EQ(kTestScenarios[i].char_ranges[j].length() * kGlyphSize, + test_api()->lines()[j].size.width()); + } + } +} + +TEST_F(RenderTextTest, Multiline_LineBreakerBehavior) { + const int kGlyphSize = 5; + const struct { + const char16_t* const text; + const WordWrapBehavior behavior; + const Range char_ranges[3]; + } kTestScenarios[] = { + {u"a single run", + IGNORE_LONG_WORDS, + {Range(0, 2), Range(2, 9), Range(9, 12)}}, + // 3 words: "That's ", ""good". ", "aaa" and 7 runs: "That", "'", "s ", + // """, "good", "". ", "aaa". They all mixed together. + {u"That's \"good\". aaa", + IGNORE_LONG_WORDS, + {Range(0, 7), Range(7, 15), Range(15, 18)}}, + // Test "\"" should be put into a new line correctly. + {u"a \"good\" one.", + IGNORE_LONG_WORDS, + {Range(0, 2), Range(2, 9), Range(9, 13)}}, + // Test for full-width space. + {u"That's\u3000good.\u3000yyy", + IGNORE_LONG_WORDS, + {Range(0, 7), Range(7, 13), Range(13, 16)}}, + {u"a single run", + TRUNCATE_LONG_WORDS, + {Range(0, 2), Range(2, 6), Range(9, 12)}}, + {u"That's \"good\". aaa", + TRUNCATE_LONG_WORDS, + {Range(0, 4), Range(7, 11), Range(15, 18)}}, + {u"That's good. aaa", + TRUNCATE_LONG_WORDS, + {Range(0, 4), Range(7, 11), Range(13, 16)}}, + {u"a \"good\" one.", + TRUNCATE_LONG_WORDS, + {Range(0, 2), Range(2, 6), Range(9, 13)}}, + {u"asingleword", + WRAP_LONG_WORDS, + {Range(0, 4), Range(4, 8), Range(8, 11)}}, + {u"That's good", + WRAP_LONG_WORDS, + {Range(0, 4), Range(4, 7), Range(7, 11)}}, + {u"That's \"g\".", + WRAP_LONG_WORDS, + {Range(0, 4), Range(4, 7), Range(7, 11)}}, + }; + + RenderTextHarfBuzz* render_text = GetRenderText(); + render_text->SetMultiline(true); + SetGlyphWidth(kGlyphSize); + render_text->SetDisplayRect(Rect(0, 0, kGlyphSize * 4, 0)); + + for (size_t i = 0; i < base::size(kTestScenarios); ++i) { + SCOPED_TRACE(base::StringPrintf("kTestStrings[%" PRIuS "]", i)); + render_text->SetText(kTestScenarios[i].text); + render_text->SetWordWrapBehavior(kTestScenarios[i].behavior); + render_text->Draw(canvas()); + + ASSERT_EQ(3u, test_api()->lines().size()); + for (size_t j = 0; j < test_api()->lines().size(); ++j) { + SCOPED_TRACE(base::StringPrintf("%" PRIuS "-th line", j)); + // Merge all the segments ranges in the same line. + size_t segment_size = test_api()->lines()[j].segments.size(); + Range line_range; + if (segment_size > 0) + line_range = Range( + test_api()->lines()[j].segments[0].char_range.start(), + test_api()->lines()[j].segments[segment_size - 1].char_range.end()); + EXPECT_EQ(kTestScenarios[i].char_ranges[j], line_range); + // Depending on kerning pairs in the font, a run's length is not strictly + // equal to number of glyphs * kGlyphsize. Size should match within a + // small margin. + const float kSizeMargin = 0.127; + EXPECT_LT( + std::abs(kTestScenarios[i].char_ranges[j].length() * kGlyphSize - + test_api()->lines()[j].size.width()), + kSizeMargin); + } + } +} + +// Test that Surrogate pairs or combining character sequences do not get +// separated by line breaking. +TEST_F(RenderTextTest, Multiline_SurrogatePairsOrCombiningChars) { + RenderTextHarfBuzz* render_text = GetRenderText(); + render_text->SetMultiline(true); + render_text->SetWordWrapBehavior(WRAP_LONG_WORDS); + + // Below is 'MUSICAL SYMBOL G CLEF' (U+1D11E), which is represented in UTF-16 + // as two code units forming a surrogate pair: 0xD834 0xDD1E. + const char16_t kSurrogate[] = {0xD834, 0xDD1E, 0}; + const std::u16string text_surrogate(kSurrogate); + const int kSurrogateWidth = + GetStringWidth(kSurrogate, render_text->font_list()); + + // Below is a Devanagari two-character combining sequence U+0921 U+093F. The + // sequence forms a single display character and should not be separated. + const char16_t kCombiningChars[] = {0x921, 0x93F, 0}; + const std::u16string text_combining(kCombiningChars); + const int kCombiningCharsWidth = + GetStringWidth(kCombiningChars, render_text->font_list()); + + const struct { + const std::u16string text; + const int display_width; + const Range char_ranges[3]; + } kTestScenarios[] = { + {text_surrogate + text_surrogate + text_surrogate, + kSurrogateWidth / 2 * 3, + {Range(0, 2), Range(2, 4), Range(4, 6)}}, + {text_surrogate + u" " + kCombiningChars, + std::min(kSurrogateWidth, kCombiningCharsWidth) / 2, + {Range(0, 2), Range(2, 3), Range(3, 5)}}, + }; + + for (size_t i = 0; i < base::size(kTestScenarios); ++i) { + SCOPED_TRACE(base::StringPrintf("kTestStrings[%" PRIuS "]", i)); + render_text->SetText(kTestScenarios[i].text); + render_text->SetDisplayRect(Rect(0, 0, kTestScenarios[i].display_width, 0)); + render_text->Draw(canvas()); + + ASSERT_EQ(3u, test_api()->lines().size()); + for (size_t j = 0; j < test_api()->lines().size(); ++j) { + SCOPED_TRACE(base::StringPrintf("%" PRIuS "-th line", j)); + // There is only one segment in each line. + EXPECT_EQ(kTestScenarios[i].char_ranges[j], + test_api()->lines()[j].segments[0].char_range); + } + } +} + +// Test that Zero width characters have the correct line breaking behavior. +TEST_F(RenderTextTest, Multiline_ZeroWidthChars) { + RenderTextHarfBuzz* render_text = GetRenderText(); + + render_text->SetMultiline(true); + render_text->SetWordWrapBehavior(WRAP_LONG_WORDS); + + // U+200B is Zero Width Space. + const std::u16string text = u"test\u200B\n\u200Btest."; + const int kTestWidth = GetStringWidth(u"test", render_text->font_list()); + const Range char_ranges[3] = {Range(0, 6), Range(6, 11), Range(11, 12)}; + + render_text->SetText(text); + render_text->SetDisplayRect(Rect(0, 0, kTestWidth, 0)); + render_text->Draw(canvas()); + + EXPECT_EQ(3u, test_api()->lines().size()); + for (size_t j = 0; + j < std::min(base::size(char_ranges), test_api()->lines().size()); ++j) { + SCOPED_TRACE(base::StringPrintf("%" PRIuS "-th line", j)); + int segment_size = test_api()->lines()[j].segments.size(); + ASSERT_GT(segment_size, 0); + Range line_range( + test_api()->lines()[j].segments[0].char_range.start(), + test_api()->lines()[j].segments[segment_size - 1].char_range.end()); + EXPECT_EQ(char_ranges[j], line_range); + } +} + +TEST_F(RenderTextTest, Multiline_ZeroWidthNewline) { + RenderTextHarfBuzz* render_text = GetRenderText(); + render_text->SetMultiline(true); + + const std::u16string text(u"\n\n"); + render_text->SetText(text); + EXPECT_EQ(3u, render_text->GetNumLines()); + for (const auto& line : test_api()->lines()) { + EXPECT_EQ(0, line.size.width()); + EXPECT_LT(0, line.size.height()); + } + + const internal::TextRunList* run_list = GetHarfBuzzRunList(); + EXPECT_EQ(2U, run_list->size()); + EXPECT_EQ(0, run_list->width()); +} + +TEST_F(RenderTextTest, Multiline_GetLineContainingCaret) { + struct { + const SelectionModel caret; + const size_t line_num; + } cases[] = { + {SelectionModel(8, CURSOR_FORWARD), 1}, + {SelectionModel(9, CURSOR_BACKWARD), 1}, + {SelectionModel(9, CURSOR_FORWARD), 2}, + {SelectionModel(12, CURSOR_BACKWARD), 2}, + {SelectionModel(12, CURSOR_FORWARD), 2}, + {SelectionModel(13, CURSOR_BACKWARD), 3}, + {SelectionModel(13, CURSOR_FORWARD), 3}, + {SelectionModel(14, CURSOR_BACKWARD), 4}, + {SelectionModel(14, CURSOR_FORWARD), 4}, + }; + + // Set a non-integral width to cause rounding errors in calculating cursor + // bounds. GetCursorBounds calculates cursor position based on the horizontal + // span of the cursor, which is compared with the line widths to find out on + // which line the span stops. Rounding errors should be taken into + // consideration in comparing these two non-integral values. + SetGlyphWidth(5.3); + RenderText* render_text = GetRenderText(); + render_text->SetDisplayRect(Rect(45, 1000)); + render_text->SetMultiline(true); + render_text->SetVerticalAlignment(ALIGN_TOP); + + for (const char16_t* text : + {u"\n123 456 789\n\n123", u"\nשנב גקכ עין\n\nחלך"}) { + for (const auto& sample : cases) { + SCOPED_TRACE(testing::Message() + << "Testing " << (text[1] == '1' ? "LTR" : "RTL") + << " Caret " << sample.caret << " line " << sample.line_num); + render_text->SetText(text); + EXPECT_EQ(5U, render_text->GetNumLines()); + EXPECT_EQ(sample.line_num, + render_text->GetLineContainingCaret(sample.caret)); + + // GetCursorBounds should be in the same line as GetLineContainingCaret. + Rect bounds = render_text->GetCursorBounds(sample.caret, true); + EXPECT_EQ(static_cast(sample.line_num), + GetLineContainingYCoord(bounds.origin().y() + 1)); + } + } +} + +TEST_F(RenderTextTest, NewlineWithoutMultilineFlag) { + const char16_t* kTestStrings[] = { + u"abc\ndef", u"a \n b ", u"ab\n", u"a\n\nb", u"\nab", u"\n", + }; + + RenderText* render_text = GetRenderText(); + render_text->SetDisplayRect(Rect(200, 1000)); + + for (size_t i = 0; i < base::size(kTestStrings); ++i) { + SCOPED_TRACE(base::StringPrintf("kTestStrings[%" PRIuS "]", i)); + render_text->SetText(kTestStrings[i]); + render_text->Draw(canvas()); + + EXPECT_EQ(1U, test_api()->lines().size()); + } +} + +TEST_F(RenderTextTest, ControlCharacterReplacement) { + static const char16_t kTextWithControlCharacters[] = u"\b\r\a\t\n\v\f"; + + RenderText* render_text = GetRenderText(); + render_text->SetText(kTextWithControlCharacters); + + // The control characters should have been replaced by their symbols. + EXPECT_EQ(u"␈␍␇␉␊␋␌", render_text->GetDisplayText()); + + // Setting multiline, the newline character will be back to the original text. + render_text->SetMultiline(true); + EXPECT_EQ(u"␈\r␇␉\n␋␌", render_text->GetDisplayText()); + + // The generic control characters should have been replaced by the replacement + // codepoints. + render_text->SetText(u"\u008f\u0080"); + EXPECT_EQ(u"\ufffd\ufffd", render_text->GetDisplayText()); +} + +TEST_F(RenderTextTest, PrivateUseCharacterReplacement) { + RenderText* render_text = GetRenderText(); + render_text->SetText(u"xx\ue78d\ue78fa\U00100042z"); + + // The private use characters should have been replaced. If the code point is + // a surrogate pair, it needs to be replaced by two characters. + EXPECT_EQ(u"xx\ufffd\ufffda\ufffdz", render_text->GetDisplayText()); + + // The private use characters from Area-B must be replaced. The rewrite step + // replaced 2 characters by 1 character. + render_text->SetText(u"x\U00100000\U00100001\U00100002"); + EXPECT_EQ(u"x\ufffd\ufffd\ufffd", render_text->GetDisplayText()); +} + +TEST_F(RenderTextTest, AppleSpecificPrivateUseCharacterReplacement) { + // see: http://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/CORPCHAR.TXT + RenderText* render_text = GetRenderText(); + render_text->SetText(u"\uf8ff"); +#if defined(OS_APPLE) + EXPECT_EQ(u"\uf8ff", render_text->GetDisplayText()); +#else + EXPECT_EQ(u"\ufffd", render_text->GetDisplayText()); +#endif +} + +TEST_F(RenderTextTest, MicrosoftSpecificPrivateUseCharacterReplacement) { + const char16_t* allowed_codepoints[] = { + u"\uF093", u"\uF094", u"\uF095", u"\uF096", u"\uF108", + u"\uF109", u"\uF10A", u"\uF10B", u"\uF10C", u"\uF10D", + u"\uF10E", u"\uEECA", u"\uEDE3"}; + for (auto* codepoint : allowed_codepoints) { + RenderText* render_text = GetRenderText(); + render_text->SetText(codepoint); +#if defined(OS_WIN) + if (base::win::GetVersion() >= base::win::Version::WIN10) { + EXPECT_EQ(codepoint, render_text->GetDisplayText()); + } else { + EXPECT_EQ(u"\uFFFD", render_text->GetDisplayText()); + } +#else + EXPECT_EQ(u"\uFFFD", render_text->GetDisplayText()); +#endif + } +} + +TEST_F(RenderTextTest, InvalidSurrogateCharacterReplacement) { + // Text with invalid surrogates (surrogates low 0xDC00 and high 0xD800). + RenderText* render_text = GetRenderText(); + render_text->SetText(u"\xDC00\xD800"); + EXPECT_EQ(u"\ufffd\ufffd", render_text->GetDisplayText()); +} + +// Make sure the horizontal positions of runs in a line (left-to-right for +// LTR languages and right-to-left for RTL languages). +TEST_F(RenderTextTest, HarfBuzz_HorizontalPositions) { + const struct { + const char16_t* const text; + const char* expected_runs; + } kTestStrings[] = { + {u"abc\u3042\u3044\u3046\u3048\u304A", "[0->2][3->7]"}, + {u"\u062A\u0641\u0627\u062D\u05EA\u05E4וז", "[7<-4][3<-0]"}, + }; + + RenderTextHarfBuzz* render_text = GetRenderText(); + + for (size_t i = 0; i < base::size(kTestStrings); ++i) { + SCOPED_TRACE(base::StringPrintf("kTestStrings[%" PRIuS "]", i)); + render_text->SetText(kTestStrings[i].text); + + EXPECT_EQ(kTestStrings[i].expected_runs, GetRunListStructureString()); + + DrawVisualText(); + + const internal::TextRunList* run_list = GetHarfBuzzRunList(); + ASSERT_EQ(2U, run_list->size()); + ASSERT_EQ(2U, text_log().size()); + + // Verifies the DrawText happens in the visual order and left-to-right. + // If the text is RTL, the logically first run should be drawn at last. + EXPECT_EQ( + run_list->runs()[run_list->logical_to_visual(0)]->shape.glyph_count, + text_log()[0].glyphs().size()); + EXPECT_EQ( + run_list->runs()[run_list->logical_to_visual(1)]->shape.glyph_count, + text_log()[1].glyphs().size()); + EXPECT_LT(text_log()[0].origin().x(), text_log()[1].origin().x()); + } +} + +// Test TextRunHarfBuzz's cluster finding logic. +TEST_F(RenderTextTest, HarfBuzz_Clusters) { + struct { + uint32_t glyph_to_char[4]; + Range chars[4]; + Range glyphs[4]; + bool is_rtl; + } cases[] = { + { // From string "A B C D" to glyphs "a b c d". + { 0, 1, 2, 3 }, + { Range(0, 1), Range(1, 2), Range(2, 3), Range(3, 4) }, + { Range(0, 1), Range(1, 2), Range(2, 3), Range(3, 4) }, + false + }, + { // From string "A B C D" to glyphs "d c b a". + { 3, 2, 1, 0 }, + { Range(0, 1), Range(1, 2), Range(2, 3), Range(3, 4) }, + { Range(3, 4), Range(2, 3), Range(1, 2), Range(0, 1) }, + true + }, + { // From string "A B C D" to glyphs "ab c c d". + { 0, 2, 2, 3 }, + { Range(0, 2), Range(0, 2), Range(2, 3), Range(3, 4) }, + { Range(0, 1), Range(0, 1), Range(1, 3), Range(3, 4) }, + false + }, + { // From string "A B C D" to glyphs "d c c ba". + { 3, 2, 2, 0 }, + { Range(0, 2), Range(0, 2), Range(2, 3), Range(3, 4) }, + { Range(3, 4), Range(3, 4), Range(1, 3), Range(0, 1) }, + true + }, + }; + + internal::TextRunHarfBuzz run((Font())); + run.range = Range(0, 4); + run.shape.glyph_count = 4; + run.shape.glyph_to_char.resize(4); + + for (size_t i = 0; i < base::size(cases); ++i) { + std::copy(cases[i].glyph_to_char, cases[i].glyph_to_char + 4, + run.shape.glyph_to_char.begin()); + run.font_params.is_rtl = cases[i].is_rtl; + + for (size_t j = 0; j < 4; ++j) { + SCOPED_TRACE(base::StringPrintf("Case %" PRIuS ", char %" PRIuS, i, j)); + Range chars; + Range glyphs; + run.GetClusterAt(j, &chars, &glyphs); + EXPECT_EQ(cases[i].chars[j], chars); + EXPECT_EQ(cases[i].glyphs[j], glyphs); + EXPECT_EQ(cases[i].glyphs[j], run.CharRangeToGlyphRange(chars)); + } + } +} + +// Ensures GetClusterAt does not crash on invalid conditions. crbug.com/724880 +TEST_F(RenderTextTest, HarfBuzz_NoCrashOnTextRunGetClusterAt) { + internal::TextRunHarfBuzz run((Font())); + run.range = Range(0, 4); + run.shape.glyph_count = 4; + // Construct a |glyph_to_char| map where no glyph maps to the first character. + run.shape.glyph_to_char = {1u, 1u, 2u, 3u}; + + Range chars, glyphs; + // GetClusterAt should not crash asking for the cluster at position 0. + ASSERT_NO_FATAL_FAILURE(run.GetClusterAt(0, &chars, &glyphs)); +} + +// Ensure that graphemes with multiple code points do not get split. +TEST_F(RenderTextTest, HarfBuzz_SubglyphGraphemeCases) { + const char16_t* cases[] = { + // Ä (A with combining umlaut), followed by a "B". + u"A\u0308B", + // कि (Devangari letter KA with vowel I), followed by an "a". + u"\u0915\u093f\u0905", + // จำ (Thai characters CHO CHAN and SARA AM, followed by Thai digit 0. + u"\u0e08\u0e33\u0E50", + }; + + RenderTextHarfBuzz* render_text = GetRenderText(); + + for (size_t i = 0; i < base::size(cases); ++i) { + SCOPED_TRACE(base::StringPrintf("Case %" PRIuS, i)); + + std::u16string text = cases[i]; + render_text->SetText(text); + const internal::TextRunList* run_list = GetHarfBuzzRunList(); + ASSERT_EQ(1U, run_list->size()); + internal::TextRunHarfBuzz* run = run_list->runs()[0].get(); + + auto first_grapheme_bounds = run->GetGraphemeBounds(render_text, 0); + EXPECT_EQ(first_grapheme_bounds, run->GetGraphemeBounds(render_text, 1)); + auto second_grapheme_bounds = run->GetGraphemeBounds(render_text, 2); + EXPECT_EQ(first_grapheme_bounds.end(), second_grapheme_bounds.start()); + } +} + +// Test the partition of a multi-grapheme cluster into grapheme ranges. +TEST_F(RenderTextTest, HarfBuzz_SubglyphGraphemePartition) { + struct { + uint32_t glyph_to_char[2]; + Range bounds[4]; + bool is_rtl; + } cases[] = { + { // From string "A B C D" to glyphs "a bcd". + { 0, 1 }, + { Range(0, 10), Range(10, 13), Range(13, 17), Range(17, 20) }, + false + }, + { // From string "A B C D" to glyphs "ab cd". + { 0, 2 }, + { Range(0, 5), Range(5, 10), Range(10, 15), Range(15, 20) }, + false + }, + { // From string "A B C D" to glyphs "dcb a". + { 1, 0 }, + { Range(10, 20), Range(7, 10), Range(3, 7), Range(0, 3) }, + true + }, + { // From string "A B C D" to glyphs "dc ba". + { 2, 0 }, + { Range(15, 20), Range(10, 15), Range(5, 10), Range(0, 5) }, + true + }, + }; + + internal::TextRunHarfBuzz run((Font())); + run.range = Range(0, 4); + run.shape.glyph_count = 2; + run.shape.glyph_to_char.resize(2); + run.shape.positions.resize(4); + run.shape.width = 20; + + RenderTextHarfBuzz* render_text = GetRenderText(); + render_text->SetText(u"abcd"); + + for (size_t i = 0; i < base::size(cases); ++i) { + std::copy(cases[i].glyph_to_char, cases[i].glyph_to_char + 2, + run.shape.glyph_to_char.begin()); + run.font_params.is_rtl = cases[i].is_rtl; + for (int j = 0; j < 2; ++j) + run.shape.positions[j].set(j * 10, 0); + + for (size_t j = 0; j < 4; ++j) { + SCOPED_TRACE(base::StringPrintf("Case %" PRIuS ", char %" PRIuS, i, j)); + RangeF bounds_f = run.GetGraphemeBounds(render_text, j); + Range bounds(std::round(bounds_f.start()), std::round(bounds_f.end())); + EXPECT_EQ(cases[i].bounds[j], bounds); + } + } +} + +TEST_F(RenderTextTest, HarfBuzz_RunDirection) { + RenderTextHarfBuzz* render_text = GetRenderText(); + const std::u16string mixed = u"אב1234גדabc"; + render_text->SetText(mixed); + + // Get the run list for both display directions. + render_text->SetDirectionalityMode(DIRECTIONALITY_FORCE_LTR); + EXPECT_EQ("[7<-6][2->5][1<-0][8->10]", GetRunListStructureString()); + + render_text->SetDirectionalityMode(DIRECTIONALITY_FORCE_RTL); + EXPECT_EQ("[8->10][7<-6][2->5][1<-0]", GetRunListStructureString()); +} + +TEST_F(RenderTextTest, HarfBuzz_RunDirection_URLs) { + RenderTextHarfBuzz* render_text = GetRenderText(); + // This string, unescaped (logical order): + // ‭www.אב.גד/הוabc/def?זח=טי‬ + const std::u16string mixed = + u"www.אב.גד/הו" + u"abc/def?זח=טי"; + render_text->SetText(mixed); + + // Normal LTR text should treat URL syntax as weak (as per the normal Bidi + // algorithm). + render_text->SetDirectionalityMode(DIRECTIONALITY_FORCE_LTR); + + // This is complex because a new run is created for each punctuation mark, but + // it simplifies down to: [0->3][11<-4][12->19][24<-20] + // Should render as: ‭www.וה/דג.באabc/def?יט=חז‬ + const char kExpectedRunListNormalBidi[] = + "[0->2][3][11<-10][9][8<-7][6][5<-4][12->14][15][16->18][19][24<-23][22]" + "[21<-20]"; + EXPECT_EQ(kExpectedRunListNormalBidi, GetRunListStructureString()); + + // DIRECTIONALITY_AS_URL should be exactly the same as + // DIRECTIONALITY_FORCE_LTR by default. + render_text->SetDirectionalityMode(DIRECTIONALITY_AS_URL); + EXPECT_EQ(kExpectedRunListNormalBidi, GetRunListStructureString()); +} + +TEST_F(RenderTextTest, HarfBuzz_BreakRunsByUnicodeBlocks) { + RenderTextHarfBuzz* render_text = GetRenderText(); + + // The ▶ (U+25B6) "play character" should break runs. http://crbug.com/278913 + render_text->SetText(u"x\u25B6y"); + EXPECT_EQ(std::vector({u"x", u"▶", u"y"}), + GetRunListStrings()); + EXPECT_EQ("[0][1][2]", GetRunListStructureString()); + + render_text->SetText(u"x \u25B6 y"); + EXPECT_EQ(std::vector({u"x", u" ", u"▶", u" ", u"y"}), + GetRunListStrings()); + EXPECT_EQ("[0][1][2][3][4]", GetRunListStructureString()); +} + +TEST_F(RenderTextTest, HarfBuzz_BreakRunsByEmoji) { + RenderTextHarfBuzz* render_text = GetRenderText(); + + // 😁 (U+1F601, a smile emoji) and ✨ (U+2728, a sparkle icon) can both be + // drawn with color emoji fonts, so runs should be separated. crbug.com/448909 + // Windows requires wide strings for \Unnnnnnnn universal character names. + render_text->SetText(u"x\U0001F601y\u2728"); + EXPECT_EQ(std::vector({u"x", u"😁", u"y", u"✨"}), + GetRunListStrings()); + // U+1F601 is represented as a surrogate pair in UTF-16. + EXPECT_EQ("[0][1->2][3][4]", GetRunListStructureString()); + + // Ensure non-latin 「foo」 brackets around Emoji correctly break runs. + render_text->SetText(u"「🦋」「"); + EXPECT_EQ(std::vector({u"「", u"🦋", u"」「"}), + GetRunListStrings()); + // Note 🦋 is a surrogate pair [1->2]. + EXPECT_EQ("[0][1->2][3->4]", GetRunListStructureString()); +} + +TEST_F(RenderTextTest, HarfBuzz_BreakRunsByNewline) { + RenderText* render_text = GetRenderText(); + render_text->SetMultiline(true); + render_text->SetText(u"x\ny"); + EXPECT_EQ(std::vector({u"x", u"\n", u"y"}), + GetRunListStrings()); + EXPECT_EQ("[0][1][2]", GetRunListStructureString()); + + // Validate that the character newline is an unknown glyph + // (see http://crbug/972090 and http://crbug/680430). + const internal::TextRunList* run_list = GetHarfBuzzRunList(); + ASSERT_EQ(3U, run_list->size()); + EXPECT_EQ(0U, run_list->runs()[0]->CountMissingGlyphs()); + EXPECT_EQ(1U, run_list->runs()[1]->CountMissingGlyphs()); + EXPECT_EQ(0U, run_list->runs()[2]->CountMissingGlyphs()); + + SkScalar x_width = + run_list->runs()[0]->GetGlyphWidthForCharRange(Range(0, 1)); + EXPECT_GT(x_width, 0); + + // Newline character must have a width of zero. + SkScalar newline_width = + run_list->runs()[1]->GetGlyphWidthForCharRange(Range(1, 2)); + EXPECT_EQ(newline_width, 0); + + SkScalar y_width = + run_list->runs()[2]->GetGlyphWidthForCharRange(Range(2, 3)); + EXPECT_GT(y_width, 0); +} + +TEST_F(RenderTextTest, HarfBuzz_BreakRunsByEmojiVariationSelectors) { + constexpr int kGlyphWidth = 30; + SetGlyphWidth(30); + RenderTextHarfBuzz* render_text = GetRenderText(); + + // ☎ (U+260E BLACK TELEPHONE) and U+FE0F (a variation selector) combine to + // form (on some platforms), ☎️, a red (or blue) telephone. The run can + // not break between the codepoints, or the incorrect glyph will be chosen. + render_text->SetText(u"z\u260E\uFE0Fy"); + render_text->SetDisplayRect(Rect(1000, 50)); + EXPECT_EQ(std::vector({u"z", u"☎\uFE0F", u"y"}), + GetRunListStrings()); + EXPECT_EQ("[0][1->2][3]", GetRunListStructureString()); + + // Also test moving the cursor across the telephone. + EXPECT_EQ(gfx::Range(0, 0), render_text->selection()); + EXPECT_EQ(0, render_text->GetUpdatedCursorBounds().x()); + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(gfx::Range(1, 1), render_text->selection()); + EXPECT_EQ(1 * kGlyphWidth, render_text->GetUpdatedCursorBounds().x()); + +#if defined(OS_APPLE) + // Early versions of macOS provide a tofu glyph for the variation selector. + // Bail out early except on 10.12 and above. + if (base::mac::IsAtMostOS10_11()) + return; +#endif + + // TODO(865709): make this work on Android. +#if !defined(OS_ANDROID) + // Jump over the telephone: two codepoints, but a single glyph. + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(gfx::Range(3, 3), render_text->selection()); + EXPECT_EQ(2 * kGlyphWidth, render_text->GetUpdatedCursorBounds().x()); + + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(gfx::Range(4, 4), render_text->selection()); + EXPECT_EQ(3 * kGlyphWidth, render_text->GetUpdatedCursorBounds().x()); + + // Nothing else. + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(gfx::Range(4, 4), render_text->selection()); + EXPECT_EQ(3 * kGlyphWidth, render_text->GetUpdatedCursorBounds().x()); +#endif +} + +TEST_F(RenderTextTest, HarfBuzz_OrphanedVariationSelector) { + RenderTextHarfBuzz* render_text = GetRenderText(); + + // It should never happen in normal usage, but a variation selector can appear + // by itself. In this case, it can form its own text run, with no glyphs. + render_text->SetText(u"\uFE0F"); + EXPECT_EQ(std::vector({u"\uFE0F"}), GetRunListStrings()); + EXPECT_EQ("[0]", GetRunListStructureString()); + CheckBoundsForCursorPositions(); +} + +TEST_F(RenderTextTest, HarfBuzz_AsciiVariationSelector) { + RenderTextHarfBuzz* render_text = GetRenderText(); +#if defined(OS_APPLE) + // Don't use a system font on macOS - asking for a variation selector on + // ASCII glyphs can tickle OS bugs. See http://crbug.com/785522. + render_text->SetFontList(FontList("Arial, 12px")); +#endif + // A variation selector doesn't have to appear with Emoji. It will probably + // cause the typesetter to render tofu in this case, but it should not break + // a text run. + render_text->SetText(u"z\uFE0Fy"); + EXPECT_EQ(std::vector({u"z\uFE0Fy"}), GetRunListStrings()); + EXPECT_EQ("[0->2]", GetRunListStructureString()); + CheckBoundsForCursorPositions(); +} + +TEST_F(RenderTextTest, HarfBuzz_LeadingVariationSelector) { + RenderTextHarfBuzz* render_text = GetRenderText(); + + // When a variation selector appears either side of an emoji, ensure the one + // after is in the same run. + render_text->SetText(u"\uFE0F\u260E\uFE0Fy"); + EXPECT_EQ(std::vector({u"\uFE0F", u"☎\uFE0F", u"y"}), + GetRunListStrings()); + EXPECT_EQ("[0][1->2][3]", GetRunListStructureString()); + CheckBoundsForCursorPositions(); +} + +TEST_F(RenderTextTest, HarfBuzz_TrailingVariationSelector) { + RenderTextHarfBuzz* render_text = GetRenderText(); + + // If a redundant variation selector appears in an emoji run, it also gets + // merged into the emoji run. Usually there should be no effect. That's + // ultimately up to the typeface but, however it choses, cursor and glyph + // positions should behave. + render_text->SetText(u"z\u260E\uFE0F\uFE0Fy"); + EXPECT_EQ(std::vector({u"z", u"☎\uFE0F\uFE0F", u"y"}), + GetRunListStrings()); + EXPECT_EQ("[0][1->3][4]", GetRunListStructureString()); + CheckBoundsForCursorPositions(); +} + +TEST_F(RenderTextTest, HarfBuzz_MultipleVariationSelectorEmoji) { + RenderTextHarfBuzz* render_text = GetRenderText(); + + // Two emoji with variation selectors appearing in a correct sequence should + // be in the same run. + render_text->SetText(u"z\u260E\uFE0F\u260E\uFE0Fy"); + EXPECT_EQ(std::vector({u"z", u"☎\uFE0F☎\uFE0F", u"y"}), + GetRunListStrings()); + EXPECT_EQ("[0][1->4][5]", GetRunListStructureString()); + CheckBoundsForCursorPositions(); +} + +TEST_F(RenderTextTest, HarfBuzz_BreakRunsByAscii) { + RenderTextHarfBuzz* render_text = GetRenderText(); + + // ▶ (U+25B6, Geometric Shapes) and an ascii character should have + // different runs. + render_text->SetText(u"▶z"); + EXPECT_EQ(std::vector({u"▶", u"z"}), GetRunListStrings()); + EXPECT_EQ("[0][1]", GetRunListStructureString()); + + // ★ (U+2605, Miscellaneous Symbols) and an ascii character should have + // different runs. + render_text->SetText(u"★1"); + EXPECT_EQ(std::vector({u"★", u"1"}), GetRunListStrings()); + EXPECT_EQ("[0][1]", GetRunListStructureString()); + + // 🐱 (U+1F431, a cat face, Miscellaneous Symbols and Pictographs) and an + // ASCII period should have separate runs. + render_text->SetText(u"🐱."); + EXPECT_EQ(std::vector({u"🐱", u"."}), GetRunListStrings()); + // U+1F431 is represented as a surrogate pair in UTF-16. + EXPECT_EQ("[0->1][2]", GetRunListStructureString()); + + // 🥴 (U+1f974, Supplemental Symbols and Pictographs) and an ascii character + // should have different runs. + render_text->SetText(u"🥴$"); + EXPECT_EQ(std::vector({u"🥴", u"$"}), GetRunListStrings()); + EXPECT_EQ("[0->1][2]", GetRunListStructureString()); +} + +// Test that, on Mac, font fallback mechanisms and Harfbuzz configuration cause +// the correct glyphs to be chosen for unicode regional indicators. +TEST_F(RenderTextTest, EmojiFlagGlyphCount) { + RenderText* render_text = GetRenderText(); + render_text->SetDisplayRect(Rect(1000, 1000)); + // Two flags: UK and Japan. Note macOS 10.9 only has flags for 10 countries. + std::u16string text(u"🇬🇧🇯🇵"); + // Each flag is 4 UTF16 characters (2 surrogate pair code points). + EXPECT_EQ(8u, text.length()); + render_text->SetText(text); + + const internal::TextRunList* run_list = GetHarfBuzzRunList(); + ASSERT_EQ(1U, run_list->runs().size()); +#if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_APPLE) + // On Linux and macOS, the flags should be found, so two glyphs result. + EXPECT_EQ(2u, run_list->runs()[0]->shape.glyph_count); +#elif defined(OS_ANDROID) + // It seems that some versions of android support the flags. Older versions + // don't support it. + EXPECT_TRUE(2u == run_list->runs()[0]->shape.glyph_count || + 4u == run_list->runs()[0]->shape.glyph_count); +#else + // Elsewhere, the flags are not found, so each surrogate pair gets a + // placeholder glyph. Eventually, all platforms should have 2 glyphs. + EXPECT_EQ(4u, run_list->runs()[0]->shape.glyph_count); +#endif +} + +TEST_F(RenderTextTest, HarfBuzz_ShapeRunsWithMultipleFonts) { + RenderTextHarfBuzz* render_text = GetRenderText(); + + // The following text will be split in 3 runs: + // 1) u+1F3F3 u+FE0F u+FE0F (Segoe UI Emoji) + // 2) u+0020 (Segoe UI) + // 3) u+1F308 u+20E0 u+20E0 (Segoe UI Symbol) + // The three runs are shape in the same group but are mapped with three + // different fonts. + render_text->SetText(u"\U0001F3F3\U0000FE0F\U00000020\U0001F308\U000020E0"); + std::vector expected; + expected.push_back(u"\U0001F3F3\U0000FE0F"); + expected.push_back(u" "); + expected.push_back(u"\U0001F308\U000020E0"); + EXPECT_EQ(expected, GetRunListStrings()); + EXPECT_EQ("[0->2][3][4->6]", GetRunListStructureString()); + +#if defined(OS_WIN) + std::vector expected_fonts; + if (base::win::GetVersion() < base::win::Version::WIN10) + expected_fonts = {"Segoe UI", "Segoe UI", "Segoe UI Symbol"}; + else + expected_fonts = {"Segoe UI Emoji", "Segoe UI", "Segoe UI Symbol"}; + + std::vector mapped_fonts; + for (const auto& font_span : GetFontSpans()) + mapped_fonts.push_back(font_span.first.GetFontName()); + EXPECT_EQ(expected_fonts, mapped_fonts); +#endif +} + +TEST_F(RenderTextTest, GlyphBounds) { + const char16_t* kTestStrings[] = {u"asdf 1234 qwer", u"\u0647\u0654", + u"\u0645\u0631\u062D\u0628\u0627"}; + RenderText* render_text = GetRenderText(); + + for (size_t i = 0; i < base::size(kTestStrings); ++i) { + render_text->SetText(kTestStrings[i]); + + for (size_t j = 0; j < render_text->text().length(); ++j) + EXPECT_FALSE(render_text->GetCursorSpan(Range(j, j + 1)).is_empty()); + } +} + +// Ensure that shaping with a non-existent font does not cause a crash. +TEST_F(RenderTextTest, HarfBuzz_NonExistentFont) { + RenderTextHarfBuzz* render_text = GetRenderText(); + render_text->SetText(u"test"); + const internal::TextRunList* run_list = GetHarfBuzzRunList(); + ASSERT_EQ(1U, run_list->size()); + internal::TextRunHarfBuzz* run = run_list->runs()[0].get(); + ShapeRunWithFont(render_text->text(), Font("TheFontThatDoesntExist", 13), + FontRenderParams(), run); +} + +// Ensure an empty run returns sane values to queries. +TEST_F(RenderTextTest, HarfBuzz_EmptyRun) { + internal::TextRunHarfBuzz run((Font())); + RenderTextHarfBuzz* render_text = GetRenderText(); + render_text->SetText(u"abcdefgh"); + + run.range = Range(3, 8); + run.shape.glyph_count = 0; + EXPECT_EQ(Range(), run.CharRangeToGlyphRange(Range(4, 5))); + EXPECT_EQ(RangeF(), run.GetGraphemeBounds(render_text, 4)); + Range chars; + Range glyphs; + run.GetClusterAt(4, &chars, &glyphs); + EXPECT_EQ(Range(3, 8), chars); + EXPECT_EQ(Range(), glyphs); +} + +// Ensure the line breaker doesn't compute the word's width bigger than the +// actual size. See http://crbug.com/470073 +TEST_F(RenderTextTest, HarfBuzz_WordWidthWithDiacritics) { + RenderTextHarfBuzz* render_text = GetRenderText(); + const std::u16string kWord = u"\u0906\u092A\u0915\u0947 "; + render_text->SetText(kWord); + const SizeF text_size = render_text->GetStringSizeF(); + + render_text->SetText(kWord + kWord); + render_text->SetMultiline(true); + EXPECT_EQ(text_size.width() * 2, render_text->GetStringSizeF().width()); + EXPECT_EQ(text_size.height(), render_text->GetStringSizeF().height()); + render_text->SetDisplayRect(Rect(0, 0, std::ceil(text_size.width()), 0)); + EXPECT_NEAR(text_size.width(), render_text->GetStringSizeF().width(), 1.0f); + EXPECT_EQ(text_size.height() * 2, render_text->GetStringSizeF().height()); +} + +// Ensure a string fits in a display rect with a width equal to the string's. +TEST_F(RenderTextTest, StringFitsOwnWidth) { + RenderText* render_text = GetRenderText(); + const std::u16string kString = u"www.example.com"; + + render_text->SetText(kString); + render_text->ApplyWeight(Font::Weight::BOLD, Range(0, 3)); + render_text->SetElideBehavior(ELIDE_TAIL); + + render_text->SetDisplayRect(Rect(0, 0, 500, 100)); + EXPECT_EQ(kString, render_text->GetDisplayText()); + render_text->SetDisplayRect(Rect(0, 0, render_text->GetContentWidth(), 100)); + EXPECT_EQ(kString, render_text->GetDisplayText()); +} + +// TODO(865715): Figure out why this fails on Android. +#if !defined(OS_ANDROID) +// Ensure that RenderText examines all of the fonts in its FontList before +// falling back to other fonts. +TEST_F(RenderTextTest, HarfBuzz_FontListFallback) { + // Double-check that the requested fonts are present. + std::string format = std::string(kTestFontName) + ", %s, 12px"; + FontList font_list(base::StringPrintf(format.c_str(), kSymbolFontName)); + const std::vector& fonts = font_list.GetFonts(); + ASSERT_EQ(2u, fonts.size()); + ASSERT_EQ(base::ToLowerASCII(kTestFontName), + base::ToLowerASCII(fonts[0].GetActualFontName())); + ASSERT_EQ(base::ToLowerASCII(kSymbolFontName), + base::ToLowerASCII(fonts[1].GetActualFontName())); + + // "⊕" (U+2295, CIRCLED PLUS) should be rendered with Symbol rather than + // falling back to some other font that's present on the system. + RenderTextHarfBuzz* render_text = GetRenderText(); + render_text->SetFontList(font_list); + render_text->SetText(u"\u2295"); + const std::vector spans = GetFontSpans(); + ASSERT_EQ(static_cast(1), spans.size()); + EXPECT_EQ(kSymbolFontName, spans[0].first.GetFontName()); +} +#endif // !defined(OS_ANDROID) + +// Ensure that the fallback fonts offered by GetFallbackFonts() are tried. Note +// this test assumes the font "Arial" doesn't provide a unicode glyph for a +// particular character, and that there is a system fallback font which does. +// TODO(msw): Fallback doesn't find a glyph on Linux and Android. +#if !defined(OS_LINUX) && !defined(OS_CHROMEOS) && !defined(OS_ANDROID) +TEST_F(RenderTextTest, HarfBuzz_UnicodeFallback) { + RenderTextHarfBuzz* render_text = GetRenderText(); + render_text->SetFontList(FontList("Arial, 12px")); + + // An invalid Unicode character that somehow yields Korean character "han". + render_text->SetText(u"\ud55c"); + const internal::TextRunList* run_list = GetHarfBuzzRunList(); + ASSERT_EQ(1U, run_list->size()); + EXPECT_EQ(0U, run_list->runs()[0]->CountMissingGlyphs()); +} +#endif // !defined(OS_LINUX) && !defined(OS_CHROMEOS) && !defined(OS_ANDROID) + +// Ensure that the fallback fonts offered by GetFallbackFont() support glyphs +// for different languages. +TEST_F(RenderTextTest, HarfBuzz_FallbackFontsSupportGlyphs) { + // The word 'test' in different languages. + static const char16_t* kLanguageTests[] = { + u"test", u"اختبار", u"Δοκιμή", u"परीक्षा", u"تست", u"Փորձարկում", + }; + + for (const auto* text : kLanguageTests) { + RenderTextHarfBuzz* render_text = GetRenderText(); + render_text->SetText(text); + + const internal::TextRunList* run_list = GetHarfBuzzRunList(); + ASSERT_EQ(1U, run_list->size()); + int missing_glyphs = run_list->runs()[0]->CountMissingGlyphs(); + if (missing_glyphs != 0) { + ADD_FAILURE() << "Text '" << text << "' is missing " << missing_glyphs + << " glyphs."; + } + } +} + +// Ensure that the fallback fonts offered by GetFallbackFont() support glyphs +// for different languages. +TEST_F(RenderTextTest, HarfBuzz_MultiRunsSupportGlyphs) { + static const char16_t* kLanguageTests[] = { + u"www.اختبار.com", + u"(اختبار)", + u"/ זה (מבחן) /", + }; + + for (const auto* text : kLanguageTests) { + RenderTextHarfBuzz* render_text = GetRenderText(); + render_text->SetText(text); + + int missing_glyphs = 0; + const internal::TextRunList* run_list = GetHarfBuzzRunList(); + for (const auto& run : run_list->runs()) + missing_glyphs += run->CountMissingGlyphs(); + + if (missing_glyphs != 0) { + ADD_FAILURE() << "Text '" << text << "' is missing " << missing_glyphs + << " glyphs."; + } + } +} + +struct FallbackFontCase { + const char* test_name; + const char16_t* text; +}; + +class RenderTextTestWithFallbackFontCase + : public RenderTextTest, + public ::testing::WithParamInterface { + public: + static std::string ParamInfoToString( + ::testing::TestParamInfo param_info) { + return param_info.param.test_name; + } +}; + +TEST_P(RenderTextTestWithFallbackFontCase, FallbackFont) { + FallbackFontCase param = GetParam(); + RenderTextHarfBuzz* render_text = GetRenderText(); + render_text->SetText(param.text); + + int missing_glyphs = 0; + const internal::TextRunList* run_list = GetHarfBuzzRunList(); + for (const auto& run : run_list->runs()) + missing_glyphs += run->CountMissingGlyphs(); + + EXPECT_EQ(missing_glyphs, 0); +} + +const FallbackFontCase kUnicodeDecomposeCases[] = { + // Decompose to "\u0041\u0300". + {"letter_A_with_grave", u"\u00c0"}, + // Decompose to "\u004f\u0328\u0304". + {"letter_O_with_ogonek_macron", u"\u01ec"}, + // Decompose to "\u0041\u030a". + {"angstrom_sign", u"\u212b"}, + // Decompose to "\u1100\u1164\u11b6". + {"hangul_syllable_gyaelh", u"\uac63"}, + // Decompose to "\u1107\u1170\u11af". + {"hangul_syllable_bwel", u"\ubdc0"}, + // Decompose to "\U00044039". + {"cjk_ideograph_fad4", u"\ufad4"}, +}; + +INSTANTIATE_TEST_SUITE_P(FallbackFontUnicodeDecompose, + RenderTextTestWithFallbackFontCase, + ::testing::ValuesIn(kUnicodeDecomposeCases), + RenderTextTestWithFallbackFontCase::ParamInfoToString); + +// Ensures that RenderText is finding an appropriate font and that every +// codepoint can be rendered by the font. An error here can be by an incorrect +// ItemizeText(...) leading to an invalid fallback font. +const FallbackFontCase kComplexTextCases[] = { + {"simple1", u"test"}, + {"simple2", u"اختبار"}, + {"simple3", u"Δοκιμή"}, + {"simple4", u"परीक्षा"}, + {"simple5", u"تست"}, + {"simple6", u"Փորձարկում"}, + {"mixed1", u"www.اختبار.com"}, + {"mixed2", u"(اختبار)"}, + {"mixed3", u"/ זה (מבחן) /"}, +#if defined(OS_WIN) + {"asc_arb", u"abcښڛڜdef"}, + {"devanagari", u"ञटठडढणतथ"}, + {"ethiopic", u"መጩጪᎅⶹⶼ"}, + {"greek", u"ξοπρς"}, + {"kannada", u"ಠಡಢಣತಥ"}, + {"lao", u"ປຝພຟມ"}, + {"oriya", u"ଔକଖଗଘଙ"}, + {"telugu_lat", u"aaఉయ!"}, + {"common_math", u"ℳ: ¬ƒ(x)=½×¾"}, + {"picto_title", u"☞☛test☚☜"}, + {"common_numbers", u"𝟭𝟐⒓¹²"}, + {"common_puncts", u",.!"}, + {"common_space_math1", u" 𝓐"}, + {"common_space_math2", u" 𝓉"}, + {"common_split_spaces", u"♬ 𝓐"}, + {"common_mixed", u"\U0001d4c9\u24d4\U0001d42c"}, + {"arrows", u"↰↱↲↳↴↵⇚⇛⇜⇝⇞⇟"}, + {"arrows_space", u"↰ ↱ ↲ ↳ ↴ ↵ ⇚ ⇛ ⇜ ⇝ ⇞ ⇟"}, + {"emoji_title", u"▶Feel goods"}, + {"enclosed_alpha", u"ⒶⒷⒸⒹⒺⒻⒼ"}, + {"shapes", u" ▶▷▸▹►▻◀◁◂◃◄◅"}, + {"symbols", u"☂☎☏☝☫☬☭☮☯"}, + {"symbols_space", u"☂ ☎ ☏ ☝ ☫ ☬ ☭ ☮ ☯"}, + {"dingbats", u"✂✃✄✆✇✈"}, + {"cjk_compatibility_ideographs", u"賈滑串句龜"}, + {"lat_dev_ZWNJ", u"a\u200Cक"}, + {"paren_picto", u"(☾☹☽)"}, + {"emoji1", u"This is 💩!"}, + {"emoji2", u"Look [🔝]"}, + {"strange1", u"💔♬ 𝓐 𝓉ⓔ𝐬т FỖ𝕣 c卄尺𝕆ᵐ€ ♘👹"}, + {"strange2", u"˜”*°•.˜”*°• A test for chrome •°*”˜.•°*”˜"}, + {"strange3", u"𝐭єⓢт fσ𝐑 𝔠ʰ𝕣ό𝐌𝔢"}, + {"strange4", u"тẸⓈ𝔱 𝔽𝕠ᖇ 𝕔𝐡ŕ𝔬ⓜẸ"}, + {"url1", u"http://www.google.com"}, + {"url2", u"http://www.nowhere.com/Lörick.html"}, + {"url3", u"http://www.nowhere.com/تسجيل الدخول"}, + {"url4", u"https://xyz.com:8080/تس(1)جيل الدخول"}, + {"url5", u"http://www.script.com/test.php?abc=42&cde=12&f=%20%20"}, + {"punct1", u"This‐is‑a‒test–for—punctuations"}, + {"punct2", u"⁅All ‷magic‴ comes with a ‶price″⁆"}, + {"punct3", u"⍟ Complete my sentence… †"}, + {"parens", u"❝This❞ 「test」 has ((a)) 【lot】 [{of}] 〚parentheses〛"}, + {"games", u"Let play: ♗♘⚀⚁♠♣"}, + {"braille", u"⠞⠑⠎⠞ ⠋⠕⠗ ⠉⠓⠗⠕⠍⠑"}, + {"emoticon1", u"¯\\_(ツ)_/¯"}, + {"emoticon2", u"٩(⁎❛ᴗ❛⁎)۶"}, + {"emoticon3", u"(͡° ͜ʖ ͡°)"}, + {"emoticon4", u"[̲̅$̲̅(̲̅5̲̅)̲̅$̲̅]"}, +#endif +}; + +INSTANTIATE_TEST_SUITE_P(FallbackFontComplexTextCases, + RenderTextTestWithFallbackFontCase, + ::testing::ValuesIn(kComplexTextCases), + RenderTextTestWithFallbackFontCase::ParamInfoToString); + +// Test cases to ensures the COMMON unicode script is split by unicode code +// block. These tests work on Windows and Mac default fonts installation. +// On other platforms, the fonts are mock (see test_fonts). +const FallbackFontCase kCommonScriptCases[] = { +#if defined(OS_WIN) + // The following tests are made to work on win7 and win10. + {"common00", u"\u237b\u2ac1\u24f5\u259f\u2a87\u23ea\u25d4\u2220"}, + {"common01", u"\u2303\u2074\u2988\u32b6\u26a2\u24e5\u2a53\u2219"}, + {"common02", u"\u29b2\u25fc\u2366\u24ae\u2647\u258e\u2654\u25fe"}, + {"common03", u"\u21ea\u22b4\u29b0\u2a84\u0008\u2657\u2731\u2697"}, + {"common04", u"\u2b3c\u2932\u21c8\u23cf\u20a1\u2aa2\u2344\u0011"}, + {"common05", u"\u22c3\u2a56\u2340\u21b7\u26ba\u2798\u220f\u2404"}, + {"common06", u"\u21f9\u25fd\u008e\u21e6\u2686\u21e4\u259f\u29ee"}, + {"common07", u"\u231e\ufe39\u0008\u2349\u2262\u2270\uff09\u2b3b"}, + {"common08", u"\u24a3\u236e\u29b2\u2259\u26ea\u2705\u00ae\u2a23"}, + {"common09", u"\u33bd\u235e\u2018\u32ba\u2973\u02c1\u20b9\u25b4"}, + {"common10", u"\u2245\u2a4d\uff19\u2042\u2aa9\u2658\u276e\uff40"}, + {"common11", u"\u0007\u21b4\u23c9\u2593\u21ba\u00a0\u258f\u23b3"}, + {"common12", u"\u2938\u250c\u2240\u2676\u2297\u2b07\u237e\u2a04"}, + {"common13", u"\u2520\u233a\u20a5\u2744\u2445\u268a\u2716\ufe62"}, + {"common14", u"\ufe4d\u25d5\u2ae1\u2a35\u2323\u273c\u26be\u2a3b"}, + {"common15", u"\u2aa2\u0000\ufe65\u2962\u2573\u21f8\u2651\u02d2"}, + {"common16", u"\u225c\u2283\u2960\u4de7\uff12\uffe1\u0016\u2905"}, + {"common17", u"\uff07\u25aa\u2076\u259e\u226c\u2568\u0026\u2691"}, + {"common18", u"\u2388\u21c2\u208d\u2a7f\u22d0\u2583\u2ad5\u240f"}, + {"common19", u"\u230a\u27ac\u001e\u261e\u259d\u25c3\u33a5\u0011"}, + {"common20", u"\ufe54\u29c7\u2477\u21ed\u2069\u4dfc\u2ae2\u21e8"}, + {"common21", u"\u2131\u2ab7\u23b9\u2660\u2083\u24c7\u228d\u2a01"}, + {"common22", u"\u2587\u2572\u21df\uff3c\u02cd\ufffd\u2404\u22b3"}, + {"common23", u"\u4dc3\u02fe\uff09\u25a3\ufe14\u255c\u2128\u2698"}, + {"common24", u"\u2b36\u3382\u02f6\u2752\uff16\u22cf\u00b0\u21d6"}, + {"common25", u"\u2561\u23db\u2958\u2782\u22af\u2621\u24a3\u29ae"}, + {"common26", u"\u2693\u22e2\u2988\u2987\u33ba\u2a94\u298e\u2328"}, + {"common27", u"\u266c\u2aa5\u2405\uffeb\uff5c\u2902\u291e\u02e6"}, + {"common28", u"\u2634\u32b2\u3385\u2032\u33be\u2366\u2ac7\u23cf"}, + {"common29", u"\u2981\ua721\u25a9\u2320\u21cf\u295a\u2273\u2ac2"}, + {"common30", u"\u22d9\u2465\u2347\u2a94\u4dca\u2389\u23b0\u208d"}, + {"common31", u"\u21cc\u2af8\u2912\u23a4\u2271\u2303\u241e\u33a1"}, +#elif defined(OS_ANDROID) + {"common00", u"\u2497\uff04\u277c\u21b6\u2076\u21e4\u2068\u21b3"}, + {"common01", u"\u2663\u2466\u338e\u226b\u2734\u21be\u3389\u00ab"}, + {"common02", u"\u2062\u2197\u3392\u2681\u33be\u206d\ufe10\ufe34"}, + {"common03", u"\u02db\u00b0\u02d3\u2745\u33d1\u21e4\u24e4\u33d6"}, + {"common04", u"\u21da\u261f\u26a1\u2586\u27af\u2560\u21cd\u25c6"}, + {"common05", u"\ufe51\uff17\u0027\u21fd\u24de\uff5e\u2606\u251f"}, + {"common06", u"\u2493\u2466\u21fc\u226f\u202d\u21a9\u0040\u265d"}, + {"common07", u"\u2103\u255a\u2153\u26be\u27ac\u222e\u2490\u21a4"}, + {"common08", u"\u270b\u2486\u246b\u263c\u27b6\u21d9\u219d\u25a9"}, + {"common09", u"\u002d\u2494\u25fd\u2321\u2111\u2511\u00d7\u2535"}, + {"common10", u"\u2523\u203e\u25b2\ufe18\u2499\u2229\ufd3e\ufe16"}, + {"common11", u"\u2133\u2716\u273f\u2064\u2248\u005c\u265f\u21e6"}, + {"common12", u"\u2060\u246a\u231b\u2726\u25bd\ufe40\u002e\u25ca"}, + {"common13", u"\ufe39\u24a2\ufe18\u254b\u249c\u3396\ua71f\u2466"}, + {"common14", u"\u21b8\u2236\u251a\uff11\u2077\u0035\u27bd\u2013"}, + {"common15", u"\u2668\u2551\u221a\u02bc\u2741\u2649\u2192\u00a1"}, + {"common16", u"\u2211\u21ca\u24dc\u2536\u201b\u21c8\u2530\u25fb"}, + {"common17", u"\u231a\u33d8\u2934\u27bb\u2109\u23ec\u20a9\u3000"}, + {"common18", u"\u2069\u205f\u33d3\u2466\u24a1\u24dd\u21ac\u21e3"}, + {"common19", u"\u2737\u219a\u21f1\u2285\u226a\u00b0\u27b2\u2746"}, + {"common20", u"\u264f\u2539\u2202\u264e\u2548\u2530\u2111\u2007"}, + {"common21", u"\u2799\u0035\u25e4\u265b\u24e2\u2044\u222b\u0021"}, + {"common22", u"\u2728\u00a2\u2533\ufe43\u33c9\u27a2\u02f9\u005d"}, + {"common23", u"\ufe68\u256c\u25b6\u276c\u2771\u33c4\u2712\u24b3"}, + {"common24", u"\ufe5d\ufe31\ufe3d\u205e\u2512\u33b8\u272b\ufe4f"}, + {"common25", u"\u24e7\u25fc\u2582\u2743\u2010\u2474\u2262\u251a"}, + {"common26", u"\u2020\u211c\u24b4\u33c7\u2007\uff0f\u267f\u00b4"}, + {"common27", u"\u266c\u3399\u2570\u33a4\u276e\u00a8\u2506\u24dc"}, + {"common28", u"\u2202\ufe43\u2511\u2191\u339a\u33b0\u02d7\u2473"}, + {"common29", u"\u2517\u2297\u2762\u2460\u25bd\u24a9\u21a7\ufe64"}, + {"common30", u"\u2105\u2722\u275d\u249c\u21a2\u2590\u2260\uff5d"}, + {"common31", u"\u33ba\u21c6\u2706\u02cb\ufe64\u02e6\u0374\u2493"}, +#elif defined(OS_APPLE) + {"common00", u"\u2153\u24e0\u2109\u02f0\u2a8f\u25ed\u02c5\u2716"}, + {"common01", u"\u02f0\u208c\u2203\u2518\u2067\u2270\u21f1\ufe66"}, + {"common02", u"\u2686\u2585\u2b15\u246f\u23e3\u21b4\u2394\ufe31"}, + {"common03", u"\u23c1\u2a97\u201e\u2200\u3389\u25d3\u02c2\u259d"}, +#else + // The following tests are made for the mock fonts (see test_fonts). + {"common00", u"\u2153\u24e0\u2109\u02f0\u2a8f\u25ed\u02c5\u2716"}, + {"common01", u"\u02f0\u208c\u2203\u2518\u2067\u2270\u21f1\ufe66"}, + {"common02", u"\u2686\u2585\u2b15\u246f\u23e3\u21b4\u2394\ufe31"}, + {"common03", u"\u23c1\u2a97\u201e\u2200\u3389\u25d3\u02c2\u259d"}, + {"common04", u"\u2075\u4dec\u252a\uff15\u4df6\u2668\u27fa\ufe17"}, + {"common05", u"\u260b\u2049\u3036\u2a85\u2b15\u23c7\u230a\u2374"}, + {"common06", u"\u2771\u27fa\u255d\uff0b\u2213\u3396\u2a85\u2276"}, + {"common07", u"\u211e\u2b06\u2255\u2727\u26c3\u33cf\u267d\u2ab2"}, + {"common08", u"\u2373\u20b3\u22b8\u2a0f\u02fd\u2585\u3036\ufe48"}, + {"common09", u"\u256d\u2940\u21d8\u4dde\u23a1\u226b\u3374\u2a99"}, + {"common10", u"\u270f\u24e5\u26c1\u2131\u21f5\u25af\u230f\u27fe"}, + {"common11", u"\u27aa\u23a2\u02ef\u2373\u2257\u2749\u2496\ufe31"}, + {"common12", u"\u230a\u25fb\u2117\u3386\u32cc\u21c5\u24c4\u207e"}, + {"common13", u"\u2467\u2791\u3393\u33bb\u02ca\u25de\ua788\u278f"}, + {"common14", u"\ua719\u25ed\u20a8\u20a1\u4dd8\u2295\u24eb\u02c8"}, + {"common15", u"\u22b6\u2520\u2036\uffee\u21df\u002d\u277a\u2b24"}, + {"common16", u"\u21f8\u211b\u22a0\u25b6\u263e\u2704\u221a\u2758"}, + {"common17", u"\ufe10\u2060\u24ac\u3385\u27a1\u2059\u2689\u2278"}, + {"common18", u"\u269b\u211b\u33a4\ufe36\u239e\u267f\u2423\u24a2"}, + {"common19", u"\u4ded\u262d\u225e\u248b\u21df\u279d\u2518\u21ba"}, + {"common20", u"\u225a\uff16\u21d4\u21c6\u02ba\u2545\u23aa\u005e"}, + {"common21", u"\u20a5\u265e\u3395\u2a6a\u2555\u22a4\u2086\u23aa"}, + {"common22", u"\u203f\u3250\u2240\u24e9\u21cb\u258f\u24b1\u3259"}, + {"common23", u"\u27bd\u263b\uff1f\u2199\u2547\u258d\u201f\u2507"}, + {"common24", u"\u2482\u2548\u02dc\u231f\u24cd\u2198\u220e\u20ad"}, + {"common25", u"\u2ff7\u2540\ufe48\u2197\u276b\u2574\u2062\u3398"}, + {"common26", u"\u2663\u21cd\u263f\u23e5\u22d7\u2518\u21b9\u2628"}, + {"common27", u"\u21fa\ufe66\u2739\u2051\u21f4\u3399\u2599\u25f7"}, + {"common28", u"\u29d3\u25ec\u27a6\u24e0\u2735\u25b4\u2737\u25db"}, + {"common29", u"\u2622\u22e8\u33d2\u21d3\u2502\u2153\u2669\u25f2"}, + {"common30", u"\u2121\u21af\u2729\u203c\u337a\u2464\u2b08\u2e24"}, + {"common31", u"\u33cd\u007b\u02d2\u22cc\u32be\u2ffa\u2787\u02e9"}, +#endif +}; + +INSTANTIATE_TEST_SUITE_P(FallbackFontCommonScript, + RenderTextTestWithFallbackFontCase, + ::testing::ValuesIn(kCommonScriptCases), + RenderTextTestWithFallbackFontCase::ParamInfoToString); + +#if defined(OS_WIN) +// Ensures that locale is used for fonts selection. +TEST_F(RenderTextTest, CJKFontWithLocale) { + const char16_t kCJKTest[] = u"\u8AA4\u904E\u9AA8"; + static const char* kLocaleTests[] = {"zh-CN", "ja-JP", "ko-KR"}; + + std::set tested_font_names; + for (const auto* locale : kLocaleTests) { + base::i18n::SetICUDefaultLocale(locale); + ResetRenderTextInstance(); + + RenderTextHarfBuzz* render_text = GetRenderText(); + render_text->SetText(kCJKTest); + + const std::vector font_spans = GetFontSpans(); + ASSERT_EQ(font_spans.size(), 1U); + + // Expect the font name to be different for each locale. + bool unique_font_name = + tested_font_names.insert(font_spans[0].first.GetFontName()).second; + EXPECT_TRUE(unique_font_name); + } +} +#endif // defined(OS_WIN) + +TEST_F(RenderTextTest, SameFontAccrossIgnorableCodepoints) { + RenderText* render_text = GetRenderText(); + + render_text->SetText(u"\u060F"); + const std::vector spans1 = GetFontSpans(); + ASSERT_EQ(1u, spans1.size()); + Font font1 = spans1[0].first; + + render_text->SetText(u"\u060F\u200C\u060F"); + const std::vector spans2 = GetFontSpans(); + ASSERT_EQ(1u, spans2.size()); + Font font2 = spans2[0].first; + + // Ensures the same font is used with or without the joiners + // (see http://crbug.com/1036652). + EXPECT_EQ(font1.GetFontName(), font2.GetFontName()); +} + +TEST_F(RenderTextTest, ZeroWidthCharacters) { + static const char16_t* kEmptyText[] = { + u"\u200C", // ZERO WIDTH NON-JOINER + u"\u200D", // ZERO WIDTH JOINER + u"\u200B", // ZERO WIDTH SPACE + u"\uFEFF", // ZERO WIDTH NO-BREAK SPACE + }; + + for (const auto* text : kEmptyText) { + RenderTextHarfBuzz* render_text = GetRenderText(); + render_text->SetText(text); + + const internal::TextRunList* run_list = GetHarfBuzzRunList(); + EXPECT_EQ(0, run_list->width()); + ASSERT_EQ(run_list->runs().size(), 1U); + EXPECT_EQ(run_list->runs()[0]->CountMissingGlyphs(), 0U); + } +} + +// Ensure that the width reported by RenderText is sufficient for drawing. Draws +// to a canvas and checks if any pixel beyond the bounding rectangle is colored. +TEST_F(RenderTextTest, DISABLED_TextDoesntClip) { + const char* kTestStrings[] = { + " ", + // TODO(dschuyler): Underscores draw outside GetStringSize; + // crbug.com/459812. This appears to be a preexisting issue that wasn't + // revealed by the prior unit tests. + // "TEST_______", + "TEST some stuff", "WWWWWWWWWW", "gAXAXAXAXAXAXA", "gÅXÅXÅXÅXÅXÅXÅ", + "هٔهٔهٔهٔمرحبا"}; + const Size kCanvasSize(300, 50); + const int kTestSize = 10; + + SkBitmap bitmap; + bitmap.allocPixels( + SkImageInfo::MakeN32Premul(kCanvasSize.width(), kCanvasSize.height())); + cc::SkiaPaintCanvas paint_canvas(bitmap); + Canvas canvas(&paint_canvas, 1.0f); + RenderText* render_text = GetRenderText(); + render_text->SetHorizontalAlignment(ALIGN_LEFT); + render_text->SetColor(SK_ColorBLACK); + + for (auto* string : kTestStrings) { + paint_canvas.clear(SK_ColorWHITE); + render_text->SetText(base::UTF8ToUTF16(string)); + render_text->ApplyBaselineStyle(SUPERSCRIPT, Range(1, 2)); + render_text->ApplyBaselineStyle(SUPERIOR, Range(3, 4)); + render_text->ApplyBaselineStyle(INFERIOR, Range(5, 6)); + render_text->ApplyBaselineStyle(SUBSCRIPT, Range(7, 8)); + const Size string_size = render_text->GetStringSize(); + render_text->SetWeight(Font::Weight::BOLD); + render_text->SetDisplayRect( + Rect(kTestSize, kTestSize, string_size.width(), string_size.height())); + // Allow the RenderText to paint outside of its display rect. + render_text->set_clip_to_display_rect(false); + ASSERT_LE(string_size.width() + kTestSize * 2, kCanvasSize.width()); + + render_text->Draw(&canvas); + ASSERT_LT(string_size.width() + kTestSize, kCanvasSize.width()); + const uint32_t* buffer = static_cast(bitmap.getPixels()); + ASSERT_NE(nullptr, buffer); + TestRectangleBuffer rect_buffer(string, buffer, kCanvasSize.width(), + kCanvasSize.height()); + { + SCOPED_TRACE("TextDoesntClip Top Side"); + rect_buffer.EnsureSolidRect(SK_ColorWHITE, 0, 0, kCanvasSize.width(), + kTestSize); + } + { + SCOPED_TRACE("TextDoesntClip Bottom Side"); + rect_buffer.EnsureSolidRect(SK_ColorWHITE, 0, + kTestSize + string_size.height(), + kCanvasSize.width(), kTestSize); + } + { + SCOPED_TRACE("TextDoesntClip Left Side"); + // TODO(dschuyler): Smoothing draws to the left of text. This appears to + // be a preexisting issue that wasn't revealed by the prior unit tests. + // RenderText currently only uses origins and advances and ignores + // bounding boxes so cannot account for under- and over-hang. + rect_buffer.EnsureSolidRect(SK_ColorWHITE, 0, kTestSize, kTestSize - 1, + string_size.height()); + } + { + SCOPED_TRACE("TextDoesntClip Right Side"); + // TODO(dschuyler): Smoothing draws to the right of text. This appears to + // be a preexisting issue that wasn't revealed by the prior unit tests. + // RenderText currently only uses origins and advances and ignores + // bounding boxes so cannot account for under- and over-hang. + rect_buffer.EnsureSolidRect(SK_ColorWHITE, + kTestSize + string_size.width() + 1, + kTestSize, kTestSize - 1, + string_size.height()); + } + } +} + +// Ensure that the text will clip to the display rect. Draws to a canvas and +// checks whether any pixel beyond the bounding rectangle is colored. +TEST_F(RenderTextTest, DISABLED_TextDoesClip) { + const char* kTestStrings[] = {"TEST", "W", "WWWW", "gAXAXWWWW"}; + const Size kCanvasSize(300, 50); + const int kTestSize = 10; + + SkBitmap bitmap; + bitmap.allocPixels( + SkImageInfo::MakeN32Premul(kCanvasSize.width(), kCanvasSize.height())); + cc::SkiaPaintCanvas paint_canvas(bitmap); + Canvas canvas(&paint_canvas, 1.0f); + RenderText* render_text = GetRenderText(); + render_text->SetHorizontalAlignment(ALIGN_LEFT); + render_text->SetColor(SK_ColorBLACK); + + for (auto* string : kTestStrings) { + paint_canvas.clear(SK_ColorWHITE); + render_text->SetText(base::UTF8ToUTF16(string)); + const Size string_size = render_text->GetStringSize(); + int fake_width = string_size.width() / 2; + int fake_height = string_size.height() / 2; + render_text->SetDisplayRect( + Rect(kTestSize, kTestSize, fake_width, fake_height)); + render_text->set_clip_to_display_rect(true); + render_text->Draw(&canvas); + ASSERT_LT(string_size.width() + kTestSize, kCanvasSize.width()); + const uint32_t* buffer = static_cast(bitmap.getPixels()); + ASSERT_NE(nullptr, buffer); + TestRectangleBuffer rect_buffer(string, buffer, kCanvasSize.width(), + kCanvasSize.height()); + { + SCOPED_TRACE("TextDoesClip Top Side"); + rect_buffer.EnsureSolidRect(SK_ColorWHITE, 0, 0, kCanvasSize.width(), + kTestSize); + } + + { + SCOPED_TRACE("TextDoesClip Bottom Side"); + rect_buffer.EnsureSolidRect(SK_ColorWHITE, 0, kTestSize + fake_height, + kCanvasSize.width(), kTestSize); + } + { + SCOPED_TRACE("TextDoesClip Left Side"); + rect_buffer.EnsureSolidRect(SK_ColorWHITE, 0, kTestSize, kTestSize, + fake_height); + } + { + SCOPED_TRACE("TextDoesClip Right Side"); + rect_buffer.EnsureSolidRect(SK_ColorWHITE, kTestSize + fake_width, + kTestSize, kTestSize, fake_height); + } + } +} + +// Ensure color changes are picked up by the RenderText implementation. +TEST_F(RenderTextTest, ColorChange) { + RenderText* render_text = GetRenderText(); + render_text->SetText(u"x"); + Draw(); + ExpectTextLog({{1, kPlaceholderColor}}); + + render_text->SetColor(SK_ColorGREEN); + Draw(); + ExpectTextLog({{1, SK_ColorGREEN}}); +} + +// Ensure style information propagates to the typeface on the text renderer. +TEST_F(RenderTextTest, StylePropagated) { + RenderText* render_text = GetRenderText(); + // Default-constructed fonts on Mac are system fonts. These can have all kinds + // of weird weights and style, which are preserved by PlatformFontMac, but do + // not map simply to a SkTypeface::Style (the full details in SkFontStyle is + // needed). They also vary depending on the OS version, so set a known font. + FontList font_list(Font("Arial", 10)); + + render_text->SetText(u"x"); + render_text->SetFontList(font_list); + + DrawVisualText(); + EXPECT_EQ(SkFontStyle::Normal(), + GetRendererFont().getTypeface()->fontStyle()); + + render_text->SetWeight(Font::Weight::BOLD); + DrawVisualText(); + EXPECT_EQ(SkFontStyle::Bold(), GetRendererFont().getTypeface()->fontStyle()); + + render_text->SetStyle(TEXT_STYLE_ITALIC, true); + DrawVisualText(); + EXPECT_EQ(SkFontStyle::BoldItalic(), + GetRendererFont().getTypeface()->fontStyle()); + + render_text->SetWeight(Font::Weight::NORMAL); + DrawVisualText(); + EXPECT_EQ(SkFontStyle::Italic(), + GetRendererFont().getTypeface()->fontStyle()); +} + +// Ensure the painter adheres to RenderText::subpixel_rendering_suppressed(). +TEST_F(RenderTextTest, SubpixelRenderingSuppressed) { + ASSERT_TRUE(IsFontsSmoothingEnabled()) + << "The test requires that fonts smoothing (anti-aliasing) is activated. " + "If this assert is failing you need to manually activate the flag in " + "your system fonts settings."; + + RenderText* render_text = GetRenderText(); + render_text->SetText(u"x"); + + DrawVisualText(); +#if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID) || \ + defined(OS_FUCHSIA) + // On Linux, whether subpixel AA is supported is determined by the platform + // FontConfig. Force it into a particular style after computing runs. Other + // platforms use a known default FontRenderParams from a static local. + GetHarfBuzzRunList() + ->runs()[0] + ->font_params.render_params.subpixel_rendering = + FontRenderParams::SUBPIXEL_RENDERING_RGB; + DrawVisualText(); +#endif + EXPECT_EQ(GetRendererFont().getEdging(), SkFont::Edging::kSubpixelAntiAlias); + + render_text->set_subpixel_rendering_suppressed(true); + DrawVisualText(); +#if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID) || \ + defined(OS_FUCHSIA) + // For Linux, runs shouldn't be re-calculated, and the suppression of the + // SUBPIXEL_RENDERING_RGB set above should now take effect. But, after + // checking, apply the override anyway to be explicit that it is suppressed. + EXPECT_NE(GetRendererFont().getEdging(), SkFont::Edging::kSubpixelAntiAlias); + GetHarfBuzzRunList() + ->runs()[0] + ->font_params.render_params.subpixel_rendering = + FontRenderParams::SUBPIXEL_RENDERING_RGB; + DrawVisualText(); +#endif + EXPECT_NE(GetRendererFont().getEdging(), SkFont::Edging::kSubpixelAntiAlias); +} + +// Ensure the SkFont Edging is computed accurately. +TEST_F(RenderTextTest, SkFontEdging) { + const auto edging = [this]() { return GetRendererFont().getEdging(); }; + + FontRenderParams params; + EXPECT_TRUE(params.antialiasing); + EXPECT_EQ(params.subpixel_rendering, + FontRenderParams::SUBPIXEL_RENDERING_NONE); + + // aa: true, subpixel: false, subpixel_suppressed: false -> kAntiAlias + renderer()->SetFontRenderParams(params, + false /*subpixel_rendering_suppressed*/); + EXPECT_EQ(edging(), SkFont::Edging::kAntiAlias); + + // aa: false, subpixel: false, subpixel_suppressed: false -> kAlias + params.antialiasing = false; + renderer()->SetFontRenderParams(params, + false /*subpixel_rendering_suppressed*/); + EXPECT_EQ(edging(), SkFont::Edging::kAlias); + + // aa: true, subpixel: true, subpixel_suppressed: false -> kSubpixelAntiAlias + params.antialiasing = true; + params.subpixel_rendering = FontRenderParams::SUBPIXEL_RENDERING_RGB; + renderer()->SetFontRenderParams(params, + false /*subpixel_rendering_suppressed*/); + EXPECT_EQ(edging(), SkFont::Edging::kSubpixelAntiAlias); + + // aa: true, subpixel: true, subpixel_suppressed: true -> kAntiAlias + renderer()->SetFontRenderParams(params, + true /*subpixel_rendering_suppressed*/); + EXPECT_EQ(edging(), SkFont::Edging::kAntiAlias); + + // aa: false, subpixel: true, subpixel_suppressed: false -> kAlias + params.antialiasing = false; + renderer()->SetFontRenderParams(params, + false /*subpixel_rendering_suppressed*/); + EXPECT_EQ(edging(), SkFont::Edging::kAlias); +} + +// Verify GetWordLookupDataAtPoint returns the correct baseline point and +// decorated word for an LTR string. +TEST_F(RenderTextTest, GetWordLookupDataAtPoint_LTR) { + // Set an integer glyph width; GetCursorBounds() and + // GetWordLookupDataAtPoint() use different rounding internally. + // + // TODO(crbug.com/1111044): this shouldn't be necessary once RenderText keeps + // float precision through GetCursorBounds(). + SetGlyphWidth(5); + const std::u16string ltr = u" ab c "; + const int kWordOneStartIndex = 2; + const int kWordTwoStartIndex = 6; + + RenderText* render_text = GetRenderText(); + render_text->SetDisplayRect(Rect(100, 30)); + render_text->SetText(ltr); + render_text->ApplyWeight(Font::Weight::SEMIBOLD, Range(0, 3)); + render_text->ApplyStyle(TEXT_STYLE_UNDERLINE, true, Range(1, 5)); + render_text->ApplyStyle(TEXT_STYLE_ITALIC, true, Range(3, 8)); + render_text->ApplyStyle(TEXT_STYLE_STRIKE, true, Range(1, 7)); + const int cursor_y = GetCursorYForTesting(); + + const std::vector font_spans = GetFontSpans(); + + // Create expected decorated text instances. + DecoratedText expected_word_1; + expected_word_1.text = u"ab"; + // Attributes for the characters 'a' and 'b' at logical indices 2 and 3 + // respectively. + expected_word_1.attributes.push_back(CreateRangedAttribute( + font_spans, 0, kWordOneStartIndex, Font::Weight::SEMIBOLD, + UNDERLINE_MASK | STRIKE_MASK)); + expected_word_1.attributes.push_back(CreateRangedAttribute( + font_spans, 1, kWordOneStartIndex + 1, Font::Weight::NORMAL, + UNDERLINE_MASK | ITALIC_MASK | STRIKE_MASK)); + const Rect left_glyph_word_1 = render_text->GetCursorBounds( + SelectionModel(kWordOneStartIndex, CURSOR_FORWARD), false); + + DecoratedText expected_word_2; + expected_word_2.text = u"c"; + // Attributes for character 'c' at logical index |kWordTwoStartIndex|. + expected_word_2.attributes.push_back( + CreateRangedAttribute(font_spans, 0, kWordTwoStartIndex, + Font::Weight::NORMAL, ITALIC_MASK | STRIKE_MASK)); + const Rect left_glyph_word_2 = render_text->GetCursorBounds( + SelectionModel(kWordTwoStartIndex, CURSOR_FORWARD), false); + + DecoratedText decorated_word; + Point baseline_point; + + { + SCOPED_TRACE(base::StringPrintf("Query to the left of text bounds")); + EXPECT_TRUE(render_text->GetWordLookupDataAtPoint( + Point(-5, cursor_y), &decorated_word, &baseline_point)); + VerifyDecoratedWordsAreEqual(expected_word_1, decorated_word); + EXPECT_TRUE(left_glyph_word_1.Contains(baseline_point)); + } + { + SCOPED_TRACE(base::StringPrintf("Query to the right of text bounds")); + EXPECT_TRUE(render_text->GetWordLookupDataAtPoint( + Point(105, cursor_y), &decorated_word, &baseline_point)); + VerifyDecoratedWordsAreEqual(expected_word_2, decorated_word); + EXPECT_TRUE(left_glyph_word_2.Contains(baseline_point)); + } + + for (size_t i = 0; i < render_text->text().length(); i++) { + SCOPED_TRACE(base::StringPrintf("Case[%" PRIuS "]", i)); + // Query the decorated word using the origin of the i'th glyph's bounds. + const Point query = + render_text->GetCursorBounds(SelectionModel(i, CURSOR_FORWARD), false) + .origin(); + + EXPECT_TRUE(render_text->GetWordLookupDataAtPoint(query, &decorated_word, + &baseline_point)); + + if (i < kWordTwoStartIndex) { + VerifyDecoratedWordsAreEqual(expected_word_1, decorated_word); + EXPECT_TRUE(left_glyph_word_1.Contains(baseline_point)); + } else { + VerifyDecoratedWordsAreEqual(expected_word_2, decorated_word); + EXPECT_TRUE(left_glyph_word_2.Contains(baseline_point)); + } + } +} + +// Verify GetWordLookupDataAtPoint returns the correct baseline point and +// decorated word for an RTL string. +TEST_F(RenderTextTest, GetWordLookupDataAtPoint_RTL) { + // Set an integer glyph width; GetCursorBounds() and + // GetWordLookupDataAtPoint() use different rounding internally. + // + // TODO(crbug.com/1111044): this shouldn't be necessary once RenderText keeps + // float precision through GetCursorBounds(). + SetGlyphWidth(5); + const std::u16string rtl = u" \u0634\u0632 \u0634"; + const int kWordOneStartIndex = 1; + const int kWordTwoStartIndex = 5; + + RenderText* render_text = GetRenderText(); + render_text->SetDisplayRect(Rect(100, 30)); + render_text->SetText(rtl); + render_text->ApplyWeight(Font::Weight::SEMIBOLD, Range(2, 3)); + render_text->ApplyStyle(TEXT_STYLE_UNDERLINE, true, Range(3, 6)); + render_text->ApplyStyle(TEXT_STYLE_ITALIC, true, Range(0, 3)); + render_text->ApplyStyle(TEXT_STYLE_STRIKE, true, Range(2, 5)); + const int cursor_y = GetCursorYForTesting(); + + const std::vector font_spans = GetFontSpans(); + + // Create expected decorated text instance. + DecoratedText expected_word_1; + expected_word_1.text = u"\u0634\u0632"; + // Attributes for characters at logical indices 1 and 2. + expected_word_1.attributes.push_back(CreateRangedAttribute( + font_spans, 0, kWordOneStartIndex, Font::Weight::NORMAL, ITALIC_MASK)); + expected_word_1.attributes.push_back( + CreateRangedAttribute(font_spans, 1, kWordOneStartIndex + 1, + Font::Weight::SEMIBOLD, ITALIC_MASK | STRIKE_MASK)); + // The leftmost glyph is the one at logical index 2. + const Rect left_glyph_word_1 = render_text->GetCursorBounds( + SelectionModel(kWordOneStartIndex + 1, CURSOR_FORWARD), false); + + DecoratedText expected_word_2; + expected_word_2.text = u"\u0634"; + // Attributes for character at logical index |kWordTwoStartIndex|. + expected_word_2.attributes.push_back(CreateRangedAttribute( + font_spans, 0, kWordTwoStartIndex, Font::Weight::NORMAL, UNDERLINE_MASK)); + const Rect left_glyph_word_2 = render_text->GetCursorBounds( + SelectionModel(kWordTwoStartIndex, CURSOR_FORWARD), false); + + DecoratedText decorated_word; + Point baseline_point; + + { + SCOPED_TRACE(base::StringPrintf("Query to the left of text bounds")); + EXPECT_TRUE(render_text->GetWordLookupDataAtPoint( + Point(-5, cursor_y), &decorated_word, &baseline_point)); + VerifyDecoratedWordsAreEqual(expected_word_2, decorated_word); + EXPECT_TRUE(left_glyph_word_2.Contains(baseline_point)); + } + { + SCOPED_TRACE(base::StringPrintf("Query to the right of text bounds")); + EXPECT_TRUE(render_text->GetWordLookupDataAtPoint( + Point(105, cursor_y), &decorated_word, &baseline_point)); + VerifyDecoratedWordsAreEqual(expected_word_1, decorated_word); + EXPECT_TRUE(left_glyph_word_1.Contains(baseline_point)); + } + + for (size_t i = 0; i < render_text->text().length(); i++) { + SCOPED_TRACE(base::StringPrintf("Case[%" PRIuS "]", i)); + + // Query the decorated word using the top right point of the i'th glyph's + // bounds. + const Point query = + render_text->GetCursorBounds(SelectionModel(i, CURSOR_FORWARD), false) + .top_right(); + + EXPECT_TRUE(render_text->GetWordLookupDataAtPoint(query, &decorated_word, + &baseline_point)); + if (i < kWordTwoStartIndex) { + VerifyDecoratedWordsAreEqual(expected_word_1, decorated_word); + EXPECT_TRUE(left_glyph_word_1.Contains(baseline_point)); + } else { + VerifyDecoratedWordsAreEqual(expected_word_2, decorated_word); + EXPECT_TRUE(left_glyph_word_2.Contains(baseline_point)); + } + } +} + +// Test that GetWordLookupDataAtPoint behaves correctly for multiline text. +TEST_F(RenderTextTest, GetWordLookupDataAtPoint_Multiline) { + const std::u16string text = u"a b\n..\ncd."; + const size_t kWordOneIndex = 0; // Index of character 'a'. + const size_t kWordTwoIndex = 2; // Index of character 'b'. + const size_t kWordThreeIndex = 7; // Index of character 'c'. + + // Set up render text. + RenderText* render_text = GetRenderText(); + render_text->SetMultiline(true); + render_text->SetDisplayRect(Rect(500, 500)); + render_text->SetText(text); + render_text->ApplyWeight(Font::Weight::SEMIBOLD, Range(0, 3)); + render_text->ApplyStyle(TEXT_STYLE_UNDERLINE, true, Range(1, 7)); + render_text->ApplyStyle(TEXT_STYLE_STRIKE, true, Range(1, 8)); + render_text->ApplyStyle(TEXT_STYLE_ITALIC, true, Range(5, 9)); + + // Set up test expectations. + const std::vector font_spans = GetFontSpans(); + + DecoratedText expected_word_1; + expected_word_1.text = u"a"; + expected_word_1.attributes.push_back(CreateRangedAttribute( + font_spans, 0, kWordOneIndex, Font::Weight::SEMIBOLD, 0)); + const Rect left_glyph_word_1 = + GetSubstringBoundsUnion(Range(kWordOneIndex, kWordOneIndex + 1)); + + DecoratedText expected_word_2; + expected_word_2.text = u"b"; + expected_word_2.attributes.push_back(CreateRangedAttribute( + font_spans, 0, kWordTwoIndex, Font::Weight::SEMIBOLD, + UNDERLINE_MASK | STRIKE_MASK)); + const Rect left_glyph_word_2 = + GetSubstringBoundsUnion(Range(kWordTwoIndex, kWordTwoIndex + 1)); + + DecoratedText expected_word_3; + expected_word_3.text = u"cd"; + expected_word_3.attributes.push_back( + CreateRangedAttribute(font_spans, 0, kWordThreeIndex, + Font::Weight::NORMAL, STRIKE_MASK | ITALIC_MASK)); + expected_word_3.attributes.push_back(CreateRangedAttribute( + font_spans, 1, kWordThreeIndex + 1, Font::Weight::NORMAL, ITALIC_MASK)); + + const Rect left_glyph_word_3 = + GetSubstringBoundsUnion(Range(kWordThreeIndex, kWordThreeIndex + 1)); + + DecoratedText decorated_word; + Point baseline_point; + { + // Query to the left of the first line. + EXPECT_TRUE(render_text->GetWordLookupDataAtPoint( + Point(-5, GetCursorYForTesting(0)), &decorated_word, &baseline_point)); + VerifyDecoratedWordsAreEqual(expected_word_1, decorated_word); + EXPECT_TRUE(left_glyph_word_1.Contains(baseline_point)); + } + { + // Query on the second line. + EXPECT_TRUE(render_text->GetWordLookupDataAtPoint( + Point(5, GetCursorYForTesting(1)), &decorated_word, &baseline_point)); + VerifyDecoratedWordsAreEqual(expected_word_2, decorated_word); + EXPECT_TRUE(left_glyph_word_2.Contains(baseline_point)); + } + { + // Query at the center point of the character 'c'. + EXPECT_TRUE(render_text->GetWordLookupDataAtPoint( + left_glyph_word_3.CenterPoint(), &decorated_word, &baseline_point)); + VerifyDecoratedWordsAreEqual(expected_word_3, decorated_word); + EXPECT_TRUE(left_glyph_word_3.Contains(baseline_point)); + } + { + // Query to the right of the third line. + EXPECT_TRUE(render_text->GetWordLookupDataAtPoint( + Point(505, GetCursorYForTesting(2)), &decorated_word, &baseline_point)); + VerifyDecoratedWordsAreEqual(expected_word_3, decorated_word); + EXPECT_TRUE(left_glyph_word_3.Contains(baseline_point)); + } +} + +// Verify the boolean return value of GetWordLookupDataAtPoint. +TEST_F(RenderTextTest, GetWordLookupDataAtPoint_Return) { + RenderText* render_text = GetRenderText(); + render_text->SetText(u"..."); + + DecoratedText decorated_word; + Point baseline_point; + + // False should be returned, when the text does not contain any word. + Point query = + render_text->GetCursorBounds(SelectionModel(0, CURSOR_FORWARD), false) + .origin(); + EXPECT_FALSE(render_text->GetWordLookupDataAtPoint(query, &decorated_word, + &baseline_point)); + + render_text->SetText(u"abc"); + query = render_text->GetCursorBounds(SelectionModel(0, CURSOR_FORWARD), false) + .origin(); + EXPECT_TRUE(render_text->GetWordLookupDataAtPoint(query, &decorated_word, + &baseline_point)); + + // False should be returned for obscured text. + render_text->SetObscured(true); + query = render_text->GetCursorBounds(SelectionModel(0, CURSOR_FORWARD), false) + .origin(); + EXPECT_FALSE(render_text->GetWordLookupDataAtPoint(query, &decorated_word, + &baseline_point)); +} + +// Test that GetLookupDataAtPoint behaves correctly when the range spans lines. +TEST_F(RenderTextTest, GetLookupDataAtRange_Multiline) { + const std::u16string text = u"a\nb"; + constexpr Range kWordOneRange = Range(0, 1); // Range of character 'a'. + constexpr Range kWordTwoRange = Range(2, 3); // Range of character 'b'. + constexpr Range kTextRange = Range(0, 3); // Range of the entire text. + + // Set up render text. Apply style ranges so that each character index gets + // a corresponding font. + RenderText* render_text = GetRenderText(); + render_text->SetMultiline(true); + render_text->SetDisplayRect(Rect(500, 500)); + render_text->SetText(text); + render_text->ApplyWeight(Font::Weight::SEMIBOLD, kWordOneRange); + render_text->ApplyStyle(TEXT_STYLE_UNDERLINE, true, kWordTwoRange); + + // Set up test expectations. + const std::vector font_spans = GetFontSpans(); + + DecoratedText expected_word_1; + expected_word_1.text = u"a"; + expected_word_1.attributes.push_back(CreateRangedAttribute( + font_spans, 0, kWordOneRange.start(), Font::Weight::SEMIBOLD, 0)); + const Rect left_glyph_word_1 = GetSubstringBoundsUnion(kWordOneRange); + + DecoratedText expected_word_2; + expected_word_2.text = u"b"; + expected_word_2.attributes.push_back( + CreateRangedAttribute(font_spans, 0, kWordTwoRange.start(), + Font::Weight::NORMAL, UNDERLINE_MASK)); + const Rect left_glyph_word_2 = GetSubstringBoundsUnion(kWordTwoRange); + + DecoratedText expected_entire_text; + expected_entire_text.text = u"a\nb"; + expected_entire_text.attributes.push_back( + CreateRangedAttribute(font_spans, kWordOneRange.start(), + kWordOneRange.start(), Font::Weight::SEMIBOLD, 0)); + expected_entire_text.attributes.push_back( + CreateRangedAttribute(font_spans, 1, 1, Font::Weight::NORMAL, 0)); + expected_entire_text.attributes.push_back(CreateRangedAttribute( + font_spans, kWordTwoRange.start(), kWordTwoRange.start(), + Font::Weight::NORMAL, UNDERLINE_MASK)); + + DecoratedText decorated_word; + Point baseline_point; + { + // Query for the range of the first word. + EXPECT_TRUE(render_text->GetLookupDataForRange( + kWordOneRange, &decorated_word, &baseline_point)); + VerifyDecoratedWordsAreEqual(expected_word_1, decorated_word); + EXPECT_TRUE(left_glyph_word_1.Contains(baseline_point)); + } + { + // Query for the range of the second word. + EXPECT_TRUE(render_text->GetLookupDataForRange( + kWordTwoRange, &decorated_word, &baseline_point)); + VerifyDecoratedWordsAreEqual(expected_word_2, decorated_word); + EXPECT_TRUE(left_glyph_word_2.Contains(baseline_point)); + } + { + // Query the entire text range. + EXPECT_TRUE(render_text->GetLookupDataForRange(kTextRange, &decorated_word, + &baseline_point)); + VerifyDecoratedWordsAreEqual(expected_entire_text, decorated_word); + EXPECT_TRUE(left_glyph_word_1.Contains(baseline_point)); + } +} + +// Tests text selection made at end points of individual lines of multiline +// text. +TEST_F(RenderTextTest, LineEndSelections) { + const char16_t* const ltr = u"abc\n\ndef"; + const char16_t* const rtl = u"שנב\n\nגקכ"; + const char16_t* const ltr_single = u"abc def ghi"; + const char16_t* const rtl_single = u"שנב גקכ עין"; + const int left_x = -100; + const int right_x = 200; + struct { + const char16_t* const text; + const int line_num; + const int x; + const char16_t* const selected_text; + } cases[] = { + {ltr, 1, left_x, u"abc\n"}, + {ltr, 1, right_x, u"abc\n"}, + {ltr, 2, left_x, u"abc\n\n"}, + {ltr, 2, right_x, ltr}, + + {rtl, 1, left_x, u"שנב\n"}, + {rtl, 1, right_x, u"שנב\n"}, + {rtl, 2, left_x, rtl}, + {rtl, 2, right_x, u"שנב\n\n"}, + + {ltr_single, 1, left_x, u"abc "}, + {ltr_single, 1, right_x, u"abc def "}, + {ltr_single, 2, left_x, u"abc def "}, + {ltr_single, 2, right_x, ltr_single}, + + {rtl_single, 1, left_x, u"שנב גקכ "}, + {rtl_single, 1, right_x, u"שנב "}, + {rtl_single, 2, left_x, rtl_single}, + {rtl_single, 2, right_x, u"שנב גקכ "}, + }; + + SetGlyphWidth(5); + RenderText* render_text = GetRenderText(); + render_text->SetMultiline(true); + render_text->SetDisplayRect(Rect(20, 1000)); + + for (size_t i = 0; i < base::size(cases); i++) { + SCOPED_TRACE(base::StringPrintf("Testing case %" PRIuS "", i)); + render_text->SetText(cases[i].text); + + EXPECT_EQ(3u, render_text->GetNumLines()); + // Position the cursor at the logical beginning of text. + render_text->SelectRange(Range(0)); + + render_text->MoveCursorToPoint( + Point(cases[i].x, GetCursorYForTesting(cases[i].line_num)), true); + EXPECT_EQ(cases[i].selected_text, GetSelectedText(render_text)); + } +} + +TEST_F(RenderTextTest, GetSubstringBounds) { + const float kGlyphWidth = 5; + SetGlyphWidth(kGlyphWidth); + RenderText* render_text = GetRenderText(); + render_text->SetText(u"abc"); + render_text->SetCursorEnabled(false); + render_text->SetElideBehavior(NO_ELIDE); + + EXPECT_EQ(GetSubstringBoundsUnion(Range(0, 1)).width(), kGlyphWidth); + EXPECT_EQ(GetSubstringBoundsUnion(Range(1, 2)).width(), kGlyphWidth); + EXPECT_EQ(GetSubstringBoundsUnion(Range(1, 3)).width(), 2 * kGlyphWidth); + + EXPECT_EQ(GetSubstringBoundsUnion(Range(0, 0)).width(), 0); + EXPECT_EQ(GetSubstringBoundsUnion(Range(3, 3)).width(), 0); + + // Apply eliding so display text has 2 visible character. + render_text->SetDisplayRect(Rect(0, 0, 2 * kGlyphWidth, 100)); + render_text->SetElideBehavior(TRUNCATE); + + EXPECT_EQ(GetSubstringBoundsUnion(Range(0, 1)).width(), kGlyphWidth); + EXPECT_EQ(GetSubstringBoundsUnion(Range(1, 2)).width(), kGlyphWidth); + EXPECT_EQ(GetSubstringBoundsUnion(Range(1, 3)).width(), kGlyphWidth); + // Check a fully elided range. + EXPECT_EQ(GetSubstringBoundsUnion(Range(2, 3)).width(), 0); + + // Empty ranges result in empty rect. + EXPECT_EQ(GetSubstringBoundsUnion(Range(0, 0)).width(), 0); + EXPECT_EQ(GetSubstringBoundsUnion(Range(3, 3)).width(), 0); +} + +// Tests that GetSubstringBounds rounds outward when glyphs have floating-point +// widths. +TEST_F(RenderTextTest, GetSubstringBoundsFloatingPoint) { + const float kGlyphWidth = 5.8; + SetGlyphWidth(kGlyphWidth); + RenderText* render_text = GetRenderText(); + render_text->SetDisplayRect(Rect(200, 1000)); + render_text->SetText(u"abcdef"); + gfx::Rect bounds = GetSubstringBoundsUnion(Range(1, 2)); + // The bounds should be rounded outwards so that the full substring is always + // contained in them. + EXPECT_EQ(base::ClampFloor(kGlyphWidth), bounds.x()); + EXPECT_EQ(base::ClampCeil(2 * kGlyphWidth), bounds.right()); +} + +// Tests that GetSubstringBounds handles integer glypth widths correctly. +TEST_F(RenderTextTest, GetSubstringBoundsInt) { + const float kGlyphWidth = 5; + SetGlyphWidth(kGlyphWidth); + RenderText* render_text = GetRenderText(); + render_text->SetDisplayRect(Rect(200, 1000)); + render_text->SetText(u"abcdef"); + gfx::Rect bounds = GetSubstringBoundsUnion(Range(1, 2)); + EXPECT_EQ(kGlyphWidth, bounds.x()); + EXPECT_EQ(2 * kGlyphWidth, bounds.right()); +} + +// Tests that GetSubstringBounds returns the correct bounds for multiline text. +TEST_F(RenderTextTest, GetSubstringBoundsMultiline) { + RenderText* render_text = GetRenderText(); + render_text->SetMultiline(true); + render_text->SetDisplayRect(Rect(200, 1000)); + render_text->SetText(u"abc\n\ndef"); + + const std::vector line_char_range = {Range(0, 4), Range(4, 5), + Range(5, 8)}; + + // Test bounds for individual lines. + EXPECT_EQ(3u, render_text->GetNumLines()); + Rect expected_total_bounds; + for (size_t i = 0; i < test_api()->lines().size(); i++) { + SCOPED_TRACE(base::StringPrintf("Testing bounds for line %" PRIuS "", i)); + const internal::Line& line = test_api()->lines()[i]; + const Size line_size(std::ceil(line.size.width()), + std::ceil(line.size.height())); + const Rect expected_line_bounds = + render_text->GetLineOffset(i) + Rect(line_size); + expected_total_bounds.Union(expected_line_bounds); + + render_text->SelectRange(line_char_range[i]); + EXPECT_EQ(expected_line_bounds, GetSelectionBoundsUnion()); + } + + // Test complete bounds. + render_text->SelectAll(false); + EXPECT_EQ(expected_total_bounds, GetSelectionBoundsUnion()); +} + +// Tests that RenderText doesn't crash even if it's passed an invalid font. Test +// for crbug.com/668058. +TEST_F(RenderTextTest, InvalidFont) { + const std::string font_name = "invalid_font"; + const int kFontSize = 13; + RenderText* render_text = GetRenderText(); + render_text->SetFontList(FontList(Font(font_name, kFontSize))); + render_text->SetText(u"abc"); + + DrawVisualText(); +} + +TEST_F(RenderTextTest, ExpandToBeVerticallySymmetric) { + Rect test_display_rect(0, 0, 400, 100); + + // Basic case. + EXPECT_EQ(Rect(20, 20, 400, 60), + test::RenderTextTestApi::ExpandToBeVerticallySymmetric( + Rect(20, 20, 400, 40), test_display_rect)); + + // Expand upwards. + EXPECT_EQ(Rect(20, 20, 400, 60), + test::RenderTextTestApi::ExpandToBeVerticallySymmetric( + Rect(20, 40, 400, 40), test_display_rect)); + + // Original rect is entirely above the center point. + EXPECT_EQ(Rect(10, 30, 200, 40), + test::RenderTextTestApi::ExpandToBeVerticallySymmetric( + Rect(10, 30, 200, 10), test_display_rect)); + + // Original rect is below the display rect entirely. + EXPECT_EQ(Rect(10, -10, 200, 120), + test::RenderTextTestApi::ExpandToBeVerticallySymmetric( + Rect(10, 100, 200, 10), test_display_rect)); + + // Sanity check that we can handle a display rect with a non-zero origin. + test_display_rect.Offset(10, 10); + EXPECT_EQ(Rect(20, 20, 400, 80), + test::RenderTextTestApi::ExpandToBeVerticallySymmetric( + Rect(20, 20, 400, 40), test_display_rect)); +} + +TEST_F(RenderTextTest, MergeIntersectingRects) { + // Basic case. + std::vector test_rects{Rect(0, 0, 10, 10), Rect(5, 0, 10, 10), + Rect(10, 0, 5, 10), Rect(12, 0, 5, 10)}; + test::RenderTextTestApi::MergeIntersectingRects(test_rects); + ASSERT_EQ(1u, test_rects.size()); + EXPECT_EQ(Rect(0, 0, 17, 10), test_rects[0]); + + // Case where some rects intersect and some don't. + test_rects = std::vector{Rect(0, 0, 10, 10), Rect(5, 0, 10, 10), + Rect(16, 0, 10, 10), Rect(25, 0, 10, 10), + Rect(40, 0, 10, 10)}; + test::RenderTextTestApi::MergeIntersectingRects(test_rects); + ASSERT_EQ(3u, test_rects.size()); + EXPECT_EQ(Rect(0, 0, 15, 10), test_rects[0]); + EXPECT_EQ(Rect(16, 0, 19, 10), test_rects[1]); + EXPECT_EQ(Rect(40, 0, 10, 10), test_rects[2]); + + // Case where no rects intersect. + test_rects = std::vector{Rect(0, 0, 10, 10), Rect(11, 0, 10, 10), + Rect(22, 0, 10, 10), Rect(33, 0, 10, 10)}; + test::RenderTextTestApi::MergeIntersectingRects(test_rects); + ASSERT_EQ(4u, test_rects.size()); + EXPECT_EQ(Rect(0, 0, 10, 10), test_rects[0]); + EXPECT_EQ(Rect(11, 0, 10, 10), test_rects[1]); + EXPECT_EQ(Rect(22, 0, 10, 10), test_rects[2]); + EXPECT_EQ(Rect(33, 0, 10, 10), test_rects[3]); + + // Rects are out-of-order. + test_rects = std::vector{Rect(10, 0, 5, 10), Rect(0, 0, 10, 10), + Rect(12, 0, 5, 10), Rect(5, 0, 10, 10)}; + test::RenderTextTestApi::MergeIntersectingRects(test_rects); + ASSERT_EQ(1u, test_rects.size()); + EXPECT_EQ(Rect(0, 0, 17, 10), test_rects[0]); + + // The first 3 rects are adjacent horizontally. The 4th rect is adjacent to + // the 3rd rect vertically, but is not merged. The last rect is adjacent to + // the 4th rect. + test_rects = std::vector{Rect(0, 0, 10, 10), Rect(10, 0, 10, 10), + Rect(20, 0, 10, 10), Rect(20, 10, 10, 10), + Rect(30, 10, 10, 10)}; + test::RenderTextTestApi::MergeIntersectingRects(test_rects); + ASSERT_EQ(2u, test_rects.size()); + EXPECT_EQ(Rect(0, 0, 30, 10), test_rects[0]); + EXPECT_EQ(Rect(20, 10, 20, 10), test_rects[1]); +} + +// Ensures that text is centered vertically and consistently when either the +// display rectangle height changes, or when the minimum line height changes. +// The difference between the two is the selection rectangle, which should match +// the line height. +TEST_F(RenderTextTest, BaselineWithLineHeight) { + RenderText* render_text = GetRenderText(); + const int font_height = render_text->font_list().GetHeight(); + render_text->SetDisplayRect(Rect(500, font_height)); + render_text->SetText(u"abc"); + + // Select everything so the test can use GetSelectionBoundsUnion(). + render_text->SelectAll(false); + + const int normal_baseline = test_api()->GetDisplayTextBaseline(); + ASSERT_EQ(1u, test_api()->lines().size()); + EXPECT_EQ(font_height, test_api()->lines()[0].size.height()); + + // With a matching display height, the baseline calculated using font metrics + // and the baseline from the layout engine should agree. This works because + // the text string is simple (exotic glyphs may use fonts with different + // metrics). + EXPECT_EQ(normal_baseline, render_text->GetBaseline()); + EXPECT_EQ(0, render_text->GetLineOffset(0).y()); + + const gfx::Rect normal_selection_bounds = GetSelectionBoundsUnion(); + + // Sanity check: selection should start from (0,0). + EXPECT_EQ(gfx::Vector2d(), normal_selection_bounds.OffsetFromOrigin()); + + constexpr int kDelta = 16; + + // Grow just the display rectangle. + render_text->SetDisplayRect(Rect(500, font_height + kDelta)); + + // The display text baseline does not move: GetLineOffset() moves it instead. + EXPECT_EQ(normal_baseline, test_api()->GetDisplayTextBaseline()); + + ASSERT_EQ(1u, test_api()->lines().size()); + EXPECT_EQ(font_height, test_api()->lines()[0].size.height()); + + // Save the baseline calculated using the display rectangle before enabling + // multi-line or SetMinLineHeight(). + const int reported_baseline = render_text->GetBaseline(); + const int baseline_shift = reported_baseline - normal_baseline; + + // When line height matches font height, this should match the line offset. + EXPECT_EQ(baseline_shift, render_text->GetLineOffset(0).y()); + + // The actual shift depends on font metrics, and the calculations done in + // RenderText::DetermineBaselineCenteringText(). Do a sanity check that the + // "centering" part is happening within some tolerance by ensuring the shift + // is within a pixel of (kDelta / 2). That is, 7, 8 or 9 pixels (for a delta + // of 16). An unusual font in future may need more leeway. + constexpr int kFuzz = 1; // If the next EXPECT fails, try increasing this. + EXPECT_LE(abs(baseline_shift - kDelta / 2), kFuzz); + + // Increasing display height (but not line height) should shift the selection + // bounds down by |baseline_shift|, but leave a matching size. + gfx::Rect current_selection_bounds = GetSelectionBoundsUnion(); + EXPECT_EQ(baseline_shift, current_selection_bounds.y()); + EXPECT_EQ(0, current_selection_bounds.x()); + EXPECT_EQ(normal_selection_bounds.size(), current_selection_bounds.size()); + + // Now increase the line height, but remain single-line. Note the line height + // now matches the display rect. + render_text->SetMinLineHeight(font_height + kDelta); + int display_text_baseline = test_api()->GetDisplayTextBaseline(); + ASSERT_EQ(1u, test_api()->lines().size()); + EXPECT_EQ(font_height + kDelta, test_api()->lines()[0].size.height()); + + // The line offset should go back to zero, but now the display text baseline + // should shift down to compensate, and the shift amount should match. + EXPECT_EQ(0, render_text->GetLineOffset(0).y()); + EXPECT_EQ(normal_baseline + baseline_shift, display_text_baseline); + + // Now selection bounds should grow in height, but not shift its origin. + current_selection_bounds = GetSelectionBoundsUnion(); + EXPECT_EQ(font_height + kDelta, current_selection_bounds.height()); + EXPECT_EQ(normal_selection_bounds.width(), current_selection_bounds.width()); + EXPECT_EQ(gfx::Vector2d(), current_selection_bounds.OffsetFromOrigin()); + + // Flipping the multiline flag should change nothing. + render_text->SetMultiline(true); + display_text_baseline = test_api()->GetDisplayTextBaseline(); + ASSERT_EQ(1u, test_api()->lines().size()); + EXPECT_EQ(font_height + kDelta, test_api()->lines()[0].size.height()); + EXPECT_EQ(0, render_text->GetLineOffset(0).y()); + EXPECT_EQ(normal_baseline + baseline_shift, display_text_baseline); + current_selection_bounds = GetSelectionBoundsUnion(); + EXPECT_EQ(font_height + kDelta, current_selection_bounds.height()); + EXPECT_EQ(normal_selection_bounds.width(), current_selection_bounds.width()); + EXPECT_EQ(gfx::Vector2d(), current_selection_bounds.OffsetFromOrigin()); +} + +TEST_F(RenderTextTest, TeluguGraphemeBoundaries) { + RenderText* render_text = GetRenderText(); + render_text->SetDisplayRect(Rect(50, 1000)); + // This is first syllable of the Telugu word for "New" in Chrome. It's a + // string of 4 UTF-8 characters: [క,్,ర,ొ]. When typeset with a supporting + // font, the second and fourth characters become diacritical marks for the + // first and third characters to form two graphemes. Then, these graphemes + // combine into a ligature "cluster". But, unlike ligatures in English (e.g. + // the "ffl" in "waffle"), this Telugu ligature is laid out vertically, with + // both graphemes occupying the same horizontal space. + render_text->SetText(u"క్రొ"); + + const int whole_width = render_text->GetStringSize().width(); + // Sanity check. A typical width is 8 pixels. Anything less than 6 could screw + // up the checks below with rounding. + EXPECT_LE(6, whole_width); + + // Go to the end and perform Shift+Left. The selection should jump to a + // grapheme boundary. + // Before ICU 65, this was in the center of the glyph, but now it encompasses + // the entire glyph. + render_text->SetCursorPosition(4); + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_LEFT, SELECTION_RETAIN); + EXPECT_EQ(Range(4, 0), render_text->selection()); + + // The cursor is already at the boundary, so there should be no change. + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_LEFT, SELECTION_RETAIN); + EXPECT_EQ(Range(4, 0), render_text->selection()); + + // The selection should cover the entire width. + Rect selection_bounds = GetSelectionBoundsUnion(); + EXPECT_EQ(0, selection_bounds.x()); + EXPECT_EQ(whole_width, selection_bounds.width()); +} + +// Test cursor bounds for Emoji flags (unicode regional indicators) when the +// flag does not merge into a single glyph. +TEST_F(RenderTextTest, MissingFlagEmoji) { + RenderText* render_text = GetRenderText(); + render_text->SetDisplayRect(Rect(1000, 1000)); + + // Usually these pair into country codes, but for this test we do not want + // them to combine into a flag. Instead, the placeholder glyphs should be used + // but cursor navigation should still behave as though they are joined. To get + // placeholder glyphs, make up a non-existent country. The codes used are + // based on ISO 3166-1 alpha-2. Codes starting with X are user-assigned. + std::u16string text(u"🇽🇽🇽🇽"); + // Each flag is 4 UTF16 characters (2 surrogate pair code points). + EXPECT_EQ(8u, text.length()); + + render_text->SetText(text); + + const int whole_width = render_text->GetStringSize().width(); + const int half_width = whole_width / 2; + EXPECT_LE(6, whole_width); // Sanity check. + + EXPECT_EQ("[0->7]", GetRunListStructureString()); + + // Move from the left to the right. + const Rect start_cursor = render_text->GetUpdatedCursorBounds(); + EXPECT_EQ(0, start_cursor.x()); + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(Range(4, 4), render_text->selection()); + const Rect middle_cursor = render_text->GetUpdatedCursorBounds(); + + // Should move about half way. Cursor bounds round to the nearest integer, so + // account for that. + EXPECT_LE(half_width - 1, middle_cursor.x()); + EXPECT_GE(half_width + 1, middle_cursor.x()); + + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, SELECTION_NONE); + EXPECT_EQ(Range(8, 8), render_text->selection()); + const Rect end_cursor = render_text->GetUpdatedCursorBounds(); + EXPECT_LE(whole_width - 1, end_cursor.x()); // Should move most of the way. + EXPECT_GE(whole_width + 1, end_cursor.x()); + + // Move right to left. + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(Range(4, 4), render_text->selection()); + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_LEFT, SELECTION_NONE); + EXPECT_EQ(Range(0, 0), render_text->selection()); + + // Select from the left to the right. The first "flag" should be selected. + // Note cursor bounds and selection bounds differ on integer rounding - see + // http://crbug.com/735346. + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, SELECTION_RETAIN); + EXPECT_EQ(Range(0, 4), render_text->selection()); + Rect selection_bounds = GetSelectionBoundsUnion(); + EXPECT_EQ(0, selection_bounds.x()); + EXPECT_LE(half_width - 1, selection_bounds.width()); // Allow for rounding. + EXPECT_GE(half_width + 1, selection_bounds.width()); + + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, SELECTION_RETAIN); + EXPECT_EQ(Range(0, 8), render_text->selection()); + selection_bounds = GetSelectionBoundsUnion(); + EXPECT_EQ(0, selection_bounds.x()); + EXPECT_EQ(whole_width, selection_bounds.width()); +} + +// Ensures that glyph spacing is correctly applied to obscured text. +TEST_F(RenderTextTest, ObscuredGlyphSpacing) { + const std::u16string seuss = u"hop on pop"; + RenderTextHarfBuzz* render_text = GetRenderText(); + render_text->SetText(seuss); + render_text->SetObscured(true); + + // The default glyph spacing is zero. + const int width_without_glyph_spacing = render_text->GetStringSize().width(); + EXPECT_EQ(0, render_text->obscured_glyph_spacing()); + + constexpr int kObscuredGlyphSpacing = 5; + render_text->SetObscuredGlyphSpacing(kObscuredGlyphSpacing); + const int width_with_glyph_spacing = render_text->GetStringSize().width(); + EXPECT_EQ(kObscuredGlyphSpacing, render_text->obscured_glyph_spacing()); + + EXPECT_EQ(width_without_glyph_spacing + + static_cast(seuss.length()) * kObscuredGlyphSpacing, + width_with_glyph_spacing); +} + +// Ensures that glyph spacing is ignored for non-obscured text. +TEST_F(RenderTextTest, ObscuredGlyphSpacingOnNonObscuredText) { + const std::u16string seuss = u"hop on pop"; + RenderTextHarfBuzz* render_text = GetRenderText(); + render_text->SetText(seuss); + render_text->SetObscured(false); + const int width_without_glyph_spacing = render_text->GetStringSize().width(); + + constexpr int kObscuredGlyphSpacing = 5; + render_text->SetObscuredGlyphSpacing(kObscuredGlyphSpacing); + const int width_with_glyph_spacing = render_text->GetStringSize().width(); + EXPECT_EQ(width_without_glyph_spacing, width_with_glyph_spacing); +} + +// Ensure font size overrides propagate through to text runs. +TEST_F(RenderTextTest, FontSizeOverride) { + RenderTextHarfBuzz* render_text = GetRenderText(); + const int default_font_size = render_text->font_list().GetFontSize(); + const int test_font_size_override = default_font_size + 5; + render_text->SetText(u"0123456789"); + render_text->ApplyFontSizeOverride(test_font_size_override, gfx::Range(3, 7)); + EXPECT_EQ(std::vector({u"012", u"3456", u"789"}), + GetRunListStrings()); + + const internal::TextRunList* run_list = GetHarfBuzzRunList(); + ASSERT_EQ(3U, run_list->size()); + + EXPECT_EQ(default_font_size, + run_list->runs()[0].get()->font_params.font_size); + EXPECT_EQ(test_font_size_override, + run_list->runs()[1].get()->font_params.font_size); + EXPECT_EQ(default_font_size, + run_list->runs()[2].get()->font_params.font_size); +} + +TEST_F(RenderTextTest, DrawVisualText_WithSelection) { + RenderText* render_text = GetRenderText(); + render_text->SetText(u"TheRedElephantIsEatingMyPumpkin"); + // Ensure selected text is drawn differently than unselected text. + render_text->set_selection_color(SK_ColorGREEN); + DrawVisualText({{3, 14}}); + ExpectTextLog( + {{3, kPlaceholderColor}, {11, SK_ColorGREEN}, {17, kPlaceholderColor}}); +} + +TEST_F(RenderTextTest, DrawVisualText_WithSelectionOnObcuredEmoji) { + RenderText* render_text = GetRenderText(); + render_text->SetText(u"\U0001F628\U0001F628\U0001F628"); + render_text->SetObscured(true); + render_text->set_selection_color(SK_ColorGREEN); + DrawVisualText({{4, 6}}); + ExpectTextLog({{2, kPlaceholderColor}, {1, SK_ColorGREEN}}); +} + +TEST_F(RenderTextTest, DrawSelectAll) { + const std::vector kUnselected = { + {4, SK_ColorBLACK}}; + const std::vector kSelected = {{4, SK_ColorGREEN}}; + const std::vector kFocused = { + {1, SK_ColorBLACK}, {2, SK_ColorGREEN}, {1, SK_ColorBLACK}}; + + RenderText* render_text = GetRenderText(); + render_text->SetText(u"Test"); + render_text->SetColor(SK_ColorBLACK); + render_text->set_selection_color(SK_ColorGREEN); + render_text->SelectRange(Range(1, 3)); + + Draw(false); + ExpectTextLog(kUnselected); + Draw(true); + ExpectTextLog(kSelected); + Draw(false); + ExpectTextLog(kUnselected); + + render_text->set_focused(true); + Draw(false); + ExpectTextLog(kFocused); + Draw(true); + ExpectTextLog(kSelected); + + render_text->set_focused(false); + Draw(true); + ExpectTextLog(kSelected); + Draw(false); + ExpectTextLog(kUnselected); +} + +#if defined(OS_LINUX) || defined(OS_CHROMEOS) +TEST_F(RenderTextTest, StringSizeUpdatedWhenDeviceScaleFactorChanges) { + RenderText* render_text = GetRenderText(); + render_text->SetText(u"Test - 1"); + const gfx::SizeF initial_size = render_text->GetStringSizeF(); + + // Non-integer device scale factor enables subpixel positioning on Linux and + // Chrome OS, which should update text size. + SetFontRenderParamsDeviceScaleFactor(1.5); + + const gfx::SizeF scaled_size = render_text->GetStringSizeF(); + + // Create render text with scale factor set from the beginning, and use is as + // a baseline to which compare the original render text string size. + ResetRenderTextInstance(); + RenderText* scaled_render_text = GetRenderText(); + scaled_render_text->SetText(u"Test - 1"); + + // Verify that original render text string size got updated after device scale + // factor changed. + EXPECT_NE(initial_size.width(), scaled_size.width()); + EXPECT_EQ(scaled_render_text->GetStringSizeF(), scaled_size); +} +#endif + +TEST_F(RenderTextTest, Clusterfuzz_Issue_1287804) { + RenderText* render_text = GetRenderText(); + render_text->SetMaxLines(1); + render_text->SetText(u">\r\r"); + render_text->SetMultiline(true); + render_text->SetDisplayRect(Rect(0, 0, 100, 24)); + render_text->SetElideBehavior(ELIDE_TAIL); + EXPECT_EQ(RangeF(0, 0), render_text->GetCursorSpan(Range(0, 0))); +} + +} // namespace gfx diff --git a/rendering_pipeline.cc b/rendering_pipeline.cc new file mode 100644 index 000000000000..174b10b0c4ae --- /dev/null +++ b/rendering_pipeline.cc @@ -0,0 +1,298 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/rendering_pipeline.h" + +#include "base/containers/flat_map.h" +#include "base/task/current_thread.h" +#include "base/task/sequence_manager/task_time_observer.h" +#include "base/thread_annotations.h" +#include "base/threading/thread_checker.h" +#include "ui/gfx/rendering_stage_scheduler.h" + +namespace gfx { +namespace { + +class ThreadSafeTimeObserver : public base::sequence_manager::TaskTimeObserver { + public: + explicit ThreadSafeTimeObserver( + scoped_refptr task_runner) + : task_runner_(std::move(task_runner)) {} + ~ThreadSafeTimeObserver() override { + // If the observer is being used on the target thread, unregister now. If it + // was being used on a different thread, then the target thread should have + // been torn down already. + SetEnabled(false); + } + + ThreadSafeTimeObserver(const ThreadSafeTimeObserver&) = delete; + ThreadSafeTimeObserver& operator=(const ThreadSafeTimeObserver&) = delete; + + void SetEnabled(bool enabled) { + { + base::AutoLock hold(time_lock_); + if (enabled_ == enabled) + return; + enabled_ = enabled; + } + + if (!task_runner_) + return; + + if (task_runner_->BelongsToCurrentThread()) { + UpdateOnTargetThread(enabled); + return; + } + + task_runner_->PostTask( + FROM_HERE, base::BindOnce(&ThreadSafeTimeObserver::UpdateOnTargetThread, + base::Unretained(this), enabled)); + } + + base::TimeDelta GetAndResetTimeSinceLastFrame() { + base::AutoLock hold(time_lock_); + + if (!start_time_active_task_.is_null()) { + auto now = base::TimeTicks::Now(); + time_since_last_frame_ += now - start_time_active_task_; + start_time_active_task_ = now; + } + + auto result = time_since_last_frame_; + time_since_last_frame_ = base::TimeDelta(); + return result; + } + + // TaskTimeObserver impl. + void WillProcessTask(base::TimeTicks start_time) override { + base::AutoLock hold(time_lock_); + if (!enabled_) + return; + + DCHECK(start_time_active_task_.is_null()); + start_time_active_task_ = start_time; + } + + void DidProcessTask(base::TimeTicks start_time, + base::TimeTicks end_time) override { + base::AutoLock hold(time_lock_); + if (!enabled_) { + start_time_active_task_ = base::TimeTicks(); + return; + } + + // This should be null for the task which adds this object to the observer + // list. + if (start_time_active_task_.is_null()) + return; + + if (start_time_active_task_ <= end_time) { + time_since_last_frame_ += (end_time - start_time_active_task_); + } else { + // This could happen if |GetAndResetTimeSinceLastFrame| is called on a + // different thread and the observed thread had to wait to acquire the + // lock to call DidProcessTask. Assume the time for this task is already + // recorded in |GetAndResetTimeSinceLastFrame|. + DCHECK_NE(start_time_active_task_, start_time); + } + + start_time_active_task_ = base::TimeTicks(); + } + + private: + void UpdateOnTargetThread(bool enabled) { + if (enabled) { + base::CurrentThread::Get().AddTaskTimeObserver(this); + + base::AutoLock hold(time_lock_); + start_time_active_task_ = base::TimeTicks(); + time_since_last_frame_ = base::TimeDelta(); + } else { + base::CurrentThread::Get().RemoveTaskTimeObserver(this); + } + } + + // Accessed only on the calling thread. The caller ensures no concurrent + // access. + scoped_refptr task_runner_; + + // Accessed on calling and target thread. + base::Lock time_lock_; + bool enabled_ GUARDED_BY(time_lock_) = false; + base::TimeTicks start_time_active_task_ GUARDED_BY(time_lock_); + base::TimeDelta time_since_last_frame_ GUARDED_BY(time_lock_); +}; + +} // namespace + +class RenderingPipelineImpl final : public RenderingPipeline { + public: + explicit RenderingPipelineImpl(const char* pipeline_type) + : pipeline_type_(pipeline_type) { + DETACH_FROM_THREAD(bound_thread_); + } + ~RenderingPipelineImpl() override { TearDown(); } + + RenderingPipelineImpl(const RenderingPipelineImpl&) = delete; + RenderingPipelineImpl& operator=(const RenderingPipelineImpl&) = delete; + + void SetTargetDuration(base::TimeDelta target_duration) override { + DCHECK_CALLED_ON_VALID_THREAD(bound_thread_); + DCHECK(!target_duration.is_zero()); + + if (target_duration_ == target_duration) + return; + + target_duration_ = target_duration; + if (should_use_scheduler()) + SetUp(); + } + + void AddSequenceManagerThread( + base::PlatformThreadId thread_id, + scoped_refptr task_runner) override { + base::AutoLock lock(lock_); + DCHECK(time_observers_.find(thread_id) == time_observers_.end()); + time_observers_[thread_id] = + std::make_unique(task_runner); + if (scheduler_) + CreateSchedulerAndEnableWithLockAcquired(); + } + + base::sequence_manager::TaskTimeObserver* AddSimpleThread( + base::PlatformThreadId thread_id) override { + base::AutoLock lock(lock_); + DCHECK(time_observers_.find(thread_id) == time_observers_.end()); + time_observers_[thread_id] = + std::make_unique(nullptr); + + if (scheduler_) + CreateSchedulerAndEnableWithLockAcquired(); + return time_observers_[thread_id].get(); + } + + void NotifyFrameFinished() override { + DCHECK_CALLED_ON_VALID_THREAD(bound_thread_); + + base::AutoLock lock(lock_); + if (!scheduler_) + return; + + // TODO(crbug.com/1157620): This can be optimized to exclude tasks which can + // be paused during rendering. The best use-case is idle tasks on the + // renderer main thread. If all non-optional work is close to the frame + // budget then the scheduler dynamically adjusts to pause work like idle + // tasks. + base::TimeDelta total_time; + for (auto& it : time_observers_) { + total_time += it.second->GetAndResetTimeSinceLastFrame(); + } + scheduler_->ReportCpuCompletionTime(total_time + gpu_latency_); + } + + void SetGpuLatency(base::TimeDelta gpu_latency) override { + DCHECK_CALLED_ON_VALID_THREAD(bound_thread_); + gpu_latency_ = gpu_latency; + } + + void UpdateActiveCount(bool active) override { + DCHECK_CALLED_ON_VALID_THREAD(bound_thread_); + + if (active) { + active_count_++; + } else { + DCHECK_GT(active_count_, 0); + active_count_--; + } + + if (should_use_scheduler()) { + SetUp(); + } else { + TearDown(); + } + } + + private: + bool should_use_scheduler() const { + // TODO(crbug.com/1157620) : Figure out what we should be doing if multiple + // independent pipelines of a type are running simultaneously. The common + // use-case for this in practice would be multi-window. The tabs could be + // hosted in the same renderer process and each window is composited + // independently by the GPU process. + return active_count_ == 1 && !target_duration_.is_zero(); + } + + void SetUp() { + base::AutoLock lock(lock_); + CreateSchedulerAndEnableWithLockAcquired(); + } + + void CreateSchedulerAndEnableWithLockAcquired() { + lock_.AssertAcquired(); + scheduler_.reset(); + + std::vector platform_threads; + for (auto& it : time_observers_) { + platform_threads.push_back(it.first); + it.second->SetEnabled(true); + } + + scheduler_ = RenderingStageScheduler::CreateAdpf( + pipeline_type_, std::move(platform_threads), target_duration_); + } + + void TearDown() { + base::AutoLock lock(lock_); + for (auto& it : time_observers_) + it.second->SetEnabled(false); + scheduler_.reset(); + } + + THREAD_CHECKER(bound_thread_); + + base::Lock lock_; + base::flat_map> + time_observers_ GUARDED_BY(lock_); + std::unique_ptr scheduler_ GUARDED_BY(lock_); + + // Pipeline name, for tracing and metrics. + const char* pipeline_type_; + + // The number of currently active pipelines of this type. + int active_count_ = 0; + + // The target time for this rendering stage for a frame. + base::TimeDelta target_duration_; + + base::TimeDelta gpu_latency_; +}; + +RenderingPipeline::ScopedPipelineActive::ScopedPipelineActive( + RenderingPipeline* pipeline) + : pipeline_(pipeline) { + pipeline_->UpdateActiveCount(true); +} + +RenderingPipeline::ScopedPipelineActive::~ScopedPipelineActive() { + pipeline_->UpdateActiveCount(false); +} + +std::unique_ptr RenderingPipeline::CreateRendererMain() { + static constexpr char kRendererMain[] = "RendererMain"; + return std::make_unique(kRendererMain); +} + +std::unique_ptr +RenderingPipeline::CreateRendererCompositor() { + static constexpr char kRendererCompositor[] = "RendererCompositor"; + return std::make_unique(kRendererCompositor); +} + +std::unique_ptr RenderingPipeline::CreateGpu() { + static constexpr char kGpu[] = "Gpu"; + return std::make_unique(kGpu); +} + +} // namespace gfx diff --git a/rendering_pipeline.h b/rendering_pipeline.h new file mode 100644 index 000000000000..0ad6729c2ff6 --- /dev/null +++ b/rendering_pipeline.h @@ -0,0 +1,82 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_RENDERING_PIPELINE_H_ +#define UI_GFX_RENDERING_PIPELINE_H_ + +#include "base/memory/singleton.h" +#include "base/single_thread_task_runner.h" +#include "base/threading/platform_thread.h" +#include "ui/gfx/gfx_export.h" + +namespace base { +namespace sequence_manager { +class TaskTimeObserver; +} +} // namespace base + +namespace gfx { + +// Tracks the desired and actual execution time of rendering threads to +// optimally schedule them on the CPU. Instances of this class should be shared +// between all compositors of the same rendering stage. +// +// This class can be created on any thread but becomes bound to the thread it's +// subsequently used on. The class may be destroyed on any thread but the caller +// is responsible for ensuring all other threads in the rendering stage, other +// than the thread the object is destroyed on, are torn down before destroying +// an instance of this class. +class GFX_EXPORT RenderingPipeline { + public: + // Notifies when this pipeline is active. Multiple pipelines of the same type + // can be concurrently active at a time. The pipeline is assumed active for + // the lifetime of this object. + class GFX_EXPORT ScopedPipelineActive { + public: + explicit ScopedPipelineActive(RenderingPipeline* pipeline); + ~ScopedPipelineActive(); + + private: + RenderingPipeline* const pipeline_; + }; + + static std::unique_ptr CreateRendererMain(); + static std::unique_ptr CreateRendererCompositor(); + static std::unique_ptr CreateGpu(); + + virtual ~RenderingPipeline() = default; + + // Add to this pipeline a thread backed by base sequence manager, where + // |base::CurrentThread| works. Most threads in chromium should fall into + // this category. + // This method is thread safe and can be called on any thread. + virtual void AddSequenceManagerThread( + base::PlatformThreadId thread_id, + scoped_refptr task_runner) = 0; + + // Add a simple thread to this pipeline. The caller is responsible for + // updating the returned observer for tasks executed on the thread. + // The returned observer is owned by this pipeline object. + // This method is thread safe and can be called on any thread. + virtual base::sequence_manager::TaskTimeObserver* AddSimpleThread( + base::PlatformThreadId thread_id) = 0; + + // Notifies when this pipeline stage has finished rendering to compute the + // execution time per frame for the associated threads. + virtual void NotifyFrameFinished() = 0; + + // Sets the desired duration for this pipeline. + virtual void SetTargetDuration(base::TimeDelta target_duration) = 0; + + // Sets the latency from composition for a display buffer finishing on the + // Gpu thread to when execution finished on the Gpu. + virtual void SetGpuLatency(base::TimeDelta delta) = 0; + + protected: + virtual void UpdateActiveCount(bool active) = 0; +}; + +} // namespace gfx + +#endif // UI_GFX_RENDERING_PIPELINE_H_ diff --git a/rendering_stage_scheduler.cc b/rendering_stage_scheduler.cc new file mode 100644 index 000000000000..0bfa2e02e777 --- /dev/null +++ b/rendering_stage_scheduler.cc @@ -0,0 +1,78 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/rendering_stage_scheduler.h" + +#include "base/logging.h" +#include "base/no_destructor.h" +#include "base/trace_event/trace_event.h" +#include "build/build_config.h" + +#if defined(OS_ANDROID) +#include "base/android/jni_array.h" +#include "base/android/scoped_java_ref.h" +#include "ui/gfx/gfx_jni_headers/AdpfRenderingStageScheduler_jni.h" +#endif // OS_ANDROID + +namespace gfx { +namespace { +#if defined(OS_ANDROID) + +class RenderingStageSchedulerAdpf : public RenderingStageScheduler { + public: + RenderingStageSchedulerAdpf(const char* pipeline_type, + std::vector threads, + base::TimeDelta desired_duration) + : pipeline_type_(pipeline_type), desired_duration_(desired_duration) { + static_assert(sizeof(base::PlatformThreadId) == sizeof(jint), + "thread id types incompatible"); + JNIEnv* env = base::android::AttachCurrentThread(); + j_object_ = Java_AdpfRenderingStageScheduler_create( + env, base::android::ToJavaIntArray(env, threads), + desired_duration_.InNanoseconds()); + } + + ~RenderingStageSchedulerAdpf() override { + if (!j_object_) + return; + JNIEnv* env = base::android::AttachCurrentThread(); + Java_AdpfRenderingStageScheduler_destroy(env, j_object_); + } + + void ReportCpuCompletionTime(base::TimeDelta actual_duration) override { + TRACE_EVENT_INSTANT2( + "benchmark", "RenderingStageSchedulerAdpf::ReportCpuCompletionTime", + TRACE_EVENT_SCOPE_THREAD, "pipeline_type", pipeline_type_, + "utilization_percentage", + static_cast(actual_duration * 100 / desired_duration_)); + if (!j_object_) + return; + JNIEnv* env = base::android::AttachCurrentThread(); + Java_AdpfRenderingStageScheduler_reportCpuCompletionTime( + env, j_object_, actual_duration.InNanoseconds()); + } + + private: + const char* pipeline_type_; + const base::TimeDelta desired_duration_; + base::android::ScopedJavaGlobalRef j_object_; +}; + +#endif // OS_ANDROID + +} // namespace + +std::unique_ptr RenderingStageScheduler::CreateAdpf( + const char* pipeline_type, + std::vector threads, + base::TimeDelta desired_duration) { +#if defined(OS_ANDROID) + return std::make_unique( + pipeline_type, std::move(threads), desired_duration); +#else + return nullptr; +#endif +} + +} // namespace gfx diff --git a/rendering_stage_scheduler.h b/rendering_stage_scheduler.h new file mode 100644 index 000000000000..3c00f17b4319 --- /dev/null +++ b/rendering_stage_scheduler.h @@ -0,0 +1,35 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_RENDERING_STAGE_SCHEDULER_H_ +#define UI_GFX_RENDERING_STAGE_SCHEDULER_H_ + +#include +#include + +#include "base/threading/platform_thread.h" +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +class GFX_EXPORT RenderingStageScheduler { + public: + // Creating instances of this class requires loading native libraries which + // require synchronous file access. This method ensures the synchronous work + // is finished. + static void EnsureInitialized(); + + static std::unique_ptr CreateAdpf( + const char* pipeline_type, + std::vector threads, + base::TimeDelta desired_duration); + + virtual ~RenderingStageScheduler() = default; + + virtual void ReportCpuCompletionTime(base::TimeDelta actual_duration) = 0; +}; + +} // namespace gfx + +#endif // UI_GFX_RENDERING_STAGE_SCHEDULER_H_ diff --git a/scoped_canvas.cc b/scoped_canvas.cc new file mode 100644 index 000000000000..fe45fcd4a07b --- /dev/null +++ b/scoped_canvas.cc @@ -0,0 +1,29 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/scoped_canvas.h" + +#include "base/i18n/rtl.h" +#include "ui/gfx/geometry/rect.h" + +namespace gfx { + +ScopedCanvas::ScopedCanvas(gfx::Canvas* canvas) : canvas_(canvas) { + if (canvas_) + canvas_->Save(); +} + +ScopedCanvas::~ScopedCanvas() { + if (canvas_) + canvas_->Restore(); +} + +void ScopedCanvas::FlipIfRTL(int width) { + if (base::i18n::IsRTL()) { + canvas_->Translate(gfx::Vector2d(width, 0)); + canvas_->Scale(-1, 1); + } +} + +} // namespace gfx diff --git a/scoped_canvas.h b/scoped_canvas.h new file mode 100644 index 000000000000..d2fee5627b51 --- /dev/null +++ b/scoped_canvas.h @@ -0,0 +1,32 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_SCOPED_CANVAS_H_ +#define UI_GFX_SCOPED_CANVAS_H_ + +#include "base/macros.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +// Saves the drawing state, and restores the state when going out of scope. +class GFX_EXPORT ScopedCanvas { + public: + explicit ScopedCanvas(gfx::Canvas* canvas); + ScopedCanvas(const ScopedCanvas&) = delete; + ScopedCanvas& operator=(const ScopedCanvas&) = delete; + virtual ~ScopedCanvas(); + + // If the UI is in RTL layout, applies a transform such that anything drawn + // inside the supplied width will be flipped horizontally. + void FlipIfRTL(int width); + + private: + gfx::Canvas* canvas_; +}; + +} // namespace gfx + +#endif // UI_GFX_SCOPED_CANVAS_H_ diff --git a/scoped_cg_context_save_gstate_mac.h b/scoped_cg_context_save_gstate_mac.h new file mode 100644 index 000000000000..c829906bcd07 --- /dev/null +++ b/scoped_cg_context_save_gstate_mac.h @@ -0,0 +1,34 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_SCOPED_CG_CONTEXT_SAVE_GSTATE_MAC_H_ +#define UI_GFX_SCOPED_CG_CONTEXT_SAVE_GSTATE_MAC_H_ + +#import + +#include "base/macros.h" + +namespace gfx { + +class ScopedCGContextSaveGState { + public: + explicit ScopedCGContextSaveGState(CGContextRef context) : context_(context) { + CGContextSaveGState(context_); + } + + ScopedCGContextSaveGState(const ScopedCGContextSaveGState&) = delete; + ScopedCGContextSaveGState& operator=(const ScopedCGContextSaveGState&) = + delete; + + ~ScopedCGContextSaveGState() { + CGContextRestoreGState(context_); + } + + private: + CGContextRef context_; +}; + +} // namespace gfx + +#endif // UI_GFX_SCOPED_CG_CONTEXT_SAVE_GSTATE_MAC_H_ diff --git a/scoped_ns_graphics_context_save_gstate_mac.h b/scoped_ns_graphics_context_save_gstate_mac.h new file mode 100644 index 000000000000..b0e755311579 --- /dev/null +++ b/scoped_ns_graphics_context_save_gstate_mac.h @@ -0,0 +1,37 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_SCOPED_NS_GRAPHICS_CONTEXT_SAVE_GSTATE_MAC_H_ +#define UI_GFX_SCOPED_NS_GRAPHICS_CONTEXT_SAVE_GSTATE_MAC_H_ + +#include "base/macros.h" +#include "ui/gfx/gfx_export.h" + +#if defined(__OBJC__) +@class NSGraphicsContext; +#else +class NSGraphicsContext; +#endif + +namespace gfx { + +// A class to save/restore the state of the current context. +class GFX_EXPORT ScopedNSGraphicsContextSaveGState { + public: + ScopedNSGraphicsContextSaveGState(); + + ScopedNSGraphicsContextSaveGState(const ScopedNSGraphicsContextSaveGState&) = + delete; + ScopedNSGraphicsContextSaveGState& operator=( + const ScopedNSGraphicsContextSaveGState&) = delete; + + ~ScopedNSGraphicsContextSaveGState(); + + private: + NSGraphicsContext* context_; // weak +}; + +} // namespace gfx + +#endif // UI_GFX_SCOPED_NS_GRAPHICS_CONTEXT_SAVE_GSTATE_MAC_H_ diff --git a/scoped_ns_graphics_context_save_gstate_mac.mm b/scoped_ns_graphics_context_save_gstate_mac.mm new file mode 100644 index 000000000000..658f4c680d46 --- /dev/null +++ b/scoped_ns_graphics_context_save_gstate_mac.mm @@ -0,0 +1,23 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h" + +#import + +#include "base/check_op.h" + +namespace gfx { + +ScopedNSGraphicsContextSaveGState::ScopedNSGraphicsContextSaveGState() + : context_([NSGraphicsContext currentContext]) { + [NSGraphicsContext saveGraphicsState]; +} + +ScopedNSGraphicsContextSaveGState::~ScopedNSGraphicsContextSaveGState() { + [NSGraphicsContext restoreGraphicsState]; + DCHECK_EQ(context_, [NSGraphicsContext currentContext]); +} + +} // namespace gfx diff --git a/scoped_ui_graphics_push_context_ios.h b/scoped_ui_graphics_push_context_ios.h new file mode 100644 index 000000000000..682da3078b28 --- /dev/null +++ b/scoped_ui_graphics_push_context_ios.h @@ -0,0 +1,30 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_SCOPED_UI_GRAPHICS_PUSH_CONTEXT_IOS_H_ +#define UI_GFX_SCOPED_UI_GRAPHICS_PUSH_CONTEXT_IOS_H_ + +#import + +#include "base/macros.h" + +namespace gfx { + +class ScopedUIGraphicsPushContext { + public: + explicit ScopedUIGraphicsPushContext(CGContextRef context); + + ScopedUIGraphicsPushContext(const ScopedUIGraphicsPushContext&) = delete; + ScopedUIGraphicsPushContext& operator=(const ScopedUIGraphicsPushContext&) = + delete; + + ~ScopedUIGraphicsPushContext(); + + private: + CGContextRef context_; +}; + +} // namespace gfx + +#endif // UI_GFX_SCOPED_UI_GRAPHICS_PUSH_CONTEXT_IOS_H_ diff --git a/scoped_ui_graphics_push_context_ios.mm b/scoped_ui_graphics_push_context_ios.mm new file mode 100644 index 000000000000..a5b9b951fb01 --- /dev/null +++ b/scoped_ui_graphics_push_context_ios.mm @@ -0,0 +1,23 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/scoped_ui_graphics_push_context_ios.h" + +#import + +#include "base/check_op.h" + +namespace gfx { + +ScopedUIGraphicsPushContext::ScopedUIGraphicsPushContext(CGContextRef context) + : context_(context) { + UIGraphicsPushContext(context_); +} + +ScopedUIGraphicsPushContext::~ScopedUIGraphicsPushContext() { + DCHECK_EQ(context_, UIGraphicsGetCurrentContext()); + UIGraphicsPopContext(); +} + +} // namespace gfx diff --git a/scrollbar_size.cc b/scrollbar_size.cc new file mode 100644 index 000000000000..040adecaa331 --- /dev/null +++ b/scrollbar_size.cc @@ -0,0 +1,24 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/scrollbar_size.h" + +#include "base/compiler_specific.h" +#include "build/build_config.h" + +#if defined(OS_WIN) +#include +#endif + +namespace gfx { + +int scrollbar_size() { +#if defined(OS_WIN) + return GetSystemMetrics(SM_CXVSCROLL); +#else + return 15; +#endif +} + +} // namespace gfx diff --git a/scrollbar_size.h b/scrollbar_size.h new file mode 100644 index 000000000000..bfea069af74c --- /dev/null +++ b/scrollbar_size.h @@ -0,0 +1,19 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_SCROLLBAR_SIZE_H_ +#define UI_GFX_SCROLLBAR_SIZE_H_ + +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +// This should return the thickness, in pixels, of a scrollbar in web content. +// This needs to match the values in WebCore's +// ScrollbarThemeChromiumXXX.cpp::scrollbarThickness(). +GFX_EXPORT int scrollbar_size(); + +} // namespace gfx + +#endif // UI_GFX_SCROLLBAR_SIZE_H_ diff --git a/selection_bound.cc b/selection_bound.cc new file mode 100644 index 000000000000..4e341072cd12 --- /dev/null +++ b/selection_bound.cc @@ -0,0 +1,125 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/macros.h" +#include "base/strings/stringprintf.h" +#include "ui/gfx/geometry/point_conversions.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/rect_f.h" +#include "ui/gfx/selection_bound.h" + +namespace gfx { + +SelectionBound::SelectionBound() : type_(EMPTY), visible_(false) {} + +SelectionBound::SelectionBound(const SelectionBound& other) = default; + +SelectionBound::~SelectionBound() {} + +void SelectionBound::SetEdgeStart(const gfx::PointF& value) { + edge_start_ = value; + edge_start_rounded_ = gfx::ToRoundedPoint(value); +} + +void SelectionBound::SetVisibleEdgeStart(const gfx::PointF& value) { + visible_edge_start_ = value; +} + +void SelectionBound::SetEdgeEnd(const gfx::PointF& value) { + edge_end_ = value; + edge_end_rounded_ = gfx::ToRoundedPoint(value); +} + +void SelectionBound::SetVisibleEdgeEnd(const gfx::PointF& value) { + visible_edge_end_ = value; +} + +void SelectionBound::SetEdge(const gfx::PointF& start, const gfx::PointF& end) { + SetEdgeStart(start); + SetEdgeEnd(end); +} + +void SelectionBound::SetVisibleEdge(const gfx::PointF& start, + const gfx::PointF& end) { + SetVisibleEdgeStart(start); + SetVisibleEdgeEnd(end); +} + +int SelectionBound::GetHeight() const { + return edge_end_rounded_.y() - edge_start_rounded_.y(); +} + +std::string SelectionBound::ToString() const { + return base::StringPrintf( + "SelectionBound(%s, %s, %s, %s, %d)", edge_start_.ToString().c_str(), + edge_end_.ToString().c_str(), edge_start_rounded_.ToString().c_str(), + edge_end_rounded_.ToString().c_str(), visible_); +} + +bool operator==(const SelectionBound& lhs, const SelectionBound& rhs) { + return lhs.type() == rhs.type() && lhs.visible() == rhs.visible() && + lhs.edge_start() == rhs.edge_start() && + lhs.edge_end() == rhs.edge_end() && + lhs.visible_edge_start() == rhs.visible_edge_start() && + lhs.visible_edge_end() == rhs.visible_edge_end(); +} + +bool operator!=(const SelectionBound& lhs, const SelectionBound& rhs) { + return !(lhs == rhs); +} + +gfx::Rect RectBetweenSelectionBounds(const SelectionBound& b1, + const SelectionBound& b2) { + gfx::Point top_left(b1.edge_start_rounded()); + top_left.SetToMin(b1.edge_end_rounded()); + top_left.SetToMin(b2.edge_start_rounded()); + top_left.SetToMin(b2.edge_end_rounded()); + + gfx::Point bottom_right(b1.edge_start_rounded()); + bottom_right.SetToMax(b1.edge_end_rounded()); + bottom_right.SetToMax(b2.edge_start_rounded()); + bottom_right.SetToMax(b2.edge_end_rounded()); + + gfx::Vector2d diff = bottom_right - top_left; + return gfx::Rect(top_left, gfx::Size(diff.x(), diff.y())); +} + +gfx::RectF RectFBetweenSelectionBounds(const SelectionBound& b1, + const SelectionBound& b2) { + gfx::PointF top_left(b1.edge_start()); + top_left.SetToMin(b1.edge_end()); + top_left.SetToMin(b2.edge_start()); + top_left.SetToMin(b2.edge_end()); + + gfx::PointF bottom_right(b1.edge_start()); + bottom_right.SetToMax(b1.edge_end()); + bottom_right.SetToMax(b2.edge_start()); + bottom_right.SetToMax(b2.edge_end()); + + gfx::Vector2dF diff = bottom_right - top_left; + return gfx::RectF(top_left, gfx::SizeF(diff.x(), diff.y())); +} + +gfx::RectF RectFBetweenVisibleSelectionBounds(const SelectionBound& b1, + const SelectionBound& b2) { + // The selection bound is determined by the |start| and |end| points. For the + // writing-mode is vertical-*, the bounds are horizontal, the |end| might + // be on the left side of the |start|. + gfx::PointF top_left(b1.visible_edge_start()); + top_left.SetToMin(b1.visible_edge_end()); + top_left.SetToMin(b2.visible_edge_start()); + top_left.SetToMin(b2.visible_edge_end()); + + gfx::PointF bottom_right(b1.visible_edge_start()); + bottom_right.SetToMax(b1.visible_edge_end()); + bottom_right.SetToMax(b2.visible_edge_start()); + bottom_right.SetToMax(b2.visible_edge_end()); + + gfx::Vector2dF diff = bottom_right - top_left; + return gfx::RectF(top_left, gfx::SizeF(diff.x(), diff.y())); +} + +} // namespace gfx diff --git a/selection_bound.h b/selection_bound.h new file mode 100644 index 000000000000..150b3a0fc7dd --- /dev/null +++ b/selection_bound.h @@ -0,0 +1,83 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_SELECTION_BOUND_H_ +#define UI_GFX_SELECTION_BOUND_H_ + +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/point_f.h" +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +class Rect; +class RectF; + +// Bound of a selection end-point. +class GFX_EXPORT SelectionBound { + public: + enum Type { LEFT, RIGHT, CENTER, EMPTY, LAST = EMPTY }; + + SelectionBound(); + SelectionBound(const SelectionBound& other); + ~SelectionBound(); + + Type type() const { return type_; } + void set_type(Type value) { type_ = value; } + + const gfx::PointF& edge_start() const { return edge_start_; } + const gfx::PointF& visible_edge_start() const { return visible_edge_start_; } + const gfx::Point& edge_start_rounded() const { return edge_start_rounded_; } + void SetEdgeStart(const gfx::PointF& value); + void SetVisibleEdgeStart(const gfx::PointF& value); + + const gfx::PointF& edge_end() const { return edge_end_; } + const gfx::PointF& visible_edge_end() const { return visible_edge_end_; } + const gfx::Point& edge_end_rounded() const { return edge_end_rounded_; } + void SetEdgeEnd(const gfx::PointF& value); + void SetVisibleEdgeEnd(const gfx::PointF& value); + + void SetEdge(const gfx::PointF& start, const gfx::PointF& end); + void SetVisibleEdge(const gfx::PointF& start, const gfx::PointF& end); + + bool visible() const { return visible_; } + void set_visible(bool value) { visible_ = value; } + + // Returns the vertical difference between rounded start and end. + int GetHeight() const; + + std::string ToString() const; + + private: + Type type_; + // The actual bounds of a selection end-point mgiht be invisible for + // occlusion. + gfx::PointF edge_start_; + gfx::PointF edge_end_; + // The visible bounds of a selection, which are equal to the above, when there + // is no occlusion. + gfx::PointF visible_edge_start_; + gfx::PointF visible_edge_end_; + gfx::Point edge_start_rounded_; + gfx::Point edge_end_rounded_; + bool visible_; +}; + +GFX_EXPORT bool operator==(const SelectionBound& lhs, + const SelectionBound& rhs); +GFX_EXPORT bool operator!=(const SelectionBound& lhs, + const SelectionBound& rhs); + +GFX_EXPORT gfx::Rect RectBetweenSelectionBounds(const SelectionBound& b1, + const SelectionBound& b2); + +GFX_EXPORT gfx::RectF RectFBetweenSelectionBounds(const SelectionBound& b1, + const SelectionBound& b2); + +GFX_EXPORT gfx::RectF RectFBetweenVisibleSelectionBounds( + const SelectionBound& b1, + const SelectionBound& b2); +} // namespace ui + +#endif // UI_GFX_SELECTION_BOUND_H_ diff --git a/selection_bound_unittest.cc b/selection_bound_unittest.cc new file mode 100644 index 000000000000..224ebe358903 --- /dev/null +++ b/selection_bound_unittest.cc @@ -0,0 +1,103 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/selection_bound.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/rect_f.h" + +namespace gfx { + +TEST(SelectionBoundTest, RectBetweenSelectionBounds) { + SelectionBound b1, b2; + // Simple case of aligned vertical bounds of equal height + b1.SetEdge(gfx::PointF(0.f, 20.f), gfx::PointF(0.f, 25.f)); + b2.SetEdge(gfx::PointF(110.f, 20.f), gfx::PointF(110.f, 25.f)); + gfx::Rect expected_rect( + b1.edge_start_rounded().x(), b1.edge_start_rounded().y(), + b2.edge_start_rounded().x() - b1.edge_start_rounded().x(), + b2.edge_end_rounded().y() - b2.edge_start_rounded().y()); + EXPECT_EQ(expected_rect, RectBetweenSelectionBounds(b1, b2)); + EXPECT_EQ(expected_rect, RectBetweenSelectionBounds(b2, b1)); + + // Both bounds are invisible. + b1.SetVisibleEdge(gfx::PointF(10.f, 20.f), gfx::PointF(10.f, 25.f)); + b2.SetVisibleEdge(gfx::PointF(100.f, 20.f), gfx::PointF(100.f, 25.f)); + gfx::RectF expected_visible_rect( + b1.visible_edge_start().x(), b1.visible_edge_start().y(), + b2.visible_edge_start().x() - b1.visible_edge_start().x(), + b2.visible_edge_end().y() - b2.visible_edge_start().y()); + EXPECT_EQ(expected_visible_rect, RectFBetweenVisibleSelectionBounds(b1, b2)); + EXPECT_EQ(expected_visible_rect, RectFBetweenVisibleSelectionBounds(b2, b1)); + + // One of the bounds is invisible. + b1.SetVisibleEdge(gfx::PointF(0.f, 20.f), gfx::PointF(0.f, 25.f)); + b2.SetVisibleEdge(gfx::PointF(100.f, 20.f), gfx::PointF(100.f, 25.f)); + expected_visible_rect = + gfx::RectF(b1.visible_edge_start().x(), b1.visible_edge_start().y(), + b2.visible_edge_start().x() - b1.visible_edge_start().x(), + b2.visible_edge_end().y() - b2.visible_edge_start().y()); + EXPECT_EQ(expected_visible_rect, RectFBetweenVisibleSelectionBounds(b1, b2)); + EXPECT_EQ(expected_visible_rect, RectFBetweenVisibleSelectionBounds(b2, b1)); + + // Parallel vertical bounds of different heights + b1.SetEdge(gfx::PointF(10.f, 20.f), gfx::PointF(10.f, 25.f)); + b2.SetEdge(gfx::PointF(110.f, 0.f), gfx::PointF(110.f, 35.f)); + expected_rect = + gfx::Rect(b1.edge_start_rounded().x(), b2.edge_start_rounded().y(), + b2.edge_start_rounded().x() - b1.edge_start_rounded().x(), + b2.edge_end_rounded().y() - b2.edge_start_rounded().y()); + EXPECT_EQ(expected_rect, RectBetweenSelectionBounds(b1, b2)); + EXPECT_EQ(expected_rect, RectBetweenSelectionBounds(b2, b1)); + + b1.SetEdge(gfx::PointF(10.f, 20.f), gfx::PointF(10.f, 30.f)); + b2.SetEdge(gfx::PointF(110.f, 25.f), gfx::PointF(110.f, 45.f)); + expected_rect = + gfx::Rect(b1.edge_start_rounded().x(), b1.edge_start_rounded().y(), + b2.edge_start_rounded().x() - b1.edge_start_rounded().x(), + b2.edge_end_rounded().y() - b1.edge_start_rounded().y()); + EXPECT_EQ(expected_rect, RectBetweenSelectionBounds(b1, b2)); + EXPECT_EQ(expected_rect, RectBetweenSelectionBounds(b2, b1)); + + b1.SetEdge(gfx::PointF(10.f, 20.f), gfx::PointF(10.f, 30.f)); + b2.SetEdge(gfx::PointF(110.f, 40.f), gfx::PointF(110.f, 60.f)); + expected_rect = + gfx::Rect(b1.edge_start_rounded().x(), b1.edge_start_rounded().y(), + b2.edge_start_rounded().x() - b1.edge_start_rounded().x(), + b2.edge_end_rounded().y() - b1.edge_start_rounded().y()); + EXPECT_EQ(expected_rect, RectBetweenSelectionBounds(b1, b2)); + EXPECT_EQ(expected_rect, RectBetweenSelectionBounds(b2, b1)); + + // Overlapping vertical bounds + b1.SetEdge(gfx::PointF(10.f, 20.f), gfx::PointF(10.f, 30.f)); + b2.SetEdge(gfx::PointF(10.f, 25.f), gfx::PointF(10.f, 40.f)); + expected_rect = + gfx::Rect(b1.edge_start_rounded().x(), b1.edge_start_rounded().y(), 0, + b2.edge_end_rounded().y() - b1.edge_start_rounded().y()); + EXPECT_EQ(expected_rect, RectBetweenSelectionBounds(b1, b2)); + EXPECT_EQ(expected_rect, RectBetweenSelectionBounds(b2, b1)); + + // Non-vertical bounds: "\ \" + b1.SetEdge(gfx::PointF(10.f, 20.f), gfx::PointF(20.f, 30.f)); + b2.SetEdge(gfx::PointF(110.f, 40.f), gfx::PointF(120.f, 60.f)); + expected_rect = + gfx::Rect(b1.edge_start_rounded().x(), b1.edge_start_rounded().y(), + b2.edge_end_rounded().x() - b1.edge_start_rounded().x(), + b2.edge_end_rounded().y() - b1.edge_start_rounded().y()); + EXPECT_EQ(expected_rect, RectBetweenSelectionBounds(b1, b2)); + EXPECT_EQ(expected_rect, RectBetweenSelectionBounds(b2, b1)); + + // Non-vertical bounds: "/ \" + b1.SetEdge(gfx::PointF(20.f, 30.f), gfx::PointF(0.f, 40.f)); + b2.SetEdge(gfx::PointF(110.f, 30.f), gfx::PointF(120.f, 40.f)); + expected_rect = + gfx::Rect(b1.edge_end_rounded().x(), b1.edge_start_rounded().y(), + b2.edge_end_rounded().x() - b1.edge_end_rounded().x(), + b2.edge_end_rounded().y() - b2.edge_start_rounded().y()); + EXPECT_EQ(expected_rect, RectBetweenSelectionBounds(b1, b2)); + EXPECT_EQ(expected_rect, RectBetweenSelectionBounds(b2, b1)); +} + +} // namespace gfx diff --git a/selection_model.cc b/selection_model.cc new file mode 100644 index 000000000000..a792731f0fc2 --- /dev/null +++ b/selection_model.cc @@ -0,0 +1,81 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/selection_model.h" + +#include + +#include "base/check.h" +#include "base/format_macros.h" +#include "base/strings/stringprintf.h" + +namespace gfx { + +SelectionModel::SelectionModel() + : selection_(0), + caret_affinity_(CURSOR_BACKWARD) {} + +SelectionModel::SelectionModel(size_t position, LogicalCursorDirection affinity) + : selection_(position), + caret_affinity_(affinity) {} + +SelectionModel::SelectionModel(const Range& selection, + LogicalCursorDirection affinity) + : selection_(selection), + caret_affinity_(affinity) {} + +SelectionModel::SelectionModel(const std::vector& selections, + LogicalCursorDirection affinity) + : selection_(selections[0]), caret_affinity_(affinity) { + for (size_t i = 1; i < selections.size(); ++i) + AddSecondarySelection(selections[i]); +} + +SelectionModel::SelectionModel(const SelectionModel& selection_model) = default; + +SelectionModel::~SelectionModel() = default; + +void SelectionModel::AddSecondarySelection(const Range& selection) { + for (auto s : GetAllSelections()) + DCHECK(!selection.Intersects(s)); + secondary_selections_.push_back(selection); +} + +std::vector SelectionModel::GetAllSelections() const { + std::vector selections = {selection()}; + selections.insert(selections.end(), secondary_selections_.begin(), + secondary_selections_.end()); + return selections; +} + +bool SelectionModel::operator==(const SelectionModel& sel) const { + return selection() == sel.selection() && + caret_affinity() == sel.caret_affinity() && + secondary_selections() == sel.secondary_selections(); +} + +std::string SelectionModel::ToString() const { + std::string str = "{"; + if (selection().is_empty()) + base::StringAppendF(&str, "%" PRIuS, caret_pos()); + else + str += selection().ToString(); + const bool backward = caret_affinity() == CURSOR_BACKWARD; + str += (backward ? ",BACKWARD" : ",FORWARD"); + for (auto selection : secondary_selections()) { + str += ","; + if (selection.is_empty()) + base::StringAppendF(&str, "%" PRIu32, selection.end()); + else + str += selection.ToString(); + } + return str + "}"; +} + +std::ostream& operator<<(std::ostream& out, const SelectionModel& model) { + out << model.ToString(); + return out; +} + +} // namespace gfx diff --git a/selection_model.h b/selection_model.h new file mode 100644 index 000000000000..150b38ca129e --- /dev/null +++ b/selection_model.h @@ -0,0 +1,136 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_SELECTION_MODEL_H_ +#define UI_GFX_SELECTION_MODEL_H_ + +#include +#include + +#include +#include + +#include "ui/gfx/gfx_export.h" +#include "ui/gfx/range/range.h" + +namespace gfx { + +// VisualCursorDirection and LogicalCursorDirection represent directions of +// motion of the cursor in BiDi text. The combinations that make sense are: +// +// base::i18n::TextDirection VisualCursorDirection LogicalCursorDirection +// LEFT_TO_RIGHT CURSOR_LEFT CURSOR_BACKWARD +// LEFT_TO_RIGHT CURSOR_RIGHT CURSOR_FORWARD +// RIGHT_TO_LEFT CURSOR_RIGHT CURSOR_BACKWARD +// RIGHT_TO_LEFT CURSOR_LEFT CURSOR_FORWARD +enum VisualCursorDirection { + CURSOR_LEFT, + CURSOR_RIGHT, + CURSOR_UP, + CURSOR_DOWN +}; +enum LogicalCursorDirection { + CURSOR_BACKWARD, + CURSOR_FORWARD +}; + +// TODO(xji): publish bidi-editing guide line and replace the place holder. +// SelectionModel is used to represent the logical selection and visual +// position of cursor. +// +// For bi-directional text, the mapping between visual position and logical +// position is not one-to-one. For example, logical text "abcDEF" where capital +// letters stand for Hebrew, the visual display is "abcFED". According to the +// bidi editing guide (http://bidi-editing-guideline): +// 1. If pointing to the right half of the cell of a LTR character, the current +// position must be set after this character and the caret must be displayed +// after this character. +// 2. If pointing to the right half of the cell of a RTL character, the current +// position must be set before this character and the caret must be displayed +// before this character. +// +// Pointing to the right half of 'c' and pointing to the right half of 'D' both +// set the logical cursor position to 3. But the cursor displayed visually at +// different places: +// Pointing to the right half of 'c' displays the cursor right of 'c' as +// "abc|FED". +// Pointing to the right half of 'D' displays the cursor right of 'D' as +// "abcFED|". +// So, besides the logical selection start point and end point, we need extra +// information to specify to which character the visual cursor is bound. This +// is given by a "caret affinity" which is either CURSOR_BACKWARD (indicating +// the trailing half of the 'c' in this case) or CURSOR_FORWARD (indicating +// the leading half of the 'D'). +class GFX_EXPORT SelectionModel { + public: + // Create a default SelectionModel to be overwritten later. + SelectionModel(); + // Create a SelectionModel representing a caret |position| without a + // selection. The |affinity| is meaningful only when the caret is positioned + // between bidi runs that are not visually contiguous: in that case, it + // indicates the run to which the caret is attached for display purposes. + SelectionModel(size_t position, LogicalCursorDirection affinity); + // Create a SelectionModel representing a selection (which may be empty). + // The caret position is the end of the range. + SelectionModel(const Range& selection, LogicalCursorDirection affinity); + // Create a SelectionModel representing multiple selections (which may be + // empty but not overlapping). The end of the first range determines the caret + // position. + SelectionModel(const std::vector& selections, + LogicalCursorDirection affinity); + SelectionModel(const SelectionModel& selection_model); + ~SelectionModel(); + + // |selection| should overlap with neither |selection_| nor + // |secondary_selections_|. + void AddSecondarySelection(const Range& selection); + + const Range& selection() const { return selection_; } + size_t caret_pos() const { return selection_.end(); } + LogicalCursorDirection caret_affinity() const { return caret_affinity_; } + const std::vector& secondary_selections() const { + return secondary_selections_; + } + std::vector GetAllSelections() const; + + // WARNING: Generally the selection start should not be changed without + // considering the effect on the caret affinity. + void set_selection_start(uint32_t pos) { selection_.set_start(pos); } + + bool operator==(const SelectionModel& sel) const; + bool operator!=(const SelectionModel& sel) const { return !(*this == sel); } + + std::string ToString() const; + + private: + // Logical selection. The logical caret position is the end of the selection. + Range selection_; + // Secondary selections not associated with the cursor. Do not overlap. + std::vector secondary_selections_; + + // The logical direction from the caret position (selection_.end()) to the + // character it is attached to for display purposes. This matters only when + // the surrounding characters are not visually contiguous, which happens only + // in bidi text (and only at bidi run boundaries). The text is treated as + // though it was surrounded on both sides by runs in the dominant text + // direction. For example, supposing the dominant direction is LTR and the + // logical text is "abcDEF", where DEF is right-to-left text, the visual + // cursor will display as follows: + // caret position CURSOR_BACKWARD affinity CURSOR_FORWARD affinity + // 0 |abcFED |abcFED + // 1 a|bcFED a|bcFED + // 2 ab|cFED ab|cFED + // 3 abc|FED abcFED| + // 4 abcFE|D abcFE|D + // 5 abcF|ED abcF|ED + // 6 abc|FED abcFED| + LogicalCursorDirection caret_affinity_; +}; + +GFX_EXPORT std::ostream& operator<<(std::ostream& out, + const SelectionModel& model); + +} // namespace gfx + +#endif // UI_GFX_SELECTION_MODEL_H_ diff --git a/selection_model_unittest.cc b/selection_model_unittest.cc new file mode 100644 index 000000000000..2147b2d12e1d --- /dev/null +++ b/selection_model_unittest.cc @@ -0,0 +1,90 @@ +// Copyright (c) 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "ui/gfx/selection_model.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/range/range.h" + +namespace gfx { + +TEST(SelectionModelTest, Construction) { + { + SelectionModel selection_model; + EXPECT_EQ(selection_model.selection(), Range(0)); + EXPECT_EQ(selection_model.caret_pos(), 0u); + EXPECT_EQ(selection_model.secondary_selections(), std::vector()); + } + { + SelectionModel selection_model{5, CURSOR_FORWARD}; + EXPECT_EQ(selection_model.selection(), Range(5)); + EXPECT_EQ(selection_model.caret_pos(), 5u); + EXPECT_EQ(selection_model.secondary_selections(), std::vector()); + } + { + SelectionModel selection_model{{3, 2}, CURSOR_BACKWARD}; + EXPECT_EQ(selection_model.selection(), Range(3, 2)); + EXPECT_EQ(selection_model.caret_pos(), 2u); + EXPECT_EQ(selection_model.secondary_selections(), std::vector()); + } + { + SelectionModel selection_model{{{2, 3}, {5, 5}, {1, 0}}, CURSOR_BACKWARD}; + EXPECT_EQ(selection_model.selection(), Range(2, 3)); + EXPECT_EQ(selection_model.caret_pos(), 3u); + EXPECT_EQ(selection_model.secondary_selections(), + std::vector({{5, 5}, {1, 0}})); + } +} + +TEST(SelectionModelTest, AddSecondarySelection) { + SelectionModel selection_model; + selection_model.AddSecondarySelection({5, 6}); + selection_model.AddSecondarySelection({7, 6}); + selection_model.AddSecondarySelection({8, 8}); + EXPECT_EQ(selection_model.selection(), Range(0)); + EXPECT_EQ(selection_model.caret_pos(), 0u); + EXPECT_EQ(selection_model.secondary_selections(), + std::vector({{5, 6}, {7, 6}, {8, 8}})); +} + +TEST(SelectionModelTest, GetAllSelections) { + SelectionModel selection_model{{3, 2}, CURSOR_BACKWARD}; + selection_model.AddSecondarySelection({5, 6}); + selection_model.AddSecondarySelection({7, 6}); + selection_model.AddSecondarySelection({8, 8}); + EXPECT_EQ(selection_model.GetAllSelections(), + std::vector({{3, 2}, {5, 6}, {7, 6}, {8, 8}})); +} + +TEST(SelectionModelTest, EqualityOperators) { + SelectionModel selection_model{{3, 2}, CURSOR_BACKWARD}; + selection_model.AddSecondarySelection({5, 6}); + selection_model.AddSecondarySelection({7, 6}); + selection_model.AddSecondarySelection({8, 8}); + + // Equal + EXPECT_EQ(selection_model, + SelectionModel({{3, 2}, {5, 6}, {7, 6}, {8, 8}}, CURSOR_BACKWARD)); + // Unequal selection + EXPECT_NE(selection_model, + SelectionModel({{3, 3}, {5, 6}, {7, 6}, {8, 8}}, CURSOR_BACKWARD)); + // Unequal secondary selections + EXPECT_NE(selection_model, + SelectionModel({{3, 2}, {5, 6}, {7, 6}, {9, 8}}, CURSOR_BACKWARD)); + // Unequal cursor affinity + EXPECT_NE(selection_model, + SelectionModel({{3, 2}, {5, 6}, {7, 6}, {8, 8}}, CURSOR_FORWARD)); +} + +TEST(SelectionModelTest, ToString) { + SelectionModel selection_model{{3, 2}, CURSOR_BACKWARD}; + selection_model.AddSecondarySelection({5, 6}); + selection_model.AddSecondarySelection({7, 6}); + selection_model.AddSecondarySelection({8, 8}); + EXPECT_EQ(selection_model.ToString(), "{{3,2},BACKWARD,{5,6},{7,6},8}"); +} + +} // namespace gfx diff --git a/sequential_id_generator.cc b/sequential_id_generator.cc new file mode 100644 index 000000000000..d8e7efb68620 --- /dev/null +++ b/sequential_id_generator.cc @@ -0,0 +1,89 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/sequential_id_generator.h" + +#include "base/check_op.h" + +namespace { + +// Removes |key| from |first|, and |first[key]| from |second|. +template +void Remove(uint32_t key, T* first, T* second) { + auto iter = first->find(key); + if (iter == first->end()) + return; + + uint32_t second_key = iter->second; + first->erase(iter); + + iter = second->find(second_key); + DCHECK(iter != second->end()); + second->erase(iter); +} + +} // namespace + +namespace ui { + +SequentialIDGenerator::SequentialIDGenerator(uint32_t min_id) + : min_id_(min_id), min_available_id_(min_id) {} + +SequentialIDGenerator::~SequentialIDGenerator() { +} + +uint32_t SequentialIDGenerator::GetGeneratedID(uint32_t number) { + auto find = number_to_id_.find(number); + if (find != number_to_id_.end()) + return find->second; + + int id = GetNextAvailableID(); + number_to_id_.emplace(number, id); + id_to_number_.emplace(id, number); + return id; +} + +bool SequentialIDGenerator::HasGeneratedIDFor(uint32_t number) const { + return number_to_id_.find(number) != number_to_id_.end(); +} + +void SequentialIDGenerator::ReleaseNumber(uint32_t number) { + if (number_to_id_.count(number) > 0U) { + UpdateNextAvailableIDAfterRelease(number_to_id_[number]); + Remove(number, &number_to_id_, &id_to_number_); + } +} + +void SequentialIDGenerator::ReleaseID(uint32_t id) { + if (id_to_number_.count(id) > 0U) { + UpdateNextAvailableIDAfterRelease(id); + Remove(id_to_number_[id], &number_to_id_, &id_to_number_); + } +} + +void SequentialIDGenerator::ResetForTest() { + number_to_id_.clear(); + id_to_number_.clear(); + min_available_id_ = min_id_; +} + +uint32_t SequentialIDGenerator::GetNextAvailableID() { + const uint32_t kMaxID = 128; + while (id_to_number_.count(min_available_id_) > 0 && + min_available_id_ < kMaxID) { + ++min_available_id_; + } + if (min_available_id_ >= kMaxID) + min_available_id_ = min_id_; + return min_available_id_; +} + +void SequentialIDGenerator::UpdateNextAvailableIDAfterRelease(uint32_t id) { + if (id < min_available_id_) { + min_available_id_ = id; + DCHECK_GE(min_available_id_, min_id_); + } +} + +} // namespace ui diff --git a/sequential_id_generator.h b/sequential_id_generator.h new file mode 100644 index 000000000000..bc24e2c1fd84 --- /dev/null +++ b/sequential_id_generator.h @@ -0,0 +1,65 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_SEQUENTIAL_ID_GENERATOR_H_ +#define UI_GFX_SEQUENTIAL_ID_GENERATOR_H_ + +#include + +#include +#include + +#include "base/macros.h" +#include "ui/gfx/gfx_export.h" + +namespace ui { + +// This is used to generate a series of sequential ID numbers in a way that a +// new ID is always the lowest possible ID in the sequence. +class GFX_EXPORT SequentialIDGenerator { + public: + // Creates a new generator with the specified lower bound for the IDs. + explicit SequentialIDGenerator(uint32_t min_id); + + SequentialIDGenerator(const SequentialIDGenerator&) = delete; + SequentialIDGenerator& operator=(const SequentialIDGenerator&) = delete; + + ~SequentialIDGenerator(); + + // Generates a unique ID to represent |number|. The generated ID is the + // smallest available ID greater than or equal to the |min_id| specified + // during creation of the generator. + uint32_t GetGeneratedID(uint32_t number); + + // Checks to see if the generator currently has a unique ID generated for + // |number|. + bool HasGeneratedIDFor(uint32_t number) const; + + // Removes the ID previously generated for |number| by calling + // |GetGeneratedID()| - does nothing if the number is not mapped. + void ReleaseNumber(uint32_t number); + + // Releases ID previously generated by calling |GetGeneratedID()|. Does + // nothing if the ID is not mapped. + void ReleaseID(uint32_t id); + + void ResetForTest(); + + private: + typedef std::unordered_map IDMap; + + uint32_t GetNextAvailableID(); + + void UpdateNextAvailableIDAfterRelease(uint32_t id); + + IDMap number_to_id_; + IDMap id_to_number_; + + const uint32_t min_id_; + uint32_t min_available_id_; +}; + +} // namespace ui + +#endif // UI_GFX_SEQUENTIAL_ID_GENERATOR_H_ diff --git a/sequential_id_generator_unittest.cc b/sequential_id_generator_unittest.cc new file mode 100644 index 000000000000..f36aa984023a --- /dev/null +++ b/sequential_id_generator_unittest.cc @@ -0,0 +1,47 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/sequential_id_generator.h" + +#include + +#include "testing/gtest/include/gtest/gtest.h" + +namespace ui { + +typedef testing::Test SequentialIDGeneratorTest; + +TEST(SequentialIDGeneratorTest, RemoveMultipleNumbers) { + const uint32_t kMinID = 4; + SequentialIDGenerator generator(kMinID); + + EXPECT_EQ(4U, generator.GetGeneratedID(45)); + EXPECT_EQ(5U, generator.GetGeneratedID(55)); + EXPECT_EQ(6U, generator.GetGeneratedID(15)); + + generator.ReleaseNumber(45); + EXPECT_FALSE(generator.HasGeneratedIDFor(45)); + generator.ReleaseNumber(15); + EXPECT_FALSE(generator.HasGeneratedIDFor(15)); + + EXPECT_EQ(5U, generator.GetGeneratedID(55)); + EXPECT_EQ(4U, generator.GetGeneratedID(12)); + + generator.ReleaseNumber(12); + generator.ReleaseNumber(55); + EXPECT_EQ(4U, generator.GetGeneratedID(0)); +} + +TEST(SequentialIDGeneratorTest, MaybeRemoveNumbers) { + const uint32_t kMinID = 0; + SequentialIDGenerator generator(kMinID); + + EXPECT_EQ(0U, generator.GetGeneratedID(42)); + + generator.ReleaseNumber(42); + EXPECT_FALSE(generator.HasGeneratedIDFor(42)); + generator.ReleaseNumber(42); +} + +} // namespace ui diff --git a/shadow_util.cc b/shadow_util.cc new file mode 100644 index 000000000000..04b7762fa072 --- /dev/null +++ b/shadow_util.cc @@ -0,0 +1,105 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/shadow_util.h" + +#include +#include + +#include "base/lazy_instance.h" +#include "base/memory/ptr_util.h" +#include "third_party/skia/include/core/SkDrawLooper.h" +#include "third_party/skia/include/core/SkRRect.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/geometry/insets.h" +#include "ui/gfx/geometry/skia_conversions.h" +#include "ui/gfx/image/canvas_image_source.h" +#include "ui/gfx/shadow_value.h" +#include "ui/gfx/skia_paint_util.h" + +namespace gfx { +namespace { + +// Creates an image with the given shadows painted around a round rect with +// the given corner radius. The image will be just large enough to paint the +// shadows appropriately with a 1px square region reserved for "content". +class ShadowNineboxSource : public CanvasImageSource { + public: + ShadowNineboxSource(const std::vector& shadows, + float corner_radius) + : CanvasImageSource(CalculateSize(shadows, corner_radius)), + shadows_(shadows), + corner_radius_(corner_radius) { + DCHECK(!shadows.empty()); + } + + ShadowNineboxSource(const ShadowNineboxSource&) = delete; + ShadowNineboxSource& operator=(const ShadowNineboxSource&) = delete; + + ~ShadowNineboxSource() override {} + + // CanvasImageSource overrides: + void Draw(Canvas* canvas) override { + cc::PaintFlags flags; + flags.setLooper(CreateShadowDrawLooper(shadows_)); + Insets insets = -ShadowValue::GetMargin(shadows_); + gfx::Rect bounds(size()); + bounds.Inset(insets); + SkRRect r_rect = SkRRect::MakeRectXY(gfx::RectToSkRect(bounds), + corner_radius_, corner_radius_); + + // Clip out the center so it's not painted with the shadow. + canvas->sk_canvas()->clipRRect(r_rect, SkClipOp::kDifference, true); + // Clipping alone is not enough --- due to anti aliasing there will still be + // some of the fill color in the rounded corners. We must make the fill + // color transparent. + flags.setColor(SK_ColorTRANSPARENT); + canvas->sk_canvas()->drawRRect(r_rect, flags); + } + + private: + static Size CalculateSize(const std::vector& shadows, + float corner_radius) { + // The "content" area (the middle tile in the 3x3 grid) is a single pixel. + gfx::Rect bounds(0, 0, 1, 1); + // We need enough space to render the full range of blur. + bounds.Inset(-ShadowValue::GetBlurRegion(shadows)); + // We also need space for the full roundrect corner rounding. + bounds.Inset(-gfx::Insets(corner_radius)); + return bounds.size(); + } + + const std::vector shadows_; + + const float corner_radius_; +}; + +// Map from elevation/corner radius pair to a cached shadow. +using ShadowDetailsMap = std::map, ShadowDetails>; +base::LazyInstance::DestructorAtExit g_shadow_cache = + LAZY_INSTANCE_INITIALIZER; + +} // namespace + +ShadowDetails::ShadowDetails() {} +ShadowDetails::ShadowDetails(const ShadowDetails& other) = default; +ShadowDetails::~ShadowDetails() {} + +const ShadowDetails& ShadowDetails::Get(int elevation, int corner_radius) { + auto iter = + g_shadow_cache.Get().find(std::make_pair(elevation, corner_radius)); + if (iter != g_shadow_cache.Get().end()) + return iter->second; + + auto insertion = g_shadow_cache.Get().emplace( + std::make_pair(elevation, corner_radius), ShadowDetails()); + DCHECK(insertion.second); + ShadowDetails* shadow = &insertion.first->second; + shadow->values = ShadowValue::MakeMdShadowValues(elevation); + auto* source = new ShadowNineboxSource(shadow->values, corner_radius); + shadow->ninebox_image = ImageSkia(base::WrapUnique(source), source->size()); + return *shadow; +} + +} // namespace gfx diff --git a/shadow_util.h b/shadow_util.h new file mode 100644 index 000000000000..8f8759d29d6b --- /dev/null +++ b/shadow_util.h @@ -0,0 +1,33 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_SHADOW_UTIL_H_ +#define UI_GFX_SHADOW_UTIL_H_ + +#include "ui/gfx/gfx_export.h" +#include "ui/gfx/image/image_skia.h" +#include "ui/gfx/shadow_value.h" + +namespace gfx { + +// A struct that describes a vector of shadows and their depiction as an image +// suitable for ninebox tiling. +struct GFX_EXPORT ShadowDetails { + ShadowDetails(); + ShadowDetails(const ShadowDetails& other); + ~ShadowDetails(); + + // Returns a cached ShadowDetails for the given elevation (which controls + // style) and corner radius. Creates the ShadowDetails first if necessary. + static const ShadowDetails& Get(int elevation, int radius); + + // Description of the shadows. + gfx::ShadowValues values; + // Cached ninebox image based on |values|. + gfx::ImageSkia ninebox_image; +}; + +} // namespace gfx + +#endif // UI_GFX_SHADOW_UTIL_H_ diff --git a/shadow_value.cc b/shadow_value.cc new file mode 100644 index 000000000000..d71ecfd78d0c --- /dev/null +++ b/shadow_value.cc @@ -0,0 +1,128 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/shadow_value.h" + +#include + +#include + +#include "base/check_op.h" +#include "base/numerics/safe_conversions.h" +#include "base/strings/stringprintf.h" +#include "ui/gfx/color_palette.h" +#include "ui/gfx/geometry/insets.h" +#include "ui/gfx/geometry/vector2d_conversions.h" + +namespace gfx { + +namespace { + +Insets GetInsets(const ShadowValues& shadows, bool include_inner_blur) { + int left = 0; + int top = 0; + int right = 0; + int bottom = 0; + + for (size_t i = 0; i < shadows.size(); ++i) { + const ShadowValue& shadow = shadows[i]; + + double blur = shadow.blur(); + if (!include_inner_blur) + blur /= 2; + int blur_length = base::ClampRound(blur); + + left = std::max(left, blur_length - shadow.x()); + top = std::max(top, blur_length - shadow.y()); + right = std::max(right, blur_length + shadow.x()); + bottom = std::max(bottom, blur_length + shadow.y()); + } + + return Insets(top, left, bottom, right); +} + +} // namespace + +ShadowValue ShadowValue::Scale(float scale) const { + Vector2d scaled_offset = ToFlooredVector2d(ScaleVector2d(offset_, scale)); + return ShadowValue(scaled_offset, blur_ * scale, color_); +} + +std::string ShadowValue::ToString() const { + return base::StringPrintf( + "(%d,%d),%.2f,rgba(%d,%d,%d,%d)", + offset_.x(), offset_.y(), + blur_, + SkColorGetR(color_), + SkColorGetG(color_), + SkColorGetB(color_), + SkColorGetA(color_)); +} + +// static +Insets ShadowValue::GetMargin(const ShadowValues& shadows) { + Insets margins = GetInsets(shadows, false); + return -margins; +} + +// static +Insets ShadowValue::GetBlurRegion(const ShadowValues& shadows) { + return GetInsets(shadows, true); +} + +// static +ShadowValues ShadowValue::MakeShadowValues(int elevation, + SkColor key_shadow_color, + SkColor ambient_shadow_color) { + // Refresh uses hand-tweaked shadows corresponding to a small set of + // elevations. Use the Refresh spec and designer input to add missing shadow + // values. + + // To match the CSS notion of blur (spread outside the bounding box) to the + // Skia notion of blur (spread outside and inside the bounding box), we have + // to double the designer-provided blur values. + const int kBlurCorrection = 2; + + switch (elevation) { + case 3: { + ShadowValue key = {Vector2d(0, 1), 12, key_shadow_color}; + ShadowValue ambient = {Vector2d(0, 4), 64, ambient_shadow_color}; + return {key, ambient}; + } + case 16: { + ShadowValue key = {Vector2d(0, 0), kBlurCorrection * 16, + key_shadow_color}; + ShadowValue ambient = {Vector2d(0, 12), kBlurCorrection * 16, + ambient_shadow_color}; + return {key, ambient}; + } + default: + // This surface has not been updated for Refresh. Fall back to the + // deprecated style. + DCHECK_EQ(key_shadow_color, ambient_shadow_color); + return MakeMdShadowValues(elevation, key_shadow_color); + } +} + +// static +ShadowValues ShadowValue::MakeMdShadowValues(int elevation, SkColor color) { + ShadowValues shadow_values; + // To match the CSS notion of blur (spread outside the bounding box) to the + // Skia notion of blur (spread outside and inside the bounding box), we have + // to double the designer-provided blur values. + const int kBlurCorrection = 2; + // "Key shadow": y offset is elevation and blur is twice the elevation. + shadow_values.emplace_back(Vector2d(0, elevation), + kBlurCorrection * elevation * 2, + SkColorSetA(color, 0x3d)); + // "Ambient shadow": no offset and blur matches the elevation. + shadow_values.emplace_back(Vector2d(), kBlurCorrection * elevation, + SkColorSetA(color, 0x1f)); + // To see what this looks like for elevation 24, try this CSS: + // box-shadow: 0 24px 48px rgba(0, 0, 0, .24), + // 0 0 24px rgba(0, 0, 0, .12); + return shadow_values; +} + +} // namespace gfx diff --git a/shadow_value.h b/shadow_value.h new file mode 100644 index 000000000000..fdb8175ed384 --- /dev/null +++ b/shadow_value.h @@ -0,0 +1,88 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_SHADOW_VALUE_H_ +#define UI_GFX_SHADOW_VALUE_H_ + +#include +#include +#include + +#include "third_party/skia/include/core/SkColor.h" +#include "ui/gfx/geometry/vector2d.h" +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +class Insets; + +class ShadowValue; +typedef std::vector ShadowValues; + +// ShadowValue encapsulates parameters needed to define a shadow, including the +// shadow's offset, blur amount and color. +class GFX_EXPORT ShadowValue { + public: + constexpr ShadowValue() = default; + constexpr ShadowValue(const gfx::Vector2d& offset, double blur, SkColor color) + : offset_(offset), blur_(blur), color_(color) {} + + constexpr int x() const { return offset_.x(); } + constexpr int y() const { return offset_.y(); } + constexpr const gfx::Vector2d& offset() const { return offset_; } + constexpr double blur() const { return blur_; } + constexpr SkColor color() const { return color_; } + + constexpr bool operator==(const ShadowValue& other) const { + return offset_ == other.offset_ && blur_ == other.blur_ && + color_ == other.color_; + } + + ShadowValue Scale(float scale) const; + + std::string ToString() const; + + // Gets margin space needed for shadows. Note that values in returned Insets + // are negative because shadow margins are outside a boundary. + static Insets GetMargin(const ShadowValues& shadows); + + // Gets the area inside a rectangle that would be affected by shadow blur. + // This is similar to the margin except it's positive (the blur region is + // inside a hypothetical rectangle) and it accounts for the blur both inside + // and outside the bounding box. The region inside the "blur region" would be + // a uniform color. + static Insets GetBlurRegion(const ShadowValues& shadows); + + // Makes ShadowValues for the given elevation and color. Calls to + // MakeShadowValues that expect to fallback to MakeMdShadowValues should pass + // in the same base color for |key_shadow_color| and |ambient_shadow_color| + // until MakeMdShadowValues is refactored to remove SkColorSetA calls and also + // take in its own |key_shadow_color| and |ambient_shadow_color|. + // TODO(elainechien): crbug.com/1056950. + static ShadowValues MakeShadowValues(int elevation, + SkColor key_shadow_color, + SkColor ambient_shadow_color); + // Makes ShadowValues for MD shadows. This style is deprecated. + static ShadowValues MakeMdShadowValues(int elevation, + SkColor color = SK_ColorBLACK); + + private: + gfx::Vector2d offset_; + + // Blur amount of the shadow in pixels. If underlying implementation supports + // (e.g. Skia), it can have fraction part such as 0.5 pixel. The value + // defines a range from full shadow color at the start point inside the + // shadow to fully transparent at the end point outside it. The range is + // perpendicular to and centered on the shadow edge. For example, a blur + // amount of 4.0 means to have a blurry shadow edge of 4 pixels that + // transitions from full shadow color to fully transparent and with 2 pixels + // inside the shadow and 2 pixels goes beyond the edge. + double blur_ = 0.; + + SkColor color_ = SK_ColorTRANSPARENT; +}; + +} // namespace gfx + +#endif // UI_GFX_SHADOW_VALUE_H_ diff --git a/shadow_value_unittest.cc b/shadow_value_unittest.cc new file mode 100644 index 000000000000..713b4733ceee --- /dev/null +++ b/shadow_value_unittest.cc @@ -0,0 +1,70 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/cxx17_backports.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/geometry/insets.h" +#include "ui/gfx/geometry/vector2d.h" +#include "ui/gfx/shadow_value.h" + +namespace gfx { + +TEST(ShadowValueTest, GetMargin) { + constexpr struct TestCase { + Insets expected_margin; + size_t shadow_count; + ShadowValue shadows[2]; + } kTestCases[] = { + { + Insets(), 0, {}, + }, + { + Insets(-2, -2, -2, -2), + 1, + { + {gfx::Vector2d(0, 0), 4, 0}, + }, + }, + { + Insets(0, -1, -4, -3), + 1, + { + {gfx::Vector2d(1, 2), 4, 0}, + }, + }, + { + Insets(-4, -3, 0, -1), + 1, + { + {gfx::Vector2d(-1, -2), 4, 0}, + }, + }, + { + Insets(0, -1, -5, -4), + 2, + { + {gfx::Vector2d(1, 2), 4, 0}, {gfx::Vector2d(2, 3), 4, 0}, + }, + }, + { + Insets(-4, -3, -5, -4), + 2, + { + {gfx::Vector2d(-1, -2), 4, 0}, {gfx::Vector2d(2, 3), 4, 0}, + }, + }, + }; + + for (size_t i = 0; i < base::size(kTestCases); ++i) { + Insets margin = ShadowValue::GetMargin( + ShadowValues(kTestCases[i].shadows, + kTestCases[i].shadows + kTestCases[i].shadow_count)); + + EXPECT_EQ(kTestCases[i].expected_margin, margin) << " i=" << i; + } +} + +} // namespace gfx diff --git a/skbitmap_operations.cc b/skbitmap_operations.cc new file mode 100644 index 000000000000..9bcbd2efbb25 --- /dev/null +++ b/skbitmap_operations.cc @@ -0,0 +1,781 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/skbitmap_operations.h" + +#include +#include +#include +#include + +#include "base/check_op.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "third_party/skia/include/core/SkColorFilter.h" +#include "third_party/skia/include/core/SkColorPriv.h" +#include "third_party/skia/include/core/SkUnPreMultiply.h" +#include "third_party/skia/include/effects/SkImageFilters.h" +#include "ui/gfx/geometry/insets.h" +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/size.h" + +static bool IsUninitializedBitmap(const SkBitmap& bitmap) { + return bitmap.isNull() && bitmap.colorType() == kUnknown_SkColorType && + bitmap.alphaType() == kUnknown_SkAlphaType; +} + +// static +SkBitmap SkBitmapOperations::CreateInvertedBitmap(const SkBitmap& image) { + if (IsUninitializedBitmap(image)) + return image; + CHECK_EQ(image.colorType(), kN32_SkColorType); + + SkBitmap inverted; + inverted.allocN32Pixels(image.width(), image.height()); + + for (int y = 0; y < image.height(); ++y) { + uint32_t* image_row = image.getAddr32(0, y); + uint32_t* dst_row = inverted.getAddr32(0, y); + + for (int x = 0; x < image.width(); ++x) { + uint32_t image_pixel = image_row[x]; + dst_row[x] = (image_pixel & 0xFF000000) | + (0x00FFFFFF - (image_pixel & 0x00FFFFFF)); + } + } + + return inverted; +} + +// static +SkBitmap SkBitmapOperations::CreateBlendedBitmap(const SkBitmap& first, + const SkBitmap& second, + double alpha) { + DCHECK((alpha >= 0) && (alpha <= 1)); + CHECK_EQ(first.width(), second.width()); + CHECK_EQ(first.height(), second.height()); + CHECK_EQ(first.colorType(), kN32_SkColorType); + CHECK_EQ(second.colorType(), kN32_SkColorType); + + // Optimize for case where we won't need to blend anything. + static const double alpha_min = 1.0 / 255; + static const double alpha_max = 254.0 / 255; + if (alpha < alpha_min) + return first; + else if (alpha > alpha_max) + return second; + + SkBitmap blended; + blended.allocN32Pixels(first.width(), first.height()); + + double first_alpha = 1 - alpha; + + for (int y = 0; y < first.height(); ++y) { + uint32_t* first_row = first.getAddr32(0, y); + uint32_t* second_row = second.getAddr32(0, y); + uint32_t* dst_row = blended.getAddr32(0, y); + + for (int x = 0; x < first.width(); ++x) { + uint32_t first_pixel = first_row[x]; + uint32_t second_pixel = second_row[x]; + + int a = static_cast((SkColorGetA(first_pixel) * first_alpha) + + (SkColorGetA(second_pixel) * alpha)); + int r = static_cast((SkColorGetR(first_pixel) * first_alpha) + + (SkColorGetR(second_pixel) * alpha)); + int g = static_cast((SkColorGetG(first_pixel) * first_alpha) + + (SkColorGetG(second_pixel) * alpha)); + int b = static_cast((SkColorGetB(first_pixel) * first_alpha) + + (SkColorGetB(second_pixel) * alpha)); + + dst_row[x] = SkColorSetARGB(a, r, g, b); + } + } + + return blended; +} + +// static +SkBitmap SkBitmapOperations::CreateMaskedBitmap(const SkBitmap& rgb, + const SkBitmap& alpha) { + CHECK_EQ(rgb.width(), alpha.width()); + CHECK_EQ(rgb.height(), alpha.height()); + CHECK_EQ(rgb.colorType(), kN32_SkColorType); + CHECK_EQ(alpha.colorType(), kN32_SkColorType); + + SkBitmap masked; + masked.allocN32Pixels(rgb.width(), rgb.height()); + + for (int y = 0; y < masked.height(); ++y) { + uint32_t* rgb_row = rgb.getAddr32(0, y); + uint32_t* alpha_row = alpha.getAddr32(0, y); + uint32_t* dst_row = masked.getAddr32(0, y); + + for (int x = 0; x < masked.width(); ++x) { + unsigned alpha32 = SkGetPackedA32(alpha_row[x]); + unsigned scale = SkAlpha255To256(alpha32); + dst_row[x] = SkAlphaMulQ(rgb_row[x], scale); + } + } + + return masked; +} + +// static +SkBitmap SkBitmapOperations::CreateButtonBackground(SkColor color, + const SkBitmap& image, + const SkBitmap& mask) { + CHECK_EQ(image.colorType(), kN32_SkColorType); + CHECK_EQ(mask.colorType(), kN32_SkColorType); + + SkBitmap background; + background.allocN32Pixels(mask.width(), mask.height()); + + double bg_a = SkColorGetA(color); + double bg_r = SkColorGetR(color) * (bg_a / 255.0); + double bg_g = SkColorGetG(color) * (bg_a / 255.0); + double bg_b = SkColorGetB(color) * (bg_a / 255.0); + + for (int y = 0; y < mask.height(); ++y) { + uint32_t* dst_row = background.getAddr32(0, y); + uint32_t* image_row = image.getAddr32(0, y % image.height()); + uint32_t* mask_row = mask.getAddr32(0, y); + + for (int x = 0; x < mask.width(); ++x) { + uint32_t image_pixel = image_row[x % image.width()]; + + double img_a = SkColorGetA(image_pixel); + double img_r = SkColorGetR(image_pixel); + double img_g = SkColorGetG(image_pixel); + double img_b = SkColorGetB(image_pixel); + + double img_alpha = img_a / 255.0; + double img_inv = 1 - img_alpha; + + double mask_a = static_cast(SkColorGetA(mask_row[x])) / 255.0; + + dst_row[x] = SkColorSetARGB( + // This is pretty weird; why not the usual SrcOver alpha? + static_cast(std::min(255.0, bg_a + img_a) * mask_a), + static_cast(((bg_r * img_inv) + (img_r * img_alpha)) * mask_a), + static_cast(((bg_g * img_inv) + (img_g * img_alpha)) * mask_a), + static_cast(((bg_b * img_inv) + (img_b * img_alpha)) * mask_a)); + } + } + + return background; +} + +namespace { +namespace HSLShift { + +// TODO(viettrungluu): Some things have yet to be optimized at all. + +// Notes on and conventions used in the following code +// +// Conventions: +// - R, G, B, A = obvious; as variables: |r|, |g|, |b|, |a| (see also below) +// - H, S, L = obvious; as variables: |h|, |s|, |l| (see also below) +// - variables derived from S, L shift parameters: |sdec| and |sinc| for S +// increase and decrease factors, |ldec| and |linc| for L (see also below) +// +// To try to optimize HSL shifts, we do several things: +// - Avoid unpremultiplying (then processing) then premultiplying. This means +// that R, G, B values (and also L, but not H and S) should be treated as +// having a range of 0..A (where A is alpha). +// - Do things in integer/fixed-point. This avoids costly conversions between +// floating-point and integer, though I should study the tradeoff more +// carefully (presumably, at some point of processing complexity, converting +// and processing using simpler floating-point code will begin to win in +// performance). Also to be studied is the speed/type of floating point +// conversions; see, e.g., . +// +// Conventions for fixed-point arithmetic +// - Each function has a constant denominator (called |den|, which should be a +// power of 2), appropriate for the computations done in that function. +// - A value |x| is then typically represented by a numerator, named |x_num|, +// so that its actual value is |x_num / den| (casting to floating-point +// before division). +// - To obtain |x_num| from |x|, simply multiply by |den|, i.e., |x_num = x * +// den| (casting appropriately). +// - When necessary, a value |x| may also be represented as a numerator over +// the denominator squared (set |den2 = den * den|). In such a case, the +// corresponding variable is called |x_num2| (so that its actual value is +// |x_num^2 / den2|. +// - The representation of the product of |x| and |y| is be called |x_y_num| if +// |x * y == x_y_num / den|, and |xy_num2| if |x * y == x_y_num2 / den2|. In +// the latter case, notice that one can calculate |x_y_num2 = x_num * y_num|. + +// Routine used to process a line; typically specialized for specific kinds of +// HSL shifts (to optimize). +typedef void (*LineProcessor)(const color_utils::HSL&, + const SkPMColor*, + SkPMColor*, + int width); + +enum OperationOnH { kOpHNone = 0, kOpHShift, kNumHOps }; +enum OperationOnS { kOpSNone = 0, kOpSDec, kOpSInc, kNumSOps }; +enum OperationOnL { kOpLNone = 0, kOpLDec, kOpLInc, kNumLOps }; + +// Epsilon used to judge when shift values are close enough to various critical +// values (typically 0.5, which yields a no-op for S and L shifts. 1/256 should +// be small enough, but let's play it safe> +const double epsilon = 0.0005; + +// Line processor: default/universal (i.e., old-school). +void LineProcDefault(const color_utils::HSL& hsl_shift, + const SkPMColor* in, + SkPMColor* out, + int width) { + for (int x = 0; x < width; x++) { + out[x] = SkPreMultiplyColor(color_utils::HSLShift( + SkUnPreMultiply::PMColorToColor(in[x]), hsl_shift)); + } +} + +// Line processor: no-op (i.e., copy). +void LineProcCopy(const color_utils::HSL& hsl_shift, + const SkPMColor* in, + SkPMColor* out, + int width) { + DCHECK(hsl_shift.h < 0); + DCHECK(hsl_shift.s < 0 || fabs(hsl_shift.s - 0.5) < HSLShift::epsilon); + DCHECK(hsl_shift.l < 0 || fabs(hsl_shift.l - 0.5) < HSLShift::epsilon); + memcpy(out, in, static_cast(width) * sizeof(out[0])); +} + +// Line processor: H no-op, S no-op, L decrease. +void LineProcHnopSnopLdec(const color_utils::HSL& hsl_shift, + const SkPMColor* in, + SkPMColor* out, + int width) { + const uint32_t den = 65536; + + DCHECK(hsl_shift.h < 0); + DCHECK(hsl_shift.s < 0 || fabs(hsl_shift.s - 0.5) < HSLShift::epsilon); + DCHECK(hsl_shift.l <= 0.5 - HSLShift::epsilon && hsl_shift.l >= 0); + + uint32_t ldec_num = static_cast(hsl_shift.l * 2 * den); + for (int x = 0; x < width; x++) { + uint32_t a = SkGetPackedA32(in[x]); + uint32_t r = SkGetPackedR32(in[x]); + uint32_t g = SkGetPackedG32(in[x]); + uint32_t b = SkGetPackedB32(in[x]); + r = r * ldec_num / den; + g = g * ldec_num / den; + b = b * ldec_num / den; + out[x] = SkPackARGB32(a, r, g, b); + } +} + +// Line processor: H no-op, S no-op, L increase. +void LineProcHnopSnopLinc(const color_utils::HSL& hsl_shift, + const SkPMColor* in, + SkPMColor* out, + int width) { + const uint32_t den = 65536; + + DCHECK(hsl_shift.h < 0); + DCHECK(hsl_shift.s < 0 || fabs(hsl_shift.s - 0.5) < HSLShift::epsilon); + DCHECK(hsl_shift.l >= 0.5 + HSLShift::epsilon && hsl_shift.l <= 1); + + uint32_t linc_num = static_cast((hsl_shift.l - 0.5) * 2 * den); + for (int x = 0; x < width; x++) { + uint32_t a = SkGetPackedA32(in[x]); + uint32_t r = SkGetPackedR32(in[x]); + uint32_t g = SkGetPackedG32(in[x]); + uint32_t b = SkGetPackedB32(in[x]); + r += (a - r) * linc_num / den; + g += (a - g) * linc_num / den; + b += (a - b) * linc_num / den; + out[x] = SkPackARGB32(a, r, g, b); + } +} + +// Saturation changes modifications in RGB +// +// (Note that as a further complication, the values we deal in are +// premultiplied, so R/G/B values must be in the range 0..A. For mathematical +// purposes, one may as well use r=R/A, g=G/A, b=B/A. Without loss of +// generality, assume that R/G/B values are in the range 0..1.) +// +// Let Max = max(R,G,B), Min = min(R,G,B), and Med be the median value. Then L = +// (Max+Min)/2. If L is to remain constant, Max+Min must also remain constant. +// +// For H to remain constant, first, the (numerical) order of R/G/B (from +// smallest to largest) must remain the same. Second, all the ratios +// (R-G)/(Max-Min), (R-B)/(Max-Min), (G-B)/(Max-Min) must remain constant (of +// course, if Max = Min, then S = 0 and no saturation change is well-defined, +// since H is not well-defined). +// +// Let C_max be a colour with value Max, C_min be one with value Min, and C_med +// the remaining colour. Increasing saturation (to the maximum) is accomplished +// by increasing the value of C_max while simultaneously decreasing C_min and +// changing C_med so that the ratios are maintained; for the latter, it suffices +// to keep (C_med-C_min)/(C_max-C_min) constant (and equal to +// (Med-Min)/(Max-Min)). + +// Line processor: H no-op, S decrease, L no-op. +void LineProcHnopSdecLnop(const color_utils::HSL& hsl_shift, + const SkPMColor* in, + SkPMColor* out, + int width) { + DCHECK(hsl_shift.h < 0); + DCHECK(hsl_shift.s >= 0 && hsl_shift.s <= 0.5 - HSLShift::epsilon); + DCHECK(hsl_shift.l < 0 || fabs(hsl_shift.l - 0.5) < HSLShift::epsilon); + + const int32_t denom = 65536; + int32_t s_numer = static_cast(hsl_shift.s * 2 * denom); + for (int x = 0; x < width; x++) { + int32_t a = static_cast(SkGetPackedA32(in[x])); + int32_t r = static_cast(SkGetPackedR32(in[x])); + int32_t g = static_cast(SkGetPackedG32(in[x])); + int32_t b = static_cast(SkGetPackedB32(in[x])); + + int32_t vmax, vmin; + if (r > g) { // This uses 3 compares rather than 4. + vmax = std::max(r, b); + vmin = std::min(g, b); + } else { + vmax = std::max(g, b); + vmin = std::min(r, b); + } + + // Use denom * L to avoid rounding. + int32_t denom_l = (vmax + vmin) * (denom / 2); + int32_t s_numer_l = (vmax + vmin) * s_numer / 2; + + r = (denom_l + r * s_numer - s_numer_l) / denom; + g = (denom_l + g * s_numer - s_numer_l) / denom; + b = (denom_l + b * s_numer - s_numer_l) / denom; + out[x] = SkPackARGB32(a, r, g, b); + } +} + +// Line processor: H no-op, S decrease, L decrease. +void LineProcHnopSdecLdec(const color_utils::HSL& hsl_shift, + const SkPMColor* in, + SkPMColor* out, + int width) { + DCHECK(hsl_shift.h < 0); + DCHECK(hsl_shift.s >= 0 && hsl_shift.s <= 0.5 - HSLShift::epsilon); + DCHECK(hsl_shift.l >= 0 && hsl_shift.l <= 0.5 - HSLShift::epsilon); + + // Can't be too big since we need room for denom*denom and a bit for sign. + const int32_t denom = 1024; + int32_t l_numer = static_cast(hsl_shift.l * 2 * denom); + int32_t s_numer = static_cast(hsl_shift.s * 2 * denom); + for (int x = 0; x < width; x++) { + int32_t a = static_cast(SkGetPackedA32(in[x])); + int32_t r = static_cast(SkGetPackedR32(in[x])); + int32_t g = static_cast(SkGetPackedG32(in[x])); + int32_t b = static_cast(SkGetPackedB32(in[x])); + + int32_t vmax, vmin; + if (r > g) { // This uses 3 compares rather than 4. + vmax = std::max(r, b); + vmin = std::min(g, b); + } else { + vmax = std::max(g, b); + vmin = std::min(r, b); + } + + // Use denom * L to avoid rounding. + int32_t denom_l = (vmax + vmin) * (denom / 2); + int32_t s_numer_l = (vmax + vmin) * s_numer / 2; + + r = (denom_l + r * s_numer - s_numer_l) * l_numer / (denom * denom); + g = (denom_l + g * s_numer - s_numer_l) * l_numer / (denom * denom); + b = (denom_l + b * s_numer - s_numer_l) * l_numer / (denom * denom); + out[x] = SkPackARGB32(a, r, g, b); + } +} + +// Line processor: H no-op, S decrease, L increase. +void LineProcHnopSdecLinc(const color_utils::HSL& hsl_shift, + const SkPMColor* in, + SkPMColor* out, + int width) { + DCHECK(hsl_shift.h < 0); + DCHECK(hsl_shift.s >= 0 && hsl_shift.s <= 0.5 - HSLShift::epsilon); + DCHECK(hsl_shift.l >= 0.5 + HSLShift::epsilon && hsl_shift.l <= 1); + + // Can't be too big since we need room for denom*denom and a bit for sign. + const int32_t denom = 1024; + int32_t l_numer = static_cast((hsl_shift.l - 0.5) * 2 * denom); + int32_t s_numer = static_cast(hsl_shift.s * 2 * denom); + for (int x = 0; x < width; x++) { + int32_t a = static_cast(SkGetPackedA32(in[x])); + int32_t r = static_cast(SkGetPackedR32(in[x])); + int32_t g = static_cast(SkGetPackedG32(in[x])); + int32_t b = static_cast(SkGetPackedB32(in[x])); + + int32_t vmax, vmin; + if (r > g) { // This uses 3 compares rather than 4. + vmax = std::max(r, b); + vmin = std::min(g, b); + } else { + vmax = std::max(g, b); + vmin = std::min(r, b); + } + + // Use denom * L to avoid rounding. + int32_t denom_l = (vmax + vmin) * (denom / 2); + int32_t s_numer_l = (vmax + vmin) * s_numer / 2; + + r = denom_l + r * s_numer - s_numer_l; + g = denom_l + g * s_numer - s_numer_l; + b = denom_l + b * s_numer - s_numer_l; + + r = (r * denom + (a * denom - r) * l_numer) / (denom * denom); + g = (g * denom + (a * denom - g) * l_numer) / (denom * denom); + b = (b * denom + (a * denom - b) * l_numer) / (denom * denom); + out[x] = SkPackARGB32(a, r, g, b); + } +} + +const LineProcessor kLineProcessors[kNumHOps][kNumSOps][kNumLOps] = { + { // H: kOpHNone + { // S: kOpSNone + LineProcCopy, // L: kOpLNone + LineProcHnopSnopLdec, // L: kOpLDec + LineProcHnopSnopLinc // L: kOpLInc + }, + { // S: kOpSDec + LineProcHnopSdecLnop, // L: kOpLNone + LineProcHnopSdecLdec, // L: kOpLDec + LineProcHnopSdecLinc // L: kOpLInc + }, + { // S: kOpSInc + LineProcDefault, // L: kOpLNone + LineProcDefault, // L: kOpLDec + LineProcDefault // L: kOpLInc + } + }, + { // H: kOpHShift + { // S: kOpSNone + LineProcDefault, // L: kOpLNone + LineProcDefault, // L: kOpLDec + LineProcDefault // L: kOpLInc + }, + { // S: kOpSDec + LineProcDefault, // L: kOpLNone + LineProcDefault, // L: kOpLDec + LineProcDefault // L: kOpLInc + }, + { // S: kOpSInc + LineProcDefault, // L: kOpLNone + LineProcDefault, // L: kOpLDec + LineProcDefault // L: kOpLInc + } + } +}; + +} // namespace HSLShift +} // namespace + +// static +SkBitmap SkBitmapOperations::CreateHSLShiftedBitmap( + const SkBitmap& bitmap, + const color_utils::HSL& hsl_shift) { + if (IsUninitializedBitmap(bitmap)) + return bitmap; + CHECK_EQ(bitmap.colorType(), kN32_SkColorType); + + // Default to NOPs. + HSLShift::OperationOnH H_op = HSLShift::kOpHNone; + HSLShift::OperationOnS S_op = HSLShift::kOpSNone; + HSLShift::OperationOnL L_op = HSLShift::kOpLNone; + + if (hsl_shift.h >= 0 && hsl_shift.h <= 1) + H_op = HSLShift::kOpHShift; + + // Saturation shift: 0 -> fully desaturate, 0.5 -> NOP, 1 -> fully saturate. + if (hsl_shift.s >= 0 && hsl_shift.s <= (0.5 - HSLShift::epsilon)) + S_op = HSLShift::kOpSDec; + else if (hsl_shift.s >= (0.5 + HSLShift::epsilon)) + S_op = HSLShift::kOpSInc; + + // Lightness shift: 0 -> black, 0.5 -> NOP, 1 -> white. + if (hsl_shift.l >= 0 && hsl_shift.l <= (0.5 - HSLShift::epsilon)) + L_op = HSLShift::kOpLDec; + else if (hsl_shift.l >= (0.5 + HSLShift::epsilon)) + L_op = HSLShift::kOpLInc; + + HSLShift::LineProcessor line_proc = + HSLShift::kLineProcessors[H_op][S_op][L_op]; + + DCHECK(bitmap.empty() == false); + DCHECK(bitmap.colorType() == kN32_SkColorType); + + SkBitmap shifted; + shifted.allocN32Pixels(bitmap.width(), bitmap.height()); + + // Loop through the pixels of the original bitmap. + for (int y = 0; y < bitmap.height(); ++y) { + SkPMColor* pixels = bitmap.getAddr32(0, y); + SkPMColor* tinted_pixels = shifted.getAddr32(0, y); + + (*line_proc)(hsl_shift, pixels, tinted_pixels, bitmap.width()); + } + + return shifted; +} + +// static +SkBitmap SkBitmapOperations::CreateTiledBitmap(const SkBitmap& source, + int src_x, int src_y, + int dst_w, int dst_h) { + CHECK_EQ(source.colorType(), kN32_SkColorType); + + SkBitmap cropped; + cropped.allocN32Pixels(dst_w, dst_h); + + // Loop through the pixels of the original bitmap. + for (int y = 0; y < dst_h; ++y) { + int y_pix = (src_y + y) % source.height(); + while (y_pix < 0) + y_pix += source.height(); + + uint32_t* source_row = source.getAddr32(0, y_pix); + uint32_t* dst_row = cropped.getAddr32(0, y); + + for (int x = 0; x < dst_w; ++x) { + int x_pix = (src_x + x) % source.width(); + while (x_pix < 0) + x_pix += source.width(); + + dst_row[x] = source_row[x_pix]; + } + } + + return cropped; +} + +// static +SkBitmap SkBitmapOperations::DownsampleByTwoUntilSize(const SkBitmap& bitmap, + int min_w, int min_h) { + if ((bitmap.width() <= min_w) || (bitmap.height() <= min_h) || + (min_w < 0) || (min_h < 0)) + return bitmap; + + // Since bitmaps are refcounted, this copy will be fast. + SkBitmap current = bitmap; + while ((current.width() >= min_w * 2) && (current.height() >= min_h * 2) && + (current.width() > 1) && (current.height() > 1)) + current = DownsampleByTwo(current); + return current; +} + +// static +SkBitmap SkBitmapOperations::DownsampleByTwo(const SkBitmap& bitmap) { + if (IsUninitializedBitmap(bitmap)) + return bitmap; + CHECK_EQ(bitmap.colorType(), kN32_SkColorType); + + // Handle the nop case. + if ((bitmap.width() <= 1) || (bitmap.height() <= 1)) + return bitmap; + + SkBitmap result; + result.allocN32Pixels((bitmap.width() + 1) / 2, (bitmap.height() + 1) / 2); + + const int resultLastX = result.width() - 1; + const int srcLastX = bitmap.width() - 1; + + for (int dest_y = 0; dest_y < result.height(); ++dest_y) { + const int src_y = dest_y << 1; + const SkPMColor* SK_RESTRICT cur_src0 = bitmap.getAddr32(0, src_y); + const SkPMColor* SK_RESTRICT cur_src1 = cur_src0; + if (src_y + 1 < bitmap.height()) + cur_src1 = bitmap.getAddr32(0, src_y + 1); + + SkPMColor* SK_RESTRICT cur_dst = result.getAddr32(0, dest_y); + + for (int dest_x = 0; dest_x <= resultLastX; ++dest_x) { + // This code is based on downsampleby2_proc32 in SkBitmap.cpp. It is very + // clever in that it does two channels at once: alpha and green ("ag") + // and red and blue ("rb"). Each channel gets averaged across 4 pixels + // to get the result. + int bump_x = (dest_x << 1) < srcLastX; + SkPMColor tmp, ag, rb; + + // Top left pixel of the 2x2 block. + tmp = cur_src0[0]; + ag = (tmp >> 8) & 0xFF00FF; + rb = tmp & 0xFF00FF; + + // Top right pixel of the 2x2 block. + tmp = cur_src0[bump_x]; + ag += (tmp >> 8) & 0xFF00FF; + rb += tmp & 0xFF00FF; + + // Bottom left pixel of the 2x2 block. + tmp = cur_src1[0]; + ag += (tmp >> 8) & 0xFF00FF; + rb += tmp & 0xFF00FF; + + // Bottom right pixel of the 2x2 block. + tmp = cur_src1[bump_x]; + ag += (tmp >> 8) & 0xFF00FF; + rb += tmp & 0xFF00FF; + + // Put the channels back together, dividing each by 4 to get the average. + // |ag| has the alpha and green channels shifted right by 8 bits from + // there they should end up, so shifting left by 6 gives them in the + // correct position divided by 4. + *cur_dst++ = ((rb >> 2) & 0xFF00FF) | ((ag << 6) & 0xFF00FF00); + + cur_src0 += 2; + cur_src1 += 2; + } + } + + return result; +} + +// static +SkBitmap SkBitmapOperations::UnPreMultiply(const SkBitmap& bitmap) { + if (IsUninitializedBitmap(bitmap)) + return bitmap; + CHECK_EQ(bitmap.colorType(), kN32_SkColorType); + + if (bitmap.alphaType() != kPremul_SkAlphaType) + return bitmap; + + const SkImageInfo& opaque_info = + bitmap.info().makeAlphaType(kUnpremul_SkAlphaType); + SkBitmap opaque_bitmap; + opaque_bitmap.allocPixels(opaque_info); + + for (int y = 0; y < opaque_bitmap.height(); y++) { + for (int x = 0; x < opaque_bitmap.width(); x++) { + uint32_t src_pixel = *bitmap.getAddr32(x, y); + uint32_t* dst_pixel = opaque_bitmap.getAddr32(x, y); + SkColor unmultiplied = SkUnPreMultiply::PMColorToColor(src_pixel); + *dst_pixel = unmultiplied; + } + } + + return opaque_bitmap; +} + +// static +SkBitmap SkBitmapOperations::CreateTransposedBitmap(const SkBitmap& image) { + if (IsUninitializedBitmap(image)) + return image; + CHECK_EQ(image.colorType(), kN32_SkColorType); + + SkBitmap transposed; + transposed.allocN32Pixels(image.height(), image.width()); + + for (int y = 0; y < image.height(); ++y) { + uint32_t* image_row = image.getAddr32(0, y); + for (int x = 0; x < image.width(); ++x) { + uint32_t* dst = transposed.getAddr32(y, x); + *dst = image_row[x]; + } + } + + return transposed; +} + +// static +SkBitmap SkBitmapOperations::CreateColorMask(const SkBitmap& bitmap, + SkColor c) { + CHECK_EQ(bitmap.colorType(), kN32_SkColorType); + + SkBitmap color_mask; + color_mask.allocN32Pixels(bitmap.width(), bitmap.height()); + color_mask.eraseARGB(0, 0, 0, 0); + + SkCanvas canvas(color_mask, SkSurfaceProps{}); + + SkPaint paint; + paint.setColorFilter(SkColorFilters::Blend(c, SkBlendMode::kSrcIn)); + canvas.drawImage(bitmap.asImage(), 0, 0, SkSamplingOptions(), &paint); + return color_mask; +} + +// static +SkBitmap SkBitmapOperations::CreateDropShadow( + const SkBitmap& bitmap, + const gfx::ShadowValues& shadows) { + CHECK_EQ(bitmap.colorType(), kN32_SkColorType); + + // Shadow margin insets are negative values because they grow outside. + // Negate them here as grow direction is not important and only pixel value + // is of interest here. + gfx::Insets shadow_margin = -gfx::ShadowValue::GetMargin(shadows); + + SkBitmap image_with_shadow; + image_with_shadow.allocN32Pixels(bitmap.width() + shadow_margin.width(), + bitmap.height() + shadow_margin.height()); + image_with_shadow.eraseARGB(0, 0, 0, 0); + + SkCanvas canvas(image_with_shadow, SkSurfaceProps{}); + canvas.translate(SkIntToScalar(shadow_margin.left()), + SkIntToScalar(shadow_margin.top())); + + SkPaint paint; + for (size_t i = 0; i < shadows.size(); ++i) { + const gfx::ShadowValue& shadow = shadows[i]; + SkBitmap shadow_image = SkBitmapOperations::CreateColorMask(bitmap, + shadow.color()); + + // The blur is halved to produce a shadow that correctly fits within the + // |shadow_margin|. + SkScalar sigma = SkDoubleToScalar(shadow.blur() / 2); + paint.setImageFilter(SkImageFilters::Blur(sigma, sigma, nullptr)); + + canvas.saveLayer(0, &paint); + canvas.drawImage(shadow_image.asImage(), SkIntToScalar(shadow.x()), + SkIntToScalar(shadow.y())); + canvas.restore(); + } + + canvas.drawImage(bitmap.asImage(), 0, 0); + return image_with_shadow; +} + +// static +SkBitmap SkBitmapOperations::Rotate(const SkBitmap& source, + RotationAmount rotation) { + if (IsUninitializedBitmap(source)) + return source; + CHECK_EQ(source.colorType(), kN32_SkColorType); + // SkCanvas::drawBitmap() fails silently with unpremultiplied SkBitmap. + DCHECK_NE(source.info().alphaType(), kUnpremul_SkAlphaType); + + SkBitmap result; + SkScalar angle = SkFloatToScalar(0.0f); + + switch (rotation) { + case ROTATION_90_CW: + angle = SkFloatToScalar(90.0f); + result.allocN32Pixels(source.height(), source.width()); + break; + case ROTATION_180_CW: + angle = SkFloatToScalar(180.0f); + result.allocN32Pixels(source.width(), source.height()); + break; + case ROTATION_270_CW: + angle = SkFloatToScalar(270.0f); + result.allocN32Pixels(source.height(), source.width()); + break; + } + + SkCanvas canvas(result, SkSurfaceProps{}); + canvas.clear(SkColorSetARGB(0, 0, 0, 0)); + + canvas.translate(SkFloatToScalar(result.width() * 0.5f), + SkFloatToScalar(result.height() * 0.5f)); + canvas.rotate(angle); + canvas.translate(-SkFloatToScalar(source.width() * 0.5f), + -SkFloatToScalar(source.height() * 0.5f)); + canvas.drawImage(source.asImage(), 0, 0); + canvas.flush(); + + return result; +} diff --git a/skbitmap_operations.h b/skbitmap_operations.h new file mode 100644 index 000000000000..4e7e6417fd0c --- /dev/null +++ b/skbitmap_operations.h @@ -0,0 +1,119 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_SKBITMAP_OPERATIONS_H_ +#define UI_GFX_SKBITMAP_OPERATIONS_H_ + +#include "base/gtest_prod_util.h" +#include "ui/gfx/color_utils.h" +#include "ui/gfx/gfx_export.h" +#include "ui/gfx/shadow_value.h" + +class SkBitmap; + +class GFX_EXPORT SkBitmapOperations { + public: + // Enum for use in rotating images (must be in 90 degree increments), + // see: Rotate. + enum RotationAmount { + ROTATION_90_CW, + ROTATION_180_CW, + ROTATION_270_CW, + }; + + // Create a bitmap that is an inverted image of the passed in image. + // Each color becomes its inverse in the color wheel. So (255, 15, 0) becomes + // (0, 240, 255). The alpha value is not inverted. + static SkBitmap CreateInvertedBitmap(const SkBitmap& image); + + // Create a bitmap that is a blend of two others. The alpha argument + // specifies the opacity of the second bitmap. The provided bitmaps must + // use have the kARGB_8888_Config config and be of equal dimensions. + static SkBitmap CreateBlendedBitmap(const SkBitmap& first, + const SkBitmap& second, + double alpha); + + // Create a bitmap that is the original bitmap masked out by the mask defined + // in the alpha bitmap. The images must use the kARGB_8888_Config config and + // be of equal dimensions. + static SkBitmap CreateMaskedBitmap(const SkBitmap& first, + const SkBitmap& alpha); + + // We create a button background image by compositing the color and image + // together, then applying the mask. This is a highly specialized composite + // operation that is the equivalent of drawing a background in |color|, + // tiling |image| over the top, and then masking the result out with |mask|. + // The images must use kARGB_8888_Config config. + static SkBitmap CreateButtonBackground(SkColor color, + const SkBitmap& image, + const SkBitmap& mask); + + // Shift a bitmap's HSL values. The shift values are in the range of 0-1, + // with the option to specify -1 for 'no change'. The shift values are + // defined as: + // hsl_shift[0] (hue): The absolute hue value for the image - 0 and 1 map + // to 0 and 360 on the hue color wheel (red). + // hsl_shift[1] (saturation): A saturation shift for the image, with the + // following key values: + // 0 = remove all color. + // 0.5 = leave unchanged. + // 1 = fully saturate the image. + // hsl_shift[2] (lightness): A lightness shift for the image, with the + // following key values: + // 0 = remove all lightness (make all pixels black). + // 0.5 = leave unchanged. + // 1 = full lightness (make all pixels white). + static SkBitmap CreateHSLShiftedBitmap(const SkBitmap& bitmap, + const color_utils::HSL& hsl_shift); + + // Create a bitmap that is cropped from another bitmap. This is special + // because it tiles the original bitmap, so your coordinates can extend + // outside the bounds of the original image. + static SkBitmap CreateTiledBitmap(const SkBitmap& bitmap, + int src_x, int src_y, + int dst_w, int dst_h); + + // Iteratively downsamples by 2 until the bitmap is no smaller than the + // input size. The normal use of this is to downsample the bitmap "close" to + // the final size, and then use traditional resampling on the result. + // Because the bitmap will be closer to the final size, it will be faster, + // and linear interpolation will generally work well as a second step. + static SkBitmap DownsampleByTwoUntilSize(const SkBitmap& bitmap, + int min_w, int min_h); + + // Makes a bitmap half has large in each direction by averaging groups of + // 4 pixels. This is one step in generating a mipmap. + static SkBitmap DownsampleByTwo(const SkBitmap& bitmap); + + // Unpremultiplies all pixels in |bitmap|. You almost never want to call + // this, as |SkBitmap|s are always premultiplied by conversion. Call this + // only if you will pass the bitmap's data into a system function that + // doesn't expect premultiplied colors. + static SkBitmap UnPreMultiply(const SkBitmap& bitmap); + + // Transpose the pixels in |bitmap| by swapping x and y. + static SkBitmap CreateTransposedBitmap(const SkBitmap& bitmap); + + // Create a bitmap by combining alpha channel of |bitmap| and color |c|. + // The image must use the kARGB_8888_Config config. + static SkBitmap CreateColorMask(const SkBitmap& bitmap, SkColor c); + + // Create a bitmap with drop shadow added to |bitmap|. |shadows| defines + // the shadows to add. The created bitmap would be padded to have enough space + // for shadows and have original bitmap in the center. The image must use the + // kARGB_8888_Config config. + static SkBitmap CreateDropShadow(const SkBitmap& bitmap, + const gfx::ShadowValues& shadows); + + // Rotates the given source bitmap clockwise by the requested amount. + static SkBitmap Rotate(const SkBitmap& source, RotationAmount rotation); + + private: + SkBitmapOperations(); // Class for scoping only. + + FRIEND_TEST_ALL_PREFIXES(SkBitmapOperationsTest, DownsampleByTwo); + FRIEND_TEST_ALL_PREFIXES(SkBitmapOperationsTest, DownsampleByTwoSmall); +}; + +#endif // UI_GFX_SKBITMAP_OPERATIONS_H_ diff --git a/skbitmap_operations_unittest.cc b/skbitmap_operations_unittest.cc new file mode 100644 index 000000000000..a0be6ed9cbfe --- /dev/null +++ b/skbitmap_operations_unittest.cc @@ -0,0 +1,561 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/skbitmap_operations.h" + +#include + +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "third_party/skia/include/core/SkColorPriv.h" +#include "third_party/skia/include/core/SkRect.h" +#include "third_party/skia/include/core/SkRegion.h" +#include "third_party/skia/include/core/SkUnPreMultiply.h" + +namespace { + +// Returns true if each channel of the given two colors are "close." This is +// used for comparing colors where rounding errors may cause off-by-one. +inline bool ColorsClose(uint32_t a, uint32_t b) { + return abs(static_cast(SkColorGetB(a) - SkColorGetB(b))) <= 2 && + abs(static_cast(SkColorGetG(a) - SkColorGetG(b))) <= 2 && + abs(static_cast(SkColorGetR(a) - SkColorGetR(b))) <= 2 && + abs(static_cast(SkColorGetA(a) - SkColorGetA(b))) <= 2; +} + +inline bool MultipliedColorsClose(uint32_t a, uint32_t b) { + return ColorsClose(SkUnPreMultiply::PMColorToColor(a), + SkUnPreMultiply::PMColorToColor(b)); +} + +bool BitmapsClose(const SkBitmap& a, const SkBitmap& b) { + for (int y = 0; y < a.height(); y++) { + for (int x = 0; x < a.width(); x++) { + SkColor a_pixel = *a.getAddr32(x, y); + SkColor b_pixel = *b.getAddr32(x, y); + if (!ColorsClose(a_pixel, b_pixel)) + return false; + } + } + return true; +} + +void FillDataToBitmap(int w, int h, SkBitmap* bmp) { + bmp->allocN32Pixels(w, h); + + unsigned char* src_data = + reinterpret_cast(bmp->getAddr32(0, 0)); + for (int i = 0; i < w * h; i++) { + const int alpha = i % 256; + src_data[i * 4 + 0] = static_cast(alpha); + src_data[i * 4 + 1] = static_cast((i + 16) % (alpha + 1)); + src_data[i * 4 + 2] = static_cast((i + 32) % (alpha + 1)); + src_data[i * 4 + 3] = static_cast((i + 64) % (alpha + 1)); + } +} + +// The reference (i.e., old) implementation of |CreateHSLShiftedBitmap()|. +SkBitmap ReferenceCreateHSLShiftedBitmap( + const SkBitmap& bitmap, + color_utils::HSL hsl_shift) { + SkBitmap shifted; + shifted.allocN32Pixels(bitmap.width(), bitmap.height()); + shifted.eraseARGB(0, 0, 0, 0); + + // Loop through the pixels of the original bitmap. + for (int y = 0; y < bitmap.height(); ++y) { + SkPMColor* pixels = bitmap.getAddr32(0, y); + SkPMColor* tinted_pixels = shifted.getAddr32(0, y); + + for (int x = 0; x < bitmap.width(); ++x) { + tinted_pixels[x] = SkPreMultiplyColor(color_utils::HSLShift( + SkUnPreMultiply::PMColorToColor(pixels[x]), hsl_shift)); + } + } + + return shifted; +} + +} // namespace + +// Invert bitmap and verify the each pixel is inverted and the alpha value is +// not changed. +TEST(SkBitmapOperationsTest, CreateInvertedBitmap) { + int src_w = 16, src_h = 16; + SkBitmap src; + src.allocN32Pixels(src_w, src_h); + + for (int y = 0; y < src_h; y++) { + for (int x = 0; x < src_w; x++) { + int i = y * src_w + x; + *src.getAddr32(x, y) = + SkColorSetARGB((255 - i) % 255, i % 255, i * 4 % 255, 0); + } + } + + SkBitmap inverted = SkBitmapOperations::CreateInvertedBitmap(src); + + for (int y = 0; y < src_h; y++) { + for (int x = 0; x < src_w; x++) { + int i = y * src_w + x; + EXPECT_EQ(static_cast((255 - i) % 255), + SkColorGetA(*inverted.getAddr32(x, y))); + EXPECT_EQ(static_cast(255 - (i % 255)), + SkColorGetR(*inverted.getAddr32(x, y))); + EXPECT_EQ(static_cast(255 - (i * 4 % 255)), + SkColorGetG(*inverted.getAddr32(x, y))); + EXPECT_EQ(static_cast(255), + SkColorGetB(*inverted.getAddr32(x, y))); + } + } +} + +// Blend two bitmaps together at 50% alpha and verify that the result +// is the middle-blend of the two. +TEST(SkBitmapOperationsTest, CreateBlendedBitmap) { + int src_w = 16, src_h = 16; + SkBitmap src_a; + src_a.allocN32Pixels(src_w, src_h); + + SkBitmap src_b; + src_b.allocN32Pixels(src_w, src_h); + + for (int y = 0, i = 0; y < src_h; y++) { + for (int x = 0; x < src_w; x++) { + *src_a.getAddr32(x, y) = SkColorSetARGB(255, 0, i * 2 % 255, i % 255); + *src_b.getAddr32(x, y) = + SkColorSetARGB((255 - i) % 255, i % 255, i * 4 % 255, 0); + i++; + } + } + + // Shift to red. + SkBitmap blended = SkBitmapOperations::CreateBlendedBitmap( + src_a, src_b, 0.5); + + for (int y = 0; y < src_h; y++) { + for (int x = 0; x < src_w; x++) { + int i = y * src_w + x; + EXPECT_EQ(static_cast((255 + ((255 - i) % 255)) / 2), + SkColorGetA(*blended.getAddr32(x, y))); + EXPECT_EQ(static_cast(i % 255 / 2), + SkColorGetR(*blended.getAddr32(x, y))); + EXPECT_EQ((static_cast((i * 2) % 255 + (i * 4) % 255) / 2), + SkColorGetG(*blended.getAddr32(x, y))); + EXPECT_EQ(static_cast(i % 255 / 2), + SkColorGetB(*blended.getAddr32(x, y))); + } + } +} + +// Test our masking functions. +TEST(SkBitmapOperationsTest, CreateMaskedBitmap) { + const int src_w = 16, src_h = 16; + + SkBitmap src; + FillDataToBitmap(src_w, src_h, &src); + + SkBitmap alpha; + alpha.allocN32Pixels(src_w, src_h); + for (int y = 0, i = 0; y < src_h; y++) { + for (int x = 0; x < src_w; x++) { + *alpha.getAddr32(x, y) = SkPackARGB32(i % 256, 0, 0, 0); + i++; + } + } + + SkBitmap masked = SkBitmapOperations::CreateMaskedBitmap(src, alpha); + + for (int y = 0; y < src_h; y++) { + for (int x = 0; x < src_w; x++) { + int alpha_pixel = *alpha.getAddr32(x, y); + int src_pixel = *src.getAddr32(x, y); + int masked_pixel = *masked.getAddr32(x, y); + + int scale = SkAlpha255To256(SkGetPackedA32(alpha_pixel)); + + int src_a = (src_pixel >> SK_A32_SHIFT) & 0xFF; + int src_r = (src_pixel >> SK_R32_SHIFT) & 0xFF; + int src_g = (src_pixel >> SK_G32_SHIFT) & 0xFF; + int src_b = (src_pixel >> SK_B32_SHIFT) & 0xFF; + + int masked_a = (masked_pixel >> SK_A32_SHIFT) & 0xFF; + int masked_r = (masked_pixel >> SK_R32_SHIFT) & 0xFF; + int masked_g = (masked_pixel >> SK_G32_SHIFT) & 0xFF; + int masked_b = (masked_pixel >> SK_B32_SHIFT) & 0xFF; + + EXPECT_EQ((src_a * scale) >> 8, masked_a); + EXPECT_EQ((src_r * scale) >> 8, masked_r); + EXPECT_EQ((src_g * scale) >> 8, masked_g); + EXPECT_EQ((src_b * scale) >> 8, masked_b); + } + } +} + +// Make sure that when shifting a bitmap without any shift parameters, +// the end result is close enough to the original (rounding errors +// notwithstanding). +TEST(SkBitmapOperationsTest, CreateHSLShiftedBitmapToSame) { + int src_w = 16, src_h = 16; + SkBitmap src; + src.allocN32Pixels(src_w, src_h); + + for (int y = 0, i = 0; y < src_h; y++) { + for (int x = 0; x < src_w; x++) { + *src.getAddr32(x, y) = SkPreMultiplyColor(SkColorSetARGB((i + 128) % 255, + (i + 128) % 255, (i + 64) % 255, (i + 0) % 255)); + i++; + } + } + + color_utils::HSL hsl = { -1, -1, -1 }; + SkBitmap shifted = ReferenceCreateHSLShiftedBitmap(src, hsl); + + for (int y = 0; y < src_h; y++) { + for (int x = 0; x < src_w; x++) { + SkColor src_pixel = *src.getAddr32(x, y); + SkColor shifted_pixel = *shifted.getAddr32(x, y); + EXPECT_TRUE(MultipliedColorsClose(src_pixel, shifted_pixel)) << + "source: (a,r,g,b) = (" << SkColorGetA(src_pixel) << "," << + SkColorGetR(src_pixel) << "," << + SkColorGetG(src_pixel) << "," << + SkColorGetB(src_pixel) << "); " << + "shifted: (a,r,g,b) = (" << SkColorGetA(shifted_pixel) << "," << + SkColorGetR(shifted_pixel) << "," << + SkColorGetG(shifted_pixel) << "," << + SkColorGetB(shifted_pixel) << ")"; + } + } +} + +// Shift a blue bitmap to red. +TEST(SkBitmapOperationsTest, CreateHSLShiftedBitmapHueOnly) { + int src_w = 16, src_h = 16; + SkBitmap src; + src.allocN32Pixels(src_w, src_h); + + for (int y = 0, i = 0; y < src_h; y++) { + for (int x = 0; x < src_w; x++) { + *src.getAddr32(x, y) = SkColorSetARGB(255, 0, 0, i % 255); + i++; + } + } + + // Shift to red. + color_utils::HSL hsl = { 0, -1, -1 }; + + SkBitmap shifted = SkBitmapOperations::CreateHSLShiftedBitmap(src, hsl); + + for (int y = 0, i = 0; y < src_h; y++) { + for (int x = 0; x < src_w; x++) { + EXPECT_TRUE(ColorsClose(shifted.getColor(x, y), + SkColorSetARGB(255, i % 255, 0, 0))); + i++; + } + } +} + +// Validate HSL shift. +TEST(SkBitmapOperationsTest, ValidateHSLShift) { + // Note: 255/51 = 5 (exactly) => 6 including 0! + const int inc = 51; + const int dim = 255 / inc + 1; + SkBitmap src; + src.allocN32Pixels(dim*dim, dim*dim); + + for (int a = 0, y = 0; a <= 255; a += inc) { + for (int r = 0; r <= 255; r += inc, y++) { + for (int g = 0, x = 0; g <= 255; g += inc) { + for (int b = 0; b <= 255; b+= inc, x++) { + *src.getAddr32(x, y) = + SkPreMultiplyColor(SkColorSetARGB(a, r, g, b)); + } + } + } + } + + // Shhhh. The spec says I should set things to -1 for "no change", but + // actually -0.1 will do. Don't tell anyone I did this. + for (double h = -0.1; h <= 1.0001; h += 0.1) { + for (double s = -0.1; s <= 1.0001; s += 0.1) { + for (double l = -0.1; l <= 1.0001; l += 0.1) { + color_utils::HSL hsl = { h, s, l }; + SkBitmap ref_shifted = ReferenceCreateHSLShiftedBitmap(src, hsl); + SkBitmap shifted = SkBitmapOperations::CreateHSLShiftedBitmap(src, hsl); + EXPECT_TRUE(BitmapsClose(ref_shifted, shifted)) + << "h = " << h << ", s = " << s << ", l = " << l; + } + } + } +} + +// Test our cropping. +TEST(SkBitmapOperationsTest, CreateCroppedBitmap) { + int src_w = 16, src_h = 16; + SkBitmap src; + FillDataToBitmap(src_w, src_h, &src); + + SkBitmap cropped = SkBitmapOperations::CreateTiledBitmap(src, 4, 4, + 8, 8); + ASSERT_EQ(8, cropped.width()); + ASSERT_EQ(8, cropped.height()); + + for (int y = 4; y < 12; y++) { + for (int x = 4; x < 12; x++) { + EXPECT_EQ(*src.getAddr32(x, y), + *cropped.getAddr32(x - 4, y - 4)); + } + } +} + +// Test whether our cropping correctly wraps across image boundaries. +TEST(SkBitmapOperationsTest, CreateCroppedBitmapWrapping) { + int src_w = 16, src_h = 16; + SkBitmap src; + FillDataToBitmap(src_w, src_h, &src); + + SkBitmap cropped = SkBitmapOperations::CreateTiledBitmap( + src, src_w / 2, src_h / 2, src_w, src_h); + ASSERT_EQ(src_w, cropped.width()); + ASSERT_EQ(src_h, cropped.height()); + + for (int y = 0; y < src_h; y++) { + for (int x = 0; x < src_w; x++) { + EXPECT_EQ(*src.getAddr32(x, y), + *cropped.getAddr32((x + src_w / 2) % src_w, + (y + src_h / 2) % src_h)); + } + } +} + +TEST(SkBitmapOperationsTest, DownsampleByTwo) { + // Use an odd-sized bitmap to make sure the edge cases where there isn't a + // 2x2 block of pixels is handled correctly. + // Here's the ARGB example + // + // 50% transparent green opaque 50% blue white + // 80008000 FF000080 FFFFFFFF + // + // 50% transparent red opaque 50% gray black + // 80800000 80808080 FF000000 + // + // black white 50% gray + // FF000000 FFFFFFFF FF808080 + // + // The result of this computation should be: + // A0404040 FF808080 + // FF808080 FF808080 + SkBitmap input; + input.allocN32Pixels(3, 3); + + // The color order may be different, but we don't care (the channels are + // trated the same). + *input.getAddr32(0, 0) = 0x80008000; + *input.getAddr32(1, 0) = 0xFF000080; + *input.getAddr32(2, 0) = 0xFFFFFFFF; + *input.getAddr32(0, 1) = 0x80800000; + *input.getAddr32(1, 1) = 0x80808080; + *input.getAddr32(2, 1) = 0xFF000000; + *input.getAddr32(0, 2) = 0xFF000000; + *input.getAddr32(1, 2) = 0xFFFFFFFF; + *input.getAddr32(2, 2) = 0xFF808080; + + SkBitmap result = SkBitmapOperations::DownsampleByTwo(input); + EXPECT_EQ(2, result.width()); + EXPECT_EQ(2, result.height()); + + // Some of the values are off-by-one due to rounding. + EXPECT_EQ(0x9f404040, *result.getAddr32(0, 0)); + EXPECT_EQ(0xFF7f7f7f, *result.getAddr32(1, 0)); + EXPECT_EQ(0xFF7f7f7f, *result.getAddr32(0, 1)); + EXPECT_EQ(0xFF808080, *result.getAddr32(1, 1)); +} + +// Test edge cases for DownsampleByTwo. +TEST(SkBitmapOperationsTest, DownsampleByTwoSmall) { + SkPMColor reference = 0xFF4080FF; + + // Test a 1x1 bitmap. + SkBitmap one_by_one; + one_by_one.allocN32Pixels(1, 1); + *one_by_one.getAddr32(0, 0) = reference; + SkBitmap result = SkBitmapOperations::DownsampleByTwo(one_by_one); + EXPECT_EQ(1, result.width()); + EXPECT_EQ(1, result.height()); + EXPECT_EQ(reference, *result.getAddr32(0, 0)); + + // Test an n by 1 bitmap. + SkBitmap one_by_n; + one_by_n.allocN32Pixels(300, 1); + result = SkBitmapOperations::DownsampleByTwo(one_by_n); + EXPECT_EQ(300, result.width()); + EXPECT_EQ(1, result.height()); + + // Test a 1 by n bitmap. + SkBitmap n_by_one; + n_by_one.allocN32Pixels(1, 300); + result = SkBitmapOperations::DownsampleByTwo(n_by_one); + EXPECT_EQ(1, result.width()); + EXPECT_EQ(300, result.height()); + + // Test an empty bitmap + SkBitmap empty; + result = SkBitmapOperations::DownsampleByTwo(empty); + EXPECT_TRUE(result.isNull()); + EXPECT_EQ(0, result.width()); + EXPECT_EQ(0, result.height()); +} + +// Here we assume DownsampleByTwo works correctly (it's tested above) and +// just make sure that the wrapper function does the right thing. +TEST(SkBitmapOperationsTest, DownsampleByTwoUntilSize) { + // First make sure a "too small" bitmap doesn't get modified at all. + SkBitmap too_small; + too_small.allocN32Pixels(10, 10); + SkBitmap result = SkBitmapOperations::DownsampleByTwoUntilSize( + too_small, 16, 16); + EXPECT_EQ(10, result.width()); + EXPECT_EQ(10, result.height()); + + // Now make sure giving it a 0x0 target returns something reasonable. + result = SkBitmapOperations::DownsampleByTwoUntilSize(too_small, 0, 0); + EXPECT_EQ(1, result.width()); + EXPECT_EQ(1, result.height()); + + // Test multiple steps of downsampling. + SkBitmap large; + large.allocN32Pixels(100, 43); + result = SkBitmapOperations::DownsampleByTwoUntilSize(large, 6, 6); + + // The result should be divided in half 100x43 -> 50x22 -> 25x11 + EXPECT_EQ(25, result.width()); + EXPECT_EQ(11, result.height()); +} + +TEST(SkBitmapOperationsTest, UnPreMultiply) { + SkBitmap input; + input.allocN32Pixels(2, 2); + EXPECT_EQ(input.alphaType(), kPremul_SkAlphaType); + + // Set PMColors into the bitmap + *input.getAddr32(0, 0) = SkPackARGB32NoCheck(0x80, 0x00, 0x00, 0x00); + *input.getAddr32(1, 0) = SkPackARGB32NoCheck(0x80, 0x80, 0x80, 0x80); + *input.getAddr32(0, 1) = SkPackARGB32NoCheck(0xFF, 0x00, 0xCC, 0x88); + *input.getAddr32(1, 1) = SkPackARGB32NoCheck(0x00, 0x00, 0xCC, 0x88); + + SkBitmap result = SkBitmapOperations::UnPreMultiply(input); + EXPECT_EQ(result.alphaType(), kUnpremul_SkAlphaType); + EXPECT_EQ(2, result.width()); + EXPECT_EQ(2, result.height()); + EXPECT_NE(result.getPixels(), input.getPixels()); + + EXPECT_EQ(0x80000000, *result.getAddr32(0, 0)); + EXPECT_EQ(0x80FFFFFF, *result.getAddr32(1, 0)); + EXPECT_EQ(0xFF00CC88, *result.getAddr32(0, 1)); + EXPECT_EQ(0x00000000u, *result.getAddr32(1, 1)); // "Division by zero". +} + +TEST(SkBitmapOperationsTest, UnPreMultiplyOpaque) { + SkBitmap input; + input.allocN32Pixels(2, 2, true); + EXPECT_EQ(input.alphaType(), kOpaque_SkAlphaType); + + SkBitmap result = SkBitmapOperations::UnPreMultiply(input); + EXPECT_EQ(result.alphaType(), kOpaque_SkAlphaType); + EXPECT_EQ(result.getPixels(), input.getPixels()); +} + +TEST(SkBitmapOperationsTest, UnPreMultiplyAlreadyUnPreMultiplied) { + SkBitmap input; + input.allocN32Pixels(2, 2); + input.setAlphaType(kUnpremul_SkAlphaType); + EXPECT_EQ(input.alphaType(), kUnpremul_SkAlphaType); + + SkBitmap result = SkBitmapOperations::UnPreMultiply(input); + EXPECT_EQ(result.alphaType(), kUnpremul_SkAlphaType); + EXPECT_EQ(result.getPixels(), input.getPixels()); +} + +TEST(SkBitmapOperationsTest, CreateTransposedBitmap) { + SkBitmap input; + input.allocN32Pixels(2, 3); + + for (int x = 0; x < input.width(); ++x) { + for (int y = 0; y < input.height(); ++y) { + *input.getAddr32(x, y) = x * input.width() + y; + } + } + + SkBitmap result = SkBitmapOperations::CreateTransposedBitmap(input); + EXPECT_EQ(3, result.width()); + EXPECT_EQ(2, result.height()); + + for (int x = 0; x < input.width(); ++x) { + for (int y = 0; y < input.height(); ++y) { + EXPECT_EQ(*input.getAddr32(x, y), *result.getAddr32(y, x)); + } + } +} + +void DrawRectWithColor(SkCanvas* canvas, + int left, + int top, + int right, + int bottom, + SkColor color) { + SkPaint paint; + paint.setColor(color); + paint.setBlendMode(SkBlendMode::kSrc); + canvas->drawRect( + SkRect::MakeLTRB(SkIntToScalar(left), SkIntToScalar(top), + SkIntToScalar(right), SkIntToScalar(bottom)), + paint); +} + +// Check that Rotate provides the desired results +TEST(SkBitmapOperationsTest, RotateImage) { + const int src_w = 6, src_h = 4; + SkBitmap src; + // Create a simple 4 color bitmap: + // RRRBBB + // RRRBBB + // GGGYYY + // GGGYYY + src.allocN32Pixels(src_w, src_h); + + SkCanvas canvas(src, SkSurfaceProps{}); + src.eraseARGB(0, 0, 0, 0); + + // This region is a semi-transparent red to test non-opaque pixels. + DrawRectWithColor(&canvas, 0, 0, src_w / 2, src_h / 2, 0x1FFF0000); + DrawRectWithColor(&canvas, src_w / 2, 0, src_w, src_h / 2, SK_ColorBLUE); + DrawRectWithColor(&canvas, 0, src_h / 2, src_w / 2, src_h, SK_ColorGREEN); + DrawRectWithColor(&canvas, src_w / 2, src_h / 2, src_w, src_h, + SK_ColorYELLOW); + + SkBitmap rotate90, rotate180, rotate270; + rotate90 = SkBitmapOperations::Rotate(src, + SkBitmapOperations::ROTATION_90_CW); + rotate180 = SkBitmapOperations::Rotate(src, + SkBitmapOperations::ROTATION_180_CW); + rotate270 = SkBitmapOperations::Rotate(src, + SkBitmapOperations::ROTATION_270_CW); + + ASSERT_EQ(rotate90.width(), src.height()); + ASSERT_EQ(rotate90.height(), src.width()); + ASSERT_EQ(rotate180.width(), src.width()); + ASSERT_EQ(rotate180.height(), src.height()); + ASSERT_EQ(rotate270.width(), src.height()); + ASSERT_EQ(rotate270.height(), src.width()); + + for (int x=0; x < src_w; ++x) { + for (int y=0; y < src_h; ++y) { + ASSERT_EQ(*src.getAddr32(x,y), *rotate90.getAddr32(src_h - (y+1),x)); + ASSERT_EQ(*src.getAddr32(x,y), *rotate270.getAddr32(y, src_w - (x+1))); + ASSERT_EQ(*src.getAddr32(x,y), + *rotate180.getAddr32(src_w - (x+1), src_h - (y+1))); + } + } +} diff --git a/skia_color_space_util.cc b/skia_color_space_util.cc new file mode 100644 index 000000000000..87d372d8413e --- /dev/null +++ b/skia_color_space_util.cc @@ -0,0 +1,96 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/skia_color_space_util.h" + +#include +#include +#include + +#include "base/cxx17_backports.h" + +namespace gfx { + +float SkTransferFnEvalUnclamped(const skcms_TransferFunction& fn, float x) { + if (x < fn.d) + return fn.c * x + fn.f; + return std::pow(fn.a * x + fn.b, fn.g) + fn.e; +} + +float SkTransferFnEval(const skcms_TransferFunction& fn, float x) { + float fn_at_x_unclamped = SkTransferFnEvalUnclamped(fn, x); + return base::clamp(fn_at_x_unclamped, 0.0f, 1.0f); +} + +skcms_TransferFunction SkTransferFnInverse(const skcms_TransferFunction& fn) { + skcms_TransferFunction fn_inv = {0}; + if (fn.a > 0 && fn.g > 0) { + double a_to_the_g = std::pow(fn.a, fn.g); + fn_inv.a = 1.f / a_to_the_g; + fn_inv.b = -fn.e / a_to_the_g; + fn_inv.g = 1.f / fn.g; + } + fn_inv.d = fn.c * fn.d + fn.f; + fn_inv.e = -fn.b / fn.a; + if (fn.c != 0) { + fn_inv.c = 1.f / fn.c; + fn_inv.f = -fn.f / fn.c; + } + return fn_inv; +} + +skcms_TransferFunction SkTransferFnScaled(const skcms_TransferFunction& fn, + float scale) { + if (scale == 1.f) + return fn; + float scale_to_g_inv = std::pow(scale, 1.f / fn.g); + skcms_TransferFunction fn_scaled = {0}; + fn_scaled.a = fn.a * scale_to_g_inv; + fn_scaled.b = fn.b * scale_to_g_inv; + fn_scaled.c = fn.c * scale; + fn_scaled.d = fn.d; + fn_scaled.e = fn.e * scale; + fn_scaled.f = fn.f * scale; + fn_scaled.g = fn.g; + return fn_scaled; +} + +bool SkTransferFnsApproximatelyCancel(const skcms_TransferFunction& a, + const skcms_TransferFunction& b) { + const float kStep = 1.f / 8.f; + const float kEpsilon = 2.5f / 256.f; + for (float x = 0; x <= 1.f; x += kStep) { + float a_of_x = SkTransferFnEval(a, x); + float b_of_a_of_x = SkTransferFnEval(b, a_of_x); + if (std::abs(b_of_a_of_x - x) > kEpsilon) + return false; + } + return true; +} + +bool SkTransferFnIsApproximatelyIdentity(const skcms_TransferFunction& a) { + const float kStep = 1.f / 8.f; + const float kEpsilon = 2.5f / 256.f; + for (float x = 0; x <= 1.f; x += kStep) { + float a_of_x = SkTransferFnEval(a, x); + if (std::abs(a_of_x - x) > kEpsilon) + return false; + } + return true; +} + +bool SkMatrixIsApproximatelyIdentity(const skia::Matrix44& m) { + const float kEpsilon = 1.f / 256.f; + for (int i = 0; i < 4; ++i) { + for (int j = 0; j < 4; ++j) { + float identity_value = i == j ? 1 : 0; + float value = m.get(i, j); + if (std::abs(identity_value - value) > kEpsilon) + return false; + } + } + return true; +} + +} // namespace gfx diff --git a/skia_color_space_util.h b/skia_color_space_util.h new file mode 100644 index 000000000000..0048eba9ea18 --- /dev/null +++ b/skia_color_space_util.h @@ -0,0 +1,43 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_SKIA_COLOR_SPACE_UTIL_H_ +#define UI_GFX_SKIA_COLOR_SPACE_UTIL_H_ + +#include "skia/ext/skia_matrix_44.h" +#include "third_party/skia/include/core/SkColorSpace.h" +#include "third_party/skia/include/core/SkICC.h" +#include "ui/gfx/color_space_export.h" + +namespace gfx { + +// Return the parameterized function in |fn|, evaluated at |x|. Note that this +// will clamp output values to the range [0, 1]. +float COLOR_SPACE_EXPORT SkTransferFnEval(const skcms_TransferFunction& fn, + float x); + +// Return the parameterized function in |fn|, evaluated at |x|. This will not +// clamp output values. +float COLOR_SPACE_EXPORT +SkTransferFnEvalUnclamped(const skcms_TransferFunction& fn, float x); + +skcms_TransferFunction COLOR_SPACE_EXPORT +SkTransferFnInverse(const skcms_TransferFunction& fn); + +skcms_TransferFunction COLOR_SPACE_EXPORT +SkTransferFnScaled(const skcms_TransferFunction& fn, float scale); + +bool COLOR_SPACE_EXPORT +SkTransferFnsApproximatelyCancel(const skcms_TransferFunction& a, + const skcms_TransferFunction& b); + +bool COLOR_SPACE_EXPORT +SkTransferFnIsApproximatelyIdentity(const skcms_TransferFunction& fn); + +bool COLOR_SPACE_EXPORT +SkMatrixIsApproximatelyIdentity(const skia::Matrix44& m); + +} // namespace gfx + +#endif // UI_GFX_SKIA_COLOR_SPACE_UTIL_H_ diff --git a/skia_font_delegate.cc b/skia_font_delegate.cc new file mode 100644 index 000000000000..f7c24c5f5b82 --- /dev/null +++ b/skia_font_delegate.cc @@ -0,0 +1,23 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/skia_font_delegate.h" + +namespace { + +gfx::SkiaFontDelegate* g_skia_font_delegate = 0; + +} // namespace + +namespace gfx { + +void SkiaFontDelegate::SetInstance(SkiaFontDelegate* instance) { + g_skia_font_delegate = instance; +} + +const SkiaFontDelegate* SkiaFontDelegate::instance() { + return g_skia_font_delegate; +} + +} // namespace gfx diff --git a/skia_font_delegate.h b/skia_font_delegate.h new file mode 100644 index 000000000000..05636e98fab8 --- /dev/null +++ b/skia_font_delegate.h @@ -0,0 +1,48 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_SKIA_FONT_DELEGATE_H_ +#define UI_GFX_SKIA_FONT_DELEGATE_H_ + +#include +#include + +#include "ui/gfx/font_render_params.h" +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +// Allows a Linux platform-specific overriding of font preferences. +class GFX_EXPORT SkiaFontDelegate { + public: + virtual ~SkiaFontDelegate() {} + + // Sets the dynamically loaded singleton that provides font preferences. + // This pointer is not owned, and if this method is called a second time, + // the first instance is not deleted. + static void SetInstance(SkiaFontDelegate* instance); + + // Returns a SkiaFontDelegate instance for the toolkit used in + // the user's desktop environment. + // + // Can return NULL, in case no toolkit has been set. (For example, if we're + // running with the "--ash" flag.) + static const SkiaFontDelegate* instance(); + + // Returns the default font rendering settings. + virtual FontRenderParams GetDefaultFontRenderParams() const = 0; + + // Returns details about the default UI font. |style_out| holds a bitfield of + // gfx::Font::Style values. + virtual void GetDefaultFontDescription( + std::string* family_out, + int* size_pixels_out, + int* style_out, + Font::Weight* weight_out, + FontRenderParams* params_out) const = 0; +}; + +} // namespace gfx + +#endif // UI_GFX_SKIA_FONT_DELEGATE_H_ diff --git a/skia_paint_util.cc b/skia_paint_util.cc new file mode 100644 index 000000000000..7bc2d44cb49f --- /dev/null +++ b/skia_paint_util.cc @@ -0,0 +1,110 @@ +// Copyright (c) 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/skia_paint_util.h" + +#include "cc/paint/paint_image_builder.h" +#include "third_party/skia/include/core/SkColorFilter.h" +#include "third_party/skia/include/core/SkMaskFilter.h" +#include "third_party/skia/include/effects/SkGradientShader.h" +#include "third_party/skia/include/effects/SkLayerDrawLooper.h" +#include "ui/gfx/geometry/skia_conversions.h" +#include "ui/gfx/image/image_skia_rep.h" +#include "ui/gfx/switches.h" + +namespace gfx { + +sk_sp CreateImageRepShader(const gfx::ImageSkiaRep& image_rep, + SkTileMode tile_mode_x, + SkTileMode tile_mode_y, + const SkMatrix& local_matrix) { + return CreateImageRepShaderForScale(image_rep, tile_mode_x, tile_mode_y, + local_matrix, image_rep.scale()); +} + +sk_sp CreateImageRepShaderForScale( + const gfx::ImageSkiaRep& image_rep, + SkTileMode tile_mode_x, + SkTileMode tile_mode_y, + const SkMatrix& local_matrix, + SkScalar scale) { + // Unscale matrix by |scale| such that the bitmap is drawn at the + // correct density. + // Convert skew and translation to pixel coordinates. + // Thus, for |bitmap_scale| = 2: + // x scale = 2, x translation = 1 DIP, + // should be converted to + // x scale = 1, x translation = 2 pixels. + SkMatrix shader_scale = local_matrix; + shader_scale.preScale(scale, scale); + shader_scale.setScaleX(local_matrix.getScaleX() / scale); + shader_scale.setScaleY(local_matrix.getScaleY() / scale); + + // TODO(malaykeshav): The check for has_paint_image was only added here to + // prevent generating a paint record in tests. Tests need an instance of + // base::DiscardableMemoryAllocator to generate the PaintRecord. However most + // test suites don't have this set. + // https://crbug.com/891469 + if (!image_rep.has_paint_image()) { + return cc::PaintShader::MakePaintRecord( + image_rep.GetPaintRecord(), + SkRect::MakeIWH(image_rep.pixel_width(), image_rep.pixel_height()), + tile_mode_x, tile_mode_y, &shader_scale); + } else { + return cc::PaintShader::MakeImage(image_rep.paint_image(), tile_mode_x, + tile_mode_y, &shader_scale); + } +} + +sk_sp CreateGradientShader(const gfx::Point& start_point, + const gfx::Point& end_point, + SkColor start_color, + SkColor end_color) { + SkColor grad_colors[2] = {start_color, end_color}; + SkPoint grad_points[2] = {gfx::PointToSkPoint(start_point), + gfx::PointToSkPoint(end_point)}; + + return cc::PaintShader::MakeLinearGradient(grad_points, grad_colors, nullptr, + 2, SkTileMode::kClamp); +} + +// This is copied from +// third_party/WebKit/Source/platform/graphics/skia/SkiaUtils.h +static SkScalar RadiusToSigma(double radius) { + return radius > 0 ? SkDoubleToScalar(0.288675f * radius + 0.5f) : 0; +} + +sk_sp CreateShadowDrawLooper( + const std::vector& shadows) { + if (shadows.empty()) + return nullptr; + + SkLayerDrawLooper::Builder looper_builder; + + looper_builder.addLayer(); // top layer of the original. + + SkLayerDrawLooper::LayerInfo layer_info; + layer_info.fPaintBits |= SkLayerDrawLooper::kMaskFilter_Bit; + layer_info.fPaintBits |= SkLayerDrawLooper::kColorFilter_Bit; + layer_info.fColorMode = SkBlendMode::kSrc; + + for (size_t i = 0; i < shadows.size(); ++i) { + const ShadowValue& shadow = shadows[i]; + + layer_info.fOffset.set(SkIntToScalar(shadow.x()), + SkIntToScalar(shadow.y())); + + SkPaint* paint = looper_builder.addLayer(layer_info); + // Skia's blur radius defines the range to extend the blur from + // original mask, which is half of blur amount as defined in ShadowValue. + paint->setMaskFilter(SkMaskFilter::MakeBlur( + kNormal_SkBlurStyle, RadiusToSigma(shadow.blur() / 2))); + paint->setColorFilter( + SkColorFilters::Blend(shadow.color(), SkBlendMode::kSrcIn)); + } + + return looper_builder.detach(); +} + +} // namespace gfx diff --git a/skia_paint_util.h b/skia_paint_util.h new file mode 100644 index 000000000000..7a00067ea610 --- /dev/null +++ b/skia_paint_util.h @@ -0,0 +1,58 @@ +// Copyright (c) 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_SKIA_PAINT_UTIL_H_ +#define UI_GFX_SKIA_PAINT_UTIL_H_ + +#include +#include + +#include "cc/paint/paint_shader.h" +#include "ui/gfx/gfx_export.h" +#include "ui/gfx/shadow_value.h" + +class SkDrawLooper; +class SkMatrix; + +namespace gfx { + +class ImageSkiaRep; + +// Creates a bitmap shader for the image rep with the image rep's scale factor. +// Sets the created shader's local matrix such that it displays the image rep at +// the correct scale factor. +// The shader's local matrix should not be changed after the shader is created. +// TODO(pkotwicz): Allow shader's local matrix to be changed after the shader +// is created. +// +GFX_EXPORT sk_sp CreateImageRepShader( + const gfx::ImageSkiaRep& image_rep, + SkTileMode tile_mode_x, + SkTileMode tile_mode_y, + const SkMatrix& local_matrix); + +// Creates a bitmap shader for the image rep with the passed in scale factor. +GFX_EXPORT sk_sp CreateImageRepShaderForScale( + const gfx::ImageSkiaRep& image_rep, + SkTileMode tile_mode_x, + SkTileMode tile_mode_y, + const SkMatrix& local_matrix, + SkScalar scale); + +// Creates a gradient shader. The caller owns the shader. +GFX_EXPORT sk_sp CreateGradientShader( + const gfx::Point& start_point, + const gfx::Point& end_point, + SkColor start_color, + SkColor end_color); + +// Creates a draw looper to generate |shadows|. The caller owns the draw looper. +// NULL is returned if |shadows| is empty since no draw looper is needed in +// this case. +GFX_EXPORT sk_sp CreateShadowDrawLooper( + const std::vector& shadows); + +} // namespace gfx + +#endif // UI_GFX_SKIA_PAINT_UTIL_H_ diff --git a/skia_util.cc b/skia_util.cc new file mode 100644 index 000000000000..9df544ab72b1 --- /dev/null +++ b/skia_util.cc @@ -0,0 +1,52 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/skia_util.h" + +#include "base/check.h" +#include "base/numerics/safe_conversions.h" +#include "third_party/skia/include/core/SkBitmap.h" + +namespace gfx { + +bool BitmapsAreEqual(const SkBitmap& bitmap1, const SkBitmap& bitmap2) { + if (bitmap1.isNull() != bitmap2.isNull() || + bitmap1.dimensions() != bitmap2.dimensions()) + return false; + + if (bitmap1.getGenerationID() == bitmap2.getGenerationID() || + (bitmap1.empty() && bitmap2.empty())) + return true; + + // Calling getAddr32() on null or empty bitmaps will assert. The conditions + // above should return early if either bitmap is empty or null. + DCHECK(!bitmap1.isNull() && !bitmap2.isNull()); + DCHECK(!bitmap1.empty() && !bitmap2.empty()); + + void* addr1 = bitmap1.getAddr32(0, 0); + void* addr2 = bitmap2.getAddr32(0, 0); + size_t size1 = bitmap1.computeByteSize(); + size_t size2 = bitmap2.computeByteSize(); + + return (size1 == size2) && (0 == memcmp(addr1, addr2, size1)); +} + +// We treat HarfBuzz ints as 16.16 fixed-point. +static const int kHbUnit1 = 1 << 16; + +int SkiaScalarToHarfBuzzUnits(SkScalar value) { + return base::saturated_cast(value * kHbUnit1); +} + +SkScalar HarfBuzzUnitsToSkiaScalar(int value) { + static const SkScalar kSkToHbRatio = SK_Scalar1 / kHbUnit1; + return kSkToHbRatio * value; +} + +float HarfBuzzUnitsToFloat(int value) { + static const float kFloatToHbRatio = 1.0f / kHbUnit1; + return kFloatToHbRatio * value; +} + +} // namespace gfx diff --git a/skia_util.h b/skia_util.h new file mode 100644 index 000000000000..fb5f20d958a1 --- /dev/null +++ b/skia_util.h @@ -0,0 +1,30 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_SKIA_UTIL_H_ +#define UI_GFX_SKIA_UTIL_H_ + +#include "third_party/skia/include/core/SkScalar.h" +#include "ui/gfx/gfx_skia_export.h" + +class SkBitmap; + +namespace gfx { + +// Returns true if the two bitmaps contain the same pixels. +GFX_SKIA_EXPORT bool BitmapsAreEqual(const SkBitmap& bitmap1, + const SkBitmap& bitmap2); + +// Converts a Skia floating-point value to an int appropriate for hb_position_t. +GFX_SKIA_EXPORT int SkiaScalarToHarfBuzzUnits(SkScalar value); + +// Converts an hb_position_t to a Skia floating-point value. +GFX_SKIA_EXPORT SkScalar HarfBuzzUnitsToSkiaScalar(int value); + +// Converts an hb_position_t to a float. +GFX_SKIA_EXPORT float HarfBuzzUnitsToFloat(int value); + +} // namespace gfx + +#endif // UI_GFX_SKIA_UTIL_H_ diff --git a/skia_util_unittest.cc b/skia_util_unittest.cc new file mode 100644 index 000000000000..34ce1261c137 --- /dev/null +++ b/skia_util_unittest.cc @@ -0,0 +1,47 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/skia_util.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkBitmap.h" + +namespace gfx { + +TEST(SkiaUtilTest, BitmapsAreEqual) { + SkBitmap a, b; + EXPECT_TRUE(gfx::BitmapsAreEqual(a, b)); // Both bitmaps are null. + + a.allocN32Pixels(0, 0); + EXPECT_FALSE(gfx::BitmapsAreEqual(a, b)); // isNull() differs. + b.allocN32Pixels(10, 0); + EXPECT_FALSE(gfx::BitmapsAreEqual(a, b)); // Dimensions differ. + a.allocN32Pixels(0, 10); + EXPECT_FALSE(gfx::BitmapsAreEqual(a, b)); // Dimensions still differ. + b.allocN32Pixels(0, 10); + EXPECT_TRUE(gfx::BitmapsAreEqual(a, b)); // Dimensions equal (but empty). + a.allocN32Pixels(10, 10); + EXPECT_FALSE(gfx::BitmapsAreEqual(a, b)); // Dimensions differ. + b.allocN32Pixels(10, 10); + EXPECT_TRUE(gfx::BitmapsAreEqual(a, b)); // Dimensions equal (non-empty). + + a.eraseColor(SK_ColorRED); + EXPECT_FALSE(gfx::BitmapsAreEqual(a, b)); // Contents differ. + b.eraseColor(SK_ColorGREEN); + EXPECT_FALSE(gfx::BitmapsAreEqual(a, b)); // Contents still differ. + b.eraseColor(SK_ColorRED); + EXPECT_TRUE(gfx::BitmapsAreEqual(a, b)); // Contents equal. + + a.eraseColor(SK_ColorBLUE); + EXPECT_FALSE(gfx::BitmapsAreEqual(a, b)); // Contents differ. + b = a; + EXPECT_TRUE(gfx::BitmapsAreEqual(a, b)); // Generation ids equal. + + a.reset(); + EXPECT_FALSE(gfx::BitmapsAreEqual(a, b)); // isNull() differs. + b.reset(); + EXPECT_TRUE(gfx::BitmapsAreEqual(a, b)); // Both bitmaps are null. +} + +} // namespace gfx diff --git a/skrect_conversion_unittest.cc b/skrect_conversion_unittest.cc new file mode 100644 index 000000000000..2725e04b1ed8 --- /dev/null +++ b/skrect_conversion_unittest.cc @@ -0,0 +1,47 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/skia_conversions.h" + +namespace gfx { + +TEST(RectTest, SkiaRectConversions) { + Rect isrc(10, 20, 30, 40); + RectF fsrc(10.5f, 20.5f, 30.5f, 40.5f); + + SkIRect skirect = RectToSkIRect(isrc); + EXPECT_EQ(isrc.ToString(), SkIRectToRect(skirect).ToString()); + + SkRect skrect = RectToSkRect(isrc); + EXPECT_EQ(gfx::RectF(isrc).ToString(), SkRectToRectF(skrect).ToString()); + + skrect = RectFToSkRect(fsrc); + EXPECT_EQ(fsrc.ToString(), SkRectToRectF(skrect).ToString()); +} + +TEST(RectTest, SkIRectToRectClamping) { + // This clamping only makes sense if SkIRect and gfx::Rect have the same size. + // Otherwise, either other overflows can occur that we don't handle, or no + // overflows can ocur. + if (sizeof(int) != sizeof(int32_t)) + return; + using Limits = std::numeric_limits; + + // right-left and bottom-top would overflow. + // These should be mapped to max width/height, which is as close as gfx::Rect + // can represent. + Rect result = SkIRectToRect(SkIRect::MakeLTRB(Limits::min(), Limits::min(), + Limits::max(), Limits::max())); + EXPECT_EQ(gfx::Size(Limits::max(), Limits::max()), result.size()); + + // right-left and bottom-top would underflow. + // These should be mapped to zero, like all negative values. + result = SkIRectToRect(SkIRect::MakeLTRB(Limits::max(), Limits::max(), + Limits::min(), Limits::min())); + EXPECT_EQ(gfx::Rect(Limits::max(), Limits::max(), 0, 0), result); +} + +} // namespace gfx diff --git a/surface_origin.h b/surface_origin.h new file mode 100644 index 000000000000..0f51513a19a0 --- /dev/null +++ b/surface_origin.h @@ -0,0 +1,19 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_SURFACE_ORIGIN_H_ +#define UI_GFX_SURFACE_ORIGIN_H_ + +namespace gfx { + +// Describes where (0,0) coordinate is in a surface. Conventionally this value +// is kBottomLeft for OpenGL. +enum class SurfaceOrigin { + kTopLeft, + kBottomLeft, +}; + +} // namespace gfx + +#endif // UI_GFX_SURFACE_ORIGIN_H_ diff --git a/swap_result.cc b/swap_result.cc new file mode 100644 index 000000000000..9d600c7438fc --- /dev/null +++ b/swap_result.cc @@ -0,0 +1,28 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/swap_result.h" + +#include "ui/gfx/ca_layer_params.h" +#include "ui/gfx/gpu_fence.h" + +namespace gfx { + +SwapCompletionResult::SwapCompletionResult(gfx::SwapResult swap_result) + : swap_result(swap_result) {} + +SwapCompletionResult::SwapCompletionResult(gfx::SwapResult swap_result, + gfx::GpuFenceHandle release_fence) + : swap_result(swap_result), release_fence(std::move(release_fence)) {} + +SwapCompletionResult::SwapCompletionResult( + gfx::SwapResult swap_result, + std::unique_ptr ca_layer_params) + : swap_result(swap_result), ca_layer_params(std::move(ca_layer_params)) {} + +SwapCompletionResult::SwapCompletionResult(SwapCompletionResult&& other) = + default; +SwapCompletionResult::~SwapCompletionResult() = default; + +} // namespace gfx diff --git a/swap_result.h b/swap_result.h new file mode 100644 index 000000000000..39a62d54e92b --- /dev/null +++ b/swap_result.h @@ -0,0 +1,98 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_SWAP_RESULT_H_ +#define UI_GFX_SWAP_RESULT_H_ + +#include + +#include "base/time/time.h" +#include "ui/gfx/gfx_export.h" +#include "ui/gfx/gpu_fence_handle.h" + +namespace gfx { + +struct CALayerParams; + +enum class SwapResult { + SWAP_ACK, + SWAP_FAILED, + // Typically, the Viz thread should decide whether to skip a swap based off + // the damage. In rare cases, however, the GPU main thread might skip the + // swap after the Viz thread requests it (e.g. the Viz thread might not know + // that the buffers are not fully initialized yet). For the purposes of + // metrics bookkeeping, we label this scenario as SWAP_SKIPPED and treat it + // much like we do a SWAP_FAILED (e.g. failed PresentationFeedback). + // TODO(https://crbug.com/1226090): Consider more explicit handling of + // SWAP_SKIPPED. + SWAP_SKIPPED, + SWAP_NAK_RECREATE_BUFFERS, + SWAP_RESULT_LAST = SWAP_NAK_RECREATE_BUFFERS, +}; + +struct SwapTimings { + // When the GPU service first started processing the SwapBuffers request. + base::TimeTicks swap_start; + + // On most platforms, this is when the GPU service finished processing the + // SwapBuffers request. On ChromeOS, this corresponds to the present time. + // TODO(brianderson): Differentiate the concepts without introducing + // dicontinuities in associated UMA data. + base::TimeTicks swap_end; + + // When Display Compositor thread scheduled work to GPU Thread. For GLRenderer + // it's when InProcessCommandBuffer::Flush() happens, for SkiaRenderer it's + // PostTask time for FinishPaintRenderPass or SwapBuffers whichever comes + // first. + base::TimeTicks viz_scheduled_draw; + + // When GPU thread started draw submitted by Display Compositor thread. For + // GLRenderer it's InProcessCommandBuffer::FlushOnGpuThread, for SkiaRenderer + // it's FinishPaintRenderPass/SwapBuffers. + base::TimeTicks gpu_started_draw; + + // When GPU scheduler removed the last required dependency. + base::TimeTicks gpu_task_ready; + + bool is_null() const { return swap_start.is_null() && swap_end.is_null(); } +}; + +// Sent by ImageTransportSurfaces to their clients in response to a SwapBuffers. +struct SwapResponse { + // The swap's sequence id which helps clients determine which SwapBuffers + // this corresponds to. We may receive responses out of order on platforms + // that allow multiple swaps pending if a failed swap returns immediately + // while a successful swap is still outstanding. + uint64_t swap_id; + + // Indicates whether the swap succeeded or not. + // TODO(https://crbug.com/894929): It may be more reasonable to add + // a full SwapCompletionResult as a member. + SwapResult result; + + // Timing information about the given swap. + SwapTimings timings; +}; + +// Sent by GLImages to their GLImage::SwapCompletionCallbacks. +struct GFX_EXPORT SwapCompletionResult { + explicit SwapCompletionResult(gfx::SwapResult swap_result); + SwapCompletionResult(gfx::SwapResult swap_result, + gfx::GpuFenceHandle release_fence); + SwapCompletionResult(gfx::SwapResult swap_result, + std::unique_ptr ca_layer_params); + SwapCompletionResult(SwapCompletionResult&& other); + ~SwapCompletionResult(); + + SwapCompletionResult(const SwapCompletionResult& other) = delete; + SwapCompletionResult& operator=(const SwapCompletionResult other) = delete; + + gfx::SwapResult swap_result = SwapResult::SWAP_FAILED; + gfx::GpuFenceHandle release_fence; + std::unique_ptr ca_layer_params; +}; + +} // namespace gfx + +#endif // UI_GFX_SWAP_RESULT_H_ diff --git a/switches.cc b/switches.cc new file mode 100644 index 000000000000..6733cbef7f71 --- /dev/null +++ b/switches.cc @@ -0,0 +1,42 @@ +// Copyright 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "build/build_config.h" +#include "ui/gfx/switches.h" + +namespace switches { + +// Scale factor to apply to every animation duration. Must be >= 0.0. This will +// only apply to LinearAnimation and its subclasses. +const char kAnimationDurationScale[] = "animation-duration-scale"; + +// Force disables font subpixel positioning. This affects the character glyph +// sharpness, kerning, hinting and layout. +const char kDisableFontSubpixelPositioning[] = + "disable-font-subpixel-positioning"; + +// Enable native CPU-mappable GPU memory buffer support on Linux. +const char kEnableNativeGpuMemoryBuffers[] = "enable-native-gpu-memory-buffers"; + +// Forces whether the user desires reduced motion, regardless of system +// settings. +const char kForcePrefersReducedMotion[] = "force-prefers-reduced-motion"; + +// Run in headless mode, i.e., without a UI or display server dependencies. +const char kHeadless[] = "headless"; + +} // namespace switches + +namespace features { + +const base::Feature kOddHeightMultiPlanarBuffers { + "OddHeightMultiPlanarBuffers", +#if defined(OS_MAC) + base::FEATURE_ENABLED_BY_DEFAULT +#else + base::FEATURE_DISABLED_BY_DEFAULT +#endif +}; + +} // namespace features diff --git a/switches.h b/switches.h new file mode 100644 index 000000000000..afb36f3e9ae8 --- /dev/null +++ b/switches.h @@ -0,0 +1,25 @@ +// Copyright 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_SWITCHES_H_ +#define UI_GFX_SWITCHES_H_ + +#include "base/feature_list.h" +#include "build/build_config.h" +#include "ui/gfx/switches_export.h" + +namespace switches { + +GFX_SWITCHES_EXPORT extern const char kAnimationDurationScale[]; +GFX_SWITCHES_EXPORT extern const char kDisableFontSubpixelPositioning[]; +GFX_SWITCHES_EXPORT extern const char kEnableNativeGpuMemoryBuffers[]; +GFX_SWITCHES_EXPORT extern const char kForcePrefersReducedMotion[]; +GFX_SWITCHES_EXPORT extern const char kHeadless[]; +} // namespace switches + +namespace features { +GFX_SWITCHES_EXPORT extern const base::Feature kOddHeightMultiPlanarBuffers; +} // namespace features + +#endif // UI_GFX_SWITCHES_H_ diff --git a/switches_export.h b/switches_export.h new file mode 100644 index 000000000000..34418ae9be53 --- /dev/null +++ b/switches_export.h @@ -0,0 +1,29 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_SWITCHES_EXPORT_H_ +#define UI_GFX_SWITCHES_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(GFX_SWITCHES_IMPLEMENTATION) +#define GFX_SWITCHES_EXPORT __declspec(dllexport) +#else +#define GFX_SWITCHES_EXPORT __declspec(dllimport) +#endif // defined(GFX_SWITCHES_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(GFX_SWITCHES_IMPLEMENTATION) +#define GFX_SWITCHES_EXPORT __attribute__((visibility("default"))) +#else +#define GFX_SWITCHES_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define GFX_SWITCHES_EXPORT +#endif + +#endif // UI_GFX_SWITCHES_EXPORT_H_ diff --git a/sys_color_change_listener.cc b/sys_color_change_listener.cc new file mode 100644 index 000000000000..72dae9fbed44 --- /dev/null +++ b/sys_color_change_listener.cc @@ -0,0 +1,79 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/sys_color_change_listener.h" + +#include + +#include + +#include "base/bind.h" +#include "base/callback_helpers.h" +#include "base/memory/singleton.h" +#include "base/observer_list.h" +#include "ui/gfx/win/singleton_hwnd_observer.h" + +namespace gfx { + +class SysColorChangeObserver { + public: + static SysColorChangeObserver* GetInstance(); + + void AddListener(SysColorChangeListener* listener); + void RemoveListener(SysColorChangeListener* listener); + + private: + friend struct base::DefaultSingletonTraits; + + SysColorChangeObserver(); + virtual ~SysColorChangeObserver(); + + void OnWndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam); + + base::ObserverList::Unchecked listeners_; + std::unique_ptr singleton_hwnd_observer_; +}; + +// static +SysColorChangeObserver* SysColorChangeObserver::GetInstance() { + return base::Singleton::get(); +} + +SysColorChangeObserver::SysColorChangeObserver() + : singleton_hwnd_observer_(new SingletonHwndObserver( + base::BindRepeating(&SysColorChangeObserver::OnWndProc, + base::Unretained(this)))) {} + +SysColorChangeObserver::~SysColorChangeObserver() {} + +void SysColorChangeObserver::AddListener(SysColorChangeListener* listener) { + listeners_.AddObserver(listener); +} + +void SysColorChangeObserver::RemoveListener(SysColorChangeListener* listener) { + listeners_.RemoveObserver(listener); +} + +void SysColorChangeObserver::OnWndProc(HWND hwnd, + UINT message, + WPARAM wparam, + LPARAM lparam) { + if (message == WM_SYSCOLORCHANGE || + (message == WM_SETTINGCHANGE && wparam == SPI_SETHIGHCONTRAST)) { + for (SysColorChangeListener& observer : listeners_) + observer.OnSysColorChange(); + } +} + +ScopedSysColorChangeListener::ScopedSysColorChangeListener( + SysColorChangeListener* listener) + : listener_(listener) { + SysColorChangeObserver::GetInstance()->AddListener(listener_); +} + +ScopedSysColorChangeListener::~ScopedSysColorChangeListener() { + SysColorChangeObserver::GetInstance()->RemoveListener(listener_); +} + +} // namespace gfx diff --git a/sys_color_change_listener.h b/sys_color_change_listener.h new file mode 100644 index 000000000000..4dbbe9d06cbe --- /dev/null +++ b/sys_color_change_listener.h @@ -0,0 +1,40 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_SYS_COLOR_CHANGE_LISTENER_H_ +#define UI_GFX_SYS_COLOR_CHANGE_LISTENER_H_ + +#include "base/macros.h" +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +// Interface for classes that want to listen to system color changes. +class GFX_EXPORT SysColorChangeListener { + public: + virtual void OnSysColorChange() = 0; + + protected: + virtual ~SysColorChangeListener() {} +}; + +// Create an instance of this class in any object that wants to listen +// for system color changes. +class GFX_EXPORT ScopedSysColorChangeListener { + public: + explicit ScopedSysColorChangeListener(SysColorChangeListener* listener); + + ScopedSysColorChangeListener(const ScopedSysColorChangeListener&) = delete; + ScopedSysColorChangeListener& operator=(const ScopedSysColorChangeListener&) = + delete; + + ~ScopedSysColorChangeListener(); + + private: + SysColorChangeListener* listener_; +}; + +} // namespace gfx; + +#endif // UI_GFX_SYS_COLOR_CHANGE_LISTENER_H_ diff --git a/system_fonts_win.cc b/system_fonts_win.cc new file mode 100644 index 000000000000..0ed444528b94 --- /dev/null +++ b/system_fonts_win.cc @@ -0,0 +1,306 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/system_fonts_win.h" + +#include + +#include "base/containers/flat_map.h" +#include "base/logging.h" +#include "base/no_destructor.h" +#include "base/numerics/safe_conversions.h" +#include "base/strings/sys_string_conversions.h" +#include "base/trace_event/trace_event.h" +#include "base/win/scoped_gdi_object.h" +#include "base/win/scoped_hdc.h" +#include "base/win/scoped_select_object.h" +#include "ui/gfx/platform_font.h" + +namespace gfx { +namespace win { + +namespace { + +class SystemFonts { + public: + const gfx::Font& GetFont(SystemFont system_font) { + if (!IsInitialized()) + Initialize(); + + auto it = system_fonts_.find(system_font); + DCHECK(it != system_fonts_.end()) + << "System font #" << static_cast(system_font) << " not found!"; + return it->second; + } + + static SystemFonts* Instance() { + static base::NoDestructor instance; + return instance.get(); + } + + SystemFonts(const SystemFonts&) = delete; + SystemFonts& operator=(const SystemFonts&) = delete; + + void ResetForTesting() { + SystemFonts::is_initialized_ = false; + SystemFonts::adjust_font_callback_ = nullptr; + SystemFonts::get_minimum_font_size_callback_ = nullptr; + system_fonts_.clear(); + } + + static int AdjustFontSize(int lf_height, int size_delta) { + // Extract out the sign of |lf_height| - we'll add it back later. + const int lf_sign = lf_height < 0 ? -1 : 1; + lf_height = std::abs(lf_height); + + // Apply the size adjustment. + lf_height += size_delta; + + // Make sure |lf_height| is not smaller than allowed min allowed font size. + int min_font_size = 0; + if (get_minimum_font_size_callback_) { + min_font_size = get_minimum_font_size_callback_(); + DCHECK_GE(min_font_size, 0); + } + lf_height = std::max(min_font_size, lf_height); + + // Add back the sign. + return lf_sign * lf_height; + } + + static void AdjustLOGFONT(const FontAdjustment& font_adjustment, + LOGFONT* logfont) { + DCHECK_GT(font_adjustment.font_scale, 0.0); + LONG new_height = + base::ClampRound(logfont->lfHeight * font_adjustment.font_scale); + if (logfont->lfHeight && !new_height) + new_height = logfont->lfHeight > 0 ? 1 : -1; + logfont->lfHeight = new_height; + if (!font_adjustment.font_family_override.empty()) { + auto result = wcscpy_s(logfont->lfFaceName, + font_adjustment.font_family_override.c_str()); + DCHECK_EQ(0, result) << "Font name " + << font_adjustment.font_family_override + << " cannot be copied into LOGFONT structure."; + } + } + + static Font GetFontFromLOGFONT(const LOGFONT& logfont) { + // Finds a matching font by triggering font mapping. The font mapper finds + // the closest physical font for a given logical font. + base::win::ScopedHFONT font(::CreateFontIndirect(&logfont)); + base::win::ScopedGetDC screen_dc(NULL); + base::win::ScopedSelectObject scoped_font(screen_dc, font.get()); + + DCHECK(font.get()) << "Font for '" + << base::SysWideToUTF8(logfont.lfFaceName) + << "' has an invalid handle."; + + // Retrieve the name and height of the mapped font (physical font). + LOGFONT mapped_font_info; + GetObject(font.get(), sizeof(mapped_font_info), &mapped_font_info); + std::string font_name = base::SysWideToUTF8(mapped_font_info.lfFaceName); + + TEXTMETRIC mapped_font_metrics; + GetTextMetrics(screen_dc, &mapped_font_metrics); + const int font_size = + std::max(1, mapped_font_metrics.tmHeight - + mapped_font_metrics.tmInternalLeading); + + Font system_font = + Font(PlatformFont::CreateFromNameAndSize(font_name, font_size)); + + // System fonts may have different styles when they are manually changed by + // the users (see crbug.com/989476). + Font::FontStyle style = logfont.lfItalic == 0 ? Font::FontStyle::NORMAL + : Font::FontStyle::ITALIC; + Font::Weight weight = logfont.lfWeight == 0 + ? Font::Weight::NORMAL + : static_cast(logfont.lfWeight); + if (style != Font::FontStyle::NORMAL || weight != Font::Weight::NORMAL) + system_font = system_font.Derive(0, style, weight); + + return system_font; + } + + static void SetGetMinimumFontSizeCallback( + GetMinimumFontSizeCallback callback) { + DCHECK(!SystemFonts::IsInitialized()); + get_minimum_font_size_callback_ = callback; + } + + static void SetAdjustFontCallback(AdjustFontCallback callback) { + DCHECK(!SystemFonts::IsInitialized()); + adjust_font_callback_ = callback; + } + + private: + friend base::NoDestructor; + + SystemFonts() {} + + void Initialize() { + TRACE_EVENT0("fonts", "gfx::SystemFonts::Initialize"); + + NONCLIENTMETRICS metrics = {}; + metrics.cbSize = sizeof(metrics); + const bool success = !!SystemParametersInfo(SPI_GETNONCLIENTMETRICS, + metrics.cbSize, &metrics, 0); + DCHECK(success); + + // NOTE(dfried): When rendering Chrome, we do all of our own font scaling + // based on a number of factors, but what Windows reports to us has some + // (but not all) of these factors baked in, and not in a way that is + // display-consistent. + // + // For example, if your system DPI is 192 (200%) but you connect a monitor + // with a standard DPI (100%) then even if Chrome starts on the second + // monitor, we will be told the system font is 24pt instead of 12pt. + // Conversely, if the system DPI is set to 96 (100%) but all of our monitors + // are currently at 150%, Windows will still report 12pt fonts. + // + // The same is true with Text Zoom (a new accessibility feature). If zoom is + // set to 150%, then Windows will report a font size of 18pt. But again, we + // already take Text Zoom into account when rendering, so we want to account + // for that. + // + // Our system fonts are in DIPs, so we must always take what Windows gives + // us, figure out which adjustments it's making (and undo them), make our + // own adjustments for localization (for example, we always render Hindi 25% + // larger for readability), and only then can we store (and report) the + // system fonts. + + // Factor in/out scale adjustment that fall outside what we can access here. + // This includes l10n adjustments and those we have to ask UWP or other COM + // interfaces for (since we don't have dependencies on that code from this + // module, and don't want to implicitly invoke COM for testing purposes if + // we don't have to). + FontAdjustment font_adjustment; + if (adjust_font_callback_) { + adjust_font_callback_(&font_adjustment); + } + + // Factor out system DPI scale that Windows will include in reported font + // sizes. Note that these are (sadly) system-wide and do not reflect + // specific displays' DPI. + double system_scale = GetSystemScale(); + font_adjustment.font_scale /= system_scale; + + // Grab each of the fonts from the NONCLIENTMETRICS block, adjust it + // appropriately, and store it in the font table. + AddFont(SystemFont::kCaption, font_adjustment, &metrics.lfCaptionFont); + AddFont(SystemFont::kSmallCaption, font_adjustment, + &metrics.lfSmCaptionFont); + AddFont(SystemFont::kMenu, font_adjustment, &metrics.lfMenuFont); + AddFont(SystemFont::kMessage, font_adjustment, &metrics.lfMessageFont); + AddFont(SystemFont::kStatus, font_adjustment, &metrics.lfStatusFont); + + is_initialized_ = true; + } + + static bool IsInitialized() { return is_initialized_; } + + void AddFont(SystemFont system_font, + const FontAdjustment& font_adjustment, + LOGFONT* logfont) { + TRACE_EVENT0("fonts", "gfx::SystemFonts::AddFont"); + + // Make adjustments to the font as necessary. + AdjustLOGFONT(font_adjustment, logfont); + + // Cap at minimum font size. + logfont->lfHeight = AdjustFontSize(logfont->lfHeight, 0); + + system_fonts_.emplace(system_font, GetFontFromLOGFONT(*logfont)); + } + + // Returns the system DPI scale (standard DPI being 1.0). + // TODO(dfried): move dpi.[h|cc] somewhere in base/win so we can share this + // logic. However, note that the similar function in dpi.h is used many places + // it ought not to be. + static double GetSystemScale() { + constexpr double kDefaultDPI = 96.0; + base::win::ScopedGetDC screen_dc(nullptr); + return ::GetDeviceCaps(screen_dc, LOGPIXELSY) / kDefaultDPI; + } + + // Use a flat map for faster lookups. + base::flat_map system_fonts_; + + static bool is_initialized_; + + // Font adjustment callback. + static AdjustFontCallback adjust_font_callback_; + + // Minimum size callback. + static GetMinimumFontSizeCallback get_minimum_font_size_callback_; +}; + +// static +bool SystemFonts::is_initialized_ = false; + +// static +AdjustFontCallback SystemFonts::adjust_font_callback_ = nullptr; + +// static +GetMinimumFontSizeCallback SystemFonts::get_minimum_font_size_callback_ = + nullptr; + +} // namespace + +void SetGetMinimumFontSizeCallback(GetMinimumFontSizeCallback callback) { + SystemFonts::SetGetMinimumFontSizeCallback(callback); +} + +void SetAdjustFontCallback(AdjustFontCallback callback) { + SystemFonts::SetAdjustFontCallback(callback); +} + +const Font& GetDefaultSystemFont() { + // The message font is the closest font for a default system font from the + // structure NONCLIENTMETRICS. The lfMessageFont field contains information + // about the logical font used to display text in message boxes. + return GetSystemFont(SystemFont::kMessage); +} + +const Font& GetSystemFont(SystemFont system_font) { + return SystemFonts::Instance()->GetFont(system_font); +} + +NativeFont AdjustExistingSystemFont(NativeFont existing_font, + const FontAdjustment& font_adjustment) { + LOGFONT logfont; + auto result = GetObject(existing_font, sizeof(logfont), &logfont); + DCHECK(result); + + // Make the necessary adjustments. + SystemFonts::AdjustLOGFONT(font_adjustment, &logfont); + + // Cap at minimum font size. + logfont.lfHeight = SystemFonts::AdjustFontSize(logfont.lfHeight, 0); + + // Create the Font object. + return ::CreateFontIndirect(&logfont); +} + +int AdjustFontSize(int lf_height, int size_delta) { + return SystemFonts::AdjustFontSize(lf_height, size_delta); +} + +void AdjustLOGFONTForTesting(const FontAdjustment& font_adjustment, + LOGFONT* logfont) { + SystemFonts::AdjustLOGFONT(font_adjustment, logfont); +} + +// Retrieve a FONT from a LOGFONT structure. +Font GetFontFromLOGFONTForTesting(const LOGFONT& logfont) { + return SystemFonts::GetFontFromLOGFONT(logfont); +} + +void ResetSystemFontsForTesting() { + SystemFonts::Instance()->ResetForTesting(); +} + +} // namespace win +} // namespace gfx diff --git a/system_fonts_win.h b/system_fonts_win.h new file mode 100644 index 000000000000..acf63cdc7990 --- /dev/null +++ b/system_fonts_win.h @@ -0,0 +1,72 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_SYSTEM_FONTS_WIN_H_ +#define UI_GFX_SYSTEM_FONTS_WIN_H_ + +#include "ui/gfx/font.h" +#include "ui/gfx/gfx_export.h" + +namespace gfx { +namespace win { + +// Represents an optional override of system font and scale. +struct FontAdjustment { + std::wstring font_family_override; + double font_scale = 1.0; +}; + +// Identifiers for Windows-specific fonts described in the NONCLIENTMETRICS +// struct. +enum class SystemFont { kCaption = 0, kSmallCaption, kMenu, kStatus, kMessage }; + +// Callback that returns the minimum height that should be used for +// gfx::Fonts. Optional. If not specified, the minimum font size is 0. +typedef int (*GetMinimumFontSizeCallback)(); +GFX_EXPORT void SetGetMinimumFontSizeCallback( + GetMinimumFontSizeCallback callback); + +// Callback that adjusts a FontAdjustment to meet suitability requirements +// of the embedding application. Optional. If not specified, no adjustments +// are performed other than clamping to a minimum font size if +// |get_minimum_font_size_callback| is specified. +typedef void (*AdjustFontCallback)(FontAdjustment* font_adjustment); +GFX_EXPORT void SetAdjustFontCallback(AdjustFontCallback callback); + +// Returns the specified Windows default system font. By default, this is the +// font used for message boxes (see struct NONCLIENTMETRICS). +GFX_EXPORT const Font& GetDefaultSystemFont(); + +// Returns the specified Windows system font, suitable for drawing on screen +// elements. +GFX_EXPORT const Font& GetSystemFont(SystemFont system_font); + +// Applies a font adjustment to an existing native font. +GFX_EXPORT NativeFont +AdjustExistingSystemFont(NativeFont existing_font, + const FontAdjustment& font_adjustment); + +// Computes and returns the adjusted size of a font, subject to the global +// minimum size. |lf_height| is the height as reported by the LOGFONT structure, +// and may be positive or negative (but is typically negative, indicating +// character size rather than cell size). The absolute value of |lf_size| will +// be adjusted by |size_delta| and then returned with the original sign. +GFX_EXPORT int AdjustFontSize(int lf_height, int size_delta); + +// Adjusts a LOGFONT structure for optional size scale and face override. +GFX_EXPORT void AdjustLOGFONTForTesting(const FontAdjustment& font_adjustment, + LOGFONT* logfont); + +// Retrieves a FONT from a LOGFONT structure. +GFX_EXPORT Font GetFontFromLOGFONTForTesting(const LOGFONT& logfont); + +// Clears the system fonts cache. SystemFonts is keeping a global state that +// must be reset in unittests when using |GetMinimumFontSizeCallback| or +// |SetAdjustFontCallback|. +GFX_EXPORT void ResetSystemFontsForTesting(); + +} // namespace win +} // namespace gfx + +#endif // UI_GFX_SYSTEM_FONTS_WIN_H_ diff --git a/system_fonts_win_unittest.cc b/system_fonts_win_unittest.cc new file mode 100644 index 000000000000..9e736b965f8d --- /dev/null +++ b/system_fonts_win_unittest.cc @@ -0,0 +1,172 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/system_fonts_win.h" + +#include + +#include "base/strings/utf_string_conversions.h" +#include "build/build_config.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace gfx { +namespace win { + +namespace { + +class SystemFontsWinTest : public testing::Test { + public: + SystemFontsWinTest() = default; + + SystemFontsWinTest(const SystemFontsWinTest&) = delete; + SystemFontsWinTest& operator=(const SystemFontsWinTest&) = delete; + + protected: + void SetUp() override { +#if defined(OS_WIN) + // System fonts is keeping a cache of loaded system fonts. These fonts are + // scaled based on global callbacks configured on startup. The tests in this + // file are testing these callbacks and need to be sure we cleared the + // global state to avoid flaky tests. + win::ResetSystemFontsForTesting(); +#endif + } +}; + +LOGFONT CreateLOGFONT(const wchar_t* name, LONG height) { + LOGFONT logfont = {}; + logfont.lfHeight = height; + auto result = wcscpy_s(logfont.lfFaceName, name); + DCHECK_EQ(0, result); + return logfont; +} + +const wchar_t kSegoeUI[] = L"Segoe UI"; +const wchar_t kArial[] = L"Arial"; + +} // namespace + +TEST_F(SystemFontsWinTest, AdjustFontSize) { + EXPECT_EQ(10, gfx::win::AdjustFontSize(10, 0)); + EXPECT_EQ(-10, gfx::win::AdjustFontSize(-10, 0)); + EXPECT_EQ(8, gfx::win::AdjustFontSize(10, -2)); + EXPECT_EQ(-8, gfx::win::AdjustFontSize(-10, -2)); + EXPECT_EQ(13, gfx::win::AdjustFontSize(10, 3)); + EXPECT_EQ(-13, gfx::win::AdjustFontSize(-10, 3)); + EXPECT_EQ(1, gfx::win::AdjustFontSize(10, -9)); + EXPECT_EQ(-1, gfx::win::AdjustFontSize(-10, -9)); + EXPECT_EQ(0, gfx::win::AdjustFontSize(10, -12)); + EXPECT_EQ(0, gfx::win::AdjustFontSize(-10, -12)); +} + +TEST_F(SystemFontsWinTest, AdjustFontSize_MinimumSizeSpecified) { + gfx::win::SetGetMinimumFontSizeCallback([] { return 1; }); + EXPECT_EQ(10, gfx::win::AdjustFontSize(10, 0)); + EXPECT_EQ(-10, gfx::win::AdjustFontSize(-10, 0)); + EXPECT_EQ(8, gfx::win::AdjustFontSize(10, -2)); + EXPECT_EQ(-8, gfx::win::AdjustFontSize(-10, -2)); + EXPECT_EQ(13, gfx::win::AdjustFontSize(10, 3)); + EXPECT_EQ(-13, gfx::win::AdjustFontSize(-10, 3)); + EXPECT_EQ(1, gfx::win::AdjustFontSize(10, -9)); + EXPECT_EQ(-1, gfx::win::AdjustFontSize(-10, -9)); + EXPECT_EQ(1, gfx::win::AdjustFontSize(10, -12)); + EXPECT_EQ(-1, gfx::win::AdjustFontSize(-10, -12)); +} + +TEST_F(SystemFontsWinTest, AdjustLOGFONT_NoAdjustment) { + LOGFONT logfont = CreateLOGFONT(kSegoeUI, -12); + FontAdjustment adjustment; + AdjustLOGFONTForTesting(adjustment, &logfont); + EXPECT_EQ(-12, logfont.lfHeight); + EXPECT_STREQ(kSegoeUI, logfont.lfFaceName); +} + +TEST_F(SystemFontsWinTest, AdjustLOGFONT_ChangeFace) { + LOGFONT logfont = CreateLOGFONT(kSegoeUI, -12); + FontAdjustment adjustment{kArial, 1.0}; + AdjustLOGFONTForTesting(adjustment, &logfont); + EXPECT_EQ(-12, logfont.lfHeight); + EXPECT_STREQ(kArial, logfont.lfFaceName); +} + +TEST_F(SystemFontsWinTest, AdjustLOGFONT_ScaleDown) { + LOGFONT logfont = CreateLOGFONT(kSegoeUI, -12); + FontAdjustment adjustment{L"", 0.5}; + AdjustLOGFONTForTesting(adjustment, &logfont); + EXPECT_EQ(-6, logfont.lfHeight); + EXPECT_STREQ(kSegoeUI, logfont.lfFaceName); + + logfont = CreateLOGFONT(kSegoeUI, 12); + adjustment = {L"", 0.5}; + AdjustLOGFONTForTesting(adjustment, &logfont); + EXPECT_EQ(6, logfont.lfHeight); + EXPECT_STREQ(kSegoeUI, logfont.lfFaceName); +} + +TEST_F(SystemFontsWinTest, AdjustLOGFONT_ScaleDownWithRounding) { + LOGFONT logfont = CreateLOGFONT(kSegoeUI, -10); + FontAdjustment adjustment{L"", 0.85}; + AdjustLOGFONTForTesting(adjustment, &logfont); + EXPECT_EQ(-9, logfont.lfHeight); + EXPECT_STREQ(kSegoeUI, logfont.lfFaceName); + + logfont = CreateLOGFONT(kSegoeUI, 10); + adjustment = {L"", 0.85}; + AdjustLOGFONTForTesting(adjustment, &logfont); + EXPECT_EQ(9, logfont.lfHeight); + EXPECT_STREQ(kSegoeUI, logfont.lfFaceName); +} + +TEST_F(SystemFontsWinTest, AdjustLOGFONT_ScaleUpWithFaceChange) { + LOGFONT logfont = CreateLOGFONT(kSegoeUI, -12); + FontAdjustment adjustment{kArial, 1.5}; + AdjustLOGFONTForTesting(adjustment, &logfont); + EXPECT_EQ(-18, logfont.lfHeight); + EXPECT_STREQ(kArial, logfont.lfFaceName); + + logfont = CreateLOGFONT(kSegoeUI, 12); + adjustment = {kArial, 1.5}; + AdjustLOGFONTForTesting(adjustment, &logfont); + EXPECT_EQ(18, logfont.lfHeight); + EXPECT_STREQ(kArial, logfont.lfFaceName); +} + +TEST_F(SystemFontsWinTest, AdjustLOGFONT_ScaleUpWithRounding) { + LOGFONT logfont = CreateLOGFONT(kSegoeUI, -10); + FontAdjustment adjustment{L"", 1.111}; + AdjustLOGFONTForTesting(adjustment, &logfont); + EXPECT_EQ(-11, logfont.lfHeight); + EXPECT_STREQ(kSegoeUI, logfont.lfFaceName); + + logfont = CreateLOGFONT(kSegoeUI, 10); + adjustment = {L"", 1.11}; + AdjustLOGFONTForTesting(adjustment, &logfont); + EXPECT_EQ(11, logfont.lfHeight); + EXPECT_STREQ(kSegoeUI, logfont.lfFaceName); +} + +TEST_F(SystemFontsWinTest, GetFontFromLOGFONT) { + LOGFONT logfont = CreateLOGFONT(kSegoeUI, -10); + Font font = GetFontFromLOGFONTForTesting(logfont); + EXPECT_EQ(font.GetStyle(), Font::FontStyle::NORMAL); + EXPECT_EQ(font.GetWeight(), Font::Weight::NORMAL); +} + +TEST_F(SystemFontsWinTest, GetFontFromLOGFONT_WithStyle) { + LOGFONT logfont = CreateLOGFONT(kSegoeUI, -10); + logfont.lfItalic = 1; + logfont.lfWeight = 700; + + Font font = GetFontFromLOGFONTForTesting(logfont); + EXPECT_EQ(font.GetStyle(), Font::FontStyle::ITALIC); + EXPECT_EQ(font.GetWeight(), Font::Weight::BOLD); +} + +TEST_F(SystemFontsWinTest, GetDefaultSystemFont) { + Font system_font = GetDefaultSystemFont(); + EXPECT_EQ(base::WideToUTF8(kSegoeUI), system_font.GetFontName()); +} + +} // namespace win +} // namespace gfx diff --git a/test/DEPS b/test/DEPS new file mode 100644 index 000000000000..960c0d15b883 --- /dev/null +++ b/test/DEPS @@ -0,0 +1,7 @@ +specific_include_rules = { + "run_all_unittests\.cc": [ + "+mojo/core/embedder/embedder.h", + "+ui/base/resource/resource_bundle.h", + "+ui/base/ui_base_paths.h", + ], +} diff --git a/test/OWNERS b/test/OWNERS new file mode 100644 index 000000000000..8b2fc7806768 --- /dev/null +++ b/test/OWNERS @@ -0,0 +1,3 @@ +# Fonts and fallback fonts. +per-file font*=etienneb@chromium.org +per-file font*=robliao@chromium.org diff --git a/test/data/compositor/BackgroundBlur1.png b/test/data/compositor/BackgroundBlur1.png new file mode 100644 index 000000000000..13adbe6ad94f Binary files /dev/null and b/test/data/compositor/BackgroundBlur1.png differ diff --git a/test/data/compositor/BackgroundBlur1_zoom.png b/test/data/compositor/BackgroundBlur1_zoom.png new file mode 100644 index 000000000000..48987edf1797 Binary files /dev/null and b/test/data/compositor/BackgroundBlur1_zoom.png differ diff --git a/test/data/compositor/BackgroundBlur2.png b/test/data/compositor/BackgroundBlur2.png new file mode 100644 index 000000000000..b110b0650bfc Binary files /dev/null and b/test/data/compositor/BackgroundBlur2.png differ diff --git a/test/data/compositor/ModifyHierarchy1.png b/test/data/compositor/ModifyHierarchy1.png new file mode 100644 index 000000000000..465c24e54f2c Binary files /dev/null and b/test/data/compositor/ModifyHierarchy1.png differ diff --git a/test/data/compositor/ModifyHierarchy2.png b/test/data/compositor/ModifyHierarchy2.png new file mode 100644 index 000000000000..f4ac5dbd2ca4 Binary files /dev/null and b/test/data/compositor/ModifyHierarchy2.png differ diff --git a/test/data/compositor/Opacity.png b/test/data/compositor/Opacity.png new file mode 100644 index 000000000000..83e119985d0d Binary files /dev/null and b/test/data/compositor/Opacity.png differ diff --git a/test/data/icon_util/128_X_128_icon.ico b/test/data/icon_util/128_X_128_icon.ico new file mode 100644 index 000000000000..a5d72fe2f130 Binary files /dev/null and b/test/data/icon_util/128_X_128_icon.ico differ diff --git a/test/data/icon_util/16_X_16_icon.ico b/test/data/icon_util/16_X_16_icon.ico new file mode 100644 index 000000000000..0e524f16e14c Binary files /dev/null and b/test/data/icon_util/16_X_16_icon.ico differ diff --git a/test/data/render_text/OWNERS b/test/data/render_text/OWNERS new file mode 100644 index 000000000000..bd3a84d41e78 --- /dev/null +++ b/test/data/render_text/OWNERS @@ -0,0 +1,3 @@ +# RenderText and related classes. +per-file render_text*=etienneb@chromium.org +per-file render_text*=msw@chromium.org diff --git a/test/data/render_text/unicode_text_fuzzer.dict b/test/data/render_text/unicode_text_fuzzer.dict new file mode 100644 index 000000000000..59f7bd6b7f0a --- /dev/null +++ b/test/data/render_text/unicode_text_fuzzer.dict @@ -0,0 +1,351 @@ +# Copyright 2019 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# Fuzzer dictionary targetting RenderText. + +# Control codes +"\x00" +"\x0a" +"\x0d" + +# Basic Latin +" " +"!" +"123" +"@" +"ABC" +"~" + +# Latin-1 Supplement +"\xA0" +"\xc2\xa5" +"\xc3\x86" +"\xc3\x98" +"\xc3\xa9" +"\xc3\xb7" + +# Latin Extended-A +"\xc4\x80" +"\xc4\x89" +"\xc4\x9e" +"\xc4\xb6" +"\xc5\x85" +"\xc5\x92" + +# Latin Extended-B +"\xc6\x82" +"\xc6\x86" +"\xc6\x90" +"\xc6\xba" +"\xc9\x83" + +# Latin Extended-C +"\xe2\xb1\xa3" +"\xe2\xb1\xab" + +# Latin Extended-D +"\xea\x9c\xb6" +"\xea\x9e\xb0" +"\xea\x9f\xbf" + +# Latin Extended-E +"\xea\xad\x82" +"\xea\xac\xba" + +# IPA Extensions +"\xca\x92" +"\xc9\x99" + +# Spacing modifier letters +"\xca\xb0" +"\xcb\x87" +"\xcb\xa8" + +# Phonetic Extensions +"\xe1\xb4\x94" +"\xe1\xb5\xab" +"\xe1\xb5\xba" + +# Phonetic Extensions Supplement +"\xe1\xb6\x95" +"\xe1\xb6\x8e" +"\xe1\xb6\xb3" + +# Combining Marks +"\xe2\x97\x8c\xcc\x85" +"\xe2\x97\x8c\xcc\x9a" +"\xe2\x97\x8c\xcc\xb8" +"\xe2\x97\x8c\xcd\xa5" +"\xe2\x97\x8c\xe2\x83\x95" +"\xe2\x97\x8c\xe2\x83\x92" + +# Greek and Coptic +"\xcd\xb2" +"\xcd\xb6" +"\xce\x86" +"\xcf\x96" + +# Greek Extended +"\xe1\xbc\xaa" +"\xe1\xbe\x8c" +"\xe1\xbe\xa5" + +# Cyrillic +"\xd0\x82" +"\xd0\x89" +"\xd0\x94" +"\xd0\xa1" + +# Cyrillic Supplement +"\xd4\x94" +"\xd4\x99" +"\xd4\xaa" + +# Cyrillic Extended-A +"\xe2\xb7\xa8" +"\xe2\xb7\xbc" + +# Cyrillic Extended-B +"\xea\x99\x9a" +"\xea\x99\xb2" +"\xea\x99\xac" +"\xea\x99\xae" + +# Cyrillic Extended-C +"\xe1\xb2\x80" +"\xe1\xb2\x82" +"\xe1\xb2\x83" +"\xe1\xb2\x85" + +# Armenian +"\xd5\x83" +"\xd4\xb9" +"\xd5\x8d" +"\xd6\x83" + +# Arabic +"\xef\xba\x90" +"\xef\xba\x9e" +"\xef\xba\xac" +"\xef\xba\xb6" +"\xe0\xa2\xa9" +"\xef\xad\xb3" +"\xef\xba\xb6" + +# Arabic Word ligatures +"\xef\xb7\xb0" +"\xef\xb7\xb2" +"\xef\xb7\xb6" + +# Hebrew +"\xd7\x90" +"\xd7\x9d" +"\xd7\xa3" +"\xef\xac\xa4" + +# Mandaic +"\xe0\xa1\x81" +"\xe0\xa1\x82" +"\xe0\xa1\x85" + +# Samaritan +"\xe0\xa0\x81" +"\xe0\xa0\x84" +"\xe0\xa0\x85" +"\xe0\xa0\x86" + +# Syriac +"\xdc\x93" +"\xdc\x9c" +"\xdc\xae" +"\xdd\x8e" + +# Thaana +"\xde\x84" +"\xde\x8c" +"\xde\x9d" + +# Devanagari +"\xe0\xa4\x96" +"\xe0\xa4\x99" +"\xe0\xa5\x9a" +"\xe0\xa5\xbe" + +# Bengali +"\xe0\xa6\x86" +"\xe0\xa6\x8b" +"\xe0\xa6\x9d" +"\xe0\xa6\xb6" + +# Gurmukhi +"\xe0\xa8\x86" +"\xe0\xa8\x98" +"\xe0\xa8\x9b" +"\xe0\xa8\xb2" + +# Gujarati +"\xe0\xaa\x86" +"\xe0\xaa\x8a" +"\xe0\xaa\x8d" +"\xe0\xaa\xb6" +"\xe0\xab\x84" +"\xe0\xab\x81" +"\xe0\xab\x80" +"\xe0\xab\xb1" + +# Oriya +"\xe0\xac\x81" +"\xe0\xac\x86" +"\xe0\xac\xaa" +"\xe0\xac\x9e" +"\xe0\xad\x8b" +"\xe0\xad\x9c" + +# Tamil +"\xe0\xae\x85" +"\xe0\xae\x87" +"\xe0\xae\x99" +"\xe0\xae\x9c" +"\xe0\xaf\xb5" + +# Telugu +"\xe0\xb0\x94" +"\xe0\xb0\x8b" +"\xe0\xb0\x9d" +"\xe0\xb0\xae" +"\xe0\xb1\xa0" +"\xe0\xb1\x98" +"\xe0\xb1\x96" + +# Kannada +"\xe0\xb2\x85" +"\xe0\xb2\x88" +"\xe0\xb2\x8a" +"\xe0\xb2\x9d" +"\xe0\xb3\x88" +"\xe0\xb3\x8b" + +# Malayalam +"\xe0\xb4\x83" +"\xe0\xb4\x86" +"\xe0\xb4\x98" +"\xe0\xb4\xb8" +"\xe0\xb5\xba" + +# Sinhala +"\xe0\xb6\x86" +"\xe0\xb6\x89" +"\xe0\xb6\x8c" +"\xe0\xb6\x9d" +"\xe0\xb7\x96" +"\xe0\xb7\xb4" + +# Thai +"\xe0\xb8\x97" +"\xe0\xb8\xa9" +"\xe0\xb9\x97" + +# Georgian +"\xe1\x82\xb3" +"\xe1\x82\xb4" +"\xe1\x83\x9a" + +# Unicode symbols +"\xe2\x80\x95" +"\xe2\x80\xa0" +"\xe2\x80\xbc" + +# General Punctuation +"\xe2\x80\x96" +"\xe2\x80\xbb" +"\xe2\x80\x8b" +"\xe2\x80\x8c" +"\xe2\x80\x8d" +"\xe2\x80\x8e" +"\xe2\x80\x8f" +"\xe2\x81\xa0" + +# Superscripts and Subscripts +"\xe2\x82\x88" +"\xe2\x82\x98" + +# Currency Symbols +"\xe2\x82\xa9" +"\xe2\x82\xad" + +# Letterlike Symbols +"\xe2\x84\x96" +"\xe2\x84\xac" +"\xe2\x84\xa2" + +# Number Forms +"\xe2\x85\x97" +"\xe2\x85\xa8" + +# Arrows +"\xe2\x86\x88" +"\xe2\x87\x86" +"\xe2\x87\x98" +"\xe2\x87\xbc" + +# Mathematical symbols +"\xe2\x88\xad" +"\xe2\x89\x99" +"\xe2\x8a\x98" + +# Miscellaneous Technical +"\xe2\x8c\x9b" +"\xe2\x8d\x89" +"\xe2\x8e\x9b" + +# Enclosed Alphanumerics +"\xe2\x91\xa5" +"\xe2\x93\x8b" +"\xe2\x93\xb4" + +# Box Drawing +"\xe2\x94\xa3" +"\xe2\x95\xa9" + +# Block Elements +"\xe2\x96\x84" +"\xe2\x96\x8a" + +# Geometric Shapes +"\xe2\x96\xa4" +"\xe2\x96\xb0" +"\xe2\x97\xb7" + +# Miscellaneous Symbols +"\xe2\x98\xb9" +"\xe2\x9a\xbd" +"\xe2\x9b\xb5" + +# Dingbat +"\xe2\x9c\xbb" +"\xe2\x9d\x89" + +# Braille +"\xe2\xa0\x9b" +"\xe2\xa1\xbb" +"\xe2\xa2\xb7" +"\xe2\xa3\xb8" + +# Music +"\xf0\x9d\x84\x87" +"\xf0\x9d\x84\xb5" +"\xf0\x9d\x87\x9a" + +# Alchemical Symbols +"\xf0\x9f\x9c\x97" +"\xf0\x9f\x9d\x9c" +"\xf0\x9f\x9d\xa9" + +# Emoji +"\x32\x36\x39\x33" +"\x36\xef\xb8\x8f\xe2\x83\xa3" +"\xc2\xa9\xef\xb8\x8f" +"\xf0\x9f\x8f\x87\xf0\x9f\x8f\xbe" +"\x23\xef\xb8\x8e" +"\xf0\x9f\x91\xa8\xf0\x9f\x8f\xbf\xe2\x80\x8d\xf0\x9f\xa4\x9d\xe2\x80\x8d\xf0\x9f\x91\xa8\xf0\x9f\x8f\xbd" diff --git a/test/font_fallback_test_data.cc b/test/font_fallback_test_data.cc new file mode 100644 index 000000000000..a95fafe50eb7 --- /dev/null +++ b/test/font_fallback_test_data.cc @@ -0,0 +1,280 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/test/font_fallback_test_data.h" + +#include + +#include "build/build_config.h" + +namespace gfx { + +#if defined(OS_WIN) +constexpr bool kWin10Only = true; +#endif + +FallbackFontTestCase::FallbackFontTestCase() = default; +FallbackFontTestCase::FallbackFontTestCase(const FallbackFontTestCase& other) = + default; + +FallbackFontTestCase::FallbackFontTestCase( + UScriptCode script_arg, + std::string language_tag_arg, + std::u16string text_arg, + std::vector fallback_fonts_arg, + bool is_win10_arg) + : script(script_arg), + language_tag(language_tag_arg), + text(text_arg), + fallback_fonts(fallback_fonts_arg), + is_win10(is_win10_arg) {} + +FallbackFontTestCase::~FallbackFontTestCase() = default; + +#if defined(OS_WIN) +// A list of script and the fallback font on a default windows installation. +// This list may need to be updated if fonts or operating systems are +// upgraded. +// TODO(drott): Some of the test cases lack a valid language tag as it's unclear +// which language in particular would be expressed with the respective ancient +// script. Ideally we'd find a meaningful language tag for those. +std::vector kGetFontFallbackTests = { + {USCRIPT_ARABIC, + "ar", + u"\u062A\u062D", + {"Segoe UI", "Tahoma", "Times New Roman"}}, + {USCRIPT_ARMENIAN, + "hy-am", + u"\u0540\u0541", + {"Segoe UI", "Tahoma", "Sylfaen", "Times New Roman"}}, + {USCRIPT_BENGALI, "bn", u"\u09B8\u09AE", {"Nirmala UI", "Vrinda"}}, + {USCRIPT_BRAILLE, "en-us-brai", u"\u2870\u2871", {"Segoe UI Symbol"}}, + {USCRIPT_BUGINESE, "bug", u"\u1A00\u1A01", {"Leelawadee UI"}, kWin10Only}, + {USCRIPT_CANADIAN_ABORIGINAL, + "cans", + u"\u1410\u1411", + {"Gadugi", "Euphemia"}}, + + {USCRIPT_CARIAN, + "xcr", + u"\U000102A0\U000102A1", + {"Segoe UI Historic"}, + kWin10Only}, + + {USCRIPT_CHEROKEE, + "chr", + u"\u13A1\u13A2", + {"Gadugi", "Plantagenet Cherokee"}}, + + {USCRIPT_COPTIC, + "copt", + u"\u2C81\u2C82", + {"Segoe UI Historic"}, + kWin10Only}, + + {USCRIPT_CUNEIFORM, + "akk", + u"\U00012000\U0001200C", + {"Segoe UI Historic"}, + kWin10Only}, + + {USCRIPT_CYPRIOT, + "ecy", + u"\U00010800\U00010801", + {"Segoe UI Historic"}, + kWin10Only}, + + {USCRIPT_CYRILLIC, "ru", u"\u0410\u0411\u0412", {"Times New Roman"}}, + + {USCRIPT_DESERET, + "en", + u"\U00010400\U00010401", + {"Segoe UI Symbol"}, + kWin10Only}, + + {USCRIPT_ETHIOPIC, "am", u"\u1201\u1202", {"Ebrima", "Nyala"}}, + {USCRIPT_GEORGIAN, + "ka", + u"\u10A0\u10A1", + {"Sylfaen", "Segoe UI"}, + kWin10Only}, + {USCRIPT_GREEK, "el", u"\u0391\u0392", {"Times New Roman"}}, + {USCRIPT_GURMUKHI, "pa", u"\u0A21\u0A22", {"Raavi", "Nirmala UI"}}, + {USCRIPT_HAN, + "zh-CN", + u"\u6211", + {"Microsoft YaHei", "Microsoft YaHei UI"}}, + {USCRIPT_HAN, + "zh-HK", + u"\u6211", + {"Microsoft JhengHei", "Microsoft JhengHei UI"}}, + {USCRIPT_HAN, + "zh-Hans", + u"\u6211", + {"Microsoft YaHei", "Microsoft YaHei UI"}}, + {USCRIPT_HAN, + "zh-Hant", + u"\u6211", + {"Microsoft JhengHei", "Microsoft JhengHei UI"}}, + {USCRIPT_HAN, "ja", u"\u6211", {"Meiryo UI", "Yu Gothic UI", "Yu Gothic"}}, + {USCRIPT_HANGUL, + "ko", + u"\u1100\u1101", + {"Malgun Gothic", "Gulim"}, + kWin10Only}, + {USCRIPT_HEBREW, + "he", + u"\u05D1\u05D2", + {"Segoe UI", "Tahoma", "Times New Roman"}}, + {USCRIPT_KHMER, + "km", + u"\u1780\u1781", + {"Leelawadee UI", "Khmer UI", "Khmer OS", "MoolBoran", "DaunPenh"}}, + + {USCRIPT_IMPERIAL_ARAMAIC, + "arc", + u"\U00010841\U00010842", + {"Segoe UI Historic"}, + kWin10Only}, + + {USCRIPT_INSCRIPTIONAL_PAHLAVI, + "pal", + u"\U00010B61\U00010B62", + {"Segoe UI Historic"}, + kWin10Only}, + + {USCRIPT_INSCRIPTIONAL_PARTHIAN, + "xpr", + u"\U00010B41\U00010B42", + {"Segoe UI Historic"}, + kWin10Only}, + + {USCRIPT_JAVANESE, "jv", u"\uA991\uA992", {"Javanese Text"}, kWin10Only}, + {USCRIPT_KANNADA, "kn", u"\u0CA1\u0CA2", {"Nirmala UI", "Tunga"}}, + + {USCRIPT_KHAROSHTHI, + "sa", + u"\U00010A10\U00010A11", + {"Segoe UI Historic"}, + kWin10Only}, + + {USCRIPT_LAO, + "lo", + u"\u0ED0\u0ED1", + {"Lao UI", "Leelawadee UI", "Segoe UI"}}, + {USCRIPT_LISU, "lis", u"\uA4D0\uA4D1", {"Segoe UI"}, kWin10Only}, + + {USCRIPT_LYCIAN, + "xlc", + u"\U00010281\U00010282", + {"Segoe UI Historic"}, + kWin10Only}, + + {USCRIPT_LYDIAN, + "xld", + u"\U00010921\U00010922", + {"Segoe UI Historic"}, + kWin10Only}, + + {USCRIPT_MALAYALAM, "ml", u"\u0D21\u0D22", {"Kartika", "Nirmala UI"}}, + + {USCRIPT_MEROITIC_CURSIVE, + "", + u"\U000109A1\U000109A2", + {"Segoe UI Historic"}, + kWin10Only}, + + {USCRIPT_MYANMAR, "my", u"\u1000\u1001", {"Myanmar Text"}, kWin10Only}, + {USCRIPT_NEW_TAI_LUE, "", u"\u1981\u1982", {"Microsoft New Tai Lue"}}, + {USCRIPT_NKO, "nko", u"\u07C1\u07C2", {"Ebrima", "Segoe UI"}}, + + {USCRIPT_OGHAM, + "", + u"\u1680\u1681", + {"Segoe UI Symbol", "Segoe UI Historic"}}, + + {USCRIPT_OL_CHIKI, "", u"\u1C51\u1C52", {"Nirmala UI"}, kWin10Only}, + + {USCRIPT_OLD_ITALIC, + "", + u"\U00010301\U00010302", + {"Segoe UI Historic", "Segoe UI Symbol"}}, + + {USCRIPT_OLD_PERSIAN, + "peo", + u"\U000103A1\U000103A2", + {"Segoe UI Historic"}, + kWin10Only}, + + {USCRIPT_OLD_SOUTH_ARABIAN, + "", + u"\U00010A61\U00010A62", + {"Segoe UI Historic"}, + kWin10Only}, + + {USCRIPT_ORIYA, "or", u"\u0B21\u0B22", {"Kalinga", "Nirmala UI"}}, + {USCRIPT_PHAGS_PA, "", u"\uA841\uA842", {"Microsoft PhagsPa"}}, + + {USCRIPT_RUNIC, + "", + u"\u16A0\u16A1", + {"Segoe UI Symbol", "Segoe UI Historic"}}, + + {USCRIPT_SHAVIAN, + "", + u"\U00010451\U00010452", + {"Segoe UI", "Segoe UI Historic"}, + kWin10Only}, + + {USCRIPT_SINHALA, "si", u"\u0D91\u0D92", {"Iskoola Pota", "Nirmala UI"}}, + + {USCRIPT_SORA_SOMPENG, + "", + u"\U000110D1\U000110D2", + {"Nirmala UI"}, + kWin10Only}, + + {USCRIPT_SYRIAC, + "syr", + u"\u0711\u0712", + {"Estrangelo Edessa", "Segoe UI Historic"}}, + + {USCRIPT_TAI_LE, "", u"\u1951\u1952", {"Microsoft Tai Le"}}, + {USCRIPT_TAMIL, "ta", u"\u0BB1\u0BB2", {"Latha", "Nirmala UI"}}, + {USCRIPT_TELUGU, "te", u"\u0C21\u0C22", {"Gautami", "Nirmala UI"}}, + {USCRIPT_THAANA, "", u"\u0781\u0782", {"Mv Boli", "MV Boli"}}, + {USCRIPT_THAI, + "th", + u"\u0e01\u0e02", + {"Tahoma", "Leelawadee UI", "Leelawadee"}, + kWin10Only}, + {USCRIPT_TIBETAN, "bo", u"\u0F01\u0F02", {"Microsoft Himalaya"}}, + {USCRIPT_TIFINAGH, "", u"\u2D31\u2D32", {"Ebrima"}}, + {USCRIPT_VAI, "vai", u"\uA501\uA502", {"Ebrima"}}, + {USCRIPT_YI, "yi", u"\uA000\uA001", {"Microsoft Yi Baiti"}}}; + +#elif defined(OS_LINUX) || defined(OS_CHROMEOS) + +// A list of script and the fallback font on the linux test environment. +// On linux, font-config configuration and fonts are mock. The config +// can be found in '${build}/etc/fonts/fonts.conf' and the test fonts +// can be found in '${build}/test_fonts/*'. +std::vector kGetFontFallbackTests = { + {USCRIPT_BENGALI, "bn", u"\u09B8\u09AE", {"Mukti Narrow"}}, + {USCRIPT_DEVANAGARI, "hi", u"\u0905\u0906", {"Lohit Devanagari"}}, + {USCRIPT_GURMUKHI, "pa", u"\u0A21\u0A22", {"Lohit Gurmukhi"}}, + {USCRIPT_HAN, "zh-CN", u"\u6211", {"Noto Sans CJK JP"}}, + {USCRIPT_KHMER, "km", u"\u1780\u1781", {"Noto Sans Khmer"}}, + {USCRIPT_TAMIL, "ta", u"\u0BB1\u0BB2", {"Lohit Tamil"}}, + {USCRIPT_THAI, "th", u"\u0e01\u0e02", {"Garuda"}}, +}; + +#else + +// No fallback font tests are defined on that platform. +std::vector kGetFontFallbackTests = {}; + +#endif + +} // namespace gfx diff --git a/test/font_fallback_test_data.h b/test/font_fallback_test_data.h new file mode 100644 index 000000000000..cfc89e374e20 --- /dev/null +++ b/test/font_fallback_test_data.h @@ -0,0 +1,36 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_TEST_FONT_FALLBACK_TEST_DATA_H_ +#define UI_GFX_TEST_FONT_FALLBACK_TEST_DATA_H_ + +#include +#include + +#include "third_party/icu/source/common/unicode/uscript.h" + +namespace gfx { + +// A font test case for the parameterized unittests. +struct FallbackFontTestCase { + FallbackFontTestCase(); + FallbackFontTestCase(UScriptCode script_arg, + std::string language_tag_arg, + std::u16string text_arg, + std::vector fallback_fonts_arg, + bool is_win10_arg = false); + FallbackFontTestCase(const FallbackFontTestCase& other); + ~FallbackFontTestCase(); + UScriptCode script; + std::string language_tag; + std::u16string text; + std::vector fallback_fonts; + bool is_win10 = false; +}; + +extern std::vector kGetFontFallbackTests; + +} // namespace gfx + +#endif // UI_GFX_TEST_FONT_FALLBACK_TEST_DATA_H_ diff --git a/test/gfx_util.cc b/test/gfx_util.cc new file mode 100644 index 000000000000..8037ee1b3419 --- /dev/null +++ b/test/gfx_util.cc @@ -0,0 +1,202 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/test/gfx_util.h" + +#include +#include +#include + +#include "ui/gfx/geometry/axis_transform2d.h" +#include "ui/gfx/geometry/box_f.h" +#include "ui/gfx/geometry/insets.h" +#include "ui/gfx/geometry/insets_f.h" +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/point3_f.h" +#include "ui/gfx/geometry/point_f.h" +#include "ui/gfx/geometry/quad_f.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/rect_f.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/geometry/size_f.h" +#include "ui/gfx/geometry/transform.h" +#include "ui/gfx/geometry/vector2d.h" +#include "ui/gfx/geometry/vector2d_f.h" +#include "ui/gfx/geometry/vector3d_f.h" + +namespace gfx { + +namespace { + +std::string ColorAsString(SkColor color) { + std::ostringstream stream; + stream << std::hex << std::uppercase << "#" << std::setfill('0') + << std::setw(2) << SkColorGetA(color) + << std::setw(2) << SkColorGetR(color) + << std::setw(2) << SkColorGetG(color) + << std::setw(2) << SkColorGetB(color); + return stream.str(); +} + +bool FloatAlmostEqual(float a, float b) { + // FloatLE is the gtest predicate for less than or almost equal to. + return ::testing::FloatLE("a", "b", a, b) && + ::testing::FloatLE("b", "a", b, a); +} + +} // namespace + +::testing::AssertionResult AssertAxisTransform2dFloatEqual( + const char* lhs_expr, + const char* rhs_expr, + const AxisTransform2d& lhs, + const AxisTransform2d& rhs) { + if (FloatAlmostEqual(lhs.scale().x(), rhs.scale().x()) && + FloatAlmostEqual(lhs.scale().y(), rhs.scale().y()) && + FloatAlmostEqual(lhs.translation().x(), rhs.translation().x()) && + FloatAlmostEqual(lhs.translation().y(), rhs.translation().y())) { + return ::testing::AssertionSuccess(); + } + return ::testing::AssertionFailure() + << "Value of: " << rhs_expr << "\n Actual: " << rhs.ToString() + << "\nExpected: " << lhs_expr << "\nWhich is: " << lhs.ToString(); +} + +::testing::AssertionResult AssertBoxFloatEqual(const char* lhs_expr, + const char* rhs_expr, + const BoxF& lhs, + const BoxF& rhs) { + if (FloatAlmostEqual(lhs.x(), rhs.x()) && + FloatAlmostEqual(lhs.y(), rhs.y()) && + FloatAlmostEqual(lhs.z(), rhs.z()) && + FloatAlmostEqual(lhs.width(), rhs.width()) && + FloatAlmostEqual(lhs.height(), rhs.height()) && + FloatAlmostEqual(lhs.depth(), rhs.depth())) { + return ::testing::AssertionSuccess(); + } + return ::testing::AssertionFailure() << "Value of: " << rhs_expr + << "\n Actual: " << rhs.ToString() + << "\nExpected: " << lhs_expr + << "\nWhich is: " << lhs.ToString(); +} + +::testing::AssertionResult AssertPointFloatEqual(const char* lhs_expr, + const char* rhs_expr, + const PointF& lhs, + const PointF& rhs) { + if (FloatAlmostEqual(lhs.x(), rhs.x()) && + FloatAlmostEqual(lhs.y(), rhs.y())) { + return ::testing::AssertionSuccess(); + } + return ::testing::AssertionFailure() + << "Value of: " << rhs_expr << "\n Actual: " << rhs.ToString() + << "\nExpected: " << lhs_expr << "\nWhich is: " << lhs.ToString(); +} + +::testing::AssertionResult AssertRectFloatEqual(const char* lhs_expr, + const char* rhs_expr, + const RectF& lhs, + const RectF& rhs) { + if (FloatAlmostEqual(lhs.x(), rhs.x()) && + FloatAlmostEqual(lhs.y(), rhs.y()) && + FloatAlmostEqual(lhs.width(), rhs.width()) && + FloatAlmostEqual(lhs.height(), rhs.height())) { + return ::testing::AssertionSuccess(); + } + return ::testing::AssertionFailure() + << "Value of: " << rhs_expr << "\n Actual: " << rhs.ToString() + << "\nExpected: " << lhs_expr << "\nWhich is: " << lhs.ToString(); +} + +::testing::AssertionResult AssertSkColorsEqual(const char* lhs_expr, + const char* rhs_expr, + SkColor lhs, + SkColor rhs) { + if (lhs == rhs) { + return ::testing::AssertionSuccess(); + } + return ::testing::AssertionFailure() << "Value of: " << rhs_expr + << "\n Actual: " << ColorAsString(rhs) + << "\nExpected: " << lhs_expr + << "\nWhich is: " << ColorAsString(lhs); +} + +::testing::AssertionResult AssertSizeFFloatEqual(const char* lhs_expr, + const char* rhs_expr, + const SizeF& lhs, + const SizeF& rhs) { + if (FloatAlmostEqual(lhs.width(), rhs.width()) && + FloatAlmostEqual(lhs.height(), rhs.height())) { + return ::testing::AssertionSuccess(); + } + return ::testing::AssertionFailure() + << "Value of: " << rhs_expr << "\n Actual: " << rhs.ToString() + << "\nExpected: " << lhs_expr << "\nWhich is: " << lhs.ToString(); +} + +void PrintTo(const AxisTransform2d& transform, ::std::ostream* os) { + *os << transform.ToString(); +} + +void PrintTo(const BoxF& box, ::std::ostream* os) { + *os << box.ToString(); +} + +void PrintTo(const Point& point, ::std::ostream* os) { + *os << point.ToString(); +} + +void PrintTo(const Point3F& point, ::std::ostream* os) { + *os << point.ToString(); +} + +void PrintTo(const PointF& point, ::std::ostream* os) { + *os << point.ToString(); +} + +void PrintTo(const Insets& insets, ::std::ostream* os) { + *os << insets.ToString(); +} + +void PrintTo(const InsetsF& insets, ::std::ostream* os) { + *os << insets.ToString(); +} + +void PrintTo(const QuadF& quad, ::std::ostream* os) { + *os << quad.ToString(); +} + +void PrintTo(const Rect& rect, ::std::ostream* os) { + *os << rect.ToString(); +} + +void PrintTo(const RectF& rect, ::std::ostream* os) { + *os << rect.ToString(); +} + +void PrintTo(const Size& size, ::std::ostream* os) { + *os << size.ToString(); +} + +void PrintTo(const SizeF& size, ::std::ostream* os) { + *os << size.ToString(); +} + +void PrintTo(const Transform& transform, ::std::ostream* os) { + *os << transform.ToString(); +} + +void PrintTo(const Vector2d& vector, ::std::ostream* os) { + *os << vector.ToString(); +} + +void PrintTo(const Vector2dF& vector, ::std::ostream* os) { + *os << vector.ToString(); +} + +void PrintTo(const Vector3dF& vector, ::std::ostream* os) { + *os << vector.ToString(); +} + +} // namespace gfx diff --git a/test/gfx_util.h b/test/gfx_util.h new file mode 100644 index 000000000000..cb59626e6d60 --- /dev/null +++ b/test/gfx_util.h @@ -0,0 +1,82 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_TEST_GFX_UTIL_H_ +#define UI_GFX_TEST_GFX_UTIL_H_ + +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkColor.h" +#include "ui/gfx/font_list.h" + +namespace gfx { + +class AxisTransform2d; +class BoxF; +class PointF; +class RectF; +class SizeF; + +// Tests should use this scoped setter, instead of calling +// SetDefaultFontDescription directly. +class ScopedDefaultFontDescription { + public: + explicit ScopedDefaultFontDescription(const std::string& font_description) { + FontList::SetDefaultFontDescription(font_description); + } + ~ScopedDefaultFontDescription() { + FontList::SetDefaultFontDescription(std::string()); + } +}; + +#define EXPECT_AXIS_TRANSFORM2D_EQ(a, b) \ + EXPECT_PRED_FORMAT2(::gfx::AssertAxisTransform2dFloatEqual, a, b) + +::testing::AssertionResult AssertAxisTransform2dFloatEqual( + const char* lhs_expr, + const char* rhs_expr, + const AxisTransform2d& lhs, + const AxisTransform2d& rhs); + +#define EXPECT_BOXF_EQ(a, b) \ + EXPECT_PRED_FORMAT2(::gfx::AssertBoxFloatEqual, a, b) + +::testing::AssertionResult AssertBoxFloatEqual(const char* lhs_expr, + const char* rhs_expr, + const BoxF& lhs, + const BoxF& rhs); + +#define EXPECT_POINTF_EQ(a, b) \ + EXPECT_PRED_FORMAT2(::gfx::AssertPointFloatEqual, a, b) + +::testing::AssertionResult AssertPointFloatEqual(const char* lhs_expr, + const char* rhs_expr, + const PointF& lhs, + const PointF& rhs); + +#define EXPECT_RECTF_EQ(a, b) \ + EXPECT_PRED_FORMAT2(::gfx::AssertRectFloatEqual, a, b) + +::testing::AssertionResult AssertRectFloatEqual(const char* lhs_expr, + const char* rhs_expr, + const RectF& lhs, + const RectF& rhs); + +#define EXPECT_SKCOLOR_EQ(a, b) \ + EXPECT_PRED_FORMAT2(::gfx::AssertSkColorsEqual, a, b) + +::testing::AssertionResult AssertSkColorsEqual(const char* lhs_expr, + const char* rhs_expr, + SkColor lhs, + SkColor rhs); + +#define EXPECT_SIZEF_EQ(a, b) \ + EXPECT_PRED_FORMAT2(::gfx::AssertSizeFFloatEqual, a, b) + +::testing::AssertionResult AssertSizeFFloatEqual(const char* lhs_expr, + const char* rhs_expr, + const SizeF& lhs, + const SizeF& rhs); +} // namespace gfx + +#endif // UI_GFX_TEST_GFX_UTIL_H_ diff --git a/test/icc_profiles.cc b/test/icc_profiles.cc new file mode 100644 index 000000000000..4ec059f29d35 --- /dev/null +++ b/test/icc_profiles.cc @@ -0,0 +1,1938 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/test/icc_profiles.h" + +#include "base/cxx17_backports.h" + +namespace gfx { + +namespace { + +const unsigned char generic_rgb_profile_data[] = { + 0x00, 0x00, 0x07, 0xd8, 0x61, 0x70, 0x70, 0x6c, 0x02, 0x20, 0x00, 0x00, + 0x6d, 0x6e, 0x74, 0x72, 0x52, 0x47, 0x42, 0x20, 0x58, 0x59, 0x5a, 0x20, + 0x07, 0xd9, 0x00, 0x02, 0x00, 0x19, 0x00, 0x0b, 0x00, 0x1a, 0x00, 0x0b, + 0x61, 0x63, 0x73, 0x70, 0x41, 0x50, 0x50, 0x4c, 0x00, 0x00, 0x00, 0x00, + 0x61, 0x70, 0x70, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6, 0xd6, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x2d, 0x61, 0x70, 0x70, 0x6c, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, + 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x00, 0x6f, + 0x64, 0x73, 0x63, 0x6d, 0x00, 0x00, 0x01, 0x78, 0x00, 0x00, 0x05, 0x9c, + 0x63, 0x70, 0x72, 0x74, 0x00, 0x00, 0x07, 0x14, 0x00, 0x00, 0x00, 0x38, + 0x77, 0x74, 0x70, 0x74, 0x00, 0x00, 0x07, 0x4c, 0x00, 0x00, 0x00, 0x14, + 0x72, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x07, 0x60, 0x00, 0x00, 0x00, 0x14, + 0x67, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x07, 0x74, 0x00, 0x00, 0x00, 0x14, + 0x62, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x07, 0x88, 0x00, 0x00, 0x00, 0x14, + 0x72, 0x54, 0x52, 0x43, 0x00, 0x00, 0x07, 0x9c, 0x00, 0x00, 0x00, 0x0e, + 0x63, 0x68, 0x61, 0x64, 0x00, 0x00, 0x07, 0xac, 0x00, 0x00, 0x00, 0x2c, + 0x62, 0x54, 0x52, 0x43, 0x00, 0x00, 0x07, 0x9c, 0x00, 0x00, 0x00, 0x0e, + 0x67, 0x54, 0x52, 0x43, 0x00, 0x00, 0x07, 0x9c, 0x00, 0x00, 0x00, 0x0e, + 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, + 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x20, 0x52, 0x47, 0x42, 0x20, + 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x47, 0x65, 0x6e, 0x65, 0x72, + 0x69, 0x63, 0x20, 0x52, 0x47, 0x42, 0x20, 0x50, 0x72, 0x6f, 0x66, 0x69, + 0x6c, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x6d, 0x6c, 0x75, 0x63, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x0c, 0x73, 0x6b, 0x53, 0x4b, + 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x01, 0x84, 0x64, 0x61, 0x44, 0x4b, + 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x01, 0xac, 0x63, 0x61, 0x45, 0x53, + 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x01, 0xda, 0x76, 0x69, 0x56, 0x4e, + 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x01, 0xfe, 0x70, 0x74, 0x42, 0x52, + 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x02, 0x22, 0x75, 0x6b, 0x55, 0x41, + 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x02, 0x48, 0x66, 0x72, 0x46, 0x55, + 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x02, 0x72, 0x68, 0x75, 0x48, 0x55, + 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x02, 0x9a, 0x7a, 0x68, 0x54, 0x57, + 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x02, 0xc2, 0x6e, 0x62, 0x4e, 0x4f, + 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x02, 0xd8, 0x63, 0x73, 0x43, 0x5a, + 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x02, 0xfe, 0x68, 0x65, 0x49, 0x4c, + 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x03, 0x20, 0x69, 0x74, 0x49, 0x54, + 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x03, 0x3e, 0x72, 0x6f, 0x52, 0x4f, + 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x03, 0x66, 0x64, 0x65, 0x44, 0x45, + 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x03, 0x8a, 0x6b, 0x6f, 0x4b, 0x52, + 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x03, 0xb6, 0x73, 0x76, 0x53, 0x45, + 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x02, 0xd8, 0x7a, 0x68, 0x43, 0x4e, + 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x03, 0xcc, 0x6a, 0x61, 0x4a, 0x50, + 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x03, 0xe2, 0x65, 0x6c, 0x47, 0x52, + 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x03, 0xfc, 0x70, 0x74, 0x50, 0x4f, + 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x04, 0x1e, 0x6e, 0x6c, 0x4e, 0x4c, + 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x04, 0x44, 0x65, 0x73, 0x45, 0x53, + 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x04, 0x1e, 0x74, 0x68, 0x54, 0x48, + 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x04, 0x6c, 0x74, 0x72, 0x54, 0x52, + 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x04, 0x90, 0x66, 0x69, 0x46, 0x49, + 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x04, 0xb2, 0x68, 0x72, 0x48, 0x52, + 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x04, 0xda, 0x70, 0x6c, 0x50, 0x4c, + 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x05, 0x02, 0x72, 0x75, 0x52, 0x55, + 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x05, 0x2e, 0x61, 0x72, 0x45, 0x47, + 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x05, 0x50, 0x65, 0x6e, 0x55, 0x53, + 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x05, 0x76, 0x00, 0x56, 0x01, 0x61, + 0x00, 0x65, 0x00, 0x6f, 0x00, 0x62, 0x00, 0x65, 0x00, 0x63, 0x00, 0x6e, + 0x00, 0xfd, 0x00, 0x20, 0x00, 0x52, 0x00, 0x47, 0x00, 0x42, 0x00, 0x20, + 0x00, 0x70, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x69, 0x00, 0x6c, + 0x00, 0x47, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x72, 0x00, 0x65, + 0x00, 0x6c, 0x00, 0x20, 0x00, 0x52, 0x00, 0x47, 0x00, 0x42, 0x00, 0x2d, + 0x00, 0x62, 0x00, 0x65, 0x00, 0x73, 0x00, 0x6b, 0x00, 0x72, 0x00, 0x69, + 0x00, 0x76, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x73, 0x00, 0x65, 0x00, 0x50, + 0x00, 0x65, 0x00, 0x72, 0x00, 0x66, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x20, + 0x00, 0x52, 0x00, 0x47, 0x00, 0x42, 0x00, 0x20, 0x00, 0x67, 0x00, 0x65, + 0x00, 0x6e, 0x00, 0xe8, 0x00, 0x72, 0x00, 0x69, 0x00, 0x63, 0x00, 0x43, + 0x1e, 0xa5, 0x00, 0x75, 0x00, 0x20, 0x00, 0x68, 0x00, 0xec, 0x00, 0x6e, + 0x00, 0x68, 0x00, 0x20, 0x00, 0x52, 0x00, 0x47, 0x00, 0x42, 0x00, 0x20, + 0x00, 0x43, 0x00, 0x68, 0x00, 0x75, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x50, + 0x00, 0x65, 0x00, 0x72, 0x00, 0x66, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x20, + 0x00, 0x52, 0x00, 0x47, 0x00, 0x42, 0x00, 0x20, 0x00, 0x47, 0x00, 0x65, + 0x00, 0x6e, 0x00, 0xe9, 0x00, 0x72, 0x00, 0x69, 0x00, 0x63, 0x00, 0x6f, + 0x04, 0x17, 0x04, 0x30, 0x04, 0x33, 0x04, 0x30, 0x04, 0x3b, 0x04, 0x4c, + 0x04, 0x3d, 0x04, 0x38, 0x04, 0x39, 0x00, 0x20, 0x04, 0x3f, 0x04, 0x40, + 0x04, 0x3e, 0x04, 0x44, 0x04, 0x30, 0x04, 0x39, 0x04, 0x3b, 0x00, 0x20, + 0x00, 0x52, 0x00, 0x47, 0x00, 0x42, 0x00, 0x50, 0x00, 0x72, 0x00, 0x6f, + 0x00, 0x66, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x20, 0x00, 0x67, 0x00, 0xe9, + 0x00, 0x6e, 0x00, 0xe9, 0x00, 0x72, 0x00, 0x69, 0x00, 0x71, 0x00, 0x75, + 0x00, 0x65, 0x00, 0x20, 0x00, 0x52, 0x00, 0x56, 0x00, 0x42, 0x00, 0xc1, + 0x00, 0x6c, 0x00, 0x74, 0x00, 0x61, 0x00, 0x6c, 0x00, 0xe1, 0x00, 0x6e, + 0x00, 0x6f, 0x00, 0x73, 0x00, 0x20, 0x00, 0x52, 0x00, 0x47, 0x00, 0x42, + 0x00, 0x20, 0x00, 0x70, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x69, + 0x00, 0x6c, 0x90, 0x1a, 0x75, 0x28, 0x00, 0x20, 0x00, 0x52, 0x00, 0x47, + 0x00, 0x42, 0x00, 0x20, 0x82, 0x72, 0x5f, 0x69, 0x63, 0xcf, 0x8f, 0xf0, + 0x00, 0x47, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x72, 0x00, 0x69, + 0x00, 0x73, 0x00, 0x6b, 0x00, 0x20, 0x00, 0x52, 0x00, 0x47, 0x00, 0x42, + 0x00, 0x2d, 0x00, 0x70, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x69, + 0x00, 0x6c, 0x00, 0x4f, 0x00, 0x62, 0x00, 0x65, 0x00, 0x63, 0x00, 0x6e, + 0x00, 0xfd, 0x00, 0x20, 0x00, 0x52, 0x00, 0x47, 0x00, 0x42, 0x00, 0x20, + 0x00, 0x70, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x69, 0x00, 0x6c, + 0x05, 0xe4, 0x05, 0xe8, 0x05, 0xd5, 0x05, 0xe4, 0x05, 0xd9, 0x05, 0xdc, + 0x00, 0x20, 0x00, 0x52, 0x00, 0x47, 0x00, 0x42, 0x00, 0x20, 0x05, 0xdb, + 0x05, 0xdc, 0x05, 0xdc, 0x05, 0xd9, 0x00, 0x50, 0x00, 0x72, 0x00, 0x6f, + 0x00, 0x66, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x20, 0x00, 0x52, + 0x00, 0x47, 0x00, 0x42, 0x00, 0x20, 0x00, 0x67, 0x00, 0x65, 0x00, 0x6e, + 0x00, 0x65, 0x00, 0x72, 0x00, 0x69, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x50, + 0x00, 0x72, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x20, + 0x00, 0x52, 0x00, 0x47, 0x00, 0x42, 0x00, 0x20, 0x00, 0x67, 0x00, 0x65, + 0x00, 0x6e, 0x00, 0x65, 0x00, 0x72, 0x00, 0x69, 0x00, 0x63, 0x00, 0x41, + 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x67, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x65, + 0x00, 0x69, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x73, 0x00, 0x20, 0x00, 0x52, + 0x00, 0x47, 0x00, 0x42, 0x00, 0x2d, 0x00, 0x50, 0x00, 0x72, 0x00, 0x6f, + 0x00, 0x66, 0x00, 0x69, 0x00, 0x6c, 0xc7, 0x7c, 0xbc, 0x18, 0x00, 0x20, + 0x00, 0x52, 0x00, 0x47, 0x00, 0x42, 0x00, 0x20, 0xd5, 0x04, 0xb8, 0x5c, + 0xd3, 0x0c, 0xc7, 0x7c, 0x66, 0x6e, 0x90, 0x1a, 0x00, 0x20, 0x00, 0x52, + 0x00, 0x47, 0x00, 0x42, 0x00, 0x20, 0x63, 0xcf, 0x8f, 0xf0, 0x65, 0x87, + 0x4e, 0xf6, 0x4e, 0x00, 0x82, 0x2c, 0x00, 0x20, 0x00, 0x52, 0x00, 0x47, + 0x00, 0x42, 0x00, 0x20, 0x30, 0xd7, 0x30, 0xed, 0x30, 0xd5, 0x30, 0xa1, + 0x30, 0xa4, 0x30, 0xeb, 0x03, 0x93, 0x03, 0xb5, 0x03, 0xbd, 0x03, 0xb9, + 0x03, 0xba, 0x03, 0xcc, 0x00, 0x20, 0x03, 0xc0, 0x03, 0xc1, 0x03, 0xbf, + 0x03, 0xc6, 0x03, 0xaf, 0x03, 0xbb, 0x00, 0x20, 0x00, 0x52, 0x00, 0x47, + 0x00, 0x42, 0x00, 0x50, 0x00, 0x65, 0x00, 0x72, 0x00, 0x66, 0x00, 0x69, + 0x00, 0x6c, 0x00, 0x20, 0x00, 0x52, 0x00, 0x47, 0x00, 0x42, 0x00, 0x20, + 0x00, 0x67, 0x00, 0x65, 0x00, 0x6e, 0x00, 0xe9, 0x00, 0x72, 0x00, 0x69, + 0x00, 0x63, 0x00, 0x6f, 0x00, 0x41, 0x00, 0x6c, 0x00, 0x67, 0x00, 0x65, + 0x00, 0x6d, 0x00, 0x65, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x20, 0x00, 0x52, + 0x00, 0x47, 0x00, 0x42, 0x00, 0x2d, 0x00, 0x70, 0x00, 0x72, 0x00, 0x6f, + 0x00, 0x66, 0x00, 0x69, 0x00, 0x65, 0x00, 0x6c, 0x0e, 0x42, 0x0e, 0x1b, + 0x0e, 0x23, 0x0e, 0x44, 0x0e, 0x1f, 0x0e, 0x25, 0x0e, 0x4c, 0x00, 0x20, + 0x00, 0x52, 0x00, 0x47, 0x00, 0x42, 0x00, 0x20, 0x0e, 0x17, 0x0e, 0x31, + 0x0e, 0x48, 0x0e, 0x27, 0x0e, 0x44, 0x0e, 0x1b, 0x00, 0x47, 0x00, 0x65, + 0x00, 0x6e, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x20, 0x00, 0x52, 0x00, 0x47, + 0x00, 0x42, 0x00, 0x20, 0x00, 0x50, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x66, + 0x00, 0x69, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x59, 0x00, 0x6c, 0x00, 0x65, + 0x00, 0x69, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x20, 0x00, 0x52, + 0x00, 0x47, 0x00, 0x42, 0x00, 0x2d, 0x00, 0x70, 0x00, 0x72, 0x00, 0x6f, + 0x00, 0x66, 0x00, 0x69, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x47, + 0x00, 0x65, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x72, 0x00, 0x69, 0x01, 0x0d, + 0x00, 0x6b, 0x00, 0x69, 0x00, 0x20, 0x00, 0x52, 0x00, 0x47, 0x00, 0x42, + 0x00, 0x20, 0x00, 0x70, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x69, + 0x00, 0x6c, 0x00, 0x55, 0x00, 0x6e, 0x00, 0x69, 0x00, 0x77, 0x00, 0x65, + 0x00, 0x72, 0x00, 0x73, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x6e, 0x00, 0x79, + 0x00, 0x20, 0x00, 0x70, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x69, + 0x00, 0x6c, 0x00, 0x20, 0x00, 0x52, 0x00, 0x47, 0x00, 0x42, 0x04, 0x1e, + 0x04, 0x31, 0x04, 0x49, 0x04, 0x38, 0x04, 0x39, 0x00, 0x20, 0x04, 0x3f, + 0x04, 0x40, 0x04, 0x3e, 0x04, 0x44, 0x04, 0x38, 0x04, 0x3b, 0x04, 0x4c, + 0x00, 0x20, 0x00, 0x52, 0x00, 0x47, 0x00, 0x42, 0x06, 0x45, 0x06, 0x44, + 0x06, 0x41, 0x00, 0x20, 0x06, 0x2a, 0x06, 0x39, 0x06, 0x31, 0x06, 0x4a, + 0x06, 0x41, 0x00, 0x20, 0x00, 0x52, 0x00, 0x47, 0x00, 0x42, 0x00, 0x20, + 0x06, 0x27, 0x06, 0x44, 0x06, 0x39, 0x06, 0x27, 0x06, 0x45, 0x00, 0x47, + 0x00, 0x65, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x72, 0x00, 0x69, 0x00, 0x63, + 0x00, 0x20, 0x00, 0x52, 0x00, 0x47, 0x00, 0x42, 0x00, 0x20, 0x00, 0x50, + 0x00, 0x72, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x65, + 0x74, 0x65, 0x78, 0x74, 0x00, 0x00, 0x00, 0x00, 0x43, 0x6f, 0x70, 0x79, + 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x32, 0x30, 0x30, 0x37, 0x20, 0x41, + 0x70, 0x70, 0x6c, 0x65, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x2c, 0x20, 0x61, + 0x6c, 0x6c, 0x20, 0x72, 0x69, 0x67, 0x68, 0x74, 0x73, 0x20, 0x72, 0x65, + 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x2e, 0x00, 0x58, 0x59, 0x5a, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf3, 0x52, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x01, 0x16, 0xcf, 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x74, 0x4d, 0x00, 0x00, 0x3d, 0xee, 0x00, 0x00, 0x03, 0xd0, + 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5a, 0x75, + 0x00, 0x00, 0xac, 0x73, 0x00, 0x00, 0x17, 0x34, 0x58, 0x59, 0x5a, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x1a, 0x00, 0x00, 0x15, 0x9f, + 0x00, 0x00, 0xb8, 0x36, 0x63, 0x75, 0x72, 0x76, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x01, 0xcd, 0x00, 0x00, 0x73, 0x66, 0x33, 0x32, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0c, 0x42, 0x00, 0x00, 0x05, 0xde, + 0xff, 0xff, 0xf3, 0x26, 0x00, 0x00, 0x07, 0x92, 0x00, 0x00, 0xfd, 0x91, + 0xff, 0xff, 0xfb, 0xa2, 0xff, 0xff, 0xfd, 0xa3, 0x00, 0x00, 0x03, 0xdc, + 0x00, 0x00, 0xc0, 0x6c}; + +const unsigned char srgb_profile_data[] = { + 0x00, 0x00, 0x0c, 0x48, 0x4c, 0x69, 0x6e, 0x6f, 0x02, 0x10, 0x00, 0x00, + 0x6d, 0x6e, 0x74, 0x72, 0x52, 0x47, 0x42, 0x20, 0x58, 0x59, 0x5a, 0x20, + 0x07, 0xce, 0x00, 0x02, 0x00, 0x09, 0x00, 0x06, 0x00, 0x31, 0x00, 0x00, + 0x61, 0x63, 0x73, 0x70, 0x4d, 0x53, 0x46, 0x54, 0x00, 0x00, 0x00, 0x00, + 0x49, 0x45, 0x43, 0x20, 0x73, 0x52, 0x47, 0x42, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6, 0xd6, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x2d, 0x48, 0x50, 0x20, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, + 0x63, 0x70, 0x72, 0x74, 0x00, 0x00, 0x01, 0x50, 0x00, 0x00, 0x00, 0x33, + 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x01, 0x84, 0x00, 0x00, 0x00, 0x6c, + 0x77, 0x74, 0x70, 0x74, 0x00, 0x00, 0x01, 0xf0, 0x00, 0x00, 0x00, 0x14, + 0x62, 0x6b, 0x70, 0x74, 0x00, 0x00, 0x02, 0x04, 0x00, 0x00, 0x00, 0x14, + 0x72, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x02, 0x18, 0x00, 0x00, 0x00, 0x14, + 0x67, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x02, 0x2c, 0x00, 0x00, 0x00, 0x14, + 0x62, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x14, + 0x64, 0x6d, 0x6e, 0x64, 0x00, 0x00, 0x02, 0x54, 0x00, 0x00, 0x00, 0x70, + 0x64, 0x6d, 0x64, 0x64, 0x00, 0x00, 0x02, 0xc4, 0x00, 0x00, 0x00, 0x88, + 0x76, 0x75, 0x65, 0x64, 0x00, 0x00, 0x03, 0x4c, 0x00, 0x00, 0x00, 0x86, + 0x76, 0x69, 0x65, 0x77, 0x00, 0x00, 0x03, 0xd4, 0x00, 0x00, 0x00, 0x24, + 0x6c, 0x75, 0x6d, 0x69, 0x00, 0x00, 0x03, 0xf8, 0x00, 0x00, 0x00, 0x14, + 0x6d, 0x65, 0x61, 0x73, 0x00, 0x00, 0x04, 0x0c, 0x00, 0x00, 0x00, 0x24, + 0x74, 0x65, 0x63, 0x68, 0x00, 0x00, 0x04, 0x30, 0x00, 0x00, 0x00, 0x0c, + 0x72, 0x54, 0x52, 0x43, 0x00, 0x00, 0x04, 0x3c, 0x00, 0x00, 0x08, 0x0c, + 0x67, 0x54, 0x52, 0x43, 0x00, 0x00, 0x04, 0x3c, 0x00, 0x00, 0x08, 0x0c, + 0x62, 0x54, 0x52, 0x43, 0x00, 0x00, 0x04, 0x3c, 0x00, 0x00, 0x08, 0x0c, + 0x74, 0x65, 0x78, 0x74, 0x00, 0x00, 0x00, 0x00, 0x43, 0x6f, 0x70, 0x79, + 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x28, 0x63, 0x29, 0x20, 0x31, 0x39, + 0x39, 0x38, 0x20, 0x48, 0x65, 0x77, 0x6c, 0x65, 0x74, 0x74, 0x2d, 0x50, + 0x61, 0x63, 0x6b, 0x61, 0x72, 0x64, 0x20, 0x43, 0x6f, 0x6d, 0x70, 0x61, + 0x6e, 0x79, 0x00, 0x00, 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x12, 0x73, 0x52, 0x47, 0x42, 0x20, 0x49, 0x45, 0x43, + 0x36, 0x31, 0x39, 0x36, 0x36, 0x2d, 0x32, 0x2e, 0x31, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x73, 0x52, 0x47, + 0x42, 0x20, 0x49, 0x45, 0x43, 0x36, 0x31, 0x39, 0x36, 0x36, 0x2d, 0x32, + 0x2e, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf3, 0x51, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x16, 0xcc, + 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x59, 0x5a, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6f, 0xa2, 0x00, 0x00, 0x38, 0xf5, + 0x00, 0x00, 0x03, 0x90, 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x62, 0x99, 0x00, 0x00, 0xb7, 0x85, 0x00, 0x00, 0x18, 0xda, + 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0xa0, + 0x00, 0x00, 0x0f, 0x84, 0x00, 0x00, 0xb6, 0xcf, 0x64, 0x65, 0x73, 0x63, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x49, 0x45, 0x43, 0x20, + 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x69, + 0x65, 0x63, 0x2e, 0x63, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x16, 0x49, 0x45, 0x43, 0x20, 0x68, 0x74, 0x74, + 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x69, 0x65, 0x63, 0x2e, + 0x63, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2e, + 0x49, 0x45, 0x43, 0x20, 0x36, 0x31, 0x39, 0x36, 0x36, 0x2d, 0x32, 0x2e, + 0x31, 0x20, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x20, 0x52, 0x47, + 0x42, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x75, 0x72, 0x20, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x20, 0x2d, 0x20, 0x73, 0x52, 0x47, 0x42, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2e, 0x49, 0x45, 0x43, + 0x20, 0x36, 0x31, 0x39, 0x36, 0x36, 0x2d, 0x32, 0x2e, 0x31, 0x20, 0x44, + 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x20, 0x52, 0x47, 0x42, 0x20, 0x63, + 0x6f, 0x6c, 0x6f, 0x75, 0x72, 0x20, 0x73, 0x70, 0x61, 0x63, 0x65, 0x20, + 0x2d, 0x20, 0x73, 0x52, 0x47, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x2c, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, + 0x65, 0x20, 0x56, 0x69, 0x65, 0x77, 0x69, 0x6e, 0x67, 0x20, 0x43, 0x6f, + 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x6e, 0x20, 0x49, + 0x45, 0x43, 0x36, 0x31, 0x39, 0x36, 0x36, 0x2d, 0x32, 0x2e, 0x31, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x52, + 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x20, 0x56, 0x69, 0x65, + 0x77, 0x69, 0x6e, 0x67, 0x20, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x20, 0x69, 0x6e, 0x20, 0x49, 0x45, 0x43, 0x36, 0x31, 0x39, + 0x36, 0x36, 0x2d, 0x32, 0x2e, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0x69, 0x65, 0x77, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0xa4, 0xfe, 0x00, 0x14, 0x5f, 0x2e, + 0x00, 0x10, 0xcf, 0x14, 0x00, 0x03, 0xed, 0xcc, 0x00, 0x04, 0x13, 0x0b, + 0x00, 0x03, 0x5c, 0x9e, 0x00, 0x00, 0x00, 0x01, 0x58, 0x59, 0x5a, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x09, 0x56, 0x00, 0x50, 0x00, 0x00, + 0x00, 0x57, 0x1f, 0xe7, 0x6d, 0x65, 0x61, 0x73, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x8f, + 0x00, 0x00, 0x00, 0x02, 0x73, 0x69, 0x67, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x43, 0x52, 0x54, 0x20, 0x63, 0x75, 0x72, 0x76, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x0a, 0x00, 0x0f, + 0x00, 0x14, 0x00, 0x19, 0x00, 0x1e, 0x00, 0x23, 0x00, 0x28, 0x00, 0x2d, + 0x00, 0x32, 0x00, 0x37, 0x00, 0x3b, 0x00, 0x40, 0x00, 0x45, 0x00, 0x4a, + 0x00, 0x4f, 0x00, 0x54, 0x00, 0x59, 0x00, 0x5e, 0x00, 0x63, 0x00, 0x68, + 0x00, 0x6d, 0x00, 0x72, 0x00, 0x77, 0x00, 0x7c, 0x00, 0x81, 0x00, 0x86, + 0x00, 0x8b, 0x00, 0x90, 0x00, 0x95, 0x00, 0x9a, 0x00, 0x9f, 0x00, 0xa4, + 0x00, 0xa9, 0x00, 0xae, 0x00, 0xb2, 0x00, 0xb7, 0x00, 0xbc, 0x00, 0xc1, + 0x00, 0xc6, 0x00, 0xcb, 0x00, 0xd0, 0x00, 0xd5, 0x00, 0xdb, 0x00, 0xe0, + 0x00, 0xe5, 0x00, 0xeb, 0x00, 0xf0, 0x00, 0xf6, 0x00, 0xfb, 0x01, 0x01, + 0x01, 0x07, 0x01, 0x0d, 0x01, 0x13, 0x01, 0x19, 0x01, 0x1f, 0x01, 0x25, + 0x01, 0x2b, 0x01, 0x32, 0x01, 0x38, 0x01, 0x3e, 0x01, 0x45, 0x01, 0x4c, + 0x01, 0x52, 0x01, 0x59, 0x01, 0x60, 0x01, 0x67, 0x01, 0x6e, 0x01, 0x75, + 0x01, 0x7c, 0x01, 0x83, 0x01, 0x8b, 0x01, 0x92, 0x01, 0x9a, 0x01, 0xa1, + 0x01, 0xa9, 0x01, 0xb1, 0x01, 0xb9, 0x01, 0xc1, 0x01, 0xc9, 0x01, 0xd1, + 0x01, 0xd9, 0x01, 0xe1, 0x01, 0xe9, 0x01, 0xf2, 0x01, 0xfa, 0x02, 0x03, + 0x02, 0x0c, 0x02, 0x14, 0x02, 0x1d, 0x02, 0x26, 0x02, 0x2f, 0x02, 0x38, + 0x02, 0x41, 0x02, 0x4b, 0x02, 0x54, 0x02, 0x5d, 0x02, 0x67, 0x02, 0x71, + 0x02, 0x7a, 0x02, 0x84, 0x02, 0x8e, 0x02, 0x98, 0x02, 0xa2, 0x02, 0xac, + 0x02, 0xb6, 0x02, 0xc1, 0x02, 0xcb, 0x02, 0xd5, 0x02, 0xe0, 0x02, 0xeb, + 0x02, 0xf5, 0x03, 0x00, 0x03, 0x0b, 0x03, 0x16, 0x03, 0x21, 0x03, 0x2d, + 0x03, 0x38, 0x03, 0x43, 0x03, 0x4f, 0x03, 0x5a, 0x03, 0x66, 0x03, 0x72, + 0x03, 0x7e, 0x03, 0x8a, 0x03, 0x96, 0x03, 0xa2, 0x03, 0xae, 0x03, 0xba, + 0x03, 0xc7, 0x03, 0xd3, 0x03, 0xe0, 0x03, 0xec, 0x03, 0xf9, 0x04, 0x06, + 0x04, 0x13, 0x04, 0x20, 0x04, 0x2d, 0x04, 0x3b, 0x04, 0x48, 0x04, 0x55, + 0x04, 0x63, 0x04, 0x71, 0x04, 0x7e, 0x04, 0x8c, 0x04, 0x9a, 0x04, 0xa8, + 0x04, 0xb6, 0x04, 0xc4, 0x04, 0xd3, 0x04, 0xe1, 0x04, 0xf0, 0x04, 0xfe, + 0x05, 0x0d, 0x05, 0x1c, 0x05, 0x2b, 0x05, 0x3a, 0x05, 0x49, 0x05, 0x58, + 0x05, 0x67, 0x05, 0x77, 0x05, 0x86, 0x05, 0x96, 0x05, 0xa6, 0x05, 0xb5, + 0x05, 0xc5, 0x05, 0xd5, 0x05, 0xe5, 0x05, 0xf6, 0x06, 0x06, 0x06, 0x16, + 0x06, 0x27, 0x06, 0x37, 0x06, 0x48, 0x06, 0x59, 0x06, 0x6a, 0x06, 0x7b, + 0x06, 0x8c, 0x06, 0x9d, 0x06, 0xaf, 0x06, 0xc0, 0x06, 0xd1, 0x06, 0xe3, + 0x06, 0xf5, 0x07, 0x07, 0x07, 0x19, 0x07, 0x2b, 0x07, 0x3d, 0x07, 0x4f, + 0x07, 0x61, 0x07, 0x74, 0x07, 0x86, 0x07, 0x99, 0x07, 0xac, 0x07, 0xbf, + 0x07, 0xd2, 0x07, 0xe5, 0x07, 0xf8, 0x08, 0x0b, 0x08, 0x1f, 0x08, 0x32, + 0x08, 0x46, 0x08, 0x5a, 0x08, 0x6e, 0x08, 0x82, 0x08, 0x96, 0x08, 0xaa, + 0x08, 0xbe, 0x08, 0xd2, 0x08, 0xe7, 0x08, 0xfb, 0x09, 0x10, 0x09, 0x25, + 0x09, 0x3a, 0x09, 0x4f, 0x09, 0x64, 0x09, 0x79, 0x09, 0x8f, 0x09, 0xa4, + 0x09, 0xba, 0x09, 0xcf, 0x09, 0xe5, 0x09, 0xfb, 0x0a, 0x11, 0x0a, 0x27, + 0x0a, 0x3d, 0x0a, 0x54, 0x0a, 0x6a, 0x0a, 0x81, 0x0a, 0x98, 0x0a, 0xae, + 0x0a, 0xc5, 0x0a, 0xdc, 0x0a, 0xf3, 0x0b, 0x0b, 0x0b, 0x22, 0x0b, 0x39, + 0x0b, 0x51, 0x0b, 0x69, 0x0b, 0x80, 0x0b, 0x98, 0x0b, 0xb0, 0x0b, 0xc8, + 0x0b, 0xe1, 0x0b, 0xf9, 0x0c, 0x12, 0x0c, 0x2a, 0x0c, 0x43, 0x0c, 0x5c, + 0x0c, 0x75, 0x0c, 0x8e, 0x0c, 0xa7, 0x0c, 0xc0, 0x0c, 0xd9, 0x0c, 0xf3, + 0x0d, 0x0d, 0x0d, 0x26, 0x0d, 0x40, 0x0d, 0x5a, 0x0d, 0x74, 0x0d, 0x8e, + 0x0d, 0xa9, 0x0d, 0xc3, 0x0d, 0xde, 0x0d, 0xf8, 0x0e, 0x13, 0x0e, 0x2e, + 0x0e, 0x49, 0x0e, 0x64, 0x0e, 0x7f, 0x0e, 0x9b, 0x0e, 0xb6, 0x0e, 0xd2, + 0x0e, 0xee, 0x0f, 0x09, 0x0f, 0x25, 0x0f, 0x41, 0x0f, 0x5e, 0x0f, 0x7a, + 0x0f, 0x96, 0x0f, 0xb3, 0x0f, 0xcf, 0x0f, 0xec, 0x10, 0x09, 0x10, 0x26, + 0x10, 0x43, 0x10, 0x61, 0x10, 0x7e, 0x10, 0x9b, 0x10, 0xb9, 0x10, 0xd7, + 0x10, 0xf5, 0x11, 0x13, 0x11, 0x31, 0x11, 0x4f, 0x11, 0x6d, 0x11, 0x8c, + 0x11, 0xaa, 0x11, 0xc9, 0x11, 0xe8, 0x12, 0x07, 0x12, 0x26, 0x12, 0x45, + 0x12, 0x64, 0x12, 0x84, 0x12, 0xa3, 0x12, 0xc3, 0x12, 0xe3, 0x13, 0x03, + 0x13, 0x23, 0x13, 0x43, 0x13, 0x63, 0x13, 0x83, 0x13, 0xa4, 0x13, 0xc5, + 0x13, 0xe5, 0x14, 0x06, 0x14, 0x27, 0x14, 0x49, 0x14, 0x6a, 0x14, 0x8b, + 0x14, 0xad, 0x14, 0xce, 0x14, 0xf0, 0x15, 0x12, 0x15, 0x34, 0x15, 0x56, + 0x15, 0x78, 0x15, 0x9b, 0x15, 0xbd, 0x15, 0xe0, 0x16, 0x03, 0x16, 0x26, + 0x16, 0x49, 0x16, 0x6c, 0x16, 0x8f, 0x16, 0xb2, 0x16, 0xd6, 0x16, 0xfa, + 0x17, 0x1d, 0x17, 0x41, 0x17, 0x65, 0x17, 0x89, 0x17, 0xae, 0x17, 0xd2, + 0x17, 0xf7, 0x18, 0x1b, 0x18, 0x40, 0x18, 0x65, 0x18, 0x8a, 0x18, 0xaf, + 0x18, 0xd5, 0x18, 0xfa, 0x19, 0x20, 0x19, 0x45, 0x19, 0x6b, 0x19, 0x91, + 0x19, 0xb7, 0x19, 0xdd, 0x1a, 0x04, 0x1a, 0x2a, 0x1a, 0x51, 0x1a, 0x77, + 0x1a, 0x9e, 0x1a, 0xc5, 0x1a, 0xec, 0x1b, 0x14, 0x1b, 0x3b, 0x1b, 0x63, + 0x1b, 0x8a, 0x1b, 0xb2, 0x1b, 0xda, 0x1c, 0x02, 0x1c, 0x2a, 0x1c, 0x52, + 0x1c, 0x7b, 0x1c, 0xa3, 0x1c, 0xcc, 0x1c, 0xf5, 0x1d, 0x1e, 0x1d, 0x47, + 0x1d, 0x70, 0x1d, 0x99, 0x1d, 0xc3, 0x1d, 0xec, 0x1e, 0x16, 0x1e, 0x40, + 0x1e, 0x6a, 0x1e, 0x94, 0x1e, 0xbe, 0x1e, 0xe9, 0x1f, 0x13, 0x1f, 0x3e, + 0x1f, 0x69, 0x1f, 0x94, 0x1f, 0xbf, 0x1f, 0xea, 0x20, 0x15, 0x20, 0x41, + 0x20, 0x6c, 0x20, 0x98, 0x20, 0xc4, 0x20, 0xf0, 0x21, 0x1c, 0x21, 0x48, + 0x21, 0x75, 0x21, 0xa1, 0x21, 0xce, 0x21, 0xfb, 0x22, 0x27, 0x22, 0x55, + 0x22, 0x82, 0x22, 0xaf, 0x22, 0xdd, 0x23, 0x0a, 0x23, 0x38, 0x23, 0x66, + 0x23, 0x94, 0x23, 0xc2, 0x23, 0xf0, 0x24, 0x1f, 0x24, 0x4d, 0x24, 0x7c, + 0x24, 0xab, 0x24, 0xda, 0x25, 0x09, 0x25, 0x38, 0x25, 0x68, 0x25, 0x97, + 0x25, 0xc7, 0x25, 0xf7, 0x26, 0x27, 0x26, 0x57, 0x26, 0x87, 0x26, 0xb7, + 0x26, 0xe8, 0x27, 0x18, 0x27, 0x49, 0x27, 0x7a, 0x27, 0xab, 0x27, 0xdc, + 0x28, 0x0d, 0x28, 0x3f, 0x28, 0x71, 0x28, 0xa2, 0x28, 0xd4, 0x29, 0x06, + 0x29, 0x38, 0x29, 0x6b, 0x29, 0x9d, 0x29, 0xd0, 0x2a, 0x02, 0x2a, 0x35, + 0x2a, 0x68, 0x2a, 0x9b, 0x2a, 0xcf, 0x2b, 0x02, 0x2b, 0x36, 0x2b, 0x69, + 0x2b, 0x9d, 0x2b, 0xd1, 0x2c, 0x05, 0x2c, 0x39, 0x2c, 0x6e, 0x2c, 0xa2, + 0x2c, 0xd7, 0x2d, 0x0c, 0x2d, 0x41, 0x2d, 0x76, 0x2d, 0xab, 0x2d, 0xe1, + 0x2e, 0x16, 0x2e, 0x4c, 0x2e, 0x82, 0x2e, 0xb7, 0x2e, 0xee, 0x2f, 0x24, + 0x2f, 0x5a, 0x2f, 0x91, 0x2f, 0xc7, 0x2f, 0xfe, 0x30, 0x35, 0x30, 0x6c, + 0x30, 0xa4, 0x30, 0xdb, 0x31, 0x12, 0x31, 0x4a, 0x31, 0x82, 0x31, 0xba, + 0x31, 0xf2, 0x32, 0x2a, 0x32, 0x63, 0x32, 0x9b, 0x32, 0xd4, 0x33, 0x0d, + 0x33, 0x46, 0x33, 0x7f, 0x33, 0xb8, 0x33, 0xf1, 0x34, 0x2b, 0x34, 0x65, + 0x34, 0x9e, 0x34, 0xd8, 0x35, 0x13, 0x35, 0x4d, 0x35, 0x87, 0x35, 0xc2, + 0x35, 0xfd, 0x36, 0x37, 0x36, 0x72, 0x36, 0xae, 0x36, 0xe9, 0x37, 0x24, + 0x37, 0x60, 0x37, 0x9c, 0x37, 0xd7, 0x38, 0x14, 0x38, 0x50, 0x38, 0x8c, + 0x38, 0xc8, 0x39, 0x05, 0x39, 0x42, 0x39, 0x7f, 0x39, 0xbc, 0x39, 0xf9, + 0x3a, 0x36, 0x3a, 0x74, 0x3a, 0xb2, 0x3a, 0xef, 0x3b, 0x2d, 0x3b, 0x6b, + 0x3b, 0xaa, 0x3b, 0xe8, 0x3c, 0x27, 0x3c, 0x65, 0x3c, 0xa4, 0x3c, 0xe3, + 0x3d, 0x22, 0x3d, 0x61, 0x3d, 0xa1, 0x3d, 0xe0, 0x3e, 0x20, 0x3e, 0x60, + 0x3e, 0xa0, 0x3e, 0xe0, 0x3f, 0x21, 0x3f, 0x61, 0x3f, 0xa2, 0x3f, 0xe2, + 0x40, 0x23, 0x40, 0x64, 0x40, 0xa6, 0x40, 0xe7, 0x41, 0x29, 0x41, 0x6a, + 0x41, 0xac, 0x41, 0xee, 0x42, 0x30, 0x42, 0x72, 0x42, 0xb5, 0x42, 0xf7, + 0x43, 0x3a, 0x43, 0x7d, 0x43, 0xc0, 0x44, 0x03, 0x44, 0x47, 0x44, 0x8a, + 0x44, 0xce, 0x45, 0x12, 0x45, 0x55, 0x45, 0x9a, 0x45, 0xde, 0x46, 0x22, + 0x46, 0x67, 0x46, 0xab, 0x46, 0xf0, 0x47, 0x35, 0x47, 0x7b, 0x47, 0xc0, + 0x48, 0x05, 0x48, 0x4b, 0x48, 0x91, 0x48, 0xd7, 0x49, 0x1d, 0x49, 0x63, + 0x49, 0xa9, 0x49, 0xf0, 0x4a, 0x37, 0x4a, 0x7d, 0x4a, 0xc4, 0x4b, 0x0c, + 0x4b, 0x53, 0x4b, 0x9a, 0x4b, 0xe2, 0x4c, 0x2a, 0x4c, 0x72, 0x4c, 0xba, + 0x4d, 0x02, 0x4d, 0x4a, 0x4d, 0x93, 0x4d, 0xdc, 0x4e, 0x25, 0x4e, 0x6e, + 0x4e, 0xb7, 0x4f, 0x00, 0x4f, 0x49, 0x4f, 0x93, 0x4f, 0xdd, 0x50, 0x27, + 0x50, 0x71, 0x50, 0xbb, 0x51, 0x06, 0x51, 0x50, 0x51, 0x9b, 0x51, 0xe6, + 0x52, 0x31, 0x52, 0x7c, 0x52, 0xc7, 0x53, 0x13, 0x53, 0x5f, 0x53, 0xaa, + 0x53, 0xf6, 0x54, 0x42, 0x54, 0x8f, 0x54, 0xdb, 0x55, 0x28, 0x55, 0x75, + 0x55, 0xc2, 0x56, 0x0f, 0x56, 0x5c, 0x56, 0xa9, 0x56, 0xf7, 0x57, 0x44, + 0x57, 0x92, 0x57, 0xe0, 0x58, 0x2f, 0x58, 0x7d, 0x58, 0xcb, 0x59, 0x1a, + 0x59, 0x69, 0x59, 0xb8, 0x5a, 0x07, 0x5a, 0x56, 0x5a, 0xa6, 0x5a, 0xf5, + 0x5b, 0x45, 0x5b, 0x95, 0x5b, 0xe5, 0x5c, 0x35, 0x5c, 0x86, 0x5c, 0xd6, + 0x5d, 0x27, 0x5d, 0x78, 0x5d, 0xc9, 0x5e, 0x1a, 0x5e, 0x6c, 0x5e, 0xbd, + 0x5f, 0x0f, 0x5f, 0x61, 0x5f, 0xb3, 0x60, 0x05, 0x60, 0x57, 0x60, 0xaa, + 0x60, 0xfc, 0x61, 0x4f, 0x61, 0xa2, 0x61, 0xf5, 0x62, 0x49, 0x62, 0x9c, + 0x62, 0xf0, 0x63, 0x43, 0x63, 0x97, 0x63, 0xeb, 0x64, 0x40, 0x64, 0x94, + 0x64, 0xe9, 0x65, 0x3d, 0x65, 0x92, 0x65, 0xe7, 0x66, 0x3d, 0x66, 0x92, + 0x66, 0xe8, 0x67, 0x3d, 0x67, 0x93, 0x67, 0xe9, 0x68, 0x3f, 0x68, 0x96, + 0x68, 0xec, 0x69, 0x43, 0x69, 0x9a, 0x69, 0xf1, 0x6a, 0x48, 0x6a, 0x9f, + 0x6a, 0xf7, 0x6b, 0x4f, 0x6b, 0xa7, 0x6b, 0xff, 0x6c, 0x57, 0x6c, 0xaf, + 0x6d, 0x08, 0x6d, 0x60, 0x6d, 0xb9, 0x6e, 0x12, 0x6e, 0x6b, 0x6e, 0xc4, + 0x6f, 0x1e, 0x6f, 0x78, 0x6f, 0xd1, 0x70, 0x2b, 0x70, 0x86, 0x70, 0xe0, + 0x71, 0x3a, 0x71, 0x95, 0x71, 0xf0, 0x72, 0x4b, 0x72, 0xa6, 0x73, 0x01, + 0x73, 0x5d, 0x73, 0xb8, 0x74, 0x14, 0x74, 0x70, 0x74, 0xcc, 0x75, 0x28, + 0x75, 0x85, 0x75, 0xe1, 0x76, 0x3e, 0x76, 0x9b, 0x76, 0xf8, 0x77, 0x56, + 0x77, 0xb3, 0x78, 0x11, 0x78, 0x6e, 0x78, 0xcc, 0x79, 0x2a, 0x79, 0x89, + 0x79, 0xe7, 0x7a, 0x46, 0x7a, 0xa5, 0x7b, 0x04, 0x7b, 0x63, 0x7b, 0xc2, + 0x7c, 0x21, 0x7c, 0x81, 0x7c, 0xe1, 0x7d, 0x41, 0x7d, 0xa1, 0x7e, 0x01, + 0x7e, 0x62, 0x7e, 0xc2, 0x7f, 0x23, 0x7f, 0x84, 0x7f, 0xe5, 0x80, 0x47, + 0x80, 0xa8, 0x81, 0x0a, 0x81, 0x6b, 0x81, 0xcd, 0x82, 0x30, 0x82, 0x92, + 0x82, 0xf4, 0x83, 0x57, 0x83, 0xba, 0x84, 0x1d, 0x84, 0x80, 0x84, 0xe3, + 0x85, 0x47, 0x85, 0xab, 0x86, 0x0e, 0x86, 0x72, 0x86, 0xd7, 0x87, 0x3b, + 0x87, 0x9f, 0x88, 0x04, 0x88, 0x69, 0x88, 0xce, 0x89, 0x33, 0x89, 0x99, + 0x89, 0xfe, 0x8a, 0x64, 0x8a, 0xca, 0x8b, 0x30, 0x8b, 0x96, 0x8b, 0xfc, + 0x8c, 0x63, 0x8c, 0xca, 0x8d, 0x31, 0x8d, 0x98, 0x8d, 0xff, 0x8e, 0x66, + 0x8e, 0xce, 0x8f, 0x36, 0x8f, 0x9e, 0x90, 0x06, 0x90, 0x6e, 0x90, 0xd6, + 0x91, 0x3f, 0x91, 0xa8, 0x92, 0x11, 0x92, 0x7a, 0x92, 0xe3, 0x93, 0x4d, + 0x93, 0xb6, 0x94, 0x20, 0x94, 0x8a, 0x94, 0xf4, 0x95, 0x5f, 0x95, 0xc9, + 0x96, 0x34, 0x96, 0x9f, 0x97, 0x0a, 0x97, 0x75, 0x97, 0xe0, 0x98, 0x4c, + 0x98, 0xb8, 0x99, 0x24, 0x99, 0x90, 0x99, 0xfc, 0x9a, 0x68, 0x9a, 0xd5, + 0x9b, 0x42, 0x9b, 0xaf, 0x9c, 0x1c, 0x9c, 0x89, 0x9c, 0xf7, 0x9d, 0x64, + 0x9d, 0xd2, 0x9e, 0x40, 0x9e, 0xae, 0x9f, 0x1d, 0x9f, 0x8b, 0x9f, 0xfa, + 0xa0, 0x69, 0xa0, 0xd8, 0xa1, 0x47, 0xa1, 0xb6, 0xa2, 0x26, 0xa2, 0x96, + 0xa3, 0x06, 0xa3, 0x76, 0xa3, 0xe6, 0xa4, 0x56, 0xa4, 0xc7, 0xa5, 0x38, + 0xa5, 0xa9, 0xa6, 0x1a, 0xa6, 0x8b, 0xa6, 0xfd, 0xa7, 0x6e, 0xa7, 0xe0, + 0xa8, 0x52, 0xa8, 0xc4, 0xa9, 0x37, 0xa9, 0xa9, 0xaa, 0x1c, 0xaa, 0x8f, + 0xab, 0x02, 0xab, 0x75, 0xab, 0xe9, 0xac, 0x5c, 0xac, 0xd0, 0xad, 0x44, + 0xad, 0xb8, 0xae, 0x2d, 0xae, 0xa1, 0xaf, 0x16, 0xaf, 0x8b, 0xb0, 0x00, + 0xb0, 0x75, 0xb0, 0xea, 0xb1, 0x60, 0xb1, 0xd6, 0xb2, 0x4b, 0xb2, 0xc2, + 0xb3, 0x38, 0xb3, 0xae, 0xb4, 0x25, 0xb4, 0x9c, 0xb5, 0x13, 0xb5, 0x8a, + 0xb6, 0x01, 0xb6, 0x79, 0xb6, 0xf0, 0xb7, 0x68, 0xb7, 0xe0, 0xb8, 0x59, + 0xb8, 0xd1, 0xb9, 0x4a, 0xb9, 0xc2, 0xba, 0x3b, 0xba, 0xb5, 0xbb, 0x2e, + 0xbb, 0xa7, 0xbc, 0x21, 0xbc, 0x9b, 0xbd, 0x15, 0xbd, 0x8f, 0xbe, 0x0a, + 0xbe, 0x84, 0xbe, 0xff, 0xbf, 0x7a, 0xbf, 0xf5, 0xc0, 0x70, 0xc0, 0xec, + 0xc1, 0x67, 0xc1, 0xe3, 0xc2, 0x5f, 0xc2, 0xdb, 0xc3, 0x58, 0xc3, 0xd4, + 0xc4, 0x51, 0xc4, 0xce, 0xc5, 0x4b, 0xc5, 0xc8, 0xc6, 0x46, 0xc6, 0xc3, + 0xc7, 0x41, 0xc7, 0xbf, 0xc8, 0x3d, 0xc8, 0xbc, 0xc9, 0x3a, 0xc9, 0xb9, + 0xca, 0x38, 0xca, 0xb7, 0xcb, 0x36, 0xcb, 0xb6, 0xcc, 0x35, 0xcc, 0xb5, + 0xcd, 0x35, 0xcd, 0xb5, 0xce, 0x36, 0xce, 0xb6, 0xcf, 0x37, 0xcf, 0xb8, + 0xd0, 0x39, 0xd0, 0xba, 0xd1, 0x3c, 0xd1, 0xbe, 0xd2, 0x3f, 0xd2, 0xc1, + 0xd3, 0x44, 0xd3, 0xc6, 0xd4, 0x49, 0xd4, 0xcb, 0xd5, 0x4e, 0xd5, 0xd1, + 0xd6, 0x55, 0xd6, 0xd8, 0xd7, 0x5c, 0xd7, 0xe0, 0xd8, 0x64, 0xd8, 0xe8, + 0xd9, 0x6c, 0xd9, 0xf1, 0xda, 0x76, 0xda, 0xfb, 0xdb, 0x80, 0xdc, 0x05, + 0xdc, 0x8a, 0xdd, 0x10, 0xdd, 0x96, 0xde, 0x1c, 0xde, 0xa2, 0xdf, 0x29, + 0xdf, 0xaf, 0xe0, 0x36, 0xe0, 0xbd, 0xe1, 0x44, 0xe1, 0xcc, 0xe2, 0x53, + 0xe2, 0xdb, 0xe3, 0x63, 0xe3, 0xeb, 0xe4, 0x73, 0xe4, 0xfc, 0xe5, 0x84, + 0xe6, 0x0d, 0xe6, 0x96, 0xe7, 0x1f, 0xe7, 0xa9, 0xe8, 0x32, 0xe8, 0xbc, + 0xe9, 0x46, 0xe9, 0xd0, 0xea, 0x5b, 0xea, 0xe5, 0xeb, 0x70, 0xeb, 0xfb, + 0xec, 0x86, 0xed, 0x11, 0xed, 0x9c, 0xee, 0x28, 0xee, 0xb4, 0xef, 0x40, + 0xef, 0xcc, 0xf0, 0x58, 0xf0, 0xe5, 0xf1, 0x72, 0xf1, 0xff, 0xf2, 0x8c, + 0xf3, 0x19, 0xf3, 0xa7, 0xf4, 0x34, 0xf4, 0xc2, 0xf5, 0x50, 0xf5, 0xde, + 0xf6, 0x6d, 0xf6, 0xfb, 0xf7, 0x8a, 0xf8, 0x19, 0xf8, 0xa8, 0xf9, 0x38, + 0xf9, 0xc7, 0xfa, 0x57, 0xfa, 0xe7, 0xfb, 0x77, 0xfc, 0x07, 0xfc, 0x98, + 0xfd, 0x29, 0xfd, 0xba, 0xfe, 0x4b, 0xfe, 0xdc, 0xff, 0x6d, 0xff, 0xff}; + +const unsigned char colorspin_profile_data[] = { + 0x00, 0x00, 0x01, 0xea, 0x54, 0x45, 0x53, 0x54, 0x00, 0x00, 0x00, 0x00, + 0x6d, 0x6e, 0x74, 0x72, 0x52, 0x47, 0x42, 0x20, 0x58, 0x59, 0x5a, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x61, 0x63, 0x73, 0x70, 0x74, 0x65, 0x73, 0x74, 0x00, 0x00, 0x00, 0x00, + 0x74, 0x65, 0x73, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6, 0xd6, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x2d, 0x74, 0x65, 0x73, 0x74, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, + 0x63, 0x70, 0x72, 0x74, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x0d, + 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x8c, + 0x77, 0x74, 0x70, 0x74, 0x00, 0x00, 0x01, 0x8c, 0x00, 0x00, 0x00, 0x14, + 0x72, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x01, 0xa0, 0x00, 0x00, 0x00, 0x14, + 0x67, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x01, 0xb4, 0x00, 0x00, 0x00, 0x14, + 0x62, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x01, 0xc8, 0x00, 0x00, 0x00, 0x14, + 0x72, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0xdc, 0x00, 0x00, 0x00, 0x0e, + 0x67, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0xdc, 0x00, 0x00, 0x00, 0x0e, + 0x62, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0xdc, 0x00, 0x00, 0x00, 0x0e, + 0x74, 0x65, 0x78, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x10, 0x77, 0x68, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x2e, + 0x69, 0x63, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf3, 0x52, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x16, 0xcc, 0x58, 0x59, 0x5a, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x8d, 0x00, 0x00, 0xa0, 0x2c, + 0x00, 0x00, 0x0f, 0x95, 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x26, 0x31, 0x00, 0x00, 0x10, 0x2f, 0x00, 0x00, 0xbe, 0x9b, + 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9c, 0x18, + 0x00, 0x00, 0x4f, 0xa5, 0x00, 0x00, 0x04, 0xfc, 0x63, 0x75, 0x72, 0x76, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x33}; + +const unsigned char adobe_rgb_profile_data[] = { + 0x00, 0x00, 0x02, 0x30, 0x41, 0x44, 0x42, 0x45, 0x02, 0x10, 0x00, 0x00, + 0x6d, 0x6e, 0x74, 0x72, 0x52, 0x47, 0x42, 0x20, 0x58, 0x59, 0x5a, 0x20, + 0x07, 0xd0, 0x00, 0x08, 0x00, 0x0b, 0x00, 0x13, 0x00, 0x33, 0x00, 0x3b, + 0x61, 0x63, 0x73, 0x70, 0x41, 0x50, 0x50, 0x4c, 0x00, 0x00, 0x00, 0x00, + 0x6e, 0x6f, 0x6e, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6, 0xd6, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x2d, 0x41, 0x44, 0x42, 0x45, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, + 0x63, 0x70, 0x72, 0x74, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x32, + 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x01, 0x30, 0x00, 0x00, 0x00, 0x6b, + 0x77, 0x74, 0x70, 0x74, 0x00, 0x00, 0x01, 0x9c, 0x00, 0x00, 0x00, 0x14, + 0x62, 0x6b, 0x70, 0x74, 0x00, 0x00, 0x01, 0xb0, 0x00, 0x00, 0x00, 0x14, + 0x72, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0xc4, 0x00, 0x00, 0x00, 0x0e, + 0x67, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0xd4, 0x00, 0x00, 0x00, 0x0e, + 0x62, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0xe4, 0x00, 0x00, 0x00, 0x0e, + 0x72, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x01, 0xf4, 0x00, 0x00, 0x00, 0x14, + 0x67, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x00, 0x14, + 0x62, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x02, 0x1c, 0x00, 0x00, 0x00, 0x14, + 0x74, 0x65, 0x78, 0x74, 0x00, 0x00, 0x00, 0x00, 0x43, 0x6f, 0x70, 0x79, + 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x32, 0x30, 0x30, 0x30, 0x20, 0x41, + 0x64, 0x6f, 0x62, 0x65, 0x20, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x73, + 0x20, 0x49, 0x6e, 0x63, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74, 0x65, + 0x64, 0x00, 0x00, 0x00, 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x11, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x52, 0x47, + 0x42, 0x20, 0x28, 0x31, 0x39, 0x39, 0x38, 0x29, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf3, 0x51, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x16, 0xcc, + 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x75, 0x72, 0x76, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x33, 0x00, 0x00, + 0x63, 0x75, 0x72, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x02, 0x33, 0x00, 0x00, 0x63, 0x75, 0x72, 0x76, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x02, 0x33, 0x00, 0x00, 0x58, 0x59, 0x5a, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9c, 0x18, 0x00, 0x00, 0x4f, 0xa5, + 0x00, 0x00, 0x04, 0xfc, 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x34, 0x8d, 0x00, 0x00, 0xa0, 0x2c, 0x00, 0x00, 0x0f, 0x95, + 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x26, 0x31, + 0x00, 0x00, 0x10, 0x2f, 0x00, 0x00, 0xbe, 0x9c}; +} + +unsigned char no_analytic_tr_fn_profile_data[] = { + 0x00, 0x00, 0x07, 0xd4, 0x00, 0x00, 0x00, 0x00, 0x02, 0x10, 0x00, 0x00, + 0x6d, 0x6e, 0x74, 0x72, 0x52, 0x47, 0x42, 0x20, 0x58, 0x59, 0x5a, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x61, 0x63, 0x73, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0xf6, 0xd6, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x2d, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, + 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x34, + 0x72, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x01, 0x24, 0x00, 0x00, 0x00, 0x14, + 0x67, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x01, 0x38, 0x00, 0x00, 0x00, 0x14, + 0x62, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x01, 0x4c, 0x00, 0x00, 0x00, 0x14, + 0x72, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0x60, 0x00, 0x00, 0x02, 0x0c, + 0x67, 0x54, 0x52, 0x43, 0x00, 0x00, 0x03, 0x6c, 0x00, 0x00, 0x02, 0x0c, + 0x62, 0x54, 0x52, 0x43, 0x00, 0x00, 0x05, 0x78, 0x00, 0x00, 0x02, 0x0c, + 0x77, 0x74, 0x70, 0x74, 0x00, 0x00, 0x07, 0x84, 0x00, 0x00, 0x00, 0x14, + 0x63, 0x70, 0x72, 0x74, 0x00, 0x00, 0x07, 0x98, 0x00, 0x00, 0x00, 0x3c, + 0x6d, 0x6c, 0x75, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x0c, 0x65, 0x6e, 0x55, 0x53, 0x00, 0x00, 0x00, 0x18, + 0x00, 0x00, 0x00, 0x1c, 0x00, 0x47, 0x00, 0x6f, 0x00, 0x6f, 0x00, 0x67, + 0x00, 0x6c, 0x00, 0x65, 0x00, 0x20, 0x00, 0x53, 0x00, 0x6b, 0x00, 0x69, + 0x00, 0x61, 0x00, 0x20, 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x73, 0x04, 0x00, 0x00, 0x39, 0x77, 0x00, 0x00, 0x00, 0x4a, + 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0xf1, + 0x00, 0x00, 0xb8, 0xec, 0x00, 0x00, 0x0d, 0xb6, 0x58, 0x59, 0x5a, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0xe1, 0x00, 0x00, 0x0d, 0x9d, + 0x00, 0x00, 0xc5, 0x2d, 0x63, 0x75, 0x72, 0x76, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x06, 0x00, 0x09, + 0x00, 0x0c, 0x00, 0x0f, 0x00, 0x12, 0x00, 0x15, 0x00, 0x18, 0x00, 0x1b, + 0x00, 0x1f, 0x00, 0x24, 0x00, 0x2b, 0x00, 0x34, 0x00, 0x3e, 0x00, 0x4a, + 0x00, 0x58, 0x00, 0x68, 0x00, 0x79, 0x00, 0x8c, 0x00, 0xa1, 0x00, 0xb8, + 0x00, 0xd1, 0x00, 0xeb, 0x01, 0x08, 0x01, 0x27, 0x01, 0x47, 0x01, 0x69, + 0x01, 0x8e, 0x01, 0xb4, 0x01, 0xdd, 0x02, 0x07, 0x02, 0x33, 0x02, 0x62, + 0x02, 0x92, 0x02, 0xc4, 0x02, 0xf9, 0x03, 0x2f, 0x03, 0x68, 0x03, 0xa2, + 0x03, 0xdf, 0x04, 0x1d, 0x04, 0x5e, 0x04, 0xa0, 0x04, 0xe5, 0x05, 0x2c, + 0x05, 0x75, 0x05, 0xbf, 0x06, 0x0c, 0x06, 0x5a, 0x06, 0xab, 0x06, 0xfd, + 0x07, 0x51, 0x07, 0xa8, 0x08, 0x00, 0x08, 0x5a, 0x08, 0xb6, 0x09, 0x14, + 0x09, 0x74, 0x09, 0xd5, 0x0a, 0x39, 0x0a, 0x9e, 0x0b, 0x05, 0x0b, 0x6e, + 0x0b, 0xd9, 0x0c, 0x46, 0x0c, 0xb4, 0x0d, 0x24, 0x0d, 0x96, 0x0e, 0x0a, + 0x0e, 0x7f, 0x0e, 0xf7, 0x0f, 0x6f, 0x0f, 0xea, 0x10, 0x65, 0x10, 0xe3, + 0x11, 0x62, 0x11, 0xe2, 0x12, 0x65, 0x12, 0xe8, 0x13, 0x6d, 0x13, 0xf4, + 0x14, 0x7c, 0x15, 0x05, 0x15, 0x90, 0x16, 0x1c, 0x16, 0xaa, 0x17, 0x39, + 0x17, 0xca, 0x18, 0x5c, 0x18, 0xf1, 0x19, 0x86, 0x1a, 0x1d, 0x1a, 0xb7, + 0x1b, 0x52, 0x1b, 0xf0, 0x1c, 0x8f, 0x1d, 0x31, 0x1d, 0xd6, 0x1e, 0x7d, + 0x1f, 0x26, 0x1f, 0xd2, 0x20, 0x80, 0x21, 0x31, 0x21, 0xe4, 0x22, 0x9a, + 0x23, 0x52, 0x24, 0x0d, 0x24, 0xca, 0x25, 0x8b, 0x26, 0x4e, 0x27, 0x14, + 0x27, 0xde, 0x28, 0xaa, 0x29, 0x7a, 0x2a, 0x4c, 0x2b, 0x22, 0x2b, 0xfc, + 0x2c, 0xd9, 0x2d, 0xb8, 0x2e, 0x9b, 0x2f, 0x81, 0x30, 0x69, 0x31, 0x54, + 0x32, 0x42, 0x33, 0x32, 0x34, 0x25, 0x35, 0x1a, 0x36, 0x12, 0x37, 0x0d, + 0x38, 0x09, 0x39, 0x07, 0x3a, 0x07, 0x3b, 0x09, 0x3c, 0x0d, 0x3d, 0x12, + 0x3e, 0x19, 0x3f, 0x22, 0x40, 0x2e, 0x41, 0x3a, 0x42, 0x47, 0x43, 0x56, + 0x44, 0x67, 0x45, 0x79, 0x46, 0x8d, 0x47, 0xa3, 0x48, 0xbb, 0x49, 0xd7, + 0x4a, 0xf5, 0x4c, 0x13, 0x4d, 0x34, 0x4e, 0x58, 0x4f, 0x80, 0x50, 0xab, + 0x51, 0xda, 0x53, 0x0c, 0x54, 0x44, 0x55, 0x7f, 0x56, 0xbe, 0x58, 0x02, + 0x59, 0x4b, 0x5a, 0x99, 0x5b, 0xec, 0x5d, 0x45, 0x5e, 0xa2, 0x60, 0x03, + 0x61, 0x67, 0x62, 0xcf, 0x64, 0x3b, 0x65, 0xab, 0x67, 0x1e, 0x68, 0x93, + 0x6a, 0x05, 0x6b, 0x77, 0x6c, 0xe7, 0x6e, 0x57, 0x6f, 0xc7, 0x71, 0x38, + 0x72, 0xaa, 0x74, 0x1b, 0x75, 0x8c, 0x76, 0xfe, 0x78, 0x71, 0x79, 0xe6, + 0x7b, 0x5d, 0x7c, 0xd4, 0x7e, 0x4c, 0x7f, 0xc6, 0x81, 0x42, 0x82, 0xc2, + 0x84, 0x48, 0x85, 0xd2, 0x87, 0x61, 0x88, 0xf4, 0x8a, 0x8c, 0x8c, 0x28, + 0x8d, 0xc8, 0x8f, 0x6c, 0x91, 0x12, 0x92, 0xbd, 0x94, 0x6a, 0x96, 0x19, + 0x97, 0xcb, 0x99, 0x80, 0x9b, 0x36, 0x9c, 0xee, 0x9e, 0xa7, 0xa0, 0x61, + 0xa2, 0x1c, 0xa3, 0xda, 0xa5, 0x9a, 0xa7, 0x5f, 0xa9, 0x28, 0xaa, 0xf5, + 0xac, 0xc5, 0xae, 0x95, 0xb0, 0x66, 0xb2, 0x38, 0xb4, 0x0c, 0xb5, 0xe5, + 0xb7, 0xc5, 0xb9, 0xac, 0xbb, 0x9b, 0xbd, 0x90, 0xbf, 0x8a, 0xc1, 0x8a, + 0xc3, 0x8f, 0xc5, 0x98, 0xc7, 0xa8, 0xc9, 0xbe, 0xcb, 0xdd, 0xce, 0x01, + 0xd0, 0x23, 0xd2, 0x42, 0xd4, 0x63, 0xd6, 0x84, 0xd8, 0xa5, 0xda, 0xc7, + 0xdc, 0xe9, 0xdf, 0x09, 0xe1, 0x26, 0xe3, 0x40, 0xe5, 0x58, 0xe7, 0x6c, + 0xe9, 0x81, 0xeb, 0x94, 0xed, 0xa5, 0xef, 0xb5, 0xf1, 0xc4, 0xf3, 0xd1, + 0xf5, 0xdd, 0xf7, 0xe6, 0xf9, 0xef, 0xfb, 0xf6, 0xfd, 0xfb, 0xff, 0xff, + 0x63, 0x75, 0x72, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x05, 0x00, 0x07, 0x00, 0x09, 0x00, 0x0c, + 0x00, 0x0f, 0x00, 0x13, 0x00, 0x18, 0x00, 0x1f, 0x00, 0x26, 0x00, 0x30, + 0x00, 0x3a, 0x00, 0x46, 0x00, 0x54, 0x00, 0x63, 0x00, 0x73, 0x00, 0x85, + 0x00, 0x99, 0x00, 0xae, 0x00, 0xc5, 0x00, 0xdd, 0x00, 0xf7, 0x01, 0x12, + 0x01, 0x30, 0x01, 0x4e, 0x01, 0x6f, 0x01, 0x91, 0x01, 0xb5, 0x01, 0xdb, + 0x02, 0x02, 0x02, 0x2b, 0x02, 0x56, 0x02, 0x83, 0x02, 0xb2, 0x02, 0xe2, + 0x03, 0x14, 0x03, 0x48, 0x03, 0x7e, 0x03, 0xb6, 0x03, 0xef, 0x04, 0x2a, + 0x04, 0x68, 0x04, 0xa7, 0x04, 0xe8, 0x05, 0x2b, 0x05, 0x70, 0x05, 0xb7, + 0x06, 0x00, 0x06, 0x4b, 0x06, 0x98, 0x06, 0xe6, 0x07, 0x37, 0x07, 0x8a, + 0x07, 0xdf, 0x08, 0x36, 0x08, 0x90, 0x08, 0xeb, 0x09, 0x48, 0x09, 0xa7, + 0x0a, 0x08, 0x0a, 0x6c, 0x0a, 0xd1, 0x0b, 0x39, 0x0b, 0xa3, 0x0c, 0x0e, + 0x0c, 0x7c, 0x0c, 0xed, 0x0d, 0x5f, 0x0d, 0xd4, 0x0e, 0x4a, 0x0e, 0xc3, + 0x0f, 0x3e, 0x0f, 0xbc, 0x10, 0x3c, 0x10, 0xbd, 0x11, 0x42, 0x11, 0xc8, + 0x12, 0x51, 0x12, 0xdc, 0x13, 0x69, 0x13, 0xf9, 0x14, 0x8b, 0x15, 0x20, + 0x15, 0xb6, 0x16, 0x50, 0x16, 0xeb, 0x17, 0x88, 0x18, 0x28, 0x18, 0xca, + 0x19, 0x6e, 0x1a, 0x16, 0x1a, 0xbe, 0x1b, 0x69, 0x1c, 0x16, 0x1c, 0xc6, + 0x1d, 0x77, 0x1e, 0x2a, 0x1e, 0xdf, 0x1f, 0x96, 0x20, 0x4e, 0x21, 0x08, + 0x21, 0xc4, 0x22, 0x81, 0x23, 0x3f, 0x23, 0xff, 0x24, 0xc0, 0x25, 0x82, + 0x26, 0x47, 0x27, 0x0e, 0x27, 0xd6, 0x28, 0xa1, 0x29, 0x6d, 0x2a, 0x3b, + 0x2b, 0x0d, 0x2b, 0xdf, 0x2c, 0xb2, 0x2d, 0x87, 0x2e, 0x5e, 0x2f, 0x37, + 0x30, 0x11, 0x30, 0xed, 0x31, 0xcb, 0x32, 0xaa, 0x33, 0x8c, 0x34, 0x6f, + 0x35, 0x56, 0x36, 0x3f, 0x37, 0x2a, 0x38, 0x18, 0x39, 0x09, 0x39, 0xfd, + 0x3a, 0xf4, 0x3b, 0xf0, 0x3c, 0xef, 0x3d, 0xf1, 0x3e, 0xf7, 0x40, 0x00, + 0x41, 0x0d, 0x42, 0x1d, 0x43, 0x31, 0x44, 0x49, 0x45, 0x64, 0x46, 0x82, + 0x47, 0xa4, 0x48, 0xc9, 0x49, 0xf1, 0x4b, 0x1a, 0x4c, 0x46, 0x4d, 0x75, + 0x4e, 0xa4, 0x4f, 0xd6, 0x51, 0x0a, 0x52, 0x40, 0x53, 0x78, 0x54, 0xb2, + 0x55, 0xee, 0x57, 0x2d, 0x58, 0x6f, 0x59, 0xb3, 0x5a, 0xfa, 0x5c, 0x44, + 0x5d, 0x91, 0x5e, 0xe2, 0x60, 0x35, 0x61, 0x8b, 0x62, 0xe4, 0x64, 0x3e, + 0x65, 0x9c, 0x66, 0xfb, 0x68, 0x5b, 0x69, 0xbb, 0x6b, 0x1b, 0x6c, 0x7b, + 0x6d, 0xda, 0x6f, 0x3a, 0x70, 0x9b, 0x71, 0xfd, 0x73, 0x61, 0x74, 0xc8, + 0x76, 0x32, 0x77, 0x9f, 0x79, 0x0e, 0x7a, 0x7f, 0x7b, 0xf3, 0x7d, 0x6a, + 0x7e, 0xe3, 0x80, 0x5f, 0x81, 0xdd, 0x83, 0x61, 0x84, 0xe9, 0x86, 0x75, + 0x88, 0x06, 0x89, 0x9d, 0x8b, 0x37, 0x8c, 0xd8, 0x8e, 0x7d, 0x90, 0x28, + 0x91, 0xd6, 0x93, 0x87, 0x95, 0x38, 0x96, 0xe9, 0x98, 0x9a, 0x9a, 0x4b, + 0x9b, 0xfd, 0x9d, 0xb2, 0x9f, 0x69, 0xa1, 0x22, 0xa2, 0xde, 0xa4, 0x98, + 0xa6, 0x53, 0xa8, 0x0d, 0xa9, 0xc7, 0xab, 0x80, 0xad, 0x3d, 0xae, 0xfe, + 0xb0, 0xc3, 0xb2, 0x8d, 0xb4, 0x5d, 0xb6, 0x30, 0xb8, 0x0a, 0xb9, 0xec, + 0xbb, 0xd8, 0xbd, 0xce, 0xbf, 0xcf, 0xc1, 0xd9, 0xc3, 0xed, 0xc6, 0x0d, + 0xc8, 0x36, 0xca, 0x66, 0xcc, 0x9c, 0xce, 0xd6, 0xd1, 0x11, 0xd3, 0x4d, + 0xd5, 0x89, 0xd7, 0xc2, 0xd9, 0xf9, 0xdc, 0x2d, 0xde, 0x5b, 0xe0, 0x83, + 0xe2, 0xa1, 0xe4, 0xb6, 0xe6, 0xc7, 0xe8, 0xd1, 0xea, 0xd8, 0xec, 0xda, + 0xee, 0xd8, 0xf0, 0xd1, 0xf2, 0xc6, 0xf4, 0xb6, 0xf6, 0xa2, 0xf8, 0x8b, + 0xfa, 0x6e, 0xfc, 0x4e, 0xfe, 0x28, 0xff, 0xff, 0x63, 0x75, 0x72, 0x76, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x03, 0x00, 0x05, 0x00, 0x07, 0x00, 0x0a, 0x00, 0x0e, 0x00, 0x13, + 0x00, 0x19, 0x00, 0x20, 0x00, 0x28, 0x00, 0x31, 0x00, 0x3c, 0x00, 0x47, + 0x00, 0x54, 0x00, 0x63, 0x00, 0x72, 0x00, 0x83, 0x00, 0x95, 0x00, 0xa9, + 0x00, 0xbe, 0x00, 0xd4, 0x00, 0xec, 0x01, 0x06, 0x01, 0x21, 0x01, 0x3d, + 0x01, 0x5b, 0x01, 0x7a, 0x01, 0x9b, 0x01, 0xbe, 0x01, 0xe2, 0x02, 0x08, + 0x02, 0x2f, 0x02, 0x58, 0x02, 0x83, 0x02, 0xb0, 0x02, 0xde, 0x03, 0x0e, + 0x03, 0x3f, 0x03, 0x72, 0x03, 0xa8, 0x03, 0xde, 0x04, 0x17, 0x04, 0x52, + 0x04, 0x8e, 0x04, 0xcc, 0x05, 0x0c, 0x05, 0x4e, 0x05, 0x92, 0x05, 0xd8, + 0x06, 0x1f, 0x06, 0x69, 0x06, 0xb5, 0x07, 0x02, 0x07, 0x52, 0x07, 0xa3, + 0x07, 0xf7, 0x08, 0x4d, 0x08, 0xa5, 0x08, 0xff, 0x09, 0x5b, 0x09, 0xb9, + 0x0a, 0x19, 0x0a, 0x7c, 0x0a, 0xe0, 0x0b, 0x47, 0x0b, 0xb0, 0x0c, 0x1c, + 0x0c, 0x8a, 0x0c, 0xfa, 0x0d, 0x6c, 0x0d, 0xe1, 0x0e, 0x58, 0x0e, 0xd1, + 0x0f, 0x4d, 0x0f, 0xcb, 0x10, 0x4c, 0x10, 0xcf, 0x11, 0x55, 0x11, 0xdd, + 0x12, 0x68, 0x12, 0xf5, 0x13, 0x85, 0x14, 0x17, 0x14, 0xac, 0x15, 0x44, + 0x15, 0xe0, 0x16, 0x7c, 0x17, 0x1a, 0x17, 0xba, 0x18, 0x5c, 0x19, 0x00, + 0x19, 0xa6, 0x1a, 0x4d, 0x1a, 0xf7, 0x1b, 0xa2, 0x1c, 0x50, 0x1d, 0x00, + 0x1d, 0xb1, 0x1e, 0x65, 0x1f, 0x1a, 0x1f, 0xd1, 0x20, 0x89, 0x21, 0x43, + 0x21, 0xfe, 0x22, 0xba, 0x23, 0x78, 0x24, 0x37, 0x24, 0xf6, 0x25, 0xb7, + 0x26, 0x7a, 0x27, 0x3d, 0x28, 0x02, 0x28, 0xc8, 0x29, 0x8f, 0x2a, 0x58, + 0x2b, 0x22, 0x2b, 0xee, 0x2c, 0xbc, 0x2d, 0x8e, 0x2e, 0x62, 0x2f, 0x38, + 0x30, 0x12, 0x30, 0xee, 0x31, 0xce, 0x32, 0xb0, 0x33, 0x95, 0x34, 0x7d, + 0x35, 0x67, 0x36, 0x54, 0x37, 0x44, 0x38, 0x37, 0x39, 0x2c, 0x3a, 0x25, + 0x3b, 0x21, 0x3c, 0x20, 0x3d, 0x23, 0x3e, 0x2a, 0x3f, 0x34, 0x40, 0x42, + 0x41, 0x52, 0x42, 0x64, 0x43, 0x78, 0x44, 0x8e, 0x45, 0xa6, 0x46, 0xc0, + 0x47, 0xdb, 0x48, 0xf9, 0x4a, 0x17, 0x4b, 0x37, 0x4c, 0x58, 0x4d, 0x7b, + 0x4e, 0x9f, 0x4f, 0xc5, 0x50, 0xee, 0x52, 0x18, 0x53, 0x45, 0x54, 0x74, + 0x55, 0xa4, 0x56, 0xd9, 0x58, 0x12, 0x59, 0x50, 0x5a, 0x93, 0x5b, 0xdb, + 0x5d, 0x28, 0x5e, 0x7b, 0x5f, 0xd3, 0x61, 0x31, 0x62, 0x93, 0x63, 0xf9, + 0x65, 0x64, 0x66, 0xd3, 0x68, 0x46, 0x69, 0xbb, 0x6b, 0x34, 0x6c, 0xab, + 0x6e, 0x21, 0x6f, 0x95, 0x71, 0x08, 0x72, 0x7a, 0x73, 0xeb, 0x75, 0x5d, + 0x76, 0xce, 0x78, 0x3e, 0x79, 0xae, 0x7b, 0x1e, 0x7c, 0x8c, 0x7d, 0xfa, + 0x7f, 0x67, 0x80, 0xd5, 0x82, 0x4a, 0x83, 0xc6, 0x85, 0x4a, 0x86, 0xd6, + 0x88, 0x6a, 0x8a, 0x07, 0x8b, 0xac, 0x8d, 0x5c, 0x8f, 0x14, 0x90, 0xd6, + 0x92, 0xa0, 0x94, 0x73, 0x96, 0x4e, 0x98, 0x2b, 0x9a, 0x09, 0x9b, 0xe7, + 0x9d, 0xc6, 0x9f, 0xa4, 0xa1, 0x82, 0xa3, 0x5e, 0xa5, 0x35, 0xa7, 0x09, + 0xa8, 0xd9, 0xaa, 0xa5, 0xac, 0x71, 0xae, 0x40, 0xb0, 0x12, 0xb1, 0xe8, + 0xb3, 0xc1, 0xb5, 0x9f, 0xb7, 0x83, 0xb9, 0x6f, 0xbb, 0x61, 0xbd, 0x5c, + 0xbf, 0x60, 0xc1, 0x6c, 0xc3, 0x7b, 0xc5, 0x8c, 0xc7, 0xa1, 0xc9, 0xb7, + 0xcb, 0xcd, 0xcd, 0xe4, 0xcf, 0xf7, 0xd2, 0x07, 0xd4, 0x12, 0xd6, 0x18, + 0xd8, 0x21, 0xda, 0x2f, 0xdc, 0x41, 0xde, 0x57, 0xe0, 0x6f, 0xe2, 0x88, + 0xe4, 0xa1, 0xe6, 0xbd, 0xe8, 0xdc, 0xeb, 0x03, 0xed, 0x29, 0xef, 0x4c, + 0xf1, 0x6c, 0xf3, 0x89, 0xf5, 0xa4, 0xf7, 0xbc, 0xf9, 0xd1, 0xfb, 0xe3, + 0xfd, 0xf2, 0xff, 0xff, 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf6, 0xd6, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x2d, + 0x6d, 0x6c, 0x75, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x0c, 0x65, 0x6e, 0x55, 0x53, 0x00, 0x00, 0x00, 0x20, + 0x00, 0x00, 0x00, 0x1c, 0x00, 0x47, 0x00, 0x6f, 0x00, 0x6f, 0x00, 0x67, + 0x00, 0x6c, 0x00, 0x65, 0x00, 0x20, 0x00, 0x49, 0x00, 0x6e, 0x00, 0x63, + 0x00, 0x2e, 0x00, 0x20, 0x00, 0x32, 0x00, 0x30, 0x00, 0x31, 0x00, 0x36}; + +const unsigned char a2b_only_profile_data[] = { + 0x00, 0x00, 0x0f, 0xd8, 0x41, 0x44, 0x42, 0x45, 0x04, 0x00, 0x00, 0x00, + 0x73, 0x63, 0x6e, 0x72, 0x52, 0x47, 0x42, 0x20, 0x58, 0x59, 0x5a, 0x20, + 0x07, 0xd2, 0x00, 0x08, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x61, 0x63, 0x73, 0x70, 0x41, 0x50, 0x50, 0x4c, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0xf6, 0xd6, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x2d, 0x41, 0x44, 0x42, 0x45, + 0x90, 0x51, 0xe0, 0x2b, 0x81, 0x28, 0xa4, 0x4d, 0x54, 0xfa, 0xae, 0x96, + 0xfb, 0x1a, 0x46, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, + 0x63, 0x70, 0x72, 0x74, 0x00, 0x00, 0x00, 0xe4, 0x00, 0x00, 0x00, 0x6e, + 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x01, 0x54, 0x00, 0x00, 0x00, 0x30, + 0x77, 0x74, 0x70, 0x74, 0x00, 0x00, 0x01, 0x84, 0x00, 0x00, 0x00, 0x14, + 0x62, 0x6b, 0x70, 0x74, 0x00, 0x00, 0x01, 0x98, 0x00, 0x00, 0x00, 0x14, + 0x63, 0x68, 0x61, 0x64, 0x00, 0x00, 0x01, 0xac, 0x00, 0x00, 0x00, 0x2c, + 0x41, 0x32, 0x42, 0x30, 0x00, 0x00, 0x01, 0xd8, 0x00, 0x00, 0x07, 0x00, + 0x41, 0x32, 0x42, 0x32, 0x00, 0x00, 0x01, 0xd8, 0x00, 0x00, 0x07, 0x00, + 0x41, 0x32, 0x42, 0x31, 0x00, 0x00, 0x08, 0xd8, 0x00, 0x00, 0x07, 0x00, + 0x6d, 0x6c, 0x75, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x0c, 0x65, 0x6e, 0x55, 0x53, 0x00, 0x00, 0x00, 0x52, + 0x00, 0x00, 0x00, 0x1c, 0x00, 0x43, 0x00, 0x6f, 0x00, 0x70, 0x00, 0x79, + 0x00, 0x72, 0x00, 0x69, 0x00, 0x67, 0x00, 0x68, 0x00, 0x74, 0x00, 0x20, + 0x00, 0x32, 0x00, 0x30, 0x00, 0x30, 0x00, 0x32, 0x00, 0x20, 0x00, 0x41, + 0x00, 0x64, 0x00, 0x6f, 0x00, 0x62, 0x00, 0x65, 0x00, 0x20, 0x00, 0x53, + 0x00, 0x79, 0x00, 0x73, 0x00, 0x74, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x73, + 0x00, 0x20, 0x00, 0x49, 0x00, 0x6e, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x72, + 0x00, 0x70, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, + 0x00, 0x64, 0x00, 0x00, 0x6d, 0x6c, 0x75, 0x63, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x65, 0x6e, 0x55, 0x53, + 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x73, 0x00, 0x59, + 0x00, 0x43, 0x00, 0x43, 0x00, 0x20, 0x00, 0x38, 0x00, 0x2d, 0x00, 0x62, + 0x00, 0x69, 0x00, 0x74, 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf6, 0xd6, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x2d, + 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x16, + 0x00, 0x00, 0x03, 0x33, 0x00, 0x00, 0x02, 0xa4, 0x73, 0x66, 0x33, 0x32, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0c, 0x3f, 0x00, 0x00, 0x05, 0xdc, + 0xff, 0xff, 0xf3, 0x26, 0x00, 0x00, 0x07, 0x90, 0x00, 0x00, 0xfd, 0x92, + 0xff, 0xff, 0xfb, 0xa1, 0xff, 0xff, 0xfd, 0xa2, 0x00, 0x00, 0x03, 0xdc, + 0x00, 0x00, 0xc0, 0x71, 0x6d, 0x41, 0x42, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x44, + 0x00, 0x00, 0x00, 0x74, 0x00, 0x00, 0x06, 0x98, 0x00, 0x00, 0x06, 0xdc, + 0x63, 0x75, 0x72, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x63, 0x75, 0x72, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x63, 0x75, 0x72, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x19, 0xde, 0x00, 0x00, 0xf8, 0xf2, 0x00, 0x00, 0x5c, 0x7d, + 0x00, 0x00, 0x8f, 0xcc, 0x00, 0x01, 0xcf, 0x78, 0x00, 0x00, 0x27, 0x30, + 0x00, 0x00, 0x08, 0xfc, 0x00, 0x00, 0x3e, 0xc1, 0x00, 0x01, 0xcd, 0x84, + 0xff, 0xff, 0xa2, 0x21, 0xff, 0xff, 0x9e, 0xa4, 0xff, 0xff, 0xaf, 0xb0, + 0x63, 0x75, 0x72, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x10, 0x02, 0x1b, 0x03, 0x22, 0x04, 0x24, 0x05, 0x23, + 0x06, 0x1d, 0x07, 0x12, 0x08, 0x04, 0x08, 0xf1, 0x09, 0xda, 0x0a, 0xbe, + 0x0b, 0x9f, 0x0c, 0x7b, 0x0d, 0x53, 0x0e, 0x28, 0x0e, 0xf8, 0x0f, 0xc3, + 0x10, 0x8b, 0x11, 0x4f, 0x12, 0x0f, 0x12, 0xcb, 0x13, 0x83, 0x14, 0x37, + 0x14, 0xe7, 0x15, 0x93, 0x16, 0x3c, 0x16, 0xe0, 0x17, 0x81, 0x18, 0x1e, + 0x18, 0xb7, 0x19, 0x4d, 0x19, 0xdf, 0x1a, 0x6d, 0x1a, 0xf7, 0x1b, 0x7e, + 0x1c, 0x01, 0x1c, 0x81, 0x1c, 0xfd, 0x1d, 0x76, 0x1d, 0xeb, 0x1e, 0x5d, + 0x1e, 0xcb, 0x1f, 0x36, 0x1f, 0x9e, 0x20, 0x02, 0x20, 0x63, 0x20, 0xc1, + 0x21, 0x1b, 0x21, 0x73, 0x21, 0xc7, 0x22, 0x18, 0x22, 0x66, 0x22, 0xb0, + 0x22, 0xf8, 0x23, 0x3d, 0x23, 0x7f, 0x23, 0xbe, 0x23, 0xfa, 0x24, 0x33, + 0x24, 0x69, 0x24, 0x9d, 0x24, 0xce, 0x24, 0xfc, 0x25, 0x28, 0x25, 0x51, + 0x25, 0x77, 0x25, 0x9b, 0x25, 0xbd, 0x25, 0xdc, 0x25, 0xf9, 0x26, 0x13, + 0x26, 0x2b, 0x26, 0x41, 0x26, 0x55, 0x26, 0x67, 0x26, 0x77, 0x26, 0x85, + 0x26, 0x91, 0x26, 0x9c, 0x26, 0xa7, 0x26, 0xb2, 0x26, 0xbd, 0x26, 0xc8, + 0x26, 0xd3, 0x26, 0xde, 0x26, 0xe9, 0x26, 0xf6, 0x27, 0x04, 0x27, 0x15, + 0x27, 0x27, 0x27, 0x3c, 0x27, 0x53, 0x27, 0x6c, 0x27, 0x87, 0x27, 0xa5, + 0x27, 0xc5, 0x27, 0xe7, 0x28, 0x0c, 0x28, 0x34, 0x28, 0x5d, 0x28, 0x8a, + 0x28, 0xb9, 0x28, 0xeb, 0x29, 0x20, 0x29, 0x57, 0x29, 0x91, 0x29, 0xce, + 0x2a, 0x0e, 0x2a, 0x51, 0x2a, 0x97, 0x2a, 0xe0, 0x2b, 0x2c, 0x2b, 0x7b, + 0x2b, 0xcd, 0x2c, 0x22, 0x2c, 0x7a, 0x2c, 0xd6, 0x2d, 0x35, 0x2d, 0x97, + 0x2d, 0xfc, 0x2e, 0x65, 0x2e, 0xd1, 0x2f, 0x41, 0x2f, 0xb4, 0x30, 0x2a, + 0x30, 0xa4, 0x31, 0x21, 0x31, 0xa2, 0x32, 0x27, 0x32, 0xaf, 0x33, 0x3b, + 0x33, 0xca, 0x34, 0x5d, 0x34, 0xf4, 0x35, 0x8f, 0x36, 0x2d, 0x36, 0xcf, + 0x37, 0x75, 0x38, 0x1f, 0x38, 0xcc, 0x39, 0x7e, 0x3a, 0x33, 0x3a, 0xed, + 0x3b, 0xaa, 0x3c, 0x6b, 0x3d, 0x31, 0x3d, 0xfa, 0x3e, 0xc7, 0x3f, 0x99, + 0x40, 0x6e, 0x41, 0x48, 0x42, 0x26, 0x43, 0x08, 0x43, 0xee, 0x44, 0xd8, + 0x45, 0xc7, 0x46, 0xba, 0x47, 0xb1, 0x48, 0xac, 0x49, 0xac, 0x4a, 0xb0, + 0x4b, 0xb9, 0x4c, 0xc6, 0x4d, 0xd7, 0x4e, 0xec, 0x50, 0x06, 0x51, 0x25, + 0x52, 0x48, 0x53, 0x70, 0x54, 0x9c, 0x55, 0xcc, 0x57, 0x01, 0x58, 0x3b, + 0x59, 0x79, 0x5a, 0xbc, 0x5c, 0x04, 0x5d, 0x50, 0x5e, 0xa1, 0x5f, 0xf7, + 0x61, 0x51, 0x62, 0xb0, 0x64, 0x14, 0x65, 0x7c, 0x66, 0xea, 0x68, 0x5c, + 0x69, 0xd3, 0x6b, 0x4f, 0x6c, 0xcf, 0x6e, 0x55, 0x6f, 0xdf, 0x71, 0x6f, + 0x73, 0x03, 0x74, 0x9c, 0x76, 0x3a, 0x77, 0xdd, 0x79, 0x85, 0x7b, 0x32, + 0x7c, 0xe5, 0x7e, 0x9c, 0x80, 0x58, 0x82, 0x19, 0x83, 0xe0, 0x85, 0xab, + 0x87, 0x7c, 0x89, 0x52, 0x8b, 0x2d, 0x8d, 0x0d, 0x8e, 0xf2, 0x90, 0xdc, + 0x92, 0xcc, 0x94, 0xc1, 0x96, 0xbb, 0x98, 0xbb, 0x9a, 0xc0, 0x9c, 0xca, + 0x9e, 0xd9, 0xa0, 0xee, 0xa3, 0x08, 0xa5, 0x27, 0xa7, 0x4c, 0xa9, 0x76, + 0xab, 0xa6, 0xad, 0xdb, 0xb0, 0x15, 0xb2, 0x55, 0xb4, 0x9a, 0xb6, 0xe5, + 0xb9, 0x36, 0xbb, 0x8b, 0xbd, 0xe7, 0xc0, 0x48, 0xc2, 0xae, 0xc5, 0x1a, + 0xc7, 0x8c, 0xca, 0x03, 0xcc, 0x80, 0xcf, 0x02, 0xd1, 0x8a, 0xd4, 0x18, + 0xd6, 0xab, 0xd9, 0x44, 0xdb, 0xe3, 0xde, 0x87, 0xe1, 0x32, 0xe3, 0xe2, + 0xe6, 0x97, 0xe9, 0x53, 0xec, 0x14, 0xee, 0xdb, 0xf1, 0xa8, 0xf4, 0x7a, + 0xf7, 0x53, 0xfa, 0x31, 0xfd, 0x15, 0xff, 0xff, 0x63, 0x75, 0x72, 0x76, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x10, + 0x02, 0x1b, 0x03, 0x22, 0x04, 0x24, 0x05, 0x23, 0x06, 0x1d, 0x07, 0x12, + 0x08, 0x04, 0x08, 0xf1, 0x09, 0xda, 0x0a, 0xbe, 0x0b, 0x9f, 0x0c, 0x7b, + 0x0d, 0x53, 0x0e, 0x28, 0x0e, 0xf8, 0x0f, 0xc3, 0x10, 0x8b, 0x11, 0x4f, + 0x12, 0x0f, 0x12, 0xcb, 0x13, 0x83, 0x14, 0x37, 0x14, 0xe7, 0x15, 0x93, + 0x16, 0x3c, 0x16, 0xe0, 0x17, 0x81, 0x18, 0x1e, 0x18, 0xb7, 0x19, 0x4d, + 0x19, 0xdf, 0x1a, 0x6d, 0x1a, 0xf7, 0x1b, 0x7e, 0x1c, 0x01, 0x1c, 0x81, + 0x1c, 0xfd, 0x1d, 0x76, 0x1d, 0xeb, 0x1e, 0x5d, 0x1e, 0xcb, 0x1f, 0x36, + 0x1f, 0x9e, 0x20, 0x02, 0x20, 0x63, 0x20, 0xc1, 0x21, 0x1b, 0x21, 0x73, + 0x21, 0xc7, 0x22, 0x18, 0x22, 0x66, 0x22, 0xb0, 0x22, 0xf8, 0x23, 0x3d, + 0x23, 0x7f, 0x23, 0xbe, 0x23, 0xfa, 0x24, 0x33, 0x24, 0x69, 0x24, 0x9d, + 0x24, 0xce, 0x24, 0xfc, 0x25, 0x28, 0x25, 0x51, 0x25, 0x77, 0x25, 0x9b, + 0x25, 0xbd, 0x25, 0xdc, 0x25, 0xf9, 0x26, 0x13, 0x26, 0x2b, 0x26, 0x41, + 0x26, 0x55, 0x26, 0x67, 0x26, 0x77, 0x26, 0x85, 0x26, 0x91, 0x26, 0x9c, + 0x26, 0xa7, 0x26, 0xb2, 0x26, 0xbd, 0x26, 0xc8, 0x26, 0xd3, 0x26, 0xde, + 0x26, 0xe9, 0x26, 0xf6, 0x27, 0x04, 0x27, 0x15, 0x27, 0x27, 0x27, 0x3c, + 0x27, 0x53, 0x27, 0x6c, 0x27, 0x87, 0x27, 0xa5, 0x27, 0xc5, 0x27, 0xe7, + 0x28, 0x0c, 0x28, 0x34, 0x28, 0x5d, 0x28, 0x8a, 0x28, 0xb9, 0x28, 0xeb, + 0x29, 0x20, 0x29, 0x57, 0x29, 0x91, 0x29, 0xce, 0x2a, 0x0e, 0x2a, 0x51, + 0x2a, 0x97, 0x2a, 0xe0, 0x2b, 0x2c, 0x2b, 0x7b, 0x2b, 0xcd, 0x2c, 0x22, + 0x2c, 0x7a, 0x2c, 0xd6, 0x2d, 0x35, 0x2d, 0x97, 0x2d, 0xfc, 0x2e, 0x65, + 0x2e, 0xd1, 0x2f, 0x41, 0x2f, 0xb4, 0x30, 0x2a, 0x30, 0xa4, 0x31, 0x21, + 0x31, 0xa2, 0x32, 0x27, 0x32, 0xaf, 0x33, 0x3b, 0x33, 0xca, 0x34, 0x5d, + 0x34, 0xf4, 0x35, 0x8f, 0x36, 0x2d, 0x36, 0xcf, 0x37, 0x75, 0x38, 0x1f, + 0x38, 0xcc, 0x39, 0x7e, 0x3a, 0x33, 0x3a, 0xed, 0x3b, 0xaa, 0x3c, 0x6b, + 0x3d, 0x31, 0x3d, 0xfa, 0x3e, 0xc7, 0x3f, 0x99, 0x40, 0x6e, 0x41, 0x48, + 0x42, 0x26, 0x43, 0x08, 0x43, 0xee, 0x44, 0xd8, 0x45, 0xc7, 0x46, 0xba, + 0x47, 0xb1, 0x48, 0xac, 0x49, 0xac, 0x4a, 0xb0, 0x4b, 0xb9, 0x4c, 0xc6, + 0x4d, 0xd7, 0x4e, 0xec, 0x50, 0x06, 0x51, 0x25, 0x52, 0x48, 0x53, 0x70, + 0x54, 0x9c, 0x55, 0xcc, 0x57, 0x01, 0x58, 0x3b, 0x59, 0x79, 0x5a, 0xbc, + 0x5c, 0x04, 0x5d, 0x50, 0x5e, 0xa1, 0x5f, 0xf7, 0x61, 0x51, 0x62, 0xb0, + 0x64, 0x14, 0x65, 0x7c, 0x66, 0xea, 0x68, 0x5c, 0x69, 0xd3, 0x6b, 0x4f, + 0x6c, 0xcf, 0x6e, 0x55, 0x6f, 0xdf, 0x71, 0x6f, 0x73, 0x03, 0x74, 0x9c, + 0x76, 0x3a, 0x77, 0xdd, 0x79, 0x85, 0x7b, 0x32, 0x7c, 0xe5, 0x7e, 0x9c, + 0x80, 0x58, 0x82, 0x19, 0x83, 0xe0, 0x85, 0xab, 0x87, 0x7c, 0x89, 0x52, + 0x8b, 0x2d, 0x8d, 0x0d, 0x8e, 0xf2, 0x90, 0xdc, 0x92, 0xcc, 0x94, 0xc1, + 0x96, 0xbb, 0x98, 0xbb, 0x9a, 0xc0, 0x9c, 0xca, 0x9e, 0xd9, 0xa0, 0xee, + 0xa3, 0x08, 0xa5, 0x27, 0xa7, 0x4c, 0xa9, 0x76, 0xab, 0xa6, 0xad, 0xdb, + 0xb0, 0x15, 0xb2, 0x55, 0xb4, 0x9a, 0xb6, 0xe5, 0xb9, 0x36, 0xbb, 0x8b, + 0xbd, 0xe7, 0xc0, 0x48, 0xc2, 0xae, 0xc5, 0x1a, 0xc7, 0x8c, 0xca, 0x03, + 0xcc, 0x80, 0xcf, 0x02, 0xd1, 0x8a, 0xd4, 0x18, 0xd6, 0xab, 0xd9, 0x44, + 0xdb, 0xe3, 0xde, 0x87, 0xe1, 0x32, 0xe3, 0xe2, 0xe6, 0x97, 0xe9, 0x53, + 0xec, 0x14, 0xee, 0xdb, 0xf1, 0xa8, 0xf4, 0x7a, 0xf7, 0x53, 0xfa, 0x31, + 0xfd, 0x15, 0xff, 0xff, 0x63, 0x75, 0x72, 0x76, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x10, 0x02, 0x1b, 0x03, 0x22, + 0x04, 0x24, 0x05, 0x23, 0x06, 0x1d, 0x07, 0x12, 0x08, 0x04, 0x08, 0xf1, + 0x09, 0xda, 0x0a, 0xbe, 0x0b, 0x9f, 0x0c, 0x7b, 0x0d, 0x53, 0x0e, 0x28, + 0x0e, 0xf8, 0x0f, 0xc3, 0x10, 0x8b, 0x11, 0x4f, 0x12, 0x0f, 0x12, 0xcb, + 0x13, 0x83, 0x14, 0x37, 0x14, 0xe7, 0x15, 0x93, 0x16, 0x3c, 0x16, 0xe0, + 0x17, 0x81, 0x18, 0x1e, 0x18, 0xb7, 0x19, 0x4d, 0x19, 0xdf, 0x1a, 0x6d, + 0x1a, 0xf7, 0x1b, 0x7e, 0x1c, 0x01, 0x1c, 0x81, 0x1c, 0xfd, 0x1d, 0x76, + 0x1d, 0xeb, 0x1e, 0x5d, 0x1e, 0xcb, 0x1f, 0x36, 0x1f, 0x9e, 0x20, 0x02, + 0x20, 0x63, 0x20, 0xc1, 0x21, 0x1b, 0x21, 0x73, 0x21, 0xc7, 0x22, 0x18, + 0x22, 0x66, 0x22, 0xb0, 0x22, 0xf8, 0x23, 0x3d, 0x23, 0x7f, 0x23, 0xbe, + 0x23, 0xfa, 0x24, 0x33, 0x24, 0x69, 0x24, 0x9d, 0x24, 0xce, 0x24, 0xfc, + 0x25, 0x28, 0x25, 0x51, 0x25, 0x77, 0x25, 0x9b, 0x25, 0xbd, 0x25, 0xdc, + 0x25, 0xf9, 0x26, 0x13, 0x26, 0x2b, 0x26, 0x41, 0x26, 0x55, 0x26, 0x67, + 0x26, 0x77, 0x26, 0x85, 0x26, 0x91, 0x26, 0x9c, 0x26, 0xa7, 0x26, 0xb2, + 0x26, 0xbd, 0x26, 0xc8, 0x26, 0xd3, 0x26, 0xde, 0x26, 0xe9, 0x26, 0xf6, + 0x27, 0x04, 0x27, 0x15, 0x27, 0x27, 0x27, 0x3c, 0x27, 0x53, 0x27, 0x6c, + 0x27, 0x87, 0x27, 0xa5, 0x27, 0xc5, 0x27, 0xe7, 0x28, 0x0c, 0x28, 0x34, + 0x28, 0x5d, 0x28, 0x8a, 0x28, 0xb9, 0x28, 0xeb, 0x29, 0x20, 0x29, 0x57, + 0x29, 0x91, 0x29, 0xce, 0x2a, 0x0e, 0x2a, 0x51, 0x2a, 0x97, 0x2a, 0xe0, + 0x2b, 0x2c, 0x2b, 0x7b, 0x2b, 0xcd, 0x2c, 0x22, 0x2c, 0x7a, 0x2c, 0xd6, + 0x2d, 0x35, 0x2d, 0x97, 0x2d, 0xfc, 0x2e, 0x65, 0x2e, 0xd1, 0x2f, 0x41, + 0x2f, 0xb4, 0x30, 0x2a, 0x30, 0xa4, 0x31, 0x21, 0x31, 0xa2, 0x32, 0x27, + 0x32, 0xaf, 0x33, 0x3b, 0x33, 0xca, 0x34, 0x5d, 0x34, 0xf4, 0x35, 0x8f, + 0x36, 0x2d, 0x36, 0xcf, 0x37, 0x75, 0x38, 0x1f, 0x38, 0xcc, 0x39, 0x7e, + 0x3a, 0x33, 0x3a, 0xed, 0x3b, 0xaa, 0x3c, 0x6b, 0x3d, 0x31, 0x3d, 0xfa, + 0x3e, 0xc7, 0x3f, 0x99, 0x40, 0x6e, 0x41, 0x48, 0x42, 0x26, 0x43, 0x08, + 0x43, 0xee, 0x44, 0xd8, 0x45, 0xc7, 0x46, 0xba, 0x47, 0xb1, 0x48, 0xac, + 0x49, 0xac, 0x4a, 0xb0, 0x4b, 0xb9, 0x4c, 0xc6, 0x4d, 0xd7, 0x4e, 0xec, + 0x50, 0x06, 0x51, 0x25, 0x52, 0x48, 0x53, 0x70, 0x54, 0x9c, 0x55, 0xcc, + 0x57, 0x01, 0x58, 0x3b, 0x59, 0x79, 0x5a, 0xbc, 0x5c, 0x04, 0x5d, 0x50, + 0x5e, 0xa1, 0x5f, 0xf7, 0x61, 0x51, 0x62, 0xb0, 0x64, 0x14, 0x65, 0x7c, + 0x66, 0xea, 0x68, 0x5c, 0x69, 0xd3, 0x6b, 0x4f, 0x6c, 0xcf, 0x6e, 0x55, + 0x6f, 0xdf, 0x71, 0x6f, 0x73, 0x03, 0x74, 0x9c, 0x76, 0x3a, 0x77, 0xdd, + 0x79, 0x85, 0x7b, 0x32, 0x7c, 0xe5, 0x7e, 0x9c, 0x80, 0x58, 0x82, 0x19, + 0x83, 0xe0, 0x85, 0xab, 0x87, 0x7c, 0x89, 0x52, 0x8b, 0x2d, 0x8d, 0x0d, + 0x8e, 0xf2, 0x90, 0xdc, 0x92, 0xcc, 0x94, 0xc1, 0x96, 0xbb, 0x98, 0xbb, + 0x9a, 0xc0, 0x9c, 0xca, 0x9e, 0xd9, 0xa0, 0xee, 0xa3, 0x08, 0xa5, 0x27, + 0xa7, 0x4c, 0xa9, 0x76, 0xab, 0xa6, 0xad, 0xdb, 0xb0, 0x15, 0xb2, 0x55, + 0xb4, 0x9a, 0xb6, 0xe5, 0xb9, 0x36, 0xbb, 0x8b, 0xbd, 0xe7, 0xc0, 0x48, + 0xc2, 0xae, 0xc5, 0x1a, 0xc7, 0x8c, 0xca, 0x03, 0xcc, 0x80, 0xcf, 0x02, + 0xd1, 0x8a, 0xd4, 0x18, 0xd6, 0xab, 0xd9, 0x44, 0xdb, 0xe3, 0xde, 0x87, + 0xe1, 0x32, 0xe3, 0xe2, 0xe6, 0x97, 0xe9, 0x53, 0xec, 0x14, 0xee, 0xdb, + 0xf1, 0xa8, 0xf4, 0x7a, 0xf7, 0x53, 0xfa, 0x31, 0xfd, 0x15, 0xff, 0xff, + 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x11, 0x27, 0x83, 0x33, + 0x00, 0x00, 0x92, 0xa1, 0x41, 0x40, 0x00, 0x00, 0x11, 0x27, 0x63, 0x6c, + 0xa3, 0xa5, 0x92, 0xa1, 0x21, 0x79, 0xa3, 0xa5, 0x6d, 0x81, 0xdf, 0x8c, + 0x5c, 0x5a, 0xee, 0xfa, 0x9d, 0x9a, 0x5c, 0x5a, 0x6d, 0x81, 0xbf, 0xc5, + 0xff, 0xff, 0xee, 0xfa, 0x7d, 0xd3, 0xff, 0xff, 0x63, 0x75, 0x72, 0x76, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x75, 0x72, 0x76, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x75, 0x72, 0x76, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6d, 0x41, 0x42, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, + 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x74, 0x00, 0x00, 0x06, 0x98, + 0x00, 0x00, 0x06, 0xdc, 0x63, 0x75, 0x72, 0x76, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x63, 0x75, 0x72, 0x76, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x63, 0x75, 0x72, 0x76, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x17, 0x51, 0x00, 0x00, 0xf6, 0xb1, + 0x00, 0x00, 0x5b, 0xa7, 0x00, 0x00, 0x8e, 0x7e, 0x00, 0x01, 0xcb, 0x45, + 0x00, 0x00, 0x26, 0xd5, 0x00, 0x00, 0x08, 0xe7, 0x00, 0x00, 0x3e, 0x30, + 0x00, 0x01, 0xc9, 0x57, 0xff, 0xff, 0xa4, 0x18, 0xff, 0xff, 0xa0, 0xaf, + 0xff, 0xff, 0xb1, 0x5f, 0x63, 0x75, 0x72, 0x76, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x10, 0x02, 0x1b, 0x03, 0x22, + 0x04, 0x24, 0x05, 0x23, 0x06, 0x1d, 0x07, 0x12, 0x08, 0x04, 0x08, 0xf1, + 0x09, 0xda, 0x0a, 0xbe, 0x0b, 0x9f, 0x0c, 0x7b, 0x0d, 0x53, 0x0e, 0x28, + 0x0e, 0xf8, 0x0f, 0xc3, 0x10, 0x8b, 0x11, 0x4f, 0x12, 0x0f, 0x12, 0xcb, + 0x13, 0x83, 0x14, 0x37, 0x14, 0xe7, 0x15, 0x93, 0x16, 0x3c, 0x16, 0xe0, + 0x17, 0x81, 0x18, 0x1e, 0x18, 0xb7, 0x19, 0x4d, 0x19, 0xdf, 0x1a, 0x6d, + 0x1a, 0xf7, 0x1b, 0x7e, 0x1c, 0x01, 0x1c, 0x81, 0x1c, 0xfd, 0x1d, 0x76, + 0x1d, 0xeb, 0x1e, 0x5d, 0x1e, 0xcb, 0x1f, 0x36, 0x1f, 0x9e, 0x20, 0x02, + 0x20, 0x63, 0x20, 0xc1, 0x21, 0x1b, 0x21, 0x73, 0x21, 0xc7, 0x22, 0x18, + 0x22, 0x66, 0x22, 0xb0, 0x22, 0xf8, 0x23, 0x3d, 0x23, 0x7f, 0x23, 0xbe, + 0x23, 0xfa, 0x24, 0x33, 0x24, 0x69, 0x24, 0x9d, 0x24, 0xce, 0x24, 0xfc, + 0x25, 0x28, 0x25, 0x51, 0x25, 0x77, 0x25, 0x9b, 0x25, 0xbd, 0x25, 0xdc, + 0x25, 0xf9, 0x26, 0x13, 0x26, 0x2b, 0x26, 0x41, 0x26, 0x55, 0x26, 0x67, + 0x26, 0x77, 0x26, 0x85, 0x26, 0x91, 0x26, 0x9c, 0x26, 0xa7, 0x26, 0xb2, + 0x26, 0xbd, 0x26, 0xc8, 0x26, 0xd3, 0x26, 0xde, 0x26, 0xe9, 0x26, 0xf6, + 0x27, 0x04, 0x27, 0x15, 0x27, 0x27, 0x27, 0x3c, 0x27, 0x53, 0x27, 0x6c, + 0x27, 0x87, 0x27, 0xa5, 0x27, 0xc5, 0x27, 0xe7, 0x28, 0x0c, 0x28, 0x34, + 0x28, 0x5d, 0x28, 0x8a, 0x28, 0xb9, 0x28, 0xeb, 0x29, 0x20, 0x29, 0x57, + 0x29, 0x91, 0x29, 0xce, 0x2a, 0x0e, 0x2a, 0x51, 0x2a, 0x97, 0x2a, 0xe0, + 0x2b, 0x2c, 0x2b, 0x7b, 0x2b, 0xcd, 0x2c, 0x22, 0x2c, 0x7a, 0x2c, 0xd6, + 0x2d, 0x35, 0x2d, 0x97, 0x2d, 0xfc, 0x2e, 0x65, 0x2e, 0xd1, 0x2f, 0x41, + 0x2f, 0xb4, 0x30, 0x2a, 0x30, 0xa4, 0x31, 0x21, 0x31, 0xa2, 0x32, 0x27, + 0x32, 0xaf, 0x33, 0x3b, 0x33, 0xca, 0x34, 0x5d, 0x34, 0xf4, 0x35, 0x8f, + 0x36, 0x2d, 0x36, 0xcf, 0x37, 0x75, 0x38, 0x1f, 0x38, 0xcc, 0x39, 0x7e, + 0x3a, 0x33, 0x3a, 0xed, 0x3b, 0xaa, 0x3c, 0x6b, 0x3d, 0x31, 0x3d, 0xfa, + 0x3e, 0xc7, 0x3f, 0x99, 0x40, 0x6e, 0x41, 0x48, 0x42, 0x26, 0x43, 0x08, + 0x43, 0xee, 0x44, 0xd8, 0x45, 0xc7, 0x46, 0xba, 0x47, 0xb1, 0x48, 0xac, + 0x49, 0xac, 0x4a, 0xb0, 0x4b, 0xb9, 0x4c, 0xc6, 0x4d, 0xd7, 0x4e, 0xec, + 0x50, 0x06, 0x51, 0x25, 0x52, 0x48, 0x53, 0x70, 0x54, 0x9c, 0x55, 0xcc, + 0x57, 0x01, 0x58, 0x3b, 0x59, 0x79, 0x5a, 0xbc, 0x5c, 0x04, 0x5d, 0x50, + 0x5e, 0xa1, 0x5f, 0xf7, 0x61, 0x51, 0x62, 0xb0, 0x64, 0x14, 0x65, 0x7c, + 0x66, 0xea, 0x68, 0x5c, 0x69, 0xd3, 0x6b, 0x4f, 0x6c, 0xcf, 0x6e, 0x55, + 0x6f, 0xdf, 0x71, 0x6f, 0x73, 0x03, 0x74, 0x9c, 0x76, 0x3a, 0x77, 0xdd, + 0x79, 0x85, 0x7b, 0x32, 0x7c, 0xe5, 0x7e, 0x9c, 0x80, 0x58, 0x82, 0x19, + 0x83, 0xe0, 0x85, 0xab, 0x87, 0x7c, 0x89, 0x52, 0x8b, 0x2d, 0x8d, 0x0d, + 0x8e, 0xf2, 0x90, 0xdc, 0x92, 0xcc, 0x94, 0xc1, 0x96, 0xbb, 0x98, 0xbb, + 0x9a, 0xc0, 0x9c, 0xca, 0x9e, 0xd9, 0xa0, 0xee, 0xa3, 0x08, 0xa5, 0x27, + 0xa7, 0x4c, 0xa9, 0x76, 0xab, 0xa6, 0xad, 0xdb, 0xb0, 0x15, 0xb2, 0x55, + 0xb4, 0x9a, 0xb6, 0xe5, 0xb9, 0x36, 0xbb, 0x8b, 0xbd, 0xe7, 0xc0, 0x48, + 0xc2, 0xae, 0xc5, 0x1a, 0xc7, 0x8c, 0xca, 0x03, 0xcc, 0x80, 0xcf, 0x02, + 0xd1, 0x8a, 0xd4, 0x18, 0xd6, 0xab, 0xd9, 0x44, 0xdb, 0xe3, 0xde, 0x87, + 0xe1, 0x32, 0xe3, 0xe2, 0xe6, 0x97, 0xe9, 0x53, 0xec, 0x14, 0xee, 0xdb, + 0xf1, 0xa8, 0xf4, 0x7a, 0xf7, 0x53, 0xfa, 0x31, 0xfd, 0x15, 0xff, 0xff, + 0x63, 0x75, 0x72, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x10, 0x02, 0x1b, 0x03, 0x22, 0x04, 0x24, 0x05, 0x23, + 0x06, 0x1d, 0x07, 0x12, 0x08, 0x04, 0x08, 0xf1, 0x09, 0xda, 0x0a, 0xbe, + 0x0b, 0x9f, 0x0c, 0x7b, 0x0d, 0x53, 0x0e, 0x28, 0x0e, 0xf8, 0x0f, 0xc3, + 0x10, 0x8b, 0x11, 0x4f, 0x12, 0x0f, 0x12, 0xcb, 0x13, 0x83, 0x14, 0x37, + 0x14, 0xe7, 0x15, 0x93, 0x16, 0x3c, 0x16, 0xe0, 0x17, 0x81, 0x18, 0x1e, + 0x18, 0xb7, 0x19, 0x4d, 0x19, 0xdf, 0x1a, 0x6d, 0x1a, 0xf7, 0x1b, 0x7e, + 0x1c, 0x01, 0x1c, 0x81, 0x1c, 0xfd, 0x1d, 0x76, 0x1d, 0xeb, 0x1e, 0x5d, + 0x1e, 0xcb, 0x1f, 0x36, 0x1f, 0x9e, 0x20, 0x02, 0x20, 0x63, 0x20, 0xc1, + 0x21, 0x1b, 0x21, 0x73, 0x21, 0xc7, 0x22, 0x18, 0x22, 0x66, 0x22, 0xb0, + 0x22, 0xf8, 0x23, 0x3d, 0x23, 0x7f, 0x23, 0xbe, 0x23, 0xfa, 0x24, 0x33, + 0x24, 0x69, 0x24, 0x9d, 0x24, 0xce, 0x24, 0xfc, 0x25, 0x28, 0x25, 0x51, + 0x25, 0x77, 0x25, 0x9b, 0x25, 0xbd, 0x25, 0xdc, 0x25, 0xf9, 0x26, 0x13, + 0x26, 0x2b, 0x26, 0x41, 0x26, 0x55, 0x26, 0x67, 0x26, 0x77, 0x26, 0x85, + 0x26, 0x91, 0x26, 0x9c, 0x26, 0xa7, 0x26, 0xb2, 0x26, 0xbd, 0x26, 0xc8, + 0x26, 0xd3, 0x26, 0xde, 0x26, 0xe9, 0x26, 0xf6, 0x27, 0x04, 0x27, 0x15, + 0x27, 0x27, 0x27, 0x3c, 0x27, 0x53, 0x27, 0x6c, 0x27, 0x87, 0x27, 0xa5, + 0x27, 0xc5, 0x27, 0xe7, 0x28, 0x0c, 0x28, 0x34, 0x28, 0x5d, 0x28, 0x8a, + 0x28, 0xb9, 0x28, 0xeb, 0x29, 0x20, 0x29, 0x57, 0x29, 0x91, 0x29, 0xce, + 0x2a, 0x0e, 0x2a, 0x51, 0x2a, 0x97, 0x2a, 0xe0, 0x2b, 0x2c, 0x2b, 0x7b, + 0x2b, 0xcd, 0x2c, 0x22, 0x2c, 0x7a, 0x2c, 0xd6, 0x2d, 0x35, 0x2d, 0x97, + 0x2d, 0xfc, 0x2e, 0x65, 0x2e, 0xd1, 0x2f, 0x41, 0x2f, 0xb4, 0x30, 0x2a, + 0x30, 0xa4, 0x31, 0x21, 0x31, 0xa2, 0x32, 0x27, 0x32, 0xaf, 0x33, 0x3b, + 0x33, 0xca, 0x34, 0x5d, 0x34, 0xf4, 0x35, 0x8f, 0x36, 0x2d, 0x36, 0xcf, + 0x37, 0x75, 0x38, 0x1f, 0x38, 0xcc, 0x39, 0x7e, 0x3a, 0x33, 0x3a, 0xed, + 0x3b, 0xaa, 0x3c, 0x6b, 0x3d, 0x31, 0x3d, 0xfa, 0x3e, 0xc7, 0x3f, 0x99, + 0x40, 0x6e, 0x41, 0x48, 0x42, 0x26, 0x43, 0x08, 0x43, 0xee, 0x44, 0xd8, + 0x45, 0xc7, 0x46, 0xba, 0x47, 0xb1, 0x48, 0xac, 0x49, 0xac, 0x4a, 0xb0, + 0x4b, 0xb9, 0x4c, 0xc6, 0x4d, 0xd7, 0x4e, 0xec, 0x50, 0x06, 0x51, 0x25, + 0x52, 0x48, 0x53, 0x70, 0x54, 0x9c, 0x55, 0xcc, 0x57, 0x01, 0x58, 0x3b, + 0x59, 0x79, 0x5a, 0xbc, 0x5c, 0x04, 0x5d, 0x50, 0x5e, 0xa1, 0x5f, 0xf7, + 0x61, 0x51, 0x62, 0xb0, 0x64, 0x14, 0x65, 0x7c, 0x66, 0xea, 0x68, 0x5c, + 0x69, 0xd3, 0x6b, 0x4f, 0x6c, 0xcf, 0x6e, 0x55, 0x6f, 0xdf, 0x71, 0x6f, + 0x73, 0x03, 0x74, 0x9c, 0x76, 0x3a, 0x77, 0xdd, 0x79, 0x85, 0x7b, 0x32, + 0x7c, 0xe5, 0x7e, 0x9c, 0x80, 0x58, 0x82, 0x19, 0x83, 0xe0, 0x85, 0xab, + 0x87, 0x7c, 0x89, 0x52, 0x8b, 0x2d, 0x8d, 0x0d, 0x8e, 0xf2, 0x90, 0xdc, + 0x92, 0xcc, 0x94, 0xc1, 0x96, 0xbb, 0x98, 0xbb, 0x9a, 0xc0, 0x9c, 0xca, + 0x9e, 0xd9, 0xa0, 0xee, 0xa3, 0x08, 0xa5, 0x27, 0xa7, 0x4c, 0xa9, 0x76, + 0xab, 0xa6, 0xad, 0xdb, 0xb0, 0x15, 0xb2, 0x55, 0xb4, 0x9a, 0xb6, 0xe5, + 0xb9, 0x36, 0xbb, 0x8b, 0xbd, 0xe7, 0xc0, 0x48, 0xc2, 0xae, 0xc5, 0x1a, + 0xc7, 0x8c, 0xca, 0x03, 0xcc, 0x80, 0xcf, 0x02, 0xd1, 0x8a, 0xd4, 0x18, + 0xd6, 0xab, 0xd9, 0x44, 0xdb, 0xe3, 0xde, 0x87, 0xe1, 0x32, 0xe3, 0xe2, + 0xe6, 0x97, 0xe9, 0x53, 0xec, 0x14, 0xee, 0xdb, 0xf1, 0xa8, 0xf4, 0x7a, + 0xf7, 0x53, 0xfa, 0x31, 0xfd, 0x15, 0xff, 0xff, 0x63, 0x75, 0x72, 0x76, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x10, + 0x02, 0x1b, 0x03, 0x22, 0x04, 0x24, 0x05, 0x23, 0x06, 0x1d, 0x07, 0x12, + 0x08, 0x04, 0x08, 0xf1, 0x09, 0xda, 0x0a, 0xbe, 0x0b, 0x9f, 0x0c, 0x7b, + 0x0d, 0x53, 0x0e, 0x28, 0x0e, 0xf8, 0x0f, 0xc3, 0x10, 0x8b, 0x11, 0x4f, + 0x12, 0x0f, 0x12, 0xcb, 0x13, 0x83, 0x14, 0x37, 0x14, 0xe7, 0x15, 0x93, + 0x16, 0x3c, 0x16, 0xe0, 0x17, 0x81, 0x18, 0x1e, 0x18, 0xb7, 0x19, 0x4d, + 0x19, 0xdf, 0x1a, 0x6d, 0x1a, 0xf7, 0x1b, 0x7e, 0x1c, 0x01, 0x1c, 0x81, + 0x1c, 0xfd, 0x1d, 0x76, 0x1d, 0xeb, 0x1e, 0x5d, 0x1e, 0xcb, 0x1f, 0x36, + 0x1f, 0x9e, 0x20, 0x02, 0x20, 0x63, 0x20, 0xc1, 0x21, 0x1b, 0x21, 0x73, + 0x21, 0xc7, 0x22, 0x18, 0x22, 0x66, 0x22, 0xb0, 0x22, 0xf8, 0x23, 0x3d, + 0x23, 0x7f, 0x23, 0xbe, 0x23, 0xfa, 0x24, 0x33, 0x24, 0x69, 0x24, 0x9d, + 0x24, 0xce, 0x24, 0xfc, 0x25, 0x28, 0x25, 0x51, 0x25, 0x77, 0x25, 0x9b, + 0x25, 0xbd, 0x25, 0xdc, 0x25, 0xf9, 0x26, 0x13, 0x26, 0x2b, 0x26, 0x41, + 0x26, 0x55, 0x26, 0x67, 0x26, 0x77, 0x26, 0x85, 0x26, 0x91, 0x26, 0x9c, + 0x26, 0xa7, 0x26, 0xb2, 0x26, 0xbd, 0x26, 0xc8, 0x26, 0xd3, 0x26, 0xde, + 0x26, 0xe9, 0x26, 0xf6, 0x27, 0x04, 0x27, 0x15, 0x27, 0x27, 0x27, 0x3c, + 0x27, 0x53, 0x27, 0x6c, 0x27, 0x87, 0x27, 0xa5, 0x27, 0xc5, 0x27, 0xe7, + 0x28, 0x0c, 0x28, 0x34, 0x28, 0x5d, 0x28, 0x8a, 0x28, 0xb9, 0x28, 0xeb, + 0x29, 0x20, 0x29, 0x57, 0x29, 0x91, 0x29, 0xce, 0x2a, 0x0e, 0x2a, 0x51, + 0x2a, 0x97, 0x2a, 0xe0, 0x2b, 0x2c, 0x2b, 0x7b, 0x2b, 0xcd, 0x2c, 0x22, + 0x2c, 0x7a, 0x2c, 0xd6, 0x2d, 0x35, 0x2d, 0x97, 0x2d, 0xfc, 0x2e, 0x65, + 0x2e, 0xd1, 0x2f, 0x41, 0x2f, 0xb4, 0x30, 0x2a, 0x30, 0xa4, 0x31, 0x21, + 0x31, 0xa2, 0x32, 0x27, 0x32, 0xaf, 0x33, 0x3b, 0x33, 0xca, 0x34, 0x5d, + 0x34, 0xf4, 0x35, 0x8f, 0x36, 0x2d, 0x36, 0xcf, 0x37, 0x75, 0x38, 0x1f, + 0x38, 0xcc, 0x39, 0x7e, 0x3a, 0x33, 0x3a, 0xed, 0x3b, 0xaa, 0x3c, 0x6b, + 0x3d, 0x31, 0x3d, 0xfa, 0x3e, 0xc7, 0x3f, 0x99, 0x40, 0x6e, 0x41, 0x48, + 0x42, 0x26, 0x43, 0x08, 0x43, 0xee, 0x44, 0xd8, 0x45, 0xc7, 0x46, 0xba, + 0x47, 0xb1, 0x48, 0xac, 0x49, 0xac, 0x4a, 0xb0, 0x4b, 0xb9, 0x4c, 0xc6, + 0x4d, 0xd7, 0x4e, 0xec, 0x50, 0x06, 0x51, 0x25, 0x52, 0x48, 0x53, 0x70, + 0x54, 0x9c, 0x55, 0xcc, 0x57, 0x01, 0x58, 0x3b, 0x59, 0x79, 0x5a, 0xbc, + 0x5c, 0x04, 0x5d, 0x50, 0x5e, 0xa1, 0x5f, 0xf7, 0x61, 0x51, 0x62, 0xb0, + 0x64, 0x14, 0x65, 0x7c, 0x66, 0xea, 0x68, 0x5c, 0x69, 0xd3, 0x6b, 0x4f, + 0x6c, 0xcf, 0x6e, 0x55, 0x6f, 0xdf, 0x71, 0x6f, 0x73, 0x03, 0x74, 0x9c, + 0x76, 0x3a, 0x77, 0xdd, 0x79, 0x85, 0x7b, 0x32, 0x7c, 0xe5, 0x7e, 0x9c, + 0x80, 0x58, 0x82, 0x19, 0x83, 0xe0, 0x85, 0xab, 0x87, 0x7c, 0x89, 0x52, + 0x8b, 0x2d, 0x8d, 0x0d, 0x8e, 0xf2, 0x90, 0xdc, 0x92, 0xcc, 0x94, 0xc1, + 0x96, 0xbb, 0x98, 0xbb, 0x9a, 0xc0, 0x9c, 0xca, 0x9e, 0xd9, 0xa0, 0xee, + 0xa3, 0x08, 0xa5, 0x27, 0xa7, 0x4c, 0xa9, 0x76, 0xab, 0xa6, 0xad, 0xdb, + 0xb0, 0x15, 0xb2, 0x55, 0xb4, 0x9a, 0xb6, 0xe5, 0xb9, 0x36, 0xbb, 0x8b, + 0xbd, 0xe7, 0xc0, 0x48, 0xc2, 0xae, 0xc5, 0x1a, 0xc7, 0x8c, 0xca, 0x03, + 0xcc, 0x80, 0xcf, 0x02, 0xd1, 0x8a, 0xd4, 0x18, 0xd6, 0xab, 0xd9, 0x44, + 0xdb, 0xe3, 0xde, 0x87, 0xe1, 0x32, 0xe3, 0xe2, 0xe6, 0x97, 0xe9, 0x53, + 0xec, 0x14, 0xee, 0xdb, 0xf1, 0xa8, 0xf4, 0x7a, 0xf7, 0x53, 0xfa, 0x31, + 0xfd, 0x15, 0xff, 0xff, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x11, 0x27, 0x83, 0x33, 0x00, 0x00, 0x92, 0xa1, 0x41, 0x40, 0x00, 0x00, + 0x11, 0x27, 0x63, 0x6c, 0xa3, 0xa5, 0x92, 0xa1, 0x21, 0x79, 0xa3, 0xa5, + 0x6d, 0x81, 0xdf, 0x8c, 0x5c, 0x5a, 0xee, 0xfa, 0x9d, 0x9a, 0x5c, 0x5a, + 0x6d, 0x81, 0xbf, 0xc5, 0xff, 0xff, 0xee, 0xfa, 0x7d, 0xd3, 0xff, 0xff, + 0x63, 0x75, 0x72, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x63, 0x75, 0x72, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x63, 0x75, 0x72, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +unsigned char overshoot_profile_data[] = { + 0x00, 0x00, 0x27, 0xa4, 0x6c, 0x69, 0x6e, 0x6f, 0x02, 0x20, 0x00, 0x00, + 0x6d, 0x6e, 0x74, 0x72, 0x52, 0x47, 0x42, 0x20, 0x58, 0x59, 0x5a, 0x20, + 0x07, 0xe0, 0x00, 0x08, 0x00, 0x1a, 0x00, 0x03, 0x00, 0x3b, 0x00, 0x20, + 0x61, 0x63, 0x73, 0x70, 0x4d, 0x53, 0x46, 0x54, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6, 0xd6, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x2d, 0x4d, 0x53, 0x46, 0x54, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, + 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x01, 0x60, + 0x63, 0x70, 0x72, 0x74, 0x00, 0x00, 0x02, 0x5c, 0x00, 0x00, 0x00, 0x31, + 0x77, 0x74, 0x70, 0x74, 0x00, 0x00, 0x02, 0x90, 0x00, 0x00, 0x00, 0x14, + 0x72, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x02, 0xa4, 0x00, 0x00, 0x00, 0x14, + 0x67, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x02, 0xb8, 0x00, 0x00, 0x00, 0x14, + 0x62, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x02, 0xcc, 0x00, 0x00, 0x00, 0x14, + 0x72, 0x54, 0x52, 0x43, 0x00, 0x00, 0x02, 0xe0, 0x00, 0x00, 0x02, 0x0c, + 0x67, 0x54, 0x52, 0x43, 0x00, 0x00, 0x04, 0xec, 0x00, 0x00, 0x02, 0x0c, + 0x62, 0x54, 0x52, 0x43, 0x00, 0x00, 0x06, 0xf8, 0x00, 0x00, 0x02, 0x0c, + 0x4d, 0x53, 0x30, 0x30, 0x00, 0x00, 0x09, 0x04, 0x00, 0x00, 0x1e, 0x9e, + 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, + 0x73, 0x52, 0x47, 0x42, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, + 0x20, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x77, 0x69, 0x74, + 0x68, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x20, 0x68, 0x61, + 0x72, 0x64, 0x77, 0x61, 0x72, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x64, 0x61, 0x74, + 0x61, 0x20, 0x64, 0x65, 0x72, 0x69, 0x76, 0x65, 0x64, 0x20, 0x66, 0x72, + 0x6f, 0x6d, 0x20, 0x63, 0x61, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x00, 0x00, 0x65, 0x6e, 0x55, 0x53, 0x00, 0x00, 0x00, 0x57, + 0x00, 0x73, 0x00, 0x52, 0x00, 0x47, 0x00, 0x42, 0x00, 0x20, 0x00, 0x64, + 0x00, 0x69, 0x00, 0x73, 0x00, 0x70, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x79, + 0x00, 0x20, 0x00, 0x70, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x69, + 0x00, 0x6c, 0x00, 0x65, 0x00, 0x20, 0x00, 0x77, 0x00, 0x69, 0x00, 0x74, + 0x00, 0x68, 0x00, 0x20, 0x00, 0x64, 0x00, 0x69, 0x00, 0x73, 0x00, 0x70, + 0x00, 0x6c, 0x00, 0x61, 0x00, 0x79, 0x00, 0x20, 0x00, 0x68, 0x00, 0x61, + 0x00, 0x72, 0x00, 0x64, 0x00, 0x77, 0x00, 0x61, 0x00, 0x72, 0x00, 0x65, + 0x00, 0x20, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x66, 0x00, 0x69, + 0x00, 0x67, 0x00, 0x75, 0x00, 0x72, 0x00, 0x61, 0x00, 0x74, 0x00, 0x69, + 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x20, 0x00, 0x64, 0x00, 0x61, 0x00, 0x74, + 0x00, 0x61, 0x00, 0x20, 0x00, 0x64, 0x00, 0x65, 0x00, 0x72, 0x00, 0x69, + 0x00, 0x76, 0x00, 0x65, 0x00, 0x64, 0x00, 0x20, 0x00, 0x66, 0x00, 0x72, + 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x20, 0x00, 0x63, 0x00, 0x61, 0x00, 0x6c, + 0x00, 0x69, 0x00, 0x62, 0x00, 0x72, 0x00, 0x61, 0x00, 0x74, 0x00, 0x69, + 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x74, 0x65, 0x78, 0x74, 0x00, 0x00, 0x00, 0x00, + 0x43, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x28, 0x63, + 0x29, 0x20, 0x32, 0x30, 0x30, 0x34, 0x20, 0x4d, 0x69, 0x63, 0x72, 0x6f, + 0x73, 0x6f, 0x66, 0x74, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x58, 0x59, 0x5a, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf3, 0x54, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x01, 0x16, 0xc9, 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x6f, 0x7b, 0x00, 0x00, 0x38, 0xc3, 0x00, 0x00, 0x03, 0x74, + 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x78, + 0x00, 0x00, 0xb8, 0xd3, 0x00, 0x00, 0x16, 0x82, 0x58, 0x59, 0x5a, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0xe4, 0x00, 0x00, 0x0e, 0x6a, + 0x00, 0x00, 0xb9, 0x36, 0x63, 0x75, 0x72, 0x76, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x28, 0x00, 0x3c, + 0x00, 0x50, 0x00, 0x63, 0x00, 0x77, 0x00, 0x8b, 0x00, 0x9f, 0x00, 0xb3, + 0x00, 0xc7, 0x00, 0xec, 0x01, 0x02, 0x01, 0x1a, 0x01, 0x33, 0x01, 0x4d, + 0x01, 0x69, 0x01, 0x86, 0x01, 0xa4, 0x01, 0xc3, 0x01, 0xe4, 0x02, 0x06, + 0x02, 0x29, 0x02, 0x4e, 0x02, 0x74, 0x02, 0x9c, 0x02, 0xc5, 0x02, 0xef, + 0x03, 0x1b, 0x03, 0x48, 0x03, 0x77, 0x03, 0xa7, 0x03, 0xd9, 0x04, 0x0c, + 0x04, 0x41, 0x04, 0x78, 0x04, 0xaf, 0x04, 0xe9, 0x05, 0x24, 0x05, 0x61, + 0x05, 0x9f, 0x05, 0xdf, 0x06, 0x20, 0x06, 0x63, 0x06, 0xa8, 0x06, 0xee, + 0x07, 0x36, 0x07, 0x80, 0x07, 0xcb, 0x08, 0x18, 0x08, 0x67, 0x08, 0xb8, + 0x09, 0x0a, 0x09, 0x5e, 0x09, 0xb4, 0x0a, 0x0b, 0x0a, 0x65, 0x0a, 0xc0, + 0x0b, 0x1d, 0x0b, 0x7b, 0x0b, 0xdc, 0x0c, 0x3e, 0x0c, 0xa2, 0x0d, 0x08, + 0x0d, 0x70, 0x0d, 0xda, 0x0e, 0x46, 0x0e, 0xb3, 0x0f, 0x22, 0x0f, 0x94, + 0x10, 0x07, 0x10, 0x7c, 0x10, 0xf3, 0x11, 0x6c, 0x11, 0xe7, 0x12, 0x64, + 0x12, 0xe2, 0x13, 0x63, 0x13, 0xe6, 0x14, 0x6b, 0x14, 0xf1, 0x15, 0x7a, + 0x16, 0x05, 0x16, 0x92, 0x17, 0x20, 0x17, 0xb1, 0x18, 0x44, 0x18, 0xd9, + 0x19, 0x70, 0x1a, 0x09, 0x1a, 0xa4, 0x1b, 0x42, 0x1b, 0xe1, 0x1c, 0x82, + 0x1d, 0x26, 0x1d, 0xcb, 0x1e, 0x73, 0x1f, 0x1d, 0x1f, 0xc9, 0x20, 0x77, + 0x21, 0x28, 0x21, 0xda, 0x22, 0x8f, 0x23, 0x46, 0x23, 0xff, 0x24, 0xba, + 0x25, 0x78, 0x26, 0x37, 0x26, 0xf9, 0x27, 0xbd, 0x28, 0x84, 0x29, 0x4c, + 0x2a, 0x17, 0x2a, 0xe4, 0x2b, 0xb4, 0x2c, 0x85, 0x2d, 0x59, 0x2e, 0x2f, + 0x2f, 0x08, 0x2f, 0xe2, 0x30, 0xc0, 0x31, 0x9f, 0x32, 0x81, 0x33, 0x65, + 0x34, 0x4b, 0x35, 0x33, 0x36, 0x1e, 0x37, 0x0c, 0x37, 0xfc, 0x38, 0xee, + 0x39, 0xe2, 0x3a, 0xd9, 0x3b, 0xd2, 0x3c, 0xce, 0x3d, 0xcb, 0x3e, 0xcc, + 0x3f, 0xcf, 0x40, 0xd4, 0x41, 0xdb, 0x42, 0xe5, 0x43, 0xf2, 0x45, 0x01, + 0x46, 0x12, 0x47, 0x26, 0x48, 0x3c, 0x49, 0x55, 0x4a, 0x70, 0x4b, 0x8e, + 0x4c, 0xae, 0x4d, 0xd1, 0x4e, 0xf6, 0x50, 0x1d, 0x51, 0x47, 0x52, 0x74, + 0x53, 0xa3, 0x54, 0xd5, 0x56, 0x09, 0x57, 0x40, 0x58, 0x79, 0x59, 0xb5, + 0x5a, 0xf4, 0x5c, 0x34, 0x5d, 0x78, 0x5e, 0xbe, 0x60, 0x07, 0x61, 0x52, + 0x62, 0xa0, 0x63, 0xf0, 0x65, 0x43, 0x66, 0x99, 0x67, 0xf1, 0x69, 0x4c, + 0x6a, 0xaa, 0x6c, 0x0a, 0x6d, 0x6d, 0x6e, 0xd2, 0x70, 0x3a, 0x71, 0xa5, + 0x73, 0x12, 0x74, 0x82, 0x75, 0xf5, 0x77, 0x6a, 0x78, 0xe3, 0x7a, 0x5d, + 0x7b, 0xdb, 0x7d, 0x5b, 0x7e, 0xde, 0x80, 0x63, 0x81, 0xeb, 0x83, 0x76, + 0x85, 0x04, 0x86, 0x95, 0x88, 0x28, 0x89, 0xbe, 0x8b, 0x56, 0x8c, 0xf2, + 0x8e, 0x90, 0x90, 0x31, 0x91, 0xd4, 0x93, 0x7b, 0x95, 0x24, 0x96, 0xd0, + 0x98, 0x7f, 0x9a, 0x30, 0x9b, 0xe5, 0x9d, 0x9c, 0x9f, 0x56, 0xa1, 0x13, + 0xa2, 0xd2, 0xa4, 0x95, 0xa6, 0x5a, 0xa8, 0x22, 0xa9, 0xed, 0xab, 0xbb, + 0xad, 0x8b, 0xaf, 0x5f, 0xb1, 0x35, 0xb3, 0x0e, 0xb4, 0xea, 0xb6, 0xc9, + 0xb8, 0xab, 0xba, 0x90, 0xbc, 0x77, 0xbe, 0x62, 0xc0, 0x4f, 0xc2, 0x3f, + 0xc4, 0x32, 0xc6, 0x28, 0xc8, 0x21, 0xca, 0x1d, 0xcc, 0x1c, 0xce, 0x1e, + 0xd0, 0x22, 0xd2, 0x2a, 0xd4, 0x35, 0xd6, 0x42, 0xd8, 0x53, 0xda, 0x66, + 0xdc, 0x7c, 0xde, 0x96, 0xe0, 0xb2, 0xe2, 0xd1, 0xe4, 0xf4, 0xe7, 0x19, + 0xe9, 0x41, 0xeb, 0x6c, 0xed, 0x9b, 0xef, 0xcc, 0xf2, 0x00, 0xf4, 0x38, + 0xf6, 0x72, 0xf8, 0xaf, 0xfa, 0xf0, 0xfd, 0x33, 0xff, 0x79, 0xff, 0xff, + 0x63, 0x75, 0x72, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x14, 0x00, 0x28, 0x00, 0x3c, 0x00, 0x50, 0x00, 0x63, + 0x00, 0x77, 0x00, 0x8b, 0x00, 0x9f, 0x00, 0xb3, 0x00, 0xc7, 0x00, 0xec, + 0x01, 0x02, 0x01, 0x1a, 0x01, 0x33, 0x01, 0x4d, 0x01, 0x69, 0x01, 0x86, + 0x01, 0xa4, 0x01, 0xc3, 0x01, 0xe4, 0x02, 0x06, 0x02, 0x29, 0x02, 0x4e, + 0x02, 0x74, 0x02, 0x9c, 0x02, 0xc5, 0x02, 0xef, 0x03, 0x1b, 0x03, 0x48, + 0x03, 0x77, 0x03, 0xa7, 0x03, 0xd9, 0x04, 0x0c, 0x04, 0x41, 0x04, 0x78, + 0x04, 0xaf, 0x04, 0xe9, 0x05, 0x24, 0x05, 0x61, 0x05, 0x9f, 0x05, 0xdf, + 0x06, 0x20, 0x06, 0x63, 0x06, 0xa8, 0x06, 0xee, 0x07, 0x36, 0x07, 0x80, + 0x07, 0xcb, 0x08, 0x18, 0x08, 0x67, 0x08, 0xb8, 0x09, 0x0a, 0x09, 0x5e, + 0x09, 0xb4, 0x0a, 0x0b, 0x0a, 0x65, 0x0a, 0xc0, 0x0b, 0x1d, 0x0b, 0x7b, + 0x0b, 0xdc, 0x0c, 0x3e, 0x0c, 0xa2, 0x0d, 0x08, 0x0d, 0x70, 0x0d, 0xda, + 0x0e, 0x46, 0x0e, 0xb3, 0x0f, 0x22, 0x0f, 0x94, 0x10, 0x07, 0x10, 0x7c, + 0x10, 0xf3, 0x11, 0x6c, 0x11, 0xe7, 0x12, 0x64, 0x12, 0xe2, 0x13, 0x63, + 0x13, 0xe6, 0x14, 0x6b, 0x14, 0xf1, 0x15, 0x7a, 0x16, 0x05, 0x16, 0x92, + 0x17, 0x20, 0x17, 0xb1, 0x18, 0x44, 0x18, 0xd9, 0x19, 0x70, 0x1a, 0x09, + 0x1a, 0xa4, 0x1b, 0x42, 0x1b, 0xe1, 0x1c, 0x82, 0x1d, 0x26, 0x1d, 0xcb, + 0x1e, 0x73, 0x1f, 0x1d, 0x1f, 0xc9, 0x20, 0x77, 0x21, 0x28, 0x21, 0xda, + 0x22, 0x8f, 0x23, 0x46, 0x23, 0xff, 0x24, 0xba, 0x25, 0x78, 0x26, 0x37, + 0x26, 0xf9, 0x27, 0xbd, 0x28, 0x84, 0x29, 0x4c, 0x2a, 0x17, 0x2a, 0xe4, + 0x2b, 0xb4, 0x2c, 0x85, 0x2d, 0x59, 0x2e, 0x2f, 0x2f, 0x08, 0x2f, 0xe2, + 0x30, 0xc0, 0x31, 0x9f, 0x32, 0x81, 0x33, 0x65, 0x34, 0x4b, 0x35, 0x33, + 0x36, 0x1e, 0x37, 0x0c, 0x37, 0xfc, 0x38, 0xee, 0x39, 0xe2, 0x3a, 0xd9, + 0x3b, 0xd2, 0x3c, 0xce, 0x3d, 0xcb, 0x3e, 0xcc, 0x3f, 0xcf, 0x40, 0xd4, + 0x41, 0xdb, 0x42, 0xe5, 0x43, 0xf2, 0x45, 0x01, 0x46, 0x12, 0x47, 0x26, + 0x48, 0x3c, 0x49, 0x55, 0x4a, 0x70, 0x4b, 0x8e, 0x4c, 0xae, 0x4d, 0xd1, + 0x4e, 0xf6, 0x50, 0x1d, 0x51, 0x47, 0x52, 0x74, 0x53, 0xa3, 0x54, 0xd5, + 0x56, 0x09, 0x57, 0x40, 0x58, 0x79, 0x59, 0xb5, 0x5a, 0xf4, 0x5c, 0x34, + 0x5d, 0x78, 0x5e, 0xbe, 0x60, 0x07, 0x61, 0x52, 0x62, 0xa0, 0x63, 0xf0, + 0x65, 0x43, 0x66, 0x99, 0x67, 0xf1, 0x69, 0x4c, 0x6a, 0xaa, 0x6c, 0x0a, + 0x6d, 0x6d, 0x6e, 0xd2, 0x70, 0x3a, 0x71, 0xa5, 0x73, 0x12, 0x74, 0x82, + 0x75, 0xf5, 0x77, 0x6a, 0x78, 0xe3, 0x7a, 0x5d, 0x7b, 0xdb, 0x7d, 0x5b, + 0x7e, 0xde, 0x80, 0x63, 0x81, 0xeb, 0x83, 0x76, 0x85, 0x04, 0x86, 0x95, + 0x88, 0x28, 0x89, 0xbe, 0x8b, 0x56, 0x8c, 0xf2, 0x8e, 0x90, 0x90, 0x31, + 0x91, 0xd4, 0x93, 0x7b, 0x95, 0x24, 0x96, 0xd0, 0x98, 0x7f, 0x9a, 0x30, + 0x9b, 0xe5, 0x9d, 0x9c, 0x9f, 0x56, 0xa1, 0x13, 0xa2, 0xd2, 0xa4, 0x95, + 0xa6, 0x5a, 0xa8, 0x22, 0xa9, 0xed, 0xab, 0xbb, 0xad, 0x8b, 0xaf, 0x5f, + 0xb1, 0x35, 0xb3, 0x0e, 0xb4, 0xea, 0xb6, 0xc9, 0xb8, 0xab, 0xba, 0x90, + 0xbc, 0x77, 0xbe, 0x62, 0xc0, 0x4f, 0xc2, 0x3f, 0xc4, 0x32, 0xc6, 0x28, + 0xc8, 0x21, 0xca, 0x1d, 0xcc, 0x1c, 0xce, 0x1e, 0xd0, 0x22, 0xd2, 0x2a, + 0xd4, 0x35, 0xd6, 0x42, 0xd8, 0x53, 0xda, 0x66, 0xdc, 0x7c, 0xde, 0x96, + 0xe0, 0xb2, 0xe2, 0xd1, 0xe4, 0xf4, 0xe7, 0x19, 0xe9, 0x41, 0xeb, 0x6c, + 0xed, 0x9b, 0xef, 0xcc, 0xf2, 0x00, 0xf4, 0x38, 0xf6, 0x72, 0xf8, 0xaf, + 0xfa, 0xf0, 0xfd, 0x33, 0xff, 0x79, 0xff, 0xff, 0x63, 0x75, 0x72, 0x76, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x14, + 0x00, 0x28, 0x00, 0x3c, 0x00, 0x50, 0x00, 0x63, 0x00, 0x77, 0x00, 0x8b, + 0x00, 0x9f, 0x00, 0xb3, 0x00, 0xc7, 0x00, 0xec, 0x01, 0x02, 0x01, 0x1a, + 0x01, 0x33, 0x01, 0x4d, 0x01, 0x69, 0x01, 0x86, 0x01, 0xa4, 0x01, 0xc3, + 0x01, 0xe4, 0x02, 0x06, 0x02, 0x29, 0x02, 0x4e, 0x02, 0x74, 0x02, 0x9c, + 0x02, 0xc5, 0x02, 0xef, 0x03, 0x1b, 0x03, 0x48, 0x03, 0x77, 0x03, 0xa7, + 0x03, 0xd9, 0x04, 0x0c, 0x04, 0x41, 0x04, 0x78, 0x04, 0xaf, 0x04, 0xe9, + 0x05, 0x24, 0x05, 0x61, 0x05, 0x9f, 0x05, 0xdf, 0x06, 0x20, 0x06, 0x63, + 0x06, 0xa8, 0x06, 0xee, 0x07, 0x36, 0x07, 0x80, 0x07, 0xcb, 0x08, 0x18, + 0x08, 0x67, 0x08, 0xb8, 0x09, 0x0a, 0x09, 0x5e, 0x09, 0xb4, 0x0a, 0x0b, + 0x0a, 0x65, 0x0a, 0xc0, 0x0b, 0x1d, 0x0b, 0x7b, 0x0b, 0xdc, 0x0c, 0x3e, + 0x0c, 0xa2, 0x0d, 0x08, 0x0d, 0x70, 0x0d, 0xda, 0x0e, 0x46, 0x0e, 0xb3, + 0x0f, 0x22, 0x0f, 0x94, 0x10, 0x07, 0x10, 0x7c, 0x10, 0xf3, 0x11, 0x6c, + 0x11, 0xe7, 0x12, 0x64, 0x12, 0xe2, 0x13, 0x63, 0x13, 0xe6, 0x14, 0x6b, + 0x14, 0xf1, 0x15, 0x7a, 0x16, 0x05, 0x16, 0x92, 0x17, 0x20, 0x17, 0xb1, + 0x18, 0x44, 0x18, 0xd9, 0x19, 0x70, 0x1a, 0x09, 0x1a, 0xa4, 0x1b, 0x42, + 0x1b, 0xe1, 0x1c, 0x82, 0x1d, 0x26, 0x1d, 0xcb, 0x1e, 0x73, 0x1f, 0x1d, + 0x1f, 0xc9, 0x20, 0x77, 0x21, 0x28, 0x21, 0xda, 0x22, 0x8f, 0x23, 0x46, + 0x23, 0xff, 0x24, 0xba, 0x25, 0x78, 0x26, 0x37, 0x26, 0xf9, 0x27, 0xbd, + 0x28, 0x84, 0x29, 0x4c, 0x2a, 0x17, 0x2a, 0xe4, 0x2b, 0xb4, 0x2c, 0x85, + 0x2d, 0x59, 0x2e, 0x2f, 0x2f, 0x08, 0x2f, 0xe2, 0x30, 0xc0, 0x31, 0x9f, + 0x32, 0x81, 0x33, 0x65, 0x34, 0x4b, 0x35, 0x33, 0x36, 0x1e, 0x37, 0x0c, + 0x37, 0xfc, 0x38, 0xee, 0x39, 0xe2, 0x3a, 0xd9, 0x3b, 0xd2, 0x3c, 0xce, + 0x3d, 0xcb, 0x3e, 0xcc, 0x3f, 0xcf, 0x40, 0xd4, 0x41, 0xdb, 0x42, 0xe5, + 0x43, 0xf2, 0x45, 0x01, 0x46, 0x12, 0x47, 0x26, 0x48, 0x3c, 0x49, 0x55, + 0x4a, 0x70, 0x4b, 0x8e, 0x4c, 0xae, 0x4d, 0xd1, 0x4e, 0xf6, 0x50, 0x1d, + 0x51, 0x47, 0x52, 0x74, 0x53, 0xa3, 0x54, 0xd5, 0x56, 0x09, 0x57, 0x40, + 0x58, 0x79, 0x59, 0xb5, 0x5a, 0xf4, 0x5c, 0x34, 0x5d, 0x78, 0x5e, 0xbe, + 0x60, 0x07, 0x61, 0x52, 0x62, 0xa0, 0x63, 0xf0, 0x65, 0x43, 0x66, 0x99, + 0x67, 0xf1, 0x69, 0x4c, 0x6a, 0xaa, 0x6c, 0x0a, 0x6d, 0x6d, 0x6e, 0xd2, + 0x70, 0x3a, 0x71, 0xa5, 0x73, 0x12, 0x74, 0x82, 0x75, 0xf5, 0x77, 0x6a, + 0x78, 0xe3, 0x7a, 0x5d, 0x7b, 0xdb, 0x7d, 0x5b, 0x7e, 0xde, 0x80, 0x63, + 0x81, 0xeb, 0x83, 0x76, 0x85, 0x04, 0x86, 0x95, 0x88, 0x28, 0x89, 0xbe, + 0x8b, 0x56, 0x8c, 0xf2, 0x8e, 0x90, 0x90, 0x31, 0x91, 0xd4, 0x93, 0x7b, + 0x95, 0x24, 0x96, 0xd0, 0x98, 0x7f, 0x9a, 0x30, 0x9b, 0xe5, 0x9d, 0x9c, + 0x9f, 0x56, 0xa1, 0x13, 0xa2, 0xd2, 0xa4, 0x95, 0xa6, 0x5a, 0xa8, 0x22, + 0xa9, 0xed, 0xab, 0xbb, 0xad, 0x8b, 0xaf, 0x5f, 0xb1, 0x35, 0xb3, 0x0e, + 0xb4, 0xea, 0xb6, 0xc9, 0xb8, 0xab, 0xba, 0x90, 0xbc, 0x77, 0xbe, 0x62, + 0xc0, 0x4f, 0xc2, 0x3f, 0xc4, 0x32, 0xc6, 0x28, 0xc8, 0x21, 0xca, 0x1d, + 0xcc, 0x1c, 0xce, 0x1e, 0xd0, 0x22, 0xd2, 0x2a, 0xd4, 0x35, 0xd6, 0x42, + 0xd8, 0x53, 0xda, 0x66, 0xdc, 0x7c, 0xde, 0x96, 0xe0, 0xb2, 0xe2, 0xd1, + 0xe4, 0xf4, 0xe7, 0x19, 0xe9, 0x41, 0xeb, 0x6c, 0xed, 0x9b, 0xef, 0xcc, + 0xf2, 0x00, 0xf4, 0x38, 0xf6, 0x72, 0xf8, 0xaf, 0xfa, 0xf0, 0xfd, 0x33, + 0xff, 0x79, 0xff, 0xff, 0x4d, 0x53, 0x31, 0x30, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x10, 0x0e, 0x00, 0x00, 0x10, 0x2e, + 0x00, 0x00, 0x08, 0x28, 0x00, 0x00, 0x18, 0x56, 0x00, 0x00, 0x06, 0x48, + 0x3c, 0x00, 0x3f, 0x00, 0x78, 0x00, 0x6d, 0x00, 0x6c, 0x00, 0x20, 0x00, + 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00, + 0x6e, 0x00, 0x3d, 0x00, 0x22, 0x00, 0x31, 0x00, 0x2e, 0x00, 0x30, 0x00, + 0x22, 0x00, 0x20, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x63, 0x00, 0x6f, 0x00, + 0x64, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x3d, 0x00, 0x22, 0x00, + 0x75, 0x00, 0x74, 0x00, 0x66, 0x00, 0x2d, 0x00, 0x31, 0x00, 0x36, 0x00, + 0x22, 0x00, 0x3f, 0x00, 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x3c, 0x00, + 0x63, 0x00, 0x64, 0x00, 0x6d, 0x00, 0x3a, 0x00, 0x43, 0x00, 0x6f, 0x00, + 0x6c, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x44, 0x00, 0x65, 0x00, 0x76, 0x00, + 0x69, 0x00, 0x63, 0x00, 0x65, 0x00, 0x4d, 0x00, 0x6f, 0x00, 0x64, 0x00, + 0x65, 0x00, 0x6c, 0x00, 0x20, 0x00, 0x78, 0x00, 0x6d, 0x00, 0x6c, 0x00, + 0x6e, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x63, 0x00, 0x64, 0x00, 0x6d, 0x00, + 0x3d, 0x00, 0x22, 0x00, 0x68, 0x00, 0x74, 0x00, 0x74, 0x00, 0x70, 0x00, + 0x3a, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x73, 0x00, 0x63, 0x00, 0x68, 0x00, + 0x65, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x73, 0x00, 0x2e, 0x00, 0x6d, 0x00, + 0x69, 0x00, 0x63, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x6f, 0x00, + 0x66, 0x00, 0x74, 0x00, 0x2e, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, + 0x2f, 0x00, 0x77, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x6f, 0x00, + 0x77, 0x00, 0x73, 0x00, 0x2f, 0x00, 0x32, 0x00, 0x30, 0x00, 0x30, 0x00, + 0x35, 0x00, 0x2f, 0x00, 0x30, 0x00, 0x32, 0x00, 0x2f, 0x00, 0x63, 0x00, + 0x6f, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x2f, 0x00, 0x43, 0x00, + 0x6f, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x44, 0x00, 0x65, 0x00, + 0x76, 0x00, 0x69, 0x00, 0x63, 0x00, 0x65, 0x00, 0x4d, 0x00, 0x6f, 0x00, + 0x64, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x22, 0x00, 0x20, 0x00, 0x78, 0x00, + 0x6d, 0x00, 0x6c, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x63, 0x00, + 0x61, 0x00, 0x6c, 0x00, 0x3d, 0x00, 0x22, 0x00, 0x68, 0x00, 0x74, 0x00, + 0x74, 0x00, 0x70, 0x00, 0x3a, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x73, 0x00, + 0x63, 0x00, 0x68, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x73, 0x00, + 0x2e, 0x00, 0x6d, 0x00, 0x69, 0x00, 0x63, 0x00, 0x72, 0x00, 0x6f, 0x00, + 0x73, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x74, 0x00, 0x2e, 0x00, 0x63, 0x00, + 0x6f, 0x00, 0x6d, 0x00, 0x2f, 0x00, 0x77, 0x00, 0x69, 0x00, 0x6e, 0x00, + 0x64, 0x00, 0x6f, 0x00, 0x77, 0x00, 0x73, 0x00, 0x2f, 0x00, 0x32, 0x00, + 0x30, 0x00, 0x30, 0x00, 0x37, 0x00, 0x2f, 0x00, 0x31, 0x00, 0x31, 0x00, + 0x2f, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x72, 0x00, + 0x2f, 0x00, 0x43, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x62, 0x00, + 0x72, 0x00, 0x61, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, + 0x22, 0x00, 0x20, 0x00, 0x78, 0x00, 0x6d, 0x00, 0x6c, 0x00, 0x6e, 0x00, + 0x73, 0x00, 0x3a, 0x00, 0x77, 0x00, 0x63, 0x00, 0x73, 0x00, 0x3d, 0x00, + 0x22, 0x00, 0x68, 0x00, 0x74, 0x00, 0x74, 0x00, 0x70, 0x00, 0x3a, 0x00, + 0x2f, 0x00, 0x2f, 0x00, 0x73, 0x00, 0x63, 0x00, 0x68, 0x00, 0x65, 0x00, + 0x6d, 0x00, 0x61, 0x00, 0x73, 0x00, 0x2e, 0x00, 0x6d, 0x00, 0x69, 0x00, + 0x63, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x6f, 0x00, 0x66, 0x00, + 0x74, 0x00, 0x2e, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2f, 0x00, + 0x77, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x77, 0x00, + 0x73, 0x00, 0x2f, 0x00, 0x32, 0x00, 0x30, 0x00, 0x30, 0x00, 0x35, 0x00, + 0x2f, 0x00, 0x30, 0x00, 0x32, 0x00, 0x2f, 0x00, 0x63, 0x00, 0x6f, 0x00, + 0x6c, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x2f, 0x00, 0x57, 0x00, 0x63, 0x00, + 0x73, 0x00, 0x43, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x6f, 0x00, + 0x6e, 0x00, 0x50, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x69, 0x00, + 0x6c, 0x00, 0x65, 0x00, 0x54, 0x00, 0x79, 0x00, 0x70, 0x00, 0x65, 0x00, + 0x73, 0x00, 0x22, 0x00, 0x20, 0x00, 0x78, 0x00, 0x6d, 0x00, 0x6c, 0x00, + 0x6e, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x6d, 0x00, 0x63, 0x00, 0x3d, 0x00, + 0x22, 0x00, 0x68, 0x00, 0x74, 0x00, 0x74, 0x00, 0x70, 0x00, 0x3a, 0x00, + 0x2f, 0x00, 0x2f, 0x00, 0x73, 0x00, 0x63, 0x00, 0x68, 0x00, 0x65, 0x00, + 0x6d, 0x00, 0x61, 0x00, 0x73, 0x00, 0x2e, 0x00, 0x6f, 0x00, 0x70, 0x00, + 0x65, 0x00, 0x6e, 0x00, 0x78, 0x00, 0x6d, 0x00, 0x6c, 0x00, 0x66, 0x00, + 0x6f, 0x00, 0x72, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x74, 0x00, 0x73, 0x00, + 0x2e, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x67, 0x00, 0x2f, 0x00, 0x6d, 0x00, + 0x61, 0x00, 0x72, 0x00, 0x6b, 0x00, 0x75, 0x00, 0x70, 0x00, 0x2d, 0x00, + 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x70, 0x00, 0x61, 0x00, 0x74, 0x00, + 0x69, 0x00, 0x62, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x74, 0x00, + 0x79, 0x00, 0x2f, 0x00, 0x32, 0x00, 0x30, 0x00, 0x30, 0x00, 0x36, 0x00, + 0x22, 0x00, 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x3c, 0x00, + 0x63, 0x00, 0x64, 0x00, 0x6d, 0x00, 0x3a, 0x00, 0x50, 0x00, 0x72, 0x00, + 0x6f, 0x00, 0x66, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x4e, 0x00, + 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, + 0x09, 0x00, 0x09, 0x00, 0x3c, 0x00, 0x77, 0x00, 0x63, 0x00, 0x73, 0x00, + 0x3a, 0x00, 0x54, 0x00, 0x65, 0x00, 0x78, 0x00, 0x74, 0x00, 0x20, 0x00, + 0x78, 0x00, 0x6d, 0x00, 0x6c, 0x00, 0x3a, 0x00, 0x6c, 0x00, 0x61, 0x00, + 0x6e, 0x00, 0x67, 0x00, 0x3d, 0x00, 0x22, 0x00, 0x65, 0x00, 0x6e, 0x00, + 0x2d, 0x00, 0x55, 0x00, 0x53, 0x00, 0x22, 0x00, 0x3e, 0x00, 0x43, 0x00, + 0x61, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x62, 0x00, 0x72, 0x00, 0x61, 0x00, + 0x74, 0x00, 0x65, 0x00, 0x64, 0x00, 0x20, 0x00, 0x64, 0x00, 0x69, 0x00, + 0x73, 0x00, 0x70, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x79, 0x00, 0x20, 0x00, + 0x70, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x69, 0x00, 0x6c, 0x00, + 0x65, 0x00, 0x3c, 0x00, 0x2f, 0x00, 0x77, 0x00, 0x63, 0x00, 0x73, 0x00, + 0x3a, 0x00, 0x54, 0x00, 0x65, 0x00, 0x78, 0x00, 0x74, 0x00, 0x3e, 0x00, + 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x3c, 0x00, 0x2f, 0x00, 0x63, 0x00, + 0x64, 0x00, 0x6d, 0x00, 0x3a, 0x00, 0x50, 0x00, 0x72, 0x00, 0x6f, 0x00, + 0x66, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x4e, 0x00, 0x61, 0x00, + 0x6d, 0x00, 0x65, 0x00, 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, + 0x3c, 0x00, 0x63, 0x00, 0x64, 0x00, 0x6d, 0x00, 0x3a, 0x00, 0x44, 0x00, + 0x65, 0x00, 0x73, 0x00, 0x63, 0x00, 0x72, 0x00, 0x69, 0x00, 0x70, 0x00, + 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x3e, 0x00, 0x0d, 0x00, + 0x0a, 0x00, 0x09, 0x00, 0x09, 0x00, 0x3c, 0x00, 0x77, 0x00, 0x63, 0x00, + 0x73, 0x00, 0x3a, 0x00, 0x54, 0x00, 0x65, 0x00, 0x78, 0x00, 0x74, 0x00, + 0x20, 0x00, 0x78, 0x00, 0x6d, 0x00, 0x6c, 0x00, 0x3a, 0x00, 0x6c, 0x00, + 0x61, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x3d, 0x00, 0x22, 0x00, 0x65, 0x00, + 0x6e, 0x00, 0x2d, 0x00, 0x55, 0x00, 0x53, 0x00, 0x22, 0x00, 0x3e, 0x00, + 0x73, 0x00, 0x52, 0x00, 0x47, 0x00, 0x42, 0x00, 0x20, 0x00, 0x64, 0x00, + 0x69, 0x00, 0x73, 0x00, 0x70, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x79, 0x00, + 0x20, 0x00, 0x70, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x69, 0x00, + 0x6c, 0x00, 0x65, 0x00, 0x20, 0x00, 0x77, 0x00, 0x69, 0x00, 0x74, 0x00, + 0x68, 0x00, 0x20, 0x00, 0x64, 0x00, 0x69, 0x00, 0x73, 0x00, 0x70, 0x00, + 0x6c, 0x00, 0x61, 0x00, 0x79, 0x00, 0x20, 0x00, 0x68, 0x00, 0x61, 0x00, + 0x72, 0x00, 0x64, 0x00, 0x77, 0x00, 0x61, 0x00, 0x72, 0x00, 0x65, 0x00, + 0x20, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x66, 0x00, 0x69, 0x00, + 0x67, 0x00, 0x75, 0x00, 0x72, 0x00, 0x61, 0x00, 0x74, 0x00, 0x69, 0x00, + 0x6f, 0x00, 0x6e, 0x00, 0x20, 0x00, 0x64, 0x00, 0x61, 0x00, 0x74, 0x00, + 0x61, 0x00, 0x20, 0x00, 0x64, 0x00, 0x65, 0x00, 0x72, 0x00, 0x69, 0x00, + 0x76, 0x00, 0x65, 0x00, 0x64, 0x00, 0x20, 0x00, 0x66, 0x00, 0x72, 0x00, + 0x6f, 0x00, 0x6d, 0x00, 0x20, 0x00, 0x63, 0x00, 0x61, 0x00, 0x6c, 0x00, + 0x69, 0x00, 0x62, 0x00, 0x72, 0x00, 0x61, 0x00, 0x74, 0x00, 0x69, 0x00, + 0x6f, 0x00, 0x6e, 0x00, 0x3c, 0x00, 0x2f, 0x00, 0x77, 0x00, 0x63, 0x00, + 0x73, 0x00, 0x3a, 0x00, 0x54, 0x00, 0x65, 0x00, 0x78, 0x00, 0x74, 0x00, + 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x3c, 0x00, 0x2f, 0x00, + 0x63, 0x00, 0x64, 0x00, 0x6d, 0x00, 0x3a, 0x00, 0x44, 0x00, 0x65, 0x00, + 0x73, 0x00, 0x63, 0x00, 0x72, 0x00, 0x69, 0x00, 0x70, 0x00, 0x74, 0x00, + 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, + 0x09, 0x00, 0x3c, 0x00, 0x63, 0x00, 0x64, 0x00, 0x6d, 0x00, 0x3a, 0x00, + 0x41, 0x00, 0x75, 0x00, 0x74, 0x00, 0x68, 0x00, 0x6f, 0x00, 0x72, 0x00, + 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x09, 0x00, 0x3c, 0x00, + 0x77, 0x00, 0x63, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x54, 0x00, 0x65, 0x00, + 0x78, 0x00, 0x74, 0x00, 0x20, 0x00, 0x78, 0x00, 0x6d, 0x00, 0x6c, 0x00, + 0x3a, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x3d, 0x00, + 0x22, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x2d, 0x00, 0x55, 0x00, 0x53, 0x00, + 0x22, 0x00, 0x3e, 0x00, 0x4d, 0x00, 0x69, 0x00, 0x63, 0x00, 0x72, 0x00, + 0x6f, 0x00, 0x73, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x74, 0x00, 0x20, 0x00, + 0x44, 0x00, 0x69, 0x00, 0x73, 0x00, 0x70, 0x00, 0x6c, 0x00, 0x61, 0x00, + 0x79, 0x00, 0x20, 0x00, 0x43, 0x00, 0x6f, 0x00, 0x6c, 0x00, 0x6f, 0x00, + 0x72, 0x00, 0x20, 0x00, 0x43, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x69, 0x00, + 0x62, 0x00, 0x72, 0x00, 0x61, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, + 0x6e, 0x00, 0x3c, 0x00, 0x2f, 0x00, 0x77, 0x00, 0x63, 0x00, 0x73, 0x00, + 0x3a, 0x00, 0x54, 0x00, 0x65, 0x00, 0x78, 0x00, 0x74, 0x00, 0x3e, 0x00, + 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x3c, 0x00, 0x2f, 0x00, 0x63, 0x00, + 0x64, 0x00, 0x6d, 0x00, 0x3a, 0x00, 0x41, 0x00, 0x75, 0x00, 0x74, 0x00, + 0x68, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, + 0x09, 0x00, 0x3c, 0x00, 0x63, 0x00, 0x64, 0x00, 0x6d, 0x00, 0x3a, 0x00, + 0x4d, 0x00, 0x65, 0x00, 0x61, 0x00, 0x73, 0x00, 0x75, 0x00, 0x72, 0x00, + 0x65, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x43, 0x00, + 0x6f, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x69, 0x00, 0x74, 0x00, 0x69, 0x00, + 0x6f, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, + 0x09, 0x00, 0x09, 0x00, 0x3c, 0x00, 0x63, 0x00, 0x64, 0x00, 0x6d, 0x00, + 0x3a, 0x00, 0x43, 0x00, 0x6f, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x72, 0x00, + 0x53, 0x00, 0x70, 0x00, 0x61, 0x00, 0x63, 0x00, 0x65, 0x00, 0x3e, 0x00, + 0x43, 0x00, 0x49, 0x00, 0x45, 0x00, 0x58, 0x00, 0x59, 0x00, 0x5a, 0x00, + 0x3c, 0x00, 0x2f, 0x00, 0x63, 0x00, 0x64, 0x00, 0x6d, 0x00, 0x3a, 0x00, + 0x43, 0x00, 0x6f, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x53, 0x00, + 0x70, 0x00, 0x61, 0x00, 0x63, 0x00, 0x65, 0x00, 0x3e, 0x00, 0x0d, 0x00, + 0x0a, 0x00, 0x09, 0x00, 0x09, 0x00, 0x3c, 0x00, 0x63, 0x00, 0x64, 0x00, + 0x6d, 0x00, 0x3a, 0x00, 0x57, 0x00, 0x68, 0x00, 0x69, 0x00, 0x74, 0x00, + 0x65, 0x00, 0x50, 0x00, 0x6f, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x74, 0x00, + 0x4e, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x3e, 0x00, 0x44, 0x00, + 0x36, 0x00, 0x35, 0x00, 0x3c, 0x00, 0x2f, 0x00, 0x63, 0x00, 0x64, 0x00, + 0x6d, 0x00, 0x3a, 0x00, 0x57, 0x00, 0x68, 0x00, 0x69, 0x00, 0x74, 0x00, + 0x65, 0x00, 0x50, 0x00, 0x6f, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x74, 0x00, + 0x4e, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x3e, 0x00, 0x0d, 0x00, + 0x0a, 0x00, 0x09, 0x00, 0x3c, 0x00, 0x2f, 0x00, 0x63, 0x00, 0x64, 0x00, + 0x6d, 0x00, 0x3a, 0x00, 0x4d, 0x00, 0x65, 0x00, 0x61, 0x00, 0x73, 0x00, + 0x75, 0x00, 0x72, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x6e, 0x00, + 0x74, 0x00, 0x43, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x69, 0x00, + 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x3e, 0x00, + 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x3c, 0x00, 0x63, 0x00, 0x64, 0x00, + 0x6d, 0x00, 0x3a, 0x00, 0x53, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x66, 0x00, + 0x4c, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x6f, 0x00, + 0x75, 0x00, 0x73, 0x00, 0x3e, 0x00, 0x74, 0x00, 0x72, 0x00, 0x75, 0x00, + 0x65, 0x00, 0x3c, 0x00, 0x2f, 0x00, 0x63, 0x00, 0x64, 0x00, 0x6d, 0x00, + 0x3a, 0x00, 0x53, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x66, 0x00, 0x4c, 0x00, + 0x75, 0x00, 0x6d, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x6f, 0x00, 0x75, 0x00, + 0x73, 0x00, 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x3c, 0x00, + 0x63, 0x00, 0x64, 0x00, 0x6d, 0x00, 0x3a, 0x00, 0x4d, 0x00, 0x61, 0x00, + 0x78, 0x00, 0x43, 0x00, 0x6f, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x72, 0x00, + 0x61, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x3e, 0x00, 0x31, 0x00, 0x2e, 0x00, + 0x30, 0x00, 0x3c, 0x00, 0x2f, 0x00, 0x63, 0x00, 0x64, 0x00, 0x6d, 0x00, + 0x3a, 0x00, 0x4d, 0x00, 0x61, 0x00, 0x78, 0x00, 0x43, 0x00, 0x6f, 0x00, + 0x6c, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x74, 0x00, + 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x3c, 0x00, 0x63, 0x00, + 0x64, 0x00, 0x6d, 0x00, 0x3a, 0x00, 0x4d, 0x00, 0x69, 0x00, 0x6e, 0x00, + 0x43, 0x00, 0x6f, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x61, 0x00, + 0x6e, 0x00, 0x74, 0x00, 0x3e, 0x00, 0x30, 0x00, 0x2e, 0x00, 0x30, 0x00, + 0x3c, 0x00, 0x2f, 0x00, 0x63, 0x00, 0x64, 0x00, 0x6d, 0x00, 0x3a, 0x00, + 0x4d, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x43, 0x00, 0x6f, 0x00, 0x6c, 0x00, + 0x6f, 0x00, 0x72, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x3e, 0x00, + 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x3c, 0x00, 0x63, 0x00, 0x64, 0x00, + 0x6d, 0x00, 0x3a, 0x00, 0x52, 0x00, 0x47, 0x00, 0x42, 0x00, 0x56, 0x00, + 0x69, 0x00, 0x72, 0x00, 0x74, 0x00, 0x75, 0x00, 0x61, 0x00, 0x6c, 0x00, + 0x44, 0x00, 0x65, 0x00, 0x76, 0x00, 0x69, 0x00, 0x63, 0x00, 0x65, 0x00, + 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x09, 0x00, 0x3c, 0x00, + 0x63, 0x00, 0x64, 0x00, 0x6d, 0x00, 0x3a, 0x00, 0x4d, 0x00, 0x65, 0x00, + 0x61, 0x00, 0x73, 0x00, 0x75, 0x00, 0x72, 0x00, 0x65, 0x00, 0x6d, 0x00, + 0x65, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x44, 0x00, 0x61, 0x00, 0x74, 0x00, + 0x61, 0x00, 0x20, 0x00, 0x54, 0x00, 0x69, 0x00, 0x6d, 0x00, 0x65, 0x00, + 0x53, 0x00, 0x74, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x70, 0x00, 0x3d, 0x00, + 0x22, 0x00, 0x32, 0x00, 0x30, 0x00, 0x31, 0x00, 0x36, 0x00, 0x2d, 0x00, + 0x30, 0x00, 0x38, 0x00, 0x2d, 0x00, 0x32, 0x00, 0x36, 0x00, 0x54, 0x00, + 0x30, 0x00, 0x33, 0x00, 0x3a, 0x00, 0x35, 0x00, 0x39, 0x00, 0x3a, 0x00, + 0x33, 0x00, 0x32, 0x00, 0x22, 0x00, 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, + 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, 0x3c, 0x00, 0x63, 0x00, 0x64, 0x00, + 0x6d, 0x00, 0x3a, 0x00, 0x4d, 0x00, 0x61, 0x00, 0x78, 0x00, 0x43, 0x00, + 0x6f, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x61, 0x00, 0x6e, 0x00, + 0x74, 0x00, 0x55, 0x00, 0x73, 0x00, 0x65, 0x00, 0x64, 0x00, 0x3e, 0x00, + 0x31, 0x00, 0x2e, 0x00, 0x30, 0x00, 0x3c, 0x00, 0x2f, 0x00, 0x63, 0x00, + 0x64, 0x00, 0x6d, 0x00, 0x3a, 0x00, 0x4d, 0x00, 0x61, 0x00, 0x78, 0x00, + 0x43, 0x00, 0x6f, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x61, 0x00, + 0x6e, 0x00, 0x74, 0x00, 0x55, 0x00, 0x73, 0x00, 0x65, 0x00, 0x64, 0x00, + 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, + 0x3c, 0x00, 0x63, 0x00, 0x64, 0x00, 0x6d, 0x00, 0x3a, 0x00, 0x4d, 0x00, + 0x69, 0x00, 0x6e, 0x00, 0x43, 0x00, 0x6f, 0x00, 0x6c, 0x00, 0x6f, 0x00, + 0x72, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x55, 0x00, 0x73, 0x00, + 0x65, 0x00, 0x64, 0x00, 0x3e, 0x00, 0x30, 0x00, 0x2e, 0x00, 0x30, 0x00, + 0x3c, 0x00, 0x2f, 0x00, 0x63, 0x00, 0x64, 0x00, 0x6d, 0x00, 0x3a, 0x00, + 0x4d, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x43, 0x00, 0x6f, 0x00, 0x6c, 0x00, + 0x6f, 0x00, 0x72, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x55, 0x00, + 0x73, 0x00, 0x65, 0x00, 0x64, 0x00, 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, + 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, 0x3c, 0x00, 0x63, 0x00, 0x64, 0x00, + 0x6d, 0x00, 0x3a, 0x00, 0x57, 0x00, 0x68, 0x00, 0x69, 0x00, 0x74, 0x00, + 0x65, 0x00, 0x50, 0x00, 0x72, 0x00, 0x69, 0x00, 0x6d, 0x00, 0x61, 0x00, + 0x72, 0x00, 0x79, 0x00, 0x20, 0x00, 0x58, 0x00, 0x3d, 0x00, 0x22, 0x00, + 0x39, 0x00, 0x35, 0x00, 0x2e, 0x00, 0x30, 0x00, 0x35, 0x00, 0x22, 0x00, + 0x20, 0x00, 0x59, 0x00, 0x3d, 0x00, 0x22, 0x00, 0x31, 0x00, 0x30, 0x00, + 0x30, 0x00, 0x2e, 0x00, 0x30, 0x00, 0x30, 0x00, 0x22, 0x00, 0x20, 0x00, + 0x5a, 0x00, 0x3d, 0x00, 0x22, 0x00, 0x31, 0x00, 0x30, 0x00, 0x38, 0x00, + 0x2e, 0x00, 0x39, 0x00, 0x30, 0x00, 0x22, 0x00, 0x2f, 0x00, 0x3e, 0x00, + 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, 0x3c, 0x00, + 0x63, 0x00, 0x64, 0x00, 0x6d, 0x00, 0x3a, 0x00, 0x52, 0x00, 0x65, 0x00, + 0x64, 0x00, 0x50, 0x00, 0x72, 0x00, 0x69, 0x00, 0x6d, 0x00, 0x61, 0x00, + 0x72, 0x00, 0x79, 0x00, 0x20, 0x00, 0x58, 0x00, 0x3d, 0x00, 0x22, 0x00, + 0x34, 0x00, 0x31, 0x00, 0x2e, 0x00, 0x32, 0x00, 0x34, 0x00, 0x22, 0x00, + 0x20, 0x00, 0x59, 0x00, 0x3d, 0x00, 0x22, 0x00, 0x32, 0x00, 0x31, 0x00, + 0x2e, 0x00, 0x32, 0x00, 0x36, 0x00, 0x22, 0x00, 0x20, 0x00, 0x5a, 0x00, + 0x3d, 0x00, 0x22, 0x00, 0x31, 0x00, 0x2e, 0x00, 0x39, 0x00, 0x33, 0x00, + 0x22, 0x00, 0x2f, 0x00, 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, + 0x09, 0x00, 0x09, 0x00, 0x3c, 0x00, 0x63, 0x00, 0x64, 0x00, 0x6d, 0x00, + 0x3a, 0x00, 0x47, 0x00, 0x72, 0x00, 0x65, 0x00, 0x65, 0x00, 0x6e, 0x00, + 0x50, 0x00, 0x72, 0x00, 0x69, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x72, 0x00, + 0x79, 0x00, 0x20, 0x00, 0x58, 0x00, 0x3d, 0x00, 0x22, 0x00, 0x33, 0x00, + 0x35, 0x00, 0x2e, 0x00, 0x37, 0x00, 0x36, 0x00, 0x22, 0x00, 0x20, 0x00, + 0x59, 0x00, 0x3d, 0x00, 0x22, 0x00, 0x37, 0x00, 0x31, 0x00, 0x2e, 0x00, + 0x35, 0x00, 0x32, 0x00, 0x22, 0x00, 0x20, 0x00, 0x5a, 0x00, 0x3d, 0x00, + 0x22, 0x00, 0x31, 0x00, 0x31, 0x00, 0x2e, 0x00, 0x39, 0x00, 0x32, 0x00, + 0x22, 0x00, 0x2f, 0x00, 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, + 0x09, 0x00, 0x09, 0x00, 0x3c, 0x00, 0x63, 0x00, 0x64, 0x00, 0x6d, 0x00, + 0x3a, 0x00, 0x42, 0x00, 0x6c, 0x00, 0x75, 0x00, 0x65, 0x00, 0x50, 0x00, + 0x72, 0x00, 0x69, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x72, 0x00, 0x79, 0x00, + 0x20, 0x00, 0x58, 0x00, 0x3d, 0x00, 0x22, 0x00, 0x31, 0x00, 0x38, 0x00, + 0x2e, 0x00, 0x30, 0x00, 0x35, 0x00, 0x22, 0x00, 0x20, 0x00, 0x59, 0x00, + 0x3d, 0x00, 0x22, 0x00, 0x37, 0x00, 0x2e, 0x00, 0x32, 0x00, 0x32, 0x00, + 0x22, 0x00, 0x20, 0x00, 0x5a, 0x00, 0x3d, 0x00, 0x22, 0x00, 0x39, 0x00, + 0x35, 0x00, 0x2e, 0x00, 0x30, 0x00, 0x35, 0x00, 0x22, 0x00, 0x2f, 0x00, + 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, + 0x3c, 0x00, 0x63, 0x00, 0x64, 0x00, 0x6d, 0x00, 0x3a, 0x00, 0x42, 0x00, + 0x6c, 0x00, 0x61, 0x00, 0x63, 0x00, 0x6b, 0x00, 0x50, 0x00, 0x72, 0x00, + 0x69, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x72, 0x00, 0x79, 0x00, 0x20, 0x00, + 0x58, 0x00, 0x3d, 0x00, 0x22, 0x00, 0x30, 0x00, 0x22, 0x00, 0x20, 0x00, + 0x59, 0x00, 0x3d, 0x00, 0x22, 0x00, 0x30, 0x00, 0x22, 0x00, 0x20, 0x00, + 0x5a, 0x00, 0x3d, 0x00, 0x22, 0x00, 0x30, 0x00, 0x22, 0x00, 0x2f, 0x00, + 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, + 0x3c, 0x00, 0x63, 0x00, 0x64, 0x00, 0x6d, 0x00, 0x3a, 0x00, 0x47, 0x00, + 0x61, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x4f, 0x00, 0x66, 0x00, + 0x66, 0x00, 0x73, 0x00, 0x65, 0x00, 0x74, 0x00, 0x47, 0x00, 0x61, 0x00, + 0x69, 0x00, 0x6e, 0x00, 0x4c, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x65, 0x00, + 0x61, 0x00, 0x72, 0x00, 0x47, 0x00, 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, + 0x20, 0x00, 0x47, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x61, 0x00, + 0x3d, 0x00, 0x22, 0x00, 0x32, 0x00, 0x2e, 0x00, 0x34, 0x00, 0x22, 0x00, + 0x20, 0x00, 0x4f, 0x00, 0x66, 0x00, 0x66, 0x00, 0x73, 0x00, 0x65, 0x00, + 0x74, 0x00, 0x3d, 0x00, 0x22, 0x00, 0x30, 0x00, 0x2e, 0x00, 0x30, 0x00, + 0x35, 0x00, 0x35, 0x00, 0x22, 0x00, 0x20, 0x00, 0x47, 0x00, 0x61, 0x00, + 0x69, 0x00, 0x6e, 0x00, 0x3d, 0x00, 0x22, 0x00, 0x30, 0x00, 0x2e, 0x00, + 0x39, 0x00, 0x34, 0x00, 0x37, 0x00, 0x38, 0x00, 0x36, 0x00, 0x37, 0x00, + 0x22, 0x00, 0x20, 0x00, 0x4c, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x65, 0x00, + 0x61, 0x00, 0x72, 0x00, 0x47, 0x00, 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, + 0x3d, 0x00, 0x22, 0x00, 0x31, 0x00, 0x32, 0x00, 0x2e, 0x00, 0x39, 0x00, + 0x32, 0x00, 0x22, 0x00, 0x20, 0x00, 0x54, 0x00, 0x72, 0x00, 0x61, 0x00, + 0x6e, 0x00, 0x73, 0x00, 0x69, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, + 0x6e, 0x00, 0x50, 0x00, 0x6f, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x74, 0x00, + 0x3d, 0x00, 0x22, 0x00, 0x30, 0x00, 0x2e, 0x00, 0x30, 0x00, 0x34, 0x00, + 0x30, 0x00, 0x34, 0x00, 0x35, 0x00, 0x22, 0x00, 0x2f, 0x00, 0x3e, 0x00, + 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x09, 0x00, 0x3c, 0x00, 0x2f, 0x00, + 0x63, 0x00, 0x64, 0x00, 0x6d, 0x00, 0x3a, 0x00, 0x4d, 0x00, 0x65, 0x00, + 0x61, 0x00, 0x73, 0x00, 0x75, 0x00, 0x72, 0x00, 0x65, 0x00, 0x6d, 0x00, + 0x65, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x44, 0x00, 0x61, 0x00, 0x74, 0x00, + 0x61, 0x00, 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x3c, 0x00, + 0x2f, 0x00, 0x63, 0x00, 0x64, 0x00, 0x6d, 0x00, 0x3a, 0x00, 0x52, 0x00, + 0x47, 0x00, 0x42, 0x00, 0x56, 0x00, 0x69, 0x00, 0x72, 0x00, 0x74, 0x00, + 0x75, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x44, 0x00, 0x65, 0x00, 0x76, 0x00, + 0x69, 0x00, 0x63, 0x00, 0x65, 0x00, 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, + 0x09, 0x00, 0x3c, 0x00, 0x63, 0x00, 0x64, 0x00, 0x6d, 0x00, 0x3a, 0x00, + 0x43, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x62, 0x00, 0x72, 0x00, + 0x61, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x3e, 0x00, + 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x09, 0x00, 0x3c, 0x00, 0x63, 0x00, + 0x61, 0x00, 0x6c, 0x00, 0x3a, 0x00, 0x41, 0x00, 0x64, 0x00, 0x61, 0x00, + 0x70, 0x00, 0x74, 0x00, 0x65, 0x00, 0x72, 0x00, 0x47, 0x00, 0x61, 0x00, + 0x6d, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x43, 0x00, 0x6f, 0x00, 0x6e, 0x00, + 0x66, 0x00, 0x69, 0x00, 0x67, 0x00, 0x75, 0x00, 0x72, 0x00, 0x61, 0x00, + 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x3e, 0x00, 0x0d, 0x00, + 0x0a, 0x00, 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, 0x3c, 0x00, 0x63, 0x00, + 0x61, 0x00, 0x6c, 0x00, 0x3a, 0x00, 0x50, 0x00, 0x61, 0x00, 0x72, 0x00, + 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x74, 0x00, 0x65, 0x00, 0x72, 0x00, + 0x69, 0x00, 0x7a, 0x00, 0x65, 0x00, 0x64, 0x00, 0x43, 0x00, 0x75, 0x00, + 0x72, 0x00, 0x76, 0x00, 0x65, 0x00, 0x73, 0x00, 0x3e, 0x00, 0x0d, 0x00, + 0x0a, 0x00, 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, 0x3c, 0x00, + 0x77, 0x00, 0x63, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x52, 0x00, 0x65, 0x00, + 0x64, 0x00, 0x54, 0x00, 0x52, 0x00, 0x43, 0x00, 0x20, 0x00, 0x47, 0x00, + 0x61, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x3d, 0x00, 0x22, 0x00, + 0x31, 0x00, 0x2e, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, + 0x30, 0x00, 0x30, 0x00, 0x22, 0x00, 0x20, 0x00, 0x47, 0x00, 0x61, 0x00, + 0x69, 0x00, 0x6e, 0x00, 0x3d, 0x00, 0x22, 0x00, 0x31, 0x00, 0x2e, 0x00, + 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, + 0x22, 0x00, 0x20, 0x00, 0x4f, 0x00, 0x66, 0x00, 0x66, 0x00, 0x73, 0x00, + 0x65, 0x00, 0x74, 0x00, 0x31, 0x00, 0x3d, 0x00, 0x22, 0x00, 0x30, 0x00, + 0x2e, 0x00, 0x30, 0x00, 0x22, 0x00, 0x2f, 0x00, 0x3e, 0x00, 0x0d, 0x00, + 0x0a, 0x00, 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, 0x3c, 0x00, + 0x77, 0x00, 0x63, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x47, 0x00, 0x72, 0x00, + 0x65, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x54, 0x00, 0x52, 0x00, 0x43, 0x00, + 0x20, 0x00, 0x47, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x61, 0x00, + 0x3d, 0x00, 0x22, 0x00, 0x31, 0x00, 0x2e, 0x00, 0x30, 0x00, 0x30, 0x00, + 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x22, 0x00, 0x20, 0x00, + 0x47, 0x00, 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x3d, 0x00, 0x22, 0x00, + 0x31, 0x00, 0x2e, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, + 0x30, 0x00, 0x30, 0x00, 0x22, 0x00, 0x20, 0x00, 0x4f, 0x00, 0x66, 0x00, + 0x66, 0x00, 0x73, 0x00, 0x65, 0x00, 0x74, 0x00, 0x31, 0x00, 0x3d, 0x00, + 0x22, 0x00, 0x30, 0x00, 0x2e, 0x00, 0x30, 0x00, 0x22, 0x00, 0x2f, 0x00, + 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, + 0x09, 0x00, 0x3c, 0x00, 0x77, 0x00, 0x63, 0x00, 0x73, 0x00, 0x3a, 0x00, + 0x42, 0x00, 0x6c, 0x00, 0x75, 0x00, 0x65, 0x00, 0x54, 0x00, 0x52, 0x00, + 0x43, 0x00, 0x20, 0x00, 0x47, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x6d, 0x00, + 0x61, 0x00, 0x3d, 0x00, 0x22, 0x00, 0x31, 0x00, 0x2e, 0x00, 0x30, 0x00, + 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x22, 0x00, + 0x20, 0x00, 0x47, 0x00, 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x3d, 0x00, + 0x22, 0x00, 0x31, 0x00, 0x2e, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, + 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x22, 0x00, 0x20, 0x00, 0x4f, 0x00, + 0x66, 0x00, 0x66, 0x00, 0x73, 0x00, 0x65, 0x00, 0x74, 0x00, 0x31, 0x00, + 0x3d, 0x00, 0x22, 0x00, 0x30, 0x00, 0x2e, 0x00, 0x30, 0x00, 0x22, 0x00, + 0x2f, 0x00, 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x09, 0x00, + 0x09, 0x00, 0x3c, 0x00, 0x2f, 0x00, 0x63, 0x00, 0x61, 0x00, 0x6c, 0x00, + 0x3a, 0x00, 0x50, 0x00, 0x61, 0x00, 0x72, 0x00, 0x61, 0x00, 0x6d, 0x00, + 0x65, 0x00, 0x74, 0x00, 0x65, 0x00, 0x72, 0x00, 0x69, 0x00, 0x7a, 0x00, + 0x65, 0x00, 0x64, 0x00, 0x43, 0x00, 0x75, 0x00, 0x72, 0x00, 0x76, 0x00, + 0x65, 0x00, 0x73, 0x00, 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, + 0x09, 0x00, 0x3c, 0x00, 0x2f, 0x00, 0x63, 0x00, 0x61, 0x00, 0x6c, 0x00, + 0x3a, 0x00, 0x41, 0x00, 0x64, 0x00, 0x61, 0x00, 0x70, 0x00, 0x74, 0x00, + 0x65, 0x00, 0x72, 0x00, 0x47, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x6d, 0x00, + 0x61, 0x00, 0x43, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x66, 0x00, 0x69, 0x00, + 0x67, 0x00, 0x75, 0x00, 0x72, 0x00, 0x61, 0x00, 0x74, 0x00, 0x69, 0x00, + 0x6f, 0x00, 0x6e, 0x00, 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, + 0x3c, 0x00, 0x2f, 0x00, 0x63, 0x00, 0x64, 0x00, 0x6d, 0x00, 0x3a, 0x00, + 0x43, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x62, 0x00, 0x72, 0x00, + 0x61, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x3e, 0x00, + 0x0d, 0x00, 0x0a, 0x00, 0x3c, 0x00, 0x2f, 0x00, 0x63, 0x00, 0x64, 0x00, + 0x6d, 0x00, 0x3a, 0x00, 0x43, 0x00, 0x6f, 0x00, 0x6c, 0x00, 0x6f, 0x00, + 0x72, 0x00, 0x44, 0x00, 0x65, 0x00, 0x76, 0x00, 0x69, 0x00, 0x63, 0x00, + 0x65, 0x00, 0x4d, 0x00, 0x6f, 0x00, 0x64, 0x00, 0x65, 0x00, 0x6c, 0x00, + 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x3c, 0x00, 0x3f, 0x00, 0x78, 0x00, + 0x6d, 0x00, 0x6c, 0x00, 0x20, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, + 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x3d, 0x00, 0x22, 0x00, + 0x31, 0x00, 0x2e, 0x00, 0x30, 0x00, 0x22, 0x00, 0x3f, 0x00, 0x3e, 0x00, + 0x0d, 0x00, 0x0a, 0x00, 0x3c, 0x00, 0x63, 0x00, 0x61, 0x00, 0x6d, 0x00, + 0x3a, 0x00, 0x43, 0x00, 0x6f, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x72, 0x00, + 0x41, 0x00, 0x70, 0x00, 0x70, 0x00, 0x65, 0x00, 0x61, 0x00, 0x72, 0x00, + 0x61, 0x00, 0x6e, 0x00, 0x63, 0x00, 0x65, 0x00, 0x4d, 0x00, 0x6f, 0x00, + 0x64, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x20, 0x00, 0x49, 0x00, 0x44, 0x00, + 0x3d, 0x00, 0x22, 0x00, 0x68, 0x00, 0x74, 0x00, 0x74, 0x00, 0x70, 0x00, + 0x3a, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x73, 0x00, 0x63, 0x00, 0x68, 0x00, + 0x65, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x73, 0x00, 0x2e, 0x00, 0x6d, 0x00, + 0x69, 0x00, 0x63, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x6f, 0x00, + 0x66, 0x00, 0x74, 0x00, 0x2e, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, + 0x2f, 0x00, 0x77, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x6f, 0x00, + 0x77, 0x00, 0x73, 0x00, 0x2f, 0x00, 0x32, 0x00, 0x30, 0x00, 0x30, 0x00, + 0x35, 0x00, 0x2f, 0x00, 0x30, 0x00, 0x32, 0x00, 0x2f, 0x00, 0x63, 0x00, + 0x6f, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x2f, 0x00, 0x44, 0x00, + 0x36, 0x00, 0x35, 0x00, 0x2e, 0x00, 0x63, 0x00, 0x61, 0x00, 0x6d, 0x00, + 0x70, 0x00, 0x22, 0x00, 0x20, 0x00, 0x78, 0x00, 0x6d, 0x00, 0x6c, 0x00, + 0x6e, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x63, 0x00, 0x61, 0x00, 0x6d, 0x00, + 0x3d, 0x00, 0x22, 0x00, 0x68, 0x00, 0x74, 0x00, 0x74, 0x00, 0x70, 0x00, + 0x3a, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x73, 0x00, 0x63, 0x00, 0x68, 0x00, + 0x65, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x73, 0x00, 0x2e, 0x00, 0x6d, 0x00, + 0x69, 0x00, 0x63, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x6f, 0x00, + 0x66, 0x00, 0x74, 0x00, 0x2e, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, + 0x2f, 0x00, 0x77, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x6f, 0x00, + 0x77, 0x00, 0x73, 0x00, 0x2f, 0x00, 0x32, 0x00, 0x30, 0x00, 0x30, 0x00, + 0x35, 0x00, 0x2f, 0x00, 0x30, 0x00, 0x32, 0x00, 0x2f, 0x00, 0x63, 0x00, + 0x6f, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x2f, 0x00, 0x43, 0x00, + 0x6f, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x41, 0x00, 0x70, 0x00, + 0x70, 0x00, 0x65, 0x00, 0x61, 0x00, 0x72, 0x00, 0x61, 0x00, 0x6e, 0x00, + 0x63, 0x00, 0x65, 0x00, 0x4d, 0x00, 0x6f, 0x00, 0x64, 0x00, 0x65, 0x00, + 0x6c, 0x00, 0x22, 0x00, 0x20, 0x00, 0x78, 0x00, 0x6d, 0x00, 0x6c, 0x00, + 0x6e, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x77, 0x00, 0x63, 0x00, 0x73, 0x00, + 0x3d, 0x00, 0x22, 0x00, 0x68, 0x00, 0x74, 0x00, 0x74, 0x00, 0x70, 0x00, + 0x3a, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x73, 0x00, 0x63, 0x00, 0x68, 0x00, + 0x65, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x73, 0x00, 0x2e, 0x00, 0x6d, 0x00, + 0x69, 0x00, 0x63, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x6f, 0x00, + 0x66, 0x00, 0x74, 0x00, 0x2e, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, + 0x2f, 0x00, 0x77, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x6f, 0x00, + 0x77, 0x00, 0x73, 0x00, 0x2f, 0x00, 0x32, 0x00, 0x30, 0x00, 0x30, 0x00, + 0x35, 0x00, 0x2f, 0x00, 0x30, 0x00, 0x32, 0x00, 0x2f, 0x00, 0x63, 0x00, + 0x6f, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x2f, 0x00, 0x57, 0x00, + 0x63, 0x00, 0x73, 0x00, 0x43, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x6d, 0x00, + 0x6f, 0x00, 0x6e, 0x00, 0x50, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x66, 0x00, + 0x69, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x54, 0x00, 0x79, 0x00, 0x70, 0x00, + 0x65, 0x00, 0x73, 0x00, 0x22, 0x00, 0x20, 0x00, 0x78, 0x00, 0x6d, 0x00, + 0x6c, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x78, 0x00, 0x73, 0x00, + 0x3d, 0x00, 0x22, 0x00, 0x68, 0x00, 0x74, 0x00, 0x74, 0x00, 0x70, 0x00, + 0x3a, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x77, 0x00, 0x77, 0x00, 0x77, 0x00, + 0x2e, 0x00, 0x77, 0x00, 0x33, 0x00, 0x2e, 0x00, 0x6f, 0x00, 0x72, 0x00, + 0x67, 0x00, 0x2f, 0x00, 0x32, 0x00, 0x30, 0x00, 0x30, 0x00, 0x31, 0x00, + 0x2f, 0x00, 0x58, 0x00, 0x4d, 0x00, 0x4c, 0x00, 0x53, 0x00, 0x63, 0x00, + 0x68, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x2d, 0x00, 0x69, 0x00, + 0x6e, 0x00, 0x73, 0x00, 0x74, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x63, 0x00, + 0x65, 0x00, 0x22, 0x00, 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, + 0x3c, 0x00, 0x63, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x3a, 0x00, 0x50, 0x00, + 0x72, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x65, 0x00, + 0x4e, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x3e, 0x00, 0x0d, 0x00, + 0x0a, 0x00, 0x09, 0x00, 0x09, 0x00, 0x3c, 0x00, 0x77, 0x00, 0x63, 0x00, + 0x73, 0x00, 0x3a, 0x00, 0x54, 0x00, 0x65, 0x00, 0x78, 0x00, 0x74, 0x00, + 0x20, 0x00, 0x78, 0x00, 0x6d, 0x00, 0x6c, 0x00, 0x3a, 0x00, 0x6c, 0x00, + 0x61, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x3d, 0x00, 0x22, 0x00, 0x65, 0x00, + 0x6e, 0x00, 0x2d, 0x00, 0x55, 0x00, 0x53, 0x00, 0x22, 0x00, 0x3e, 0x00, + 0x57, 0x00, 0x43, 0x00, 0x53, 0x00, 0x20, 0x00, 0x70, 0x00, 0x72, 0x00, + 0x6f, 0x00, 0x66, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x20, 0x00, + 0x66, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x20, 0x00, 0x73, 0x00, 0x52, 0x00, + 0x47, 0x00, 0x42, 0x00, 0x20, 0x00, 0x76, 0x00, 0x69, 0x00, 0x65, 0x00, + 0x77, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x20, 0x00, 0x63, 0x00, + 0x6f, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x69, 0x00, 0x74, 0x00, 0x69, 0x00, + 0x6f, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x3c, 0x00, 0x2f, 0x00, 0x77, 0x00, + 0x63, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x54, 0x00, 0x65, 0x00, 0x78, 0x00, + 0x74, 0x00, 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x3c, 0x00, + 0x2f, 0x00, 0x63, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x3a, 0x00, 0x50, 0x00, + 0x72, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x65, 0x00, + 0x4e, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x3e, 0x00, 0x0d, 0x00, + 0x0a, 0x00, 0x09, 0x00, 0x3c, 0x00, 0x63, 0x00, 0x61, 0x00, 0x6d, 0x00, + 0x3a, 0x00, 0x44, 0x00, 0x65, 0x00, 0x73, 0x00, 0x63, 0x00, 0x72, 0x00, + 0x69, 0x00, 0x70, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, + 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x09, 0x00, 0x3c, 0x00, + 0x77, 0x00, 0x63, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x54, 0x00, 0x65, 0x00, + 0x78, 0x00, 0x74, 0x00, 0x20, 0x00, 0x78, 0x00, 0x6d, 0x00, 0x6c, 0x00, + 0x3a, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x3d, 0x00, + 0x22, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x2d, 0x00, 0x55, 0x00, 0x53, 0x00, + 0x22, 0x00, 0x3e, 0x00, 0x44, 0x00, 0x65, 0x00, 0x66, 0x00, 0x61, 0x00, + 0x75, 0x00, 0x6c, 0x00, 0x74, 0x00, 0x20, 0x00, 0x70, 0x00, 0x72, 0x00, + 0x6f, 0x00, 0x66, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x20, 0x00, + 0x66, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x20, 0x00, 0x61, 0x00, 0x20, 0x00, + 0x73, 0x00, 0x52, 0x00, 0x47, 0x00, 0x42, 0x00, 0x20, 0x00, 0x6d, 0x00, + 0x6f, 0x00, 0x6e, 0x00, 0x69, 0x00, 0x74, 0x00, 0x6f, 0x00, 0x72, 0x00, + 0x20, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x20, 0x00, 0x73, 0x00, 0x74, 0x00, + 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x61, 0x00, 0x72, 0x00, 0x64, 0x00, + 0x20, 0x00, 0x76, 0x00, 0x69, 0x00, 0x65, 0x00, 0x77, 0x00, 0x69, 0x00, + 0x6e, 0x00, 0x67, 0x00, 0x20, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6e, 0x00, + 0x64, 0x00, 0x69, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, + 0x73, 0x00, 0x3c, 0x00, 0x2f, 0x00, 0x77, 0x00, 0x63, 0x00, 0x73, 0x00, + 0x3a, 0x00, 0x54, 0x00, 0x65, 0x00, 0x78, 0x00, 0x74, 0x00, 0x3e, 0x00, + 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x3c, 0x00, 0x2f, 0x00, 0x63, 0x00, + 0x61, 0x00, 0x6d, 0x00, 0x3a, 0x00, 0x44, 0x00, 0x65, 0x00, 0x73, 0x00, + 0x63, 0x00, 0x72, 0x00, 0x69, 0x00, 0x70, 0x00, 0x74, 0x00, 0x69, 0x00, + 0x6f, 0x00, 0x6e, 0x00, 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, + 0x3c, 0x00, 0x63, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x3a, 0x00, 0x41, 0x00, + 0x75, 0x00, 0x74, 0x00, 0x68, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x3e, 0x00, + 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x09, 0x00, 0x3c, 0x00, 0x77, 0x00, + 0x63, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x54, 0x00, 0x65, 0x00, 0x78, 0x00, + 0x74, 0x00, 0x20, 0x00, 0x78, 0x00, 0x6d, 0x00, 0x6c, 0x00, 0x3a, 0x00, + 0x6c, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x3d, 0x00, 0x22, 0x00, + 0x65, 0x00, 0x6e, 0x00, 0x2d, 0x00, 0x55, 0x00, 0x53, 0x00, 0x22, 0x00, + 0x3e, 0x00, 0x4d, 0x00, 0x69, 0x00, 0x63, 0x00, 0x72, 0x00, 0x6f, 0x00, + 0x73, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x74, 0x00, 0x20, 0x00, 0x43, 0x00, + 0x6f, 0x00, 0x72, 0x00, 0x70, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x61, 0x00, + 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x3c, 0x00, 0x2f, 0x00, + 0x77, 0x00, 0x63, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x54, 0x00, 0x65, 0x00, + 0x78, 0x00, 0x74, 0x00, 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, + 0x3c, 0x00, 0x2f, 0x00, 0x63, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x3a, 0x00, + 0x41, 0x00, 0x75, 0x00, 0x74, 0x00, 0x68, 0x00, 0x6f, 0x00, 0x72, 0x00, + 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x3c, 0x00, 0x63, 0x00, + 0x61, 0x00, 0x6d, 0x00, 0x3a, 0x00, 0x56, 0x00, 0x69, 0x00, 0x65, 0x00, + 0x77, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x43, 0x00, 0x6f, 0x00, + 0x6e, 0x00, 0x64, 0x00, 0x69, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, + 0x6e, 0x00, 0x73, 0x00, 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, + 0x09, 0x00, 0x3c, 0x00, 0x63, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x3a, 0x00, + 0x57, 0x00, 0x68, 0x00, 0x69, 0x00, 0x74, 0x00, 0x65, 0x00, 0x50, 0x00, + 0x6f, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x4e, 0x00, 0x61, 0x00, + 0x6d, 0x00, 0x65, 0x00, 0x3e, 0x00, 0x44, 0x00, 0x36, 0x00, 0x35, 0x00, + 0x3c, 0x00, 0x2f, 0x00, 0x63, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x3a, 0x00, + 0x57, 0x00, 0x68, 0x00, 0x69, 0x00, 0x74, 0x00, 0x65, 0x00, 0x50, 0x00, + 0x6f, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x4e, 0x00, 0x61, 0x00, + 0x6d, 0x00, 0x65, 0x00, 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, + 0x09, 0x00, 0x3c, 0x00, 0x63, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x3a, 0x00, + 0x42, 0x00, 0x61, 0x00, 0x63, 0x00, 0x6b, 0x00, 0x67, 0x00, 0x72, 0x00, + 0x6f, 0x00, 0x75, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x20, 0x00, 0x58, 0x00, + 0x3d, 0x00, 0x22, 0x00, 0x31, 0x00, 0x39, 0x00, 0x2e, 0x00, 0x30, 0x00, + 0x22, 0x00, 0x20, 0x00, 0x59, 0x00, 0x3d, 0x00, 0x22, 0x00, 0x32, 0x00, + 0x30, 0x00, 0x2e, 0x00, 0x30, 0x00, 0x22, 0x00, 0x20, 0x00, 0x5a, 0x00, + 0x3d, 0x00, 0x22, 0x00, 0x32, 0x00, 0x31, 0x00, 0x2e, 0x00, 0x37, 0x00, + 0x38, 0x00, 0x22, 0x00, 0x2f, 0x00, 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, + 0x09, 0x00, 0x09, 0x00, 0x3c, 0x00, 0x63, 0x00, 0x61, 0x00, 0x6d, 0x00, + 0x3a, 0x00, 0x53, 0x00, 0x75, 0x00, 0x72, 0x00, 0x72, 0x00, 0x6f, 0x00, + 0x75, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x3e, 0x00, 0x41, 0x00, 0x76, 0x00, + 0x65, 0x00, 0x72, 0x00, 0x61, 0x00, 0x67, 0x00, 0x65, 0x00, 0x3c, 0x00, + 0x2f, 0x00, 0x63, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x3a, 0x00, 0x53, 0x00, + 0x75, 0x00, 0x72, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x6e, 0x00, + 0x64, 0x00, 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x09, 0x00, + 0x3c, 0x00, 0x63, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x3a, 0x00, 0x4c, 0x00, + 0x75, 0x00, 0x6d, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6e, 0x00, + 0x63, 0x00, 0x65, 0x00, 0x4f, 0x00, 0x66, 0x00, 0x41, 0x00, 0x64, 0x00, + 0x61, 0x00, 0x70, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, + 0x46, 0x00, 0x69, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x64, 0x00, 0x3e, 0x00, + 0x31, 0x00, 0x36, 0x00, 0x2e, 0x00, 0x30, 0x00, 0x3c, 0x00, 0x2f, 0x00, + 0x63, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x3a, 0x00, 0x4c, 0x00, 0x75, 0x00, + 0x6d, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x63, 0x00, + 0x65, 0x00, 0x4f, 0x00, 0x66, 0x00, 0x41, 0x00, 0x64, 0x00, 0x61, 0x00, + 0x70, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x46, 0x00, + 0x69, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x64, 0x00, 0x3e, 0x00, 0x0d, 0x00, + 0x0a, 0x00, 0x09, 0x00, 0x09, 0x00, 0x3c, 0x00, 0x63, 0x00, 0x61, 0x00, + 0x6d, 0x00, 0x3a, 0x00, 0x44, 0x00, 0x65, 0x00, 0x67, 0x00, 0x72, 0x00, + 0x65, 0x00, 0x65, 0x00, 0x4f, 0x00, 0x66, 0x00, 0x41, 0x00, 0x64, 0x00, + 0x61, 0x00, 0x70, 0x00, 0x74, 0x00, 0x61, 0x00, 0x74, 0x00, 0x69, 0x00, + 0x6f, 0x00, 0x6e, 0x00, 0x3e, 0x00, 0x31, 0x00, 0x3c, 0x00, 0x2f, 0x00, + 0x63, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x3a, 0x00, 0x44, 0x00, 0x65, 0x00, + 0x67, 0x00, 0x72, 0x00, 0x65, 0x00, 0x65, 0x00, 0x4f, 0x00, 0x66, 0x00, + 0x41, 0x00, 0x64, 0x00, 0x61, 0x00, 0x70, 0x00, 0x74, 0x00, 0x61, 0x00, + 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x3e, 0x00, 0x0d, 0x00, + 0x0a, 0x00, 0x09, 0x00, 0x3c, 0x00, 0x2f, 0x00, 0x63, 0x00, 0x61, 0x00, + 0x6d, 0x00, 0x3a, 0x00, 0x56, 0x00, 0x69, 0x00, 0x65, 0x00, 0x77, 0x00, + 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x43, 0x00, 0x6f, 0x00, 0x6e, 0x00, + 0x64, 0x00, 0x69, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, + 0x73, 0x00, 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x3c, 0x00, 0x2f, 0x00, + 0x63, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x3a, 0x00, 0x43, 0x00, 0x6f, 0x00, + 0x6c, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x41, 0x00, 0x70, 0x00, 0x70, 0x00, + 0x65, 0x00, 0x61, 0x00, 0x72, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x63, 0x00, + 0x65, 0x00, 0x4d, 0x00, 0x6f, 0x00, 0x64, 0x00, 0x65, 0x00, 0x6c, 0x00, + 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x3c, 0x00, 0x3f, 0x00, 0x78, 0x00, + 0x6d, 0x00, 0x6c, 0x00, 0x20, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, + 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x3d, 0x00, 0x22, 0x00, + 0x31, 0x00, 0x2e, 0x00, 0x30, 0x00, 0x22, 0x00, 0x3f, 0x00, 0x3e, 0x00, + 0x0d, 0x00, 0x0a, 0x00, 0x3c, 0x00, 0x67, 0x00, 0x6d, 0x00, 0x6d, 0x00, + 0x3a, 0x00, 0x47, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x75, 0x00, 0x74, 0x00, + 0x4d, 0x00, 0x61, 0x00, 0x70, 0x00, 0x4d, 0x00, 0x6f, 0x00, 0x64, 0x00, + 0x65, 0x00, 0x6c, 0x00, 0x20, 0x00, 0x49, 0x00, 0x44, 0x00, 0x3d, 0x00, + 0x22, 0x00, 0x68, 0x00, 0x74, 0x00, 0x74, 0x00, 0x70, 0x00, 0x3a, 0x00, + 0x2f, 0x00, 0x2f, 0x00, 0x73, 0x00, 0x63, 0x00, 0x68, 0x00, 0x65, 0x00, + 0x6d, 0x00, 0x61, 0x00, 0x73, 0x00, 0x2e, 0x00, 0x6d, 0x00, 0x69, 0x00, + 0x63, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x6f, 0x00, 0x66, 0x00, + 0x74, 0x00, 0x2e, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2f, 0x00, + 0x77, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x77, 0x00, + 0x73, 0x00, 0x2f, 0x00, 0x32, 0x00, 0x30, 0x00, 0x30, 0x00, 0x35, 0x00, + 0x2f, 0x00, 0x30, 0x00, 0x32, 0x00, 0x2f, 0x00, 0x63, 0x00, 0x6f, 0x00, + 0x6c, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x2f, 0x00, 0x4d, 0x00, 0x65, 0x00, + 0x64, 0x00, 0x69, 0x00, 0x61, 0x00, 0x53, 0x00, 0x69, 0x00, 0x6d, 0x00, + 0x2e, 0x00, 0x67, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x70, 0x00, 0x22, 0x00, + 0x20, 0x00, 0x78, 0x00, 0x6d, 0x00, 0x6c, 0x00, 0x6e, 0x00, 0x73, 0x00, + 0x3a, 0x00, 0x67, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x3d, 0x00, 0x22, 0x00, + 0x68, 0x00, 0x74, 0x00, 0x74, 0x00, 0x70, 0x00, 0x3a, 0x00, 0x2f, 0x00, + 0x2f, 0x00, 0x73, 0x00, 0x63, 0x00, 0x68, 0x00, 0x65, 0x00, 0x6d, 0x00, + 0x61, 0x00, 0x73, 0x00, 0x2e, 0x00, 0x6d, 0x00, 0x69, 0x00, 0x63, 0x00, + 0x72, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x74, 0x00, + 0x2e, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2f, 0x00, 0x77, 0x00, + 0x69, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x77, 0x00, 0x73, 0x00, + 0x2f, 0x00, 0x32, 0x00, 0x30, 0x00, 0x30, 0x00, 0x35, 0x00, 0x2f, 0x00, + 0x30, 0x00, 0x32, 0x00, 0x2f, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6c, 0x00, + 0x6f, 0x00, 0x72, 0x00, 0x2f, 0x00, 0x47, 0x00, 0x61, 0x00, 0x6d, 0x00, + 0x75, 0x00, 0x74, 0x00, 0x4d, 0x00, 0x61, 0x00, 0x70, 0x00, 0x4d, 0x00, + 0x6f, 0x00, 0x64, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x22, 0x00, 0x20, 0x00, + 0x78, 0x00, 0x6d, 0x00, 0x6c, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x3a, 0x00, + 0x77, 0x00, 0x63, 0x00, 0x73, 0x00, 0x3d, 0x00, 0x22, 0x00, 0x68, 0x00, + 0x74, 0x00, 0x74, 0x00, 0x70, 0x00, 0x3a, 0x00, 0x2f, 0x00, 0x2f, 0x00, + 0x73, 0x00, 0x63, 0x00, 0x68, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x61, 0x00, + 0x73, 0x00, 0x2e, 0x00, 0x6d, 0x00, 0x69, 0x00, 0x63, 0x00, 0x72, 0x00, + 0x6f, 0x00, 0x73, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x74, 0x00, 0x2e, 0x00, + 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2f, 0x00, 0x77, 0x00, 0x69, 0x00, + 0x6e, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x77, 0x00, 0x73, 0x00, 0x2f, 0x00, + 0x32, 0x00, 0x30, 0x00, 0x30, 0x00, 0x35, 0x00, 0x2f, 0x00, 0x30, 0x00, + 0x32, 0x00, 0x2f, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6c, 0x00, 0x6f, 0x00, + 0x72, 0x00, 0x2f, 0x00, 0x57, 0x00, 0x63, 0x00, 0x73, 0x00, 0x43, 0x00, + 0x6f, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x50, 0x00, + 0x72, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x65, 0x00, + 0x54, 0x00, 0x79, 0x00, 0x70, 0x00, 0x65, 0x00, 0x73, 0x00, 0x22, 0x00, + 0x20, 0x00, 0x78, 0x00, 0x6d, 0x00, 0x6c, 0x00, 0x6e, 0x00, 0x73, 0x00, + 0x3a, 0x00, 0x78, 0x00, 0x73, 0x00, 0x3d, 0x00, 0x22, 0x00, 0x68, 0x00, + 0x74, 0x00, 0x74, 0x00, 0x70, 0x00, 0x3a, 0x00, 0x2f, 0x00, 0x2f, 0x00, + 0x77, 0x00, 0x77, 0x00, 0x77, 0x00, 0x2e, 0x00, 0x77, 0x00, 0x33, 0x00, + 0x2e, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x67, 0x00, 0x2f, 0x00, 0x32, 0x00, + 0x30, 0x00, 0x30, 0x00, 0x31, 0x00, 0x2f, 0x00, 0x58, 0x00, 0x4d, 0x00, + 0x4c, 0x00, 0x53, 0x00, 0x63, 0x00, 0x68, 0x00, 0x65, 0x00, 0x6d, 0x00, + 0x61, 0x00, 0x2d, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x74, 0x00, + 0x61, 0x00, 0x6e, 0x00, 0x63, 0x00, 0x65, 0x00, 0x22, 0x00, 0x3e, 0x00, + 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x3c, 0x00, 0x67, 0x00, 0x6d, 0x00, + 0x6d, 0x00, 0x3a, 0x00, 0x50, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x66, 0x00, + 0x69, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x4e, 0x00, 0x61, 0x00, 0x6d, 0x00, + 0x65, 0x00, 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x09, 0x00, + 0x3c, 0x00, 0x77, 0x00, 0x63, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x54, 0x00, + 0x65, 0x00, 0x78, 0x00, 0x74, 0x00, 0x20, 0x00, 0x78, 0x00, 0x6d, 0x00, + 0x6c, 0x00, 0x3a, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x67, 0x00, + 0x3d, 0x00, 0x22, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x2d, 0x00, 0x55, 0x00, + 0x53, 0x00, 0x22, 0x00, 0x3e, 0x00, 0x50, 0x00, 0x72, 0x00, 0x6f, 0x00, + 0x6f, 0x00, 0x66, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x20, 0x00, + 0x2d, 0x00, 0x20, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6d, 0x00, 0x75, 0x00, + 0x6c, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00, 0x20, 0x00, 0x70, 0x00, + 0x61, 0x00, 0x70, 0x00, 0x65, 0x00, 0x72, 0x00, 0x2f, 0x00, 0x6d, 0x00, + 0x65, 0x00, 0x64, 0x00, 0x69, 0x00, 0x61, 0x00, 0x20, 0x00, 0x63, 0x00, + 0x6f, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x3c, 0x00, 0x2f, 0x00, + 0x77, 0x00, 0x63, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x54, 0x00, 0x65, 0x00, + 0x78, 0x00, 0x74, 0x00, 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, + 0x3c, 0x00, 0x2f, 0x00, 0x67, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x3a, 0x00, + 0x50, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x69, 0x00, 0x6c, 0x00, + 0x65, 0x00, 0x4e, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x3e, 0x00, + 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x3c, 0x00, 0x67, 0x00, 0x6d, 0x00, + 0x6d, 0x00, 0x3a, 0x00, 0x44, 0x00, 0x65, 0x00, 0x73, 0x00, 0x63, 0x00, + 0x72, 0x00, 0x69, 0x00, 0x70, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, + 0x6e, 0x00, 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x09, 0x00, + 0x3c, 0x00, 0x77, 0x00, 0x63, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x54, 0x00, + 0x65, 0x00, 0x78, 0x00, 0x74, 0x00, 0x20, 0x00, 0x78, 0x00, 0x6d, 0x00, + 0x6c, 0x00, 0x3a, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x67, 0x00, + 0x3d, 0x00, 0x22, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x2d, 0x00, 0x55, 0x00, + 0x53, 0x00, 0x22, 0x00, 0x3e, 0x00, 0x41, 0x00, 0x70, 0x00, 0x70, 0x00, + 0x72, 0x00, 0x6f, 0x00, 0x70, 0x00, 0x72, 0x00, 0x69, 0x00, 0x61, 0x00, + 0x74, 0x00, 0x65, 0x00, 0x20, 0x00, 0x66, 0x00, 0x6f, 0x00, 0x72, 0x00, + 0x20, 0x00, 0x49, 0x00, 0x43, 0x00, 0x43, 0x00, 0x20, 0x00, 0x61, 0x00, + 0x62, 0x00, 0x73, 0x00, 0x6f, 0x00, 0x6c, 0x00, 0x75, 0x00, 0x74, 0x00, + 0x65, 0x00, 0x20, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6c, 0x00, 0x6f, 0x00, + 0x72, 0x00, 0x69, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x74, 0x00, 0x72, 0x00, + 0x69, 0x00, 0x63, 0x00, 0x20, 0x00, 0x72, 0x00, 0x65, 0x00, 0x6e, 0x00, + 0x64, 0x00, 0x65, 0x00, 0x72, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, + 0x20, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x6e, 0x00, + 0x74, 0x00, 0x20, 0x00, 0x77, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x6b, 0x00, + 0x66, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x77, 0x00, 0x73, 0x00, 0x3c, 0x00, + 0x2f, 0x00, 0x77, 0x00, 0x63, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x54, 0x00, + 0x65, 0x00, 0x78, 0x00, 0x74, 0x00, 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, + 0x09, 0x00, 0x3c, 0x00, 0x2f, 0x00, 0x67, 0x00, 0x6d, 0x00, 0x6d, 0x00, + 0x3a, 0x00, 0x44, 0x00, 0x65, 0x00, 0x73, 0x00, 0x63, 0x00, 0x72, 0x00, + 0x69, 0x00, 0x70, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, + 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x3c, 0x00, 0x67, 0x00, + 0x6d, 0x00, 0x6d, 0x00, 0x3a, 0x00, 0x41, 0x00, 0x75, 0x00, 0x74, 0x00, + 0x68, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, + 0x09, 0x00, 0x09, 0x00, 0x3c, 0x00, 0x77, 0x00, 0x63, 0x00, 0x73, 0x00, + 0x3a, 0x00, 0x54, 0x00, 0x65, 0x00, 0x78, 0x00, 0x74, 0x00, 0x20, 0x00, + 0x78, 0x00, 0x6d, 0x00, 0x6c, 0x00, 0x3a, 0x00, 0x6c, 0x00, 0x61, 0x00, + 0x6e, 0x00, 0x67, 0x00, 0x3d, 0x00, 0x22, 0x00, 0x65, 0x00, 0x6e, 0x00, + 0x2d, 0x00, 0x55, 0x00, 0x53, 0x00, 0x22, 0x00, 0x3e, 0x00, 0x4d, 0x00, + 0x69, 0x00, 0x63, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x6f, 0x00, + 0x66, 0x00, 0x74, 0x00, 0x20, 0x00, 0x43, 0x00, 0x6f, 0x00, 0x72, 0x00, + 0x70, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x61, 0x00, 0x74, 0x00, 0x69, 0x00, + 0x6f, 0x00, 0x6e, 0x00, 0x3c, 0x00, 0x2f, 0x00, 0x77, 0x00, 0x63, 0x00, + 0x73, 0x00, 0x3a, 0x00, 0x54, 0x00, 0x65, 0x00, 0x78, 0x00, 0x74, 0x00, + 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x3c, 0x00, 0x2f, 0x00, + 0x67, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x3a, 0x00, 0x41, 0x00, 0x75, 0x00, + 0x74, 0x00, 0x68, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x3e, 0x00, 0x0d, 0x00, + 0x0a, 0x00, 0x09, 0x00, 0x3c, 0x00, 0x67, 0x00, 0x6d, 0x00, 0x6d, 0x00, + 0x3a, 0x00, 0x44, 0x00, 0x65, 0x00, 0x66, 0x00, 0x61, 0x00, 0x75, 0x00, + 0x6c, 0x00, 0x74, 0x00, 0x42, 0x00, 0x61, 0x00, 0x73, 0x00, 0x65, 0x00, + 0x6c, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x47, 0x00, 0x61, 0x00, + 0x6d, 0x00, 0x75, 0x00, 0x74, 0x00, 0x4d, 0x00, 0x61, 0x00, 0x70, 0x00, + 0x4d, 0x00, 0x6f, 0x00, 0x64, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x3e, 0x00, + 0x48, 0x00, 0x50, 0x00, 0x4d, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x43, 0x00, + 0x44, 0x00, 0x5f, 0x00, 0x41, 0x00, 0x62, 0x00, 0x73, 0x00, 0x6f, 0x00, + 0x6c, 0x00, 0x75, 0x00, 0x74, 0x00, 0x65, 0x00, 0x3c, 0x00, 0x2f, 0x00, + 0x67, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x3a, 0x00, 0x44, 0x00, 0x65, 0x00, + 0x66, 0x00, 0x61, 0x00, 0x75, 0x00, 0x6c, 0x00, 0x74, 0x00, 0x42, 0x00, + 0x61, 0x00, 0x73, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x6e, 0x00, + 0x65, 0x00, 0x47, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x75, 0x00, 0x74, 0x00, + 0x4d, 0x00, 0x61, 0x00, 0x70, 0x00, 0x4d, 0x00, 0x6f, 0x00, 0x64, 0x00, + 0x65, 0x00, 0x6c, 0x00, 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x3c, 0x00, + 0x2f, 0x00, 0x67, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x3a, 0x00, 0x47, 0x00, + 0x61, 0x00, 0x6d, 0x00, 0x75, 0x00, 0x74, 0x00, 0x4d, 0x00, 0x61, 0x00, + 0x70, 0x00, 0x4d, 0x00, 0x6f, 0x00, 0x64, 0x00, 0x65, 0x00, 0x6c, 0x00, + 0x3e, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x00, 0x00}; + +ICCProfile ICCProfileForTestingAdobeRGB() { + return ICCProfile::FromData( + reinterpret_cast(adobe_rgb_profile_data), + base::size(adobe_rgb_profile_data)); +} + +ICCProfile ICCProfileForTestingGenericRGB() { + return ICCProfile::FromData( + reinterpret_cast(generic_rgb_profile_data), + base::size(generic_rgb_profile_data)); +} + +ICCProfile ICCProfileForTestingSRGB() { + return ICCProfile::FromData(reinterpret_cast(srgb_profile_data), + base::size(srgb_profile_data)); +} + +ICCProfile ICCProfileForTestingColorSpin() { + return ICCProfile::FromData( + reinterpret_cast(colorspin_profile_data), + base::size(colorspin_profile_data)); +} + +ICCProfile ICCProfileForTestingNoAnalyticTrFn() { + return ICCProfile::FromData( + reinterpret_cast(no_analytic_tr_fn_profile_data), + base::size(no_analytic_tr_fn_profile_data)); +} + +ICCProfile ICCProfileForTestingA2BOnly() { + return ICCProfile::FromData( + reinterpret_cast(a2b_only_profile_data), + base::size(a2b_only_profile_data)); +} + +ICCProfile ICCProfileForTestingOvershoot() { + return ICCProfile::FromData( + reinterpret_cast(overshoot_profile_data), + base::size(overshoot_profile_data)); +} + +} // namespace gfx diff --git a/test/icc_profiles.h b/test/icc_profiles.h new file mode 100644 index 000000000000..8b13d4a3db07 --- /dev/null +++ b/test/icc_profiles.h @@ -0,0 +1,28 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_TEST_ICC_PROFILES_H_ +#define UI_GFX_TEST_ICC_PROFILES_H_ + +#include "ui/gfx/icc_profile.h" + +namespace gfx { + +ICCProfile ICCProfileForTestingAdobeRGB(); +ICCProfile ICCProfileForTestingColorSpin(); +ICCProfile ICCProfileForTestingGenericRGB(); +ICCProfile ICCProfileForTestingSRGB(); + +// A profile that does not have an analytic transfer function. +ICCProfile ICCProfileForTestingNoAnalyticTrFn(); + +// A profile that is A2B only. +ICCProfile ICCProfileForTestingA2BOnly(); + +// A profile that with an approxmation that shoots above 1. +ICCProfile ICCProfileForTestingOvershoot(); + +} // namespace gfx + +#endif // UI_GFX_TEST_ICC_PROFILES_H_ \ No newline at end of file diff --git a/test/run_all_unittests.cc b/test/run_all_unittests.cc new file mode 100644 index 000000000000..e0b546da6394 --- /dev/null +++ b/test/run_all_unittests.cc @@ -0,0 +1,88 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/path_service.h" +#include "base/test/launcher/unit_test_launcher.h" +#include "base/test/test_discardable_memory_allocator.h" +#include "base/test/test_suite.h" +#include "build/build_config.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/base/ui_base_paths.h" +#include "ui/gfx/font_util.h" + +#if defined(OS_MAC) +#include "base/test/mock_chrome_application_mac.h" +#endif + +#if !defined(OS_IOS) +#include "mojo/core/embedder/embedder.h" // nogncheck +#endif + +#if defined(OS_FUCHSIA) +#include "skia/ext/test_fonts.h" // nogncheck +#endif + +namespace { + +class GfxTestSuite : public base::TestSuite { + public: + GfxTestSuite(int argc, char** argv) : base::TestSuite(argc, argv) { + } + + GfxTestSuite(const GfxTestSuite&) = delete; + GfxTestSuite& operator=(const GfxTestSuite&) = delete; + + protected: + void Initialize() override { + base::TestSuite::Initialize(); + +#if defined(OS_MAC) + mock_cr_app::RegisterMockCrApp(); +#endif + + ui::RegisterPathProvider(); + + base::FilePath ui_test_pak_path; + ASSERT_TRUE(base::PathService::Get(ui::UI_TEST_PAK, &ui_test_pak_path)); + ui::ResourceBundle::InitSharedInstanceWithPakPath(ui_test_pak_path); + +#if defined(OS_ANDROID) + // Android needs a discardable memory allocator when loading fallback fonts. + base::DiscardableMemoryAllocator::SetInstance( + &discardable_memory_allocator); +#endif + +#if defined(OS_FUCHSIA) + skia::ConfigureTestFont(); +#endif + + gfx::InitializeFonts(); + } + + void Shutdown() override { + ui::ResourceBundle::CleanupSharedInstance(); + base::TestSuite::Shutdown(); + } + + private: + base::TestDiscardableMemoryAllocator discardable_memory_allocator; +}; + +} // namespace + +int main(int argc, char** argv) { + GfxTestSuite test_suite(argc, argv); + +#if !defined(OS_IOS) + mojo::core::Init(); +#endif + + return base::LaunchUnitTests( + argc, argv, + base::BindOnce(&GfxTestSuite::Run, base::Unretained(&test_suite))); +} diff --git a/text_constants.h b/text_constants.h new file mode 100644 index 000000000000..f22a5d25f242 --- /dev/null +++ b/text_constants.h @@ -0,0 +1,129 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_TEXT_CONSTANTS_H_ +#define UI_GFX_TEXT_CONSTANTS_H_ + +namespace gfx { + +// TODO(msw): Distinguish between logical character stops and glyph stops? +// TODO(msw): Merge with base::i18n::BreakIterator::BreakType. +enum BreakType { + CHARACTER_BREAK = 0, // Stop cursor movement on neighboring characters. + WORD_BREAK, // Stop cursor movement on nearest word boundaries. + LINE_BREAK, // Stop cursor movement on line ends as shown on screen. + FIELD_BREAK, // Stop cursor movement on text ends. +}; + +// Specifies the selection behavior for a move/move-and-select command. For +// example consider the state "ab|cd|e", i.e. cd is selected. Assume the +// selection direction is from left to right. If we move to the beginning of the +// line (LINE_BREAK, CURSOR_LEFT), the resultant state is: +// "|ab|cde" for SELECTION_RETAIN, selection direction from right to left. +// "|abcd|e" for SELECTION_EXTEND, selection direction from right to left. +// "ab|cde" for SELECTION_CARET. +// "|abcde" for SELECTION_NONE. +enum SelectionBehavior { + // Default behavior for a move-and-select command. The selection start point + // remains the same. For example, this is the behavior of textfields on Mac + // for the command moveUpAndModifySelection (Shift + Up). + SELECTION_RETAIN, + + // Use for move-and-select commands that want the existing selection to be + // extended in the opposite direction, when the selection direction is + // reversed. For example, this is the behavior for textfields on Mac for the + // command moveToLeftEndOfLineAndModifySelection (Command + Shift + Left). + SELECTION_EXTEND, + + // Use for move-and-select commands that want the existing selection to reduce + // to a caret, when the selection direction is reversed. For example, this is + // the behavior for textfields on Mac for the command + // moveWordLeftAndModifySelection (Alt + Shift + Left). + SELECTION_CARET, + + // No selection. To be used for move commands that don't want to cause a + // selection, and that want to collapse any pre-existing selection. + SELECTION_NONE, +}; + +// Specifies the word wrapping behavior when a word would exceed the available +// display width. All words that are too wide will be put on a new line, and +// then: +enum WordWrapBehavior { + IGNORE_LONG_WORDS, // Overflowing word text is left on that line. + TRUNCATE_LONG_WORDS, // Overflowing word text is truncated. + ELIDE_LONG_WORDS, // Overflowing word text is elided at the ellipsis. + WRAP_LONG_WORDS, // Overflowing word text is wrapped over multiple lines. +}; + +// Horizontal text alignment modes. +enum HorizontalAlignment { + ALIGN_LEFT = 0, // Align the text's left edge with that of its display area. + ALIGN_CENTER, // Align the text's center with that of its display area. + ALIGN_RIGHT, // Align the text's right edge with that of its display area. + ALIGN_TO_HEAD, // Align the text to its first strong character's direction. +}; + +// Vertical text alignment modes for multiline text. +enum VerticalAlignment { + ALIGN_TOP = 0, // Align the text's top edge with that of its display area. + ALIGN_MIDDLE, // Align the text's center with that of its display area. + ALIGN_BOTTOM, // Align the text's bottom edge with that of its display area. +}; + +// The directionality modes used to determine the base text direction. +enum DirectionalityMode { + DIRECTIONALITY_FROM_TEXT = 0, // Use the first strong character's direction. + DIRECTIONALITY_FROM_UI, // Use the UI locale's text reading direction. + DIRECTIONALITY_FORCE_LTR, // Use LTR regardless of content or UI locale. + DIRECTIONALITY_FORCE_RTL, // Use RTL regardless of content or UI locale. + // Note: Unless the experimental feature LeftToRightUrls is enabled, + // DIRECTIONALITY_AS_URL is the same as DIRECTIONALITY_FORCE_LTR. + DIRECTIONALITY_AS_URL, // FORCE_LTR with additional rules for URLs. +}; + +// Text styles and adornments. +// TODO(msw): Merge with gfx::Font::FontStyle. +enum TextStyle { + TEXT_STYLE_ITALIC = 0, + TEXT_STYLE_STRIKE, + TEXT_STYLE_UNDERLINE, + TEXT_STYLE_HEAVY_UNDERLINE, + + TEXT_STYLE_COUNT, +}; + +// Text baseline offset types. +// Figure of font metrics: +// +--------+--------+------------------------+-------------+ +// | | | internal leading | SUPERSCRIPT | +// | | +------------+-----------| | +// | | ascent | | SUPERIOR |-------------+ +// | height | | cap height |-----------| +// | | | | INFERIOR |-------------+ +// | |--------+------------+-----------| | +// | | descent | SUBSCRIPT | +// +--------+---------------------------------+-------------+ +enum BaselineStyle { + NORMAL_BASELINE = 0, + SUPERSCRIPT, // e.g. a mathematical exponent would be superscript. + SUPERIOR, // e.g. 8th, the "th" would be superior script. + INFERIOR, // e.g. 1/2, the "2" would be inferior ("1" is superior). + SUBSCRIPT, // e.g. H2O, the "2" would be subscript. +}; + +// Elision behaviors of text that exceeds constrained dimensions. +enum ElideBehavior { + NO_ELIDE = 0, // Do not modify the text, it may overflow its available bounds. + TRUNCATE, // Do not elide or fade, just truncate at the end of the string. + ELIDE_HEAD, // Add an ellipsis at the start of the string. + ELIDE_MIDDLE, // Add an ellipsis in the middle of the string. + ELIDE_TAIL, // Add an ellipsis at the end of the string. + ELIDE_EMAIL, // Add ellipses to username and domain substrings. + FADE_TAIL, // Fade the string's end opposite of its horizontal alignment. +}; + +} // namespace gfx + +#endif // UI_GFX_TEXT_CONSTANTS_H_ diff --git a/text_elider.cc b/text_elider.cc new file mode 100644 index 000000000000..211da2e11cb9 --- /dev/null +++ b/text_elider.cc @@ -0,0 +1,846 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// This file implements utility functions for eliding and formatting UI text. +// +// Note that several of the functions declared in text_elider.h are implemented +// in this file using helper classes in an unnamed namespace. + +#include "ui/gfx/text_elider.h" + +#include + +#include +#include +#include +#include + +#include "base/check_op.h" +#include "base/files/file_path.h" +#include "base/i18n/break_iterator.h" +#include "base/i18n/char_iterator.h" +#include "base/i18n/rtl.h" +#include "base/macros.h" +#include "base/notreached.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/strings/sys_string_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "build/build_config.h" +#include "third_party/icu/source/common/unicode/rbbi.h" +#include "third_party/icu/source/common/unicode/uloc.h" +#include "third_party/icu/source/common/unicode/umachine.h" +#include "ui/gfx/font_list.h" +#include "ui/gfx/geometry/rect_conversions.h" +#include "ui/gfx/render_text.h" +#include "ui/gfx/text_utils.h" + +using base::ASCIIToUTF16; +using base::UTF8ToUTF16; +using base::WideToUTF16; + +namespace gfx { + +namespace { + +#if defined(OS_IOS) +// The returned string will have at least one character besides the ellipsis +// on either side of '@'; if that's impossible, a single ellipsis is returned. +// If possible, only the username is elided. Otherwise, the domain is elided +// in the middle, splitting available width equally with the elided username. +// If the username is short enough that it doesn't need half the available +// width, the elided domain will occupy that extra width. +std::u16string ElideEmail(const std::u16string& email, + const FontList& font_list, + float available_pixel_width) { + if (GetStringWidthF(email, font_list) <= available_pixel_width) + return email; + + // Split the email into its local-part (username) and domain-part. The email + // spec allows for @ symbols in the username under some special requirements, + // but not in the domain part, so splitting at the last @ symbol is safe. + const size_t split_index = email.find_last_of('@'); + DCHECK_NE(split_index, std::u16string::npos); + std::u16string username = email.substr(0, split_index); + std::u16string domain = email.substr(split_index + 1); + DCHECK(!username.empty()); + DCHECK(!domain.empty()); + + // Subtract the @ symbol from the available width as it is mandatory. + const std::u16string kAtSignUTF16 = u"@"; + available_pixel_width -= GetStringWidthF(kAtSignUTF16, font_list); + + // Check whether eliding the domain is necessary: if eliding the username + // is sufficient, the domain will not be elided. + const float full_username_width = GetStringWidthF(username, font_list); + const float available_domain_width = + available_pixel_width - + std::min(full_username_width, + GetStringWidthF(username.substr(0, 1) + kEllipsisUTF16, + font_list)); + if (GetStringWidthF(domain, font_list) > available_domain_width) { + // Elide the domain so that it only takes half of the available width. + // Should the username not need all the width available in its half, the + // domain will occupy the leftover width. + // If |desired_domain_width| is greater than |available_domain_width|: the + // minimal username elision allowed by the specifications will not fit; thus + // |desired_domain_width| must be <= |available_domain_width| at all cost. + const float desired_domain_width = + std::min(available_domain_width, + std::max(available_pixel_width - full_username_width, + available_pixel_width / 2)); + domain = ElideText(domain, font_list, desired_domain_width, ELIDE_MIDDLE); + // Failing to elide the domain such that at least one character remains + // (other than the ellipsis itself) remains: return a single ellipsis. + if (domain.length() <= 1U) + return std::u16string(kEllipsisUTF16); + } + + // Fit the username in the remaining width (at this point the elided username + // is guaranteed to fit with at least one character remaining given all the + // precautions taken earlier). + available_pixel_width -= GetStringWidthF(domain, font_list); + username = ElideText(username, font_list, available_pixel_width, ELIDE_TAIL); + return username + kAtSignUTF16 + domain; +} +#endif + +bool GetDefaultWhitespaceElision(bool elide_in_middle, + bool elide_at_beginning) { + return elide_at_beginning || !elide_in_middle; +} + +} // namespace + +// U+2026 in utf8 +const char kEllipsis[] = "\xE2\x80\xA6"; +const char16_t kEllipsisUTF16[] = {0x2026, 0}; +const char16_t kForwardSlash = '/'; + +StringSlicer::StringSlicer(const std::u16string& text, + const std::u16string& ellipsis, + bool elide_in_middle, + bool elide_at_beginning, + absl::optional elide_whitespace) + : text_(text), + ellipsis_(ellipsis), + elide_in_middle_(elide_in_middle), + elide_at_beginning_(elide_at_beginning), + elide_whitespace_(elide_whitespace + ? *elide_whitespace + : GetDefaultWhitespaceElision(elide_in_middle, + elide_at_beginning)) { +} + +std::u16string StringSlicer::CutString(size_t length, + bool insert_ellipsis) const { + const std::u16string ellipsis_text = + insert_ellipsis ? ellipsis_ : std::u16string(); + + // For visual consistency, when eliding at either end of the string, excess + // space should be trimmed from the text to return "Foo bar..." instead of + // "Foo bar ...". + + if (elide_at_beginning_) { + return ellipsis_text + + text_.substr(FindValidBoundaryAfter(text_, text_.length() - length, + elide_whitespace_)); + } + + if (!elide_in_middle_) { + return text_.substr( + 0, FindValidBoundaryBefore(text_, length, elide_whitespace_)) + + ellipsis_text; + } + + // Put the extra character, if any, before the cut. + // Extra space around the ellipses will *not* be trimmed for |elide_in_middle| + // mode (we can change this later). The reason is that when laying out a + // column of middle-trimmed lines of text (such as a list of paths), the + // desired appearance is to be fully justified and the elipses should more or + // less line up; eliminating space would make the text look more ragged. + const size_t half_length = length / 2; + const size_t prefix_length = + FindValidBoundaryBefore(text_, length - half_length, elide_whitespace_); + const size_t suffix_start = FindValidBoundaryAfter( + text_, text_.length() - half_length, elide_whitespace_); + return text_.substr(0, prefix_length) + ellipsis_text + + text_.substr(suffix_start); +} + +std::u16string ElideFilename(const base::FilePath& filename, + const FontList& font_list, + float available_pixel_width) { +#if defined(OS_WIN) + std::u16string filename_utf16 = WideToUTF16(filename.value()); + std::u16string extension = WideToUTF16(filename.Extension()); + std::u16string rootname = + WideToUTF16(filename.BaseName().RemoveExtension().value()); +#elif defined(OS_POSIX) || defined(OS_FUCHSIA) + std::u16string filename_utf16 = + WideToUTF16(base::SysNativeMBToWide(filename.value())); + std::u16string extension = + WideToUTF16(base::SysNativeMBToWide(filename.Extension())); + std::u16string rootname = WideToUTF16( + base::SysNativeMBToWide(filename.BaseName().RemoveExtension().value())); +#endif + + const float full_width = GetStringWidthF(filename_utf16, font_list); + if (full_width <= available_pixel_width) + return base::i18n::GetDisplayStringInLTRDirectionality(filename_utf16); + + if (rootname.empty() || extension.empty()) { + const std::u16string elided_name = + ElideText(filename_utf16, font_list, available_pixel_width, ELIDE_TAIL); + return base::i18n::GetDisplayStringInLTRDirectionality(elided_name); + } + + const float ext_width = GetStringWidthF(extension, font_list); + const float root_width = GetStringWidthF(rootname, font_list); + + // We may have trimmed the path. + if (root_width + ext_width <= available_pixel_width) { + const std::u16string elided_name = rootname + extension; + return base::i18n::GetDisplayStringInLTRDirectionality(elided_name); + } + + if (ext_width >= available_pixel_width) { + const std::u16string elided_name = ElideText( + rootname + extension, font_list, available_pixel_width, ELIDE_MIDDLE); + return base::i18n::GetDisplayStringInLTRDirectionality(elided_name); + } + + float available_root_width = available_pixel_width - ext_width; + std::u16string elided_name = + ElideText(rootname, font_list, available_root_width, ELIDE_TAIL); + elided_name += extension; + return base::i18n::GetDisplayStringInLTRDirectionality(elided_name); +} + +std::u16string ElideText(const std::u16string& text, + const FontList& font_list, + float available_pixel_width, + ElideBehavior behavior) { +#if !defined(OS_IOS) + DCHECK_NE(behavior, FADE_TAIL); + std::unique_ptr render_text = RenderText::CreateRenderText(); + render_text->SetCursorEnabled(false); + render_text->SetFontList(font_list); + available_pixel_width = std::ceil(available_pixel_width); + render_text->SetDisplayRect( + gfx::ToEnclosingRect(gfx::RectF(gfx::SizeF(available_pixel_width, 1)))); + render_text->SetElideBehavior(behavior); + render_text->SetText(text); + return render_text->GetDisplayText(); +#else + DCHECK_NE(behavior, FADE_TAIL); + if (text.empty() || behavior == FADE_TAIL || behavior == NO_ELIDE || + GetStringWidthF(text, font_list) <= available_pixel_width) { + return text; + } + if (behavior == ELIDE_EMAIL) + return ElideEmail(text, font_list, available_pixel_width); + + const bool elide_in_middle = (behavior == ELIDE_MIDDLE); + const bool elide_at_beginning = (behavior == ELIDE_HEAD); + const bool insert_ellipsis = (behavior != TRUNCATE); + const std::u16string ellipsis = std::u16string(kEllipsisUTF16); + StringSlicer slicer(text, ellipsis, elide_in_middle, elide_at_beginning); + + if (insert_ellipsis && + GetStringWidthF(ellipsis, font_list) > available_pixel_width) + return std::u16string(); + + // Use binary search to compute the elided text. + size_t lo = 0; + size_t hi = text.length() - 1; + size_t guess; + std::u16string cut; + for (guess = (lo + hi) / 2; lo <= hi; guess = (lo + hi) / 2) { + // We check the width of the whole desired string at once to ensure we + // handle kerning/ligatures/etc. correctly. + // TODO(skanuj) : Handle directionality of ellipsis based on adjacent + // characters. See crbug.com/327963. + cut = slicer.CutString(guess, insert_ellipsis); + const float guess_width = GetStringWidthF(cut, font_list); + if (guess_width == available_pixel_width) + break; + if (guess_width > available_pixel_width) { + hi = guess - 1; + // Move back on the loop terminating condition when the guess is too wide. + if (hi < lo) + lo = hi; + } else { + lo = guess + 1; + } + } + + return cut; +#endif +} + +bool ElideString(const std::u16string& input, + size_t max_len, + std::u16string* output) { + if (input.length() <= max_len) { + output->assign(input); + return false; + } + + switch (max_len) { + case 0: + output->clear(); + break; + case 1: + output->assign(input.substr(0, 1)); + break; + case 2: + output->assign(input.substr(0, 2)); + break; + case 3: + output->assign(input.substr(0, 1) + u"." + + input.substr(input.length() - 1)); + break; + case 4: + output->assign(input.substr(0, 1) + u".." + + input.substr(input.length() - 1)); + break; + default: { + size_t rstr_len = (max_len - 3) / 2; + size_t lstr_len = rstr_len + ((max_len - 3) % 2); + output->assign(input.substr(0, lstr_len) + u"..." + + input.substr(input.length() - rstr_len)); + break; + } + } + + return true; +} + +namespace { + +// Internal class used to track progress of a rectangular string elide +// operation. Exists so the top-level ElideRectangleString() function +// can be broken into smaller methods sharing this state. +class RectangleString { + public: + RectangleString(size_t max_rows, + size_t max_cols, + bool strict, + std::u16string* output) + : max_rows_(max_rows), + max_cols_(max_cols), + current_row_(0), + current_col_(0), + strict_(strict), + suppressed_(false), + output_(output) {} + + RectangleString(const RectangleString&) = delete; + RectangleString& operator=(const RectangleString&) = delete; + + // Perform deferred initializations following creation. Must be called + // before any input can be added via AddString(). + void Init() { output_->clear(); } + + // Add an input string, reformatting to fit the desired dimensions. + // AddString() may be called multiple times to concatenate together + // multiple strings into the region (the current caller doesn't do + // this, however). + void AddString(const std::u16string& input); + + // Perform any deferred output processing. Must be called after the + // last AddString() call has occurred. + bool Finalize(); + + private: + // Add a line to the rectangular region at the current position, + // either by itself or by breaking it into words. + void AddLine(const std::u16string& line); + + // Add a word to the rectangular region at the current position, + // either by itself or by breaking it into characters. + void AddWord(const std::u16string& word); + + // Add text to the output string if the rectangular boundaries + // have not been exceeded, advancing the current position. + void Append(const std::u16string& string); + + // Set the current position to the beginning of the next line. If + // |output| is true, add a newline to the output string if the rectangular + // boundaries have not been exceeded. If |output| is false, we assume + // some other mechanism will (likely) do similar breaking after the fact. + void NewLine(bool output); + + // Maximum number of rows allowed in the output string. + size_t max_rows_; + + // Maximum number of characters allowed in the output string. + size_t max_cols_; + + // Current row position, always incremented and may exceed max_rows_ + // when the input can not fit in the region. We stop appending to + // the output string, however, when this condition occurs. In the + // future, we may want to expose this value to allow the caller to + // determine how many rows would actually be required to hold the + // formatted string. + size_t current_row_; + + // Current character position, should never exceed max_cols_. + size_t current_col_; + + // True when we do whitespace to newline conversions ourselves. + bool strict_; + + // True when some of the input has been truncated. + bool suppressed_; + + // String onto which the output is accumulated. + std::u16string* output_; +}; + +void RectangleString::AddString(const std::u16string& input) { + base::i18n::BreakIterator lines(input, + base::i18n::BreakIterator::BREAK_NEWLINE); + if (lines.Init()) { + while (lines.Advance()) + AddLine(lines.GetString()); + } else { + NOTREACHED() << "BreakIterator (lines) init failed"; + } +} + +bool RectangleString::Finalize() { + if (suppressed_) { + output_->append(u"..."); + return true; + } + return false; +} + +void RectangleString::AddLine(const std::u16string& line) { + if (line.length() < max_cols_) { + Append(line); + } else { + base::i18n::BreakIterator words(line, + base::i18n::BreakIterator::BREAK_SPACE); + if (words.Init()) { + while (words.Advance()) + AddWord(words.GetString()); + } else { + NOTREACHED() << "BreakIterator (words) init failed"; + } + } + // Account for naturally-occuring newlines. + ++current_row_; + current_col_ = 0; +} + +void RectangleString::AddWord(const std::u16string& word) { + if (word.length() < max_cols_) { + // Word can be made to fit, no need to fragment it. + if (current_col_ + word.length() >= max_cols_) + NewLine(strict_); + Append(word); + } else { + // Word is so big that it must be fragmented. + size_t array_start = 0; + int char_start = 0; + base::i18n::UTF16CharIterator chars(word); + for (; !chars.end(); chars.Advance()) { + // When boundary is hit, add as much as will fit on this line. + if (current_col_ + (chars.char_offset() - char_start) >= max_cols_) { + Append(word.substr(array_start, chars.array_pos() - array_start)); + NewLine(true); + array_start = chars.array_pos(); + char_start = chars.char_offset(); + } + } + // Add the last remaining fragment, if any. + if (array_start != chars.array_pos()) + Append(word.substr(array_start, chars.array_pos() - array_start)); + } +} + +void RectangleString::Append(const std::u16string& string) { + if (current_row_ < max_rows_) + output_->append(string); + else + suppressed_ = true; + current_col_ += string.length(); +} + +void RectangleString::NewLine(bool output) { + if (current_row_ < max_rows_) { + if (output) + output_->append(u"\n"); + } else { + suppressed_ = true; + } + ++current_row_; + current_col_ = 0; +} + +// Internal class used to track progress of a rectangular text elide +// operation. Exists so the top-level ElideRectangleText() function +// can be broken into smaller methods sharing this state. +class RectangleText { + public: + RectangleText(const FontList& font_list, + float available_pixel_width, + int available_pixel_height, + WordWrapBehavior wrap_behavior, + std::vector* lines) + : font_list_(font_list), + line_height_(font_list.GetHeight()), + available_pixel_width_(available_pixel_width), + available_pixel_height_(available_pixel_height), + wrap_behavior_(wrap_behavior), + lines_(lines) {} + + RectangleText(const RectangleText&) = delete; + RectangleText& operator=(const RectangleText&) = delete; + + // Perform deferred initializations following creation. Must be called + // before any input can be added via AddString(). + void Init() { lines_->clear(); } + + // Add an input string, reformatting to fit the desired dimensions. + // AddString() may be called multiple times to concatenate together + // multiple strings into the region (the current caller doesn't do + // this, however). + void AddString(const std::u16string& input); + + // Perform any deferred output processing. Must be called after the last + // AddString() call has occurred. Returns a combination of + // |ReformattingResultFlags| indicating whether the given width or height was + // insufficient, leading to elision or truncation. + int Finalize(); + + private: + // Add a line to the rectangular region at the current position, + // either by itself or by breaking it into words. + void AddLine(const std::u16string& line); + + // Wrap the specified word across multiple lines. + int WrapWord(const std::u16string& word); + + // Add a long word - wrapping, eliding or truncating per the wrap behavior. + int AddWordOverflow(const std::u16string& word); + + // Add a word to the rectangular region at the current position. + int AddWord(const std::u16string& word); + + // Append the specified |text| to the current output line, incrementing the + // running width by the specified amount. This is an optimization over + // |AddToCurrentLine()| when |text_width| is already known. + void AddToCurrentLineWithWidth(const std::u16string& text, float text_width); + + // Append the specified |text| to the current output line. + void AddToCurrentLine(const std::u16string& text); + + // Set the current position to the beginning of the next line. + bool NewLine(); + + // The font list used for measuring text width. + const FontList& font_list_; + + // The height of each line of text. + const int line_height_; + + // The number of pixels of available width in the rectangle. + const float available_pixel_width_; + + // The number of pixels of available height in the rectangle. + const int available_pixel_height_; + + // The wrap behavior for words that are too long to fit on a single line. + const WordWrapBehavior wrap_behavior_; + + // The current running width. + float current_width_ = 0; + + // The current running height. + int current_height_ = 0; + + // The current line of text. + std::u16string current_line_; + + // Indicates whether the last line ended with \n. + bool last_line_ended_in_lf_ = false; + + // The output vector of lines. + std::vector* lines_; + + // Indicates whether a word was so long that it had to be truncated or elided + // to fit the available width. + bool insufficient_width_ = false; + + // Indicates whether there were too many lines for the available height. + bool insufficient_height_ = false; + + // Indicates whether the very first word was truncated. + bool first_word_truncated_ = false; +}; + +void RectangleText::AddString(const std::u16string& input) { + base::i18n::BreakIterator lines(input, + base::i18n::BreakIterator::BREAK_NEWLINE); + if (lines.Init()) { + while (!insufficient_height_ && lines.Advance()) { + std::u16string line = lines.GetString(); + // The BREAK_NEWLINE iterator will keep the trailing newline character, + // except in the case of the last line, which may not have one. Remove + // the newline character, if it exists. + last_line_ended_in_lf_ = !line.empty() && line.back() == '\n'; + if (last_line_ended_in_lf_) + line.resize(line.length() - 1); + AddLine(line); + } + } else { + NOTREACHED() << "BreakIterator (lines) init failed"; + } +} + +int RectangleText::Finalize() { + // Remove trailing whitespace from the last line or remove the last line + // completely, if it's just whitespace. + if (!insufficient_height_ && !lines_->empty()) { + base::TrimWhitespace(lines_->back(), base::TRIM_TRAILING, &lines_->back()); + if (lines_->back().empty() && !last_line_ended_in_lf_) + lines_->pop_back(); + } + if (last_line_ended_in_lf_) + lines_->push_back(std::u16string()); + return (insufficient_width_ ? INSUFFICIENT_SPACE_HORIZONTAL : 0) | + (insufficient_height_ ? INSUFFICIENT_SPACE_VERTICAL : 0) | + (first_word_truncated_ ? INSUFFICIENT_SPACE_FOR_FIRST_WORD : 0); +} + +void RectangleText::AddLine(const std::u16string& line) { + const float line_width = GetStringWidthF(line, font_list_); + if (line_width <= available_pixel_width_) { + AddToCurrentLineWithWidth(line, line_width); + } else { + // Iterate over positions that are valid to break the line at. In general, + // these are word boundaries but after any punctuation following the word. + base::i18n::BreakIterator words(line, + base::i18n::BreakIterator::BREAK_LINE); + if (words.Init()) { + while (words.Advance()) { + const bool truncate = !current_line_.empty(); + const std::u16string& word = words.GetString(); + const int lines_added = AddWord(word); + if (lines_added) { + if (truncate) { + // Trim trailing whitespace from the line that was added. + const size_t new_line = lines_->size() - lines_added; + base::TrimWhitespace(lines_->at(new_line), base::TRIM_TRAILING, + &lines_->at(new_line)); + } + if (base::ContainsOnlyChars(word, base::kWhitespaceUTF16)) { + // Skip the first space if the previous line was carried over. + current_width_ = 0; + current_line_.clear(); + } + } + } + } else { + NOTREACHED() << "BreakIterator (words) init failed"; + } + } + // Account for naturally-occuring newlines. + NewLine(); +} + +int RectangleText::WrapWord(const std::u16string& word) { + // Word is so wide that it must be fragmented. + std::u16string text = word; + int lines_added = 0; + bool first_fragment = true; + while (!insufficient_height_ && !text.empty()) { + std::u16string fragment = + ElideText(text, font_list_, available_pixel_width_, TRUNCATE); + // At least one character has to be added at every line, even if the + // available space is too small. + if (fragment.empty()) + fragment = text.substr(0, 1); + if (!first_fragment && NewLine()) + lines_added++; + AddToCurrentLine(fragment); + text = text.substr(fragment.length()); + first_fragment = false; + } + return lines_added; +} + +int RectangleText::AddWordOverflow(const std::u16string& word) { + int lines_added = 0; + + // Unless this is the very first word, put it on a new line. + if (!current_line_.empty()) { + if (!NewLine()) + return 0; + lines_added++; + } else if (lines_->empty()) { + first_word_truncated_ = true; + } + + if (wrap_behavior_ == IGNORE_LONG_WORDS) { + current_line_ = word; + current_width_ = available_pixel_width_; + } else if (wrap_behavior_ == WRAP_LONG_WORDS) { + lines_added += WrapWord(word); + } else { + const ElideBehavior elide_behavior = + (wrap_behavior_ == ELIDE_LONG_WORDS ? ELIDE_TAIL : TRUNCATE); + const std::u16string elided_word = + ElideText(word, font_list_, available_pixel_width_, elide_behavior); + AddToCurrentLine(elided_word); + insufficient_width_ = true; + } + + return lines_added; +} + +int RectangleText::AddWord(const std::u16string& word) { + int lines_added = 0; + std::u16string trimmed; + base::TrimWhitespace(word, base::TRIM_TRAILING, &trimmed); + const float trimmed_width = GetStringWidthF(trimmed, font_list_); + if (trimmed_width <= available_pixel_width_) { + // Word can be made to fit, no need to fragment it. + if ((current_width_ + trimmed_width > available_pixel_width_) && NewLine()) + lines_added++; + // Append the non-trimmed word, in case more words are added after. + AddToCurrentLine(word); + } else { + lines_added = AddWordOverflow(wrap_behavior_ == IGNORE_LONG_WORDS ? + trimmed : word); + } + return lines_added; +} + +void RectangleText::AddToCurrentLine(const std::u16string& text) { + AddToCurrentLineWithWidth(text, GetStringWidthF(text, font_list_)); +} + +void RectangleText::AddToCurrentLineWithWidth(const std::u16string& text, + float text_width) { + if (current_height_ >= available_pixel_height_) { + insufficient_height_ = true; + return; + } + current_line_.append(text); + current_width_ += text_width; +} + +bool RectangleText::NewLine() { + bool line_added = false; + if (current_height_ < available_pixel_height_) { + lines_->push_back(current_line_); + current_line_.clear(); + line_added = true; + } else { + insufficient_height_ = true; + } + current_height_ += line_height_; + current_width_ = 0; + return line_added; +} + +} // namespace + +bool ElideRectangleString(const std::u16string& input, + size_t max_rows, + size_t max_cols, + bool strict, + std::u16string* output) { + RectangleString rect(max_rows, max_cols, strict, output); + rect.Init(); + rect.AddString(input); + return rect.Finalize(); +} + +int ElideRectangleText(const std::u16string& input, + const FontList& font_list, + float available_pixel_width, + int available_pixel_height, + WordWrapBehavior wrap_behavior, + std::vector* lines) { + RectangleText rect(font_list, available_pixel_width, available_pixel_height, + wrap_behavior, lines); + rect.Init(); + rect.AddString(input); + return rect.Finalize(); +} + +std::u16string TruncateString(const std::u16string& string, + size_t length, + BreakType break_type) { + const bool word_break = break_type == WORD_BREAK; + DCHECK(word_break || (break_type == CHARACTER_BREAK)); + + if (string.size() <= length) + return string; // No need to elide. + + if (length == 0) + return std::u16string(); // No room for anything, even an ellipsis. + + // Added to the end of strings that are too big. + static const char16_t kElideString[] = {0x2026, 0}; + + if (length == 1) + return kElideString; // Only room for an ellipsis. + + int32_t index = static_cast(length - 1); + if (word_break) { + // Use a word iterator to find the first boundary. + UErrorCode status = U_ZERO_ERROR; + std::unique_ptr bi( + icu::RuleBasedBreakIterator::createWordInstance( + icu::Locale::getDefault(), status)); + if (U_FAILURE(status)) + return string.substr(0, length - 1) + kElideString; + icu::UnicodeString bi_text(string.c_str()); + bi->setText(bi_text); + index = bi->preceding(static_cast(length)); + if (index == icu::BreakIterator::DONE || index == 0) { + // We either found no valid word break at all, or one right at the + // beginning of the string. Go back to the end; we'll have to break in the + // middle of a word. + index = static_cast(length - 1); + } + } + + // By this point, |index| should point at the character that's a candidate for + // replacing with an ellipsis. Use a character iterator to check previous + // characters and stop as soon as we find a previous non-whitespace character. + icu::StringCharacterIterator char_iterator(string.c_str()); + char_iterator.setIndex(index); + while (char_iterator.hasPrevious()) { + char_iterator.previous(); + if (!(u_isspace(char_iterator.current()) || + u_charType(char_iterator.current()) == U_CONTROL_CHAR || + u_charType(char_iterator.current()) == U_NON_SPACING_MARK)) { + // Not a whitespace character. Truncate to everything up to and including + // this character, and append an ellipsis. + char_iterator.next(); + return string.substr(0, char_iterator.getIndex()) + kElideString; + } + } + + // Couldn't find a previous non-whitespace character. If we were originally + // word-breaking, and index != length - 1, then the string is |index| + // whitespace characters followed by a word we're trying to break in the midst + // of, and we can fit at least one character of the word in the elided string. + // Do that rather than just returning an ellipsis. + if (word_break && (index != static_cast(length - 1))) + return string.substr(0, length - 1) + kElideString; + + // Trying to break after only whitespace, elide all of it. + return kElideString; +} + +} // namespace gfx diff --git a/text_elider.h b/text_elider.h new file mode 100644 index 000000000000..200ad046220a --- /dev/null +++ b/text_elider.h @@ -0,0 +1,162 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// This file defines utility functions for eliding and formatting UI text. + +#ifndef UI_GFX_TEXT_ELIDER_H_ +#define UI_GFX_TEXT_ELIDER_H_ + +#include + +#include +#include + +#include "base/macros.h" +#include "build/build_config.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/gfx/gfx_export.h" +#include "ui/gfx/text_constants.h" + +namespace base { +class FilePath; +} + +namespace gfx { +class FontList; + +GFX_EXPORT extern const char kEllipsis[]; +GFX_EXPORT extern const char16_t kEllipsisUTF16[]; +GFX_EXPORT extern const char16_t kForwardSlash; + +// Helper class to split + elide text, while respecting UTF-16 surrogate pairs +// and combining character sequences. +class GFX_EXPORT StringSlicer { + public: + // Warning: Retains a reference to |text| and |ellipsis|. They must have a + // longer lifetime than the StringSlicer. + // + // Note: if |elide_whitespace| is absl::nullopt, the default whitespace + // elision strategy for the type of elision being done will be chosen. + // Defaults are to trim for beginning and end elision; no trimming for middle + // elision. + StringSlicer(const std::u16string& text, + const std::u16string& ellipsis, + bool elide_in_middle, + bool elide_at_beginning, + absl::optional elide_whitespace = absl::nullopt); + + StringSlicer(const StringSlicer&) = delete; + StringSlicer& operator=(const StringSlicer&) = delete; + + // Cuts |text_| to be at most |length| UTF-16 code units long. If + // |elide_in_middle_| is true, the middle of the string is removed to leave + // equal-length pieces from the beginning and end of the string; otherwise, + // the end of the string is removed and only the beginning remains. If + // |insert_ellipsis| is true, then an ellipsis character will be inserted at + // the cut point (note that the ellipsis will does not count towards the + // |length| limit). + // Note: Characters may still be omitted even if |length| is the full string + // length, if surrogate pairs fall on the split boundary. + std::u16string CutString(size_t length, bool insert_ellipsis) const; + + private: + // The text to be sliced. + const std::u16string& text_; + + // Ellipsis string to use. + const std::u16string& ellipsis_; + + // If true, the middle of the string will be elided. + const bool elide_in_middle_; + + // If true, the beginning of the string will be elided. + const bool elide_at_beginning_; + + // How whitespace around an elision point is handled. + const bool elide_whitespace_; +}; + +// Elides |text| to fit the |available_pixel_width| with the specified behavior. +GFX_EXPORT std::u16string ElideText(const std::u16string& text, + const gfx::FontList& font_list, + float available_pixel_width, + ElideBehavior elide_behavior); + +// Elide a filename to fit a given pixel width, with an emphasis on not hiding +// the extension unless we have to. If filename contains a path, the path will +// be removed if filename doesn't fit into available_pixel_width. The elided +// filename is forced to have LTR directionality, which means that in RTL UI +// the elided filename is wrapped with LRE (Left-To-Right Embedding) mark and +// PDF (Pop Directional Formatting) mark. +GFX_EXPORT std::u16string ElideFilename(const base::FilePath& filename, + const gfx::FontList& font_list, + float available_pixel_width); + +// Functions to elide strings when the font information is unknown. As opposed +// to the above functions, ElideString() and ElideRectangleString() operate in +// terms of character units, not pixels. + +// If the size of |input| is more than |max_len|, this function returns +// true and |input| is shortened into |output| by removing chars in the +// middle (they are replaced with up to 3 dots, as size permits). +// Ex: ElideString(u"Hello", 10, &str) puts Hello in str and +// returns false. ElideString(u"Hello my name is Tom", 10, &str) +// puts "Hell...Tom" in str and returns true. +// TODO(tsepez): Doesn't handle UTF-16 surrogate pairs properly. +// TODO(tsepez): Doesn't handle bidi properly. +GFX_EXPORT bool ElideString(const std::u16string& input, + size_t max_len, + std::u16string* output); + +// Reformat |input| into |output| so that it fits into a |max_rows| by +// |max_cols| rectangle of characters. Input newlines are respected, but +// lines that are too long are broken into pieces. If |strict| is true, +// we break first at naturally occurring whitespace boundaries, otherwise +// we assume some other mechanism will do this in approximately the same +// spot after the fact. If the word itself is too long, we always break +// intra-word (respecting UTF-16 surrogate pairs) as necessary. Truncation +// (indicated by an added 3 dots) occurs if the result is still too long. +// Returns true if the input had to be truncated (and not just reformatted). +GFX_EXPORT bool ElideRectangleString(const std::u16string& input, + size_t max_rows, + size_t max_cols, + bool strict, + std::u16string* output); + +// Indicates whether the |available_pixel_width| by |available_pixel_height| +// rectangle passed to |ElideRectangleText()| had insufficient space to +// accommodate the given |text|, leading to elision or truncation. +enum ReformattingResultFlags { + INSUFFICIENT_SPACE_HORIZONTAL = 1 << 0, + INSUFFICIENT_SPACE_VERTICAL = 1 << 1, + INSUFFICIENT_SPACE_FOR_FIRST_WORD = 1 << 2, +}; + +// Reformats |text| into output vector |lines| so that the resulting text fits +// into an |available_pixel_width| by |available_pixel_height| rectangle with +// the specified |font_list|. Input newlines are respected, but lines that are +// too long are broken into pieces. For words that are too wide to fit on a +// single line, the wrapping behavior can be specified with the |wrap_behavior| +// param. Returns a combination of |ReformattingResultFlags| that indicate +// whether the given rectangle had insufficient space to accommodate |text|, +// leading to elision or truncation (and not just reformatting). +GFX_EXPORT int ElideRectangleText(const std::u16string& text, + const gfx::FontList& font_list, + float available_pixel_width, + int available_pixel_height, + WordWrapBehavior wrap_behavior, + std::vector* lines); + +// Truncates |string| to |length| characters. This breaks the string according +// to the specified |break_type|, which must be either WORD_BREAK or +// CHARACTER_BREAK, and adds the horizontal ellipsis character (unicode +// character 0x2026) to render "...". The supplied string is returned if the +// string has |length| characters or less. +GFX_EXPORT std::u16string TruncateString(const std::u16string& string, + size_t length, + BreakType break_type); + +} // namespace gfx + +#endif // UI_GFX_TEXT_ELIDER_H_ diff --git a/text_elider_unittest.cc b/text_elider_unittest.cc new file mode 100644 index 000000000000..579f73bcb8da --- /dev/null +++ b/text_elider_unittest.cc @@ -0,0 +1,1158 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Unit tests for eliding and formatting utility functions. + +#include "ui/gfx/text_elider.h" + +#include + +#include +#include + +#include "base/cxx17_backports.h" +#include "base/files/file_path.h" +#include "base/i18n/rtl.h" +#include "base/logging.h" +#include "base/run_loop.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/test/task_environment.h" +#include "build/build_config.h" +#include "build/chromeos_buildflags.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/font.h" +#include "ui/gfx/font_list.h" +#include "ui/gfx/font_render_params.h" +#include "ui/gfx/text_utils.h" + +namespace gfx { + +namespace { + +struct FileTestcase { + const base::FilePath::StringType input; + const std::u16string output; + // If this value is specified, we will try to cut the path down to the render + // width of this string; if not specified, output will be used. + const std::u16string using_width_of = std::u16string(); +}; + +struct Testcase { + const std::u16string input; + const std::u16string output; +}; + +} // namespace + +TEST(TextEliderTest, ElideEmail) { + // Test emails and their expected elided forms (from which the available + // widths will be derived). + // For elided forms in which both the username and domain must be elided: + // the result (how many characters are left on each side) can be font + // dependent. To avoid this, the username is prefixed with the characters + // expected to remain in the domain. + Testcase testcases[] = { + {u"g@g.c", u"g@g.c"}, + {u"g@g.c", u"…"}, + {u"ga@co.ca", u"ga@c…a"}, + {u"short@small.com", u"s…@s…"}, + {u"short@small.com", u"s…@small.com"}, + {u"short@longbutlotsofspace.com", u"short@longbutlotsofspace.com"}, + {u"short@longbutnotverymuchspace.com", u"short@long….com"}, + {u"la_short@longbutverytightspace.ca", u"la…@l…a"}, + {u"longusername@gmail.com", u"long…@gmail.com"}, + {u"elidetothemax@justfits.com", u"e…@justfits.com"}, + {u"thatom_somelongemail@thatdoesntfit.com", u"thatom…@tha…om"}, + {u"namefits@butthedomaindoesnt.com", u"namefits@butthedo…snt.com"}, + {u"widthtootight@nospace.com", u"…"}, + {u"nospaceforusername@l", u"…"}, + {u"little@littlespace.com", u"l…@l…"}, + {u"l@llllllllllllllllllllllll.com", u"l@lllll….com"}, + {u"messed\"up@whyanat\"++@notgoogley.com", + u"messed\"up@whyanat\"++@notgoogley.com"}, + {u"messed\"up@whyanat\"++@notgoogley.com", + u"messed\"up@why…@notgoogley.com"}, + {u"noca_messed\"up@whyanat\"++@notgoogley.ca", u"noca…@no…ca"}, + {u"at\"@@@@@@@@@...@@.@.@.@@@\"@madness.com", + u"at\"@@@@@@@@@...@@.@.…@madness.com"}, + // Special case: "m..." takes more than half of the available width; thus + // the domain must elide to "l..." and not "l...l" as it must allow enough + // space for the minimal username elision although its half of the + // available width would normally allow it to elide to "l...l". + {u"mmmmm@llllllllll", u"m…@l…"}, + }; + + const FontList font_list; + for (size_t i = 0; i < base::size(testcases); ++i) { + const std::u16string expected_output = testcases[i].output; + EXPECT_EQ( + expected_output, + ElideText(testcases[i].input, font_list, + GetStringWidthF(expected_output, font_list), ELIDE_EMAIL)); + } +} + +TEST(TextEliderTest, ElideEmailMoreSpace) { + const int test_widths_extra_spaces[] = { + 10, + 1000, + 100'000, + }; + const char16_t* const test_emails[] = { + u"a@c", + u"test@email.com", + u"short@verysuperdupperlongdomain.com", + u"supermegalongusername@withasuperlonnnggggdomain.gouv.qc.ca", + }; + + const FontList font_list; + for (const auto* test_email : test_emails) { + const int mimimum_width = GetStringWidth(test_email, font_list); + for (int extra_space : test_widths_extra_spaces) { + // Extra space is available: the email should not be elided. + EXPECT_EQ(test_email, + ElideText(test_email, font_list, mimimum_width + extra_space, + ELIDE_EMAIL)); + } + } +} + +TEST(TextEliderTest, TestFilenameEliding) { + const base::FilePath::StringType kPathSeparator = + base::FilePath::StringType().append(1, base::FilePath::kSeparators[0]); + + FileTestcase testcases[] = { + {FILE_PATH_LITERAL(""), u""}, + {FILE_PATH_LITERAL("."), u"."}, + {FILE_PATH_LITERAL("filename.exe"), u"filename.exe"}, + {FILE_PATH_LITERAL(".longext"), u".longext"}, + {FILE_PATH_LITERAL("pie"), u"pie"}, + {FILE_PATH_LITERAL("c:") + kPathSeparator + FILE_PATH_LITERAL("path") + + kPathSeparator + FILE_PATH_LITERAL("filename.pie"), + u"filename.pie"}, + {FILE_PATH_LITERAL("c:") + kPathSeparator + FILE_PATH_LITERAL("path") + + kPathSeparator + FILE_PATH_LITERAL("longfilename.pie"), + u"long….pie"}, + {FILE_PATH_LITERAL("http://path.com/filename.pie"), u"filename.pie"}, + {FILE_PATH_LITERAL("http://path.com/longfilename.pie"), u"long….pie"}, + {FILE_PATH_LITERAL("piesmashingtacularpants"), u"pie…"}, + {FILE_PATH_LITERAL(".piesmashingtacularpants"), u".pie…"}, + {FILE_PATH_LITERAL("cheese."), u"cheese."}, + {FILE_PATH_LITERAL("file name.longext"), u"file….longext"}, + {FILE_PATH_LITERAL("fil ename.longext"), u"fil….longext", + u"fil ….longext"}, + {FILE_PATH_LITERAL("filename.longext"), u"file….longext"}, + {FILE_PATH_LITERAL("filename.middleext.longext"), + u"filename.mid….longext"}, + {FILE_PATH_LITERAL("filename.superduperextremelylongext"), + u"filename.sup…emelylongext"}, + {FILE_PATH_LITERAL("filenamereallylongtext.superdeduperextremelylongext"), + u"filenamereall…emelylongext"}, + {FILE_PATH_LITERAL( + "file.name.really.long.text.superduperextremelylongext"), + u"file.name.re…emelylongext"}}; + + static const FontList font_list; + for (size_t i = 0; i < base::size(testcases); ++i) { + base::FilePath filepath(testcases[i].input); + std::u16string expected = testcases[i].output; + std::u16string using_width_of = testcases[i].using_width_of.empty() + ? testcases[i].output + : testcases[i].using_width_of; + expected = base::i18n::GetDisplayStringInLTRDirectionality(expected); + EXPECT_EQ(expected, + ElideFilename(filepath, font_list, + GetStringWidthF(using_width_of, font_list))); + } +} + +TEST(TextEliderTest, ElideTextTruncate) { + const FontList font_list; + const float kTestWidth = GetStringWidthF(u"Test", font_list); + struct TestData { + const char16_t* input; + float width; + const char16_t* output; + } cases[] = { + {u"", 0, u""}, + {u"Test", 0, u""}, + {u"", kTestWidth, u""}, + {u"Tes", kTestWidth, u"Tes"}, + {u"Test", kTestWidth, u"Test"}, + {u"Tests", kTestWidth, u"Test"}, + }; + + for (size_t i = 0; i < base::size(cases); ++i) { + std::u16string result = + ElideText(cases[i].input, font_list, cases[i].width, TRUNCATE); + EXPECT_EQ(cases[i].output, result); + } +} + +TEST(TextEliderTest, ElideTextEllipsis) { + const FontList font_list; + const float kTestWidth = GetStringWidthF(u"Test", font_list); + const float kEllipsisWidth = GetStringWidthF(u"…", font_list); + struct TestData { + const char16_t* input; + float width; + const char16_t* output; + } cases[] = { + {u"", 0, u""}, + {u"Test", 0, u""}, + {u"Test", kEllipsisWidth, u"…"}, + {u"", kTestWidth, u""}, + {u"Tes", kTestWidth, u"Tes"}, + {u"Test", kTestWidth, u"Test"}, + }; + + for (size_t i = 0; i < base::size(cases); ++i) { + std::u16string result = + ElideText(cases[i].input, font_list, cases[i].width, ELIDE_TAIL); + EXPECT_EQ(cases[i].output, result); + } +} + +TEST(TextEliderTest, ElideTextEllipsisFront) { + const FontList font_list; + const float kTestWidth = GetStringWidthF(u"Test", font_list); + const float kEllipsisWidth = GetStringWidthF(u"…", font_list); + const float kEllipsis23Width = GetStringWidthF(u"…23", font_list); + struct TestData { + const char16_t* input; + float width; + const std::u16string output; + } cases[] = { + {u"", 0, std::u16string()}, + {u"Test", 0, std::u16string()}, + {u"Test", kEllipsisWidth, u"…"}, + {u"", kTestWidth, std::u16string()}, + {u"Tes", kTestWidth, u"Tes"}, + {u"Test", kTestWidth, u"Test"}, + {u"Test123", kEllipsis23Width, u"…23"}, + }; + + for (size_t i = 0; i < base::size(cases); ++i) { + std::u16string result = + ElideText(cases[i].input, font_list, cases[i].width, ELIDE_HEAD); + EXPECT_EQ(cases[i].output, result); + } +} + +// Checks that all occurrences of |first_char| are followed by |second_char| and +// all occurrences of |second_char| are preceded by |first_char| in |text|. Can +// be used to test surrogate pairs or two-character combining sequences. +static void CheckCodeUnitPairs(const std::u16string& text, + char16_t first_char, + char16_t second_char) { + for (size_t index = 0; index < text.length(); ++index) { + EXPECT_NE(second_char, text[index]); + if (text[index] == first_char) { + ASSERT_LT(++index, text.length()); + EXPECT_EQ(second_char, text[index]); + } + } +} + +// Test that both both UTF-16 surrogate pairs and combining character sequences +// do not get split by ElideText. +TEST(TextEliderTest, ElideTextAtomicSequences) { +#if defined(OS_WIN) + // Needed to bypass DCHECK in GetFallbackFont. + base::test::SingleThreadTaskEnvironment task_environment( + base::test::SingleThreadTaskEnvironment::MainThreadType::UI); +#endif + const FontList font_list; + std::vector pairs; + // The below is 'MUSICAL SYMBOL G CLEF' (U+1D11E), which is represented in + // UTF-16 as two code units forming a surrogate pair: 0xD834 0xDD1E. + pairs.push_back(u"\U0001d11e"); + // The below is a Devanagari two-character combining sequence U+0921 U+093F. + // The sequence forms a single display character and should not be separated. + pairs.push_back(u"\u0921\u093f"); + + for (const std::u16string& pair : pairs) { + char16_t first_char = pair[0]; + char16_t second_char = pair[1]; + std::u16string test_string = pair + u"x" + pair; + SCOPED_TRACE(test_string); + const float test_string_width = GetStringWidthF(test_string, font_list); + std::u16string result; + + // Elide |text_string| to all possible widths and check that no instance of + // |pair| was split in two. + for (float width = 0; width <= test_string_width; width++) { + result = ElideText(test_string, font_list, width, TRUNCATE); + CheckCodeUnitPairs(result, first_char, second_char); + + result = ElideText(test_string, font_list, width, ELIDE_TAIL); + CheckCodeUnitPairs(result, first_char, second_char); + + result = ElideText(test_string, font_list, width, ELIDE_MIDDLE); + CheckCodeUnitPairs(result, first_char, second_char); + + result = ElideText(test_string, font_list, width, ELIDE_HEAD); + CheckCodeUnitPairs(result, first_char, second_char); + } + } +} + +TEST(TextEliderTest, ElideTextLongStrings) { + std::u16string data_scheme(u"data:text/plain,"); + size_t data_scheme_length = data_scheme.length(); + + std::u16string ten_a(10, 'a'); + std::u16string hundred_a(100, 'a'); + std::u16string thousand_a(1000, 'a'); + std::u16string ten_thousand_a(10'000, 'a'); + std::u16string hundred_thousand_a(100'000, 'a'); + std::u16string million_a(1'000'000, 'a'); + + // TODO(gbillock): Improve these tests by adding more string diversity and + // doing string compares instead of length compares. See bug 338836. + + size_t number_of_as = 156; + std::u16string long_string_end(data_scheme + + std::u16string(number_of_as, 'a') + u"…"); + Testcase testcases_end[] = { + {data_scheme + ten_a, data_scheme + ten_a}, + {data_scheme + hundred_a, data_scheme + hundred_a}, + {data_scheme + thousand_a, long_string_end}, + {data_scheme + ten_thousand_a, long_string_end}, + {data_scheme + hundred_thousand_a, long_string_end}, + {data_scheme + million_a, long_string_end}, + }; + + const FontList font_list; + float ellipsis_width = GetStringWidthF(u"…", font_list); + for (size_t i = 0; i < base::size(testcases_end); ++i) { + // Compare sizes rather than actual contents because if the test fails, + // output is rather long. + EXPECT_EQ(testcases_end[i].output.size(), + ElideText(testcases_end[i].input, font_list, + GetStringWidthF(testcases_end[i].output, font_list), + ELIDE_TAIL).size()); + EXPECT_EQ(u"…", ElideText(testcases_end[i].input, font_list, ellipsis_width, + ELIDE_TAIL)); + } + + size_t number_of_trailing_as = (data_scheme_length + number_of_as) / 2; + std::u16string long_string_middle( + data_scheme + std::u16string(number_of_as - number_of_trailing_as, 'a') + + u"…" + std::u16string(number_of_trailing_as, 'a')); +#if !defined(OS_IOS) + long_string_middle += u"…"; +#endif + + Testcase testcases_middle[] = { + {data_scheme + ten_a, data_scheme + ten_a}, + {data_scheme + hundred_a, data_scheme + hundred_a}, + {data_scheme + thousand_a, long_string_middle}, + {data_scheme + ten_thousand_a, long_string_middle}, + {data_scheme + hundred_thousand_a, long_string_middle}, + {data_scheme + million_a, long_string_middle}, + }; + + for (size_t i = 0; i < base::size(testcases_middle); ++i) { + // Compare sizes rather than actual contents because if the test fails, + // output is rather long. + EXPECT_EQ(testcases_middle[i].output.size(), + ElideText(testcases_middle[i].input, font_list, + GetStringWidthF(testcases_middle[i].output, font_list), + ELIDE_MIDDLE) + .size()); + EXPECT_EQ(u"…", ElideText(testcases_middle[i].input, font_list, + ellipsis_width, ELIDE_MIDDLE)); + } + + std::u16string long_string_beginning(u"…" + + std::u16string(number_of_as, 'a')); +#if !defined(OS_IOS) + long_string_beginning += u"…"; +#endif + + Testcase testcases_beginning[] = { + {data_scheme + ten_a, data_scheme + ten_a}, + {data_scheme + hundred_a, data_scheme + hundred_a}, + {data_scheme + thousand_a, long_string_beginning}, + {data_scheme + ten_thousand_a, long_string_beginning}, + {data_scheme + hundred_thousand_a, long_string_beginning}, + {data_scheme + million_a, long_string_beginning}, + }; + for (size_t i = 0; i < base::size(testcases_beginning); ++i) { + EXPECT_EQ(testcases_beginning[i].output.size(), + ElideText( + testcases_beginning[i].input, font_list, + GetStringWidthF(testcases_beginning[i].output, font_list), + ELIDE_HEAD).size()); + EXPECT_EQ(u"…", ElideText(testcases_beginning[i].input, font_list, + ellipsis_width, ELIDE_HEAD)); + } +} + +// Detailed tests for StringSlicer. These are faster and test more of the edge +// cases than the above tests which are more end-to-end. + +TEST(TextEliderTest, StringSlicerBasicTest) { + // Must store strings in variables (StringSlicer retains a reference to them). + std::u16string text(u"Hello, world!"); + std::u16string ellipsis(u"…"); + StringSlicer slicer(text, ellipsis, false, false); + + EXPECT_EQ(u"", slicer.CutString(0, false)); + EXPECT_EQ(u"…", slicer.CutString(0, true)); + + EXPECT_EQ(u"Hell", slicer.CutString(4, false)); + EXPECT_EQ(u"Hell…", slicer.CutString(4, true)); + + EXPECT_EQ(text, slicer.CutString(text.length(), false)); + EXPECT_EQ(text + u"…", slicer.CutString(text.length(), true)); + + StringSlicer slicer_begin(text, ellipsis, false, true); + EXPECT_EQ(u"rld!", slicer_begin.CutString(4, false)); + EXPECT_EQ(u"…rld!", slicer_begin.CutString(4, true)); + + StringSlicer slicer_mid(text, ellipsis, true, false); + EXPECT_EQ(u"Held!", slicer_mid.CutString(5, false)); + EXPECT_EQ(u"Hel…d!", slicer_mid.CutString(5, true)); +} + +TEST(TextEliderTest, StringSlicerWhitespace_UseDefault) { + // Must store strings in variables (StringSlicer retains a reference to them). + std::u16string text(u"Hello, world!"); + std::u16string ellipsis(u"…"); + + // Eliding the end of a string should result in whitespace being removed + // before the ellipsis by default. + StringSlicer slicer_end(text, ellipsis, false, false); + EXPECT_EQ(u"Hello,…", slicer_end.CutString(6, true)); + EXPECT_EQ(u"Hello,…", slicer_end.CutString(7, true)); + EXPECT_EQ(u"Hello, w…", slicer_end.CutString(8, true)); + + // Eliding the start of a string should result in whitespace being removed + // after the ellipsis by default. + StringSlicer slicer_begin(text, ellipsis, false, true); + EXPECT_EQ(u"…world!", slicer_begin.CutString(6, true)); + EXPECT_EQ(u"…world!", slicer_begin.CutString(7, true)); + EXPECT_EQ(u"…, world!", slicer_begin.CutString(8, true)); + + // Eliding the middle of a string should *NOT* result in whitespace being + // removed around the ellipsis by default. + StringSlicer slicer_mid(text, ellipsis, true, false); + text = u"Hey world!"; + EXPECT_EQ(u"Hey…ld!", slicer_mid.CutString(6, true)); + EXPECT_EQ(u"Hey …ld!", slicer_mid.CutString(7, true)); + EXPECT_EQ(u"Hey …rld!", slicer_mid.CutString(8, true)); +} + +TEST(TextEliderTest, StringSlicerWhitespace_NoTrim) { + // Must store strings in variables (StringSlicer retains a reference to them). + std::u16string text(u"Hello, world!"); + std::u16string ellipsis(u"…"); + + // Eliding the end of a string should not result in whitespace being removed + // before the ellipsis in no-trim mode. + StringSlicer slicer_end(text, ellipsis, false, false, false); + EXPECT_EQ(u"Hello,…", slicer_end.CutString(6, true)); + EXPECT_EQ(u"Hello, …", slicer_end.CutString(7, true)); + EXPECT_EQ(u"Hello, w…", slicer_end.CutString(8, true)); + + // Eliding the start of a string should not result in whitespace being removed + // after the ellipsis in no-trim mode. + StringSlicer slicer_begin(text, ellipsis, false, true, false); + EXPECT_EQ(u"…world!", slicer_begin.CutString(6, true)); + EXPECT_EQ(u"… world!", slicer_begin.CutString(7, true)); + EXPECT_EQ(u"…, world!", slicer_begin.CutString(8, true)); + + // Eliding the middle of a string should *NOT* result in whitespace being + // removed around the ellipsis in no-trim mode. + StringSlicer slicer_mid(text, ellipsis, true, false, false); + text = u"Hey world!"; + EXPECT_EQ(u"Hey…ld!", slicer_mid.CutString(6, true)); + EXPECT_EQ(u"Hey …ld!", slicer_mid.CutString(7, true)); + EXPECT_EQ(u"Hey …rld!", slicer_mid.CutString(8, true)); +} + +TEST(TextEliderTest, StringSlicerWhitespace_Trim) { + // Must store strings in variables (StringSlicer retains a reference to them). + std::u16string text(u"Hello, world!"); + std::u16string ellipsis(u"…"); + + // Eliding the end of a string should result in whitespace being removed + // before the ellipsis in trim mode. + StringSlicer slicer_end(text, ellipsis, false, false, true); + EXPECT_EQ(u"Hello,…", slicer_end.CutString(6, true)); + EXPECT_EQ(u"Hello,…", slicer_end.CutString(7, true)); + EXPECT_EQ(u"Hello, w…", slicer_end.CutString(8, true)); + + // Eliding the start of a string should result in whitespace being removed + // after the ellipsis in trim mode. + StringSlicer slicer_begin(text, ellipsis, false, true, true); + EXPECT_EQ(u"…world!", slicer_begin.CutString(6, true)); + EXPECT_EQ(u"…world!", slicer_begin.CutString(7, true)); + EXPECT_EQ(u"…, world!", slicer_begin.CutString(8, true)); + + // Eliding the middle of a string *should* result in whitespace being removed + // around the ellipsis in trim mode. + StringSlicer slicer_mid(text, ellipsis, true, false, true); + text = u"Hey world!"; + EXPECT_EQ(u"Hey…ld!", slicer_mid.CutString(6, true)); + EXPECT_EQ(u"Hey…ld!", slicer_mid.CutString(7, true)); + EXPECT_EQ(u"Hey…rld!", slicer_mid.CutString(8, true)); +} + +TEST(TextEliderTest, StringSlicer_ElideMiddle_MultipleWhitespace) { + // Must store strings in variables (StringSlicer retains a reference to them). + std::u16string text(u"Hello world!"); + std::u16string ellipsis(u"…"); + + // Eliding the middle of a string should not result in whitespace being + // removed around the ellipsis in default whitespace mode. + StringSlicer slicer_default(text, ellipsis, true, false); + text = u"Hey U man"; + EXPECT_EQ(u"Hey…man", slicer_default.CutString(6, true)); + EXPECT_EQ(u"Hey …man", slicer_default.CutString(7, true)); + EXPECT_EQ(u"Hey … man", slicer_default.CutString(8, true)); + EXPECT_EQ(u"Hey … man", slicer_default.CutString(9, true)); + EXPECT_EQ(u"Hey … man", slicer_default.CutString(10, true)); + + // Eliding the middle of a string should not result in whitespace being + // removed around the ellipsis in no-trim mode. + StringSlicer slicer_notrim(text, ellipsis, true, false, false); + text = u"Hey U man"; + EXPECT_EQ(u"Hey…man", slicer_notrim.CutString(6, true)); + EXPECT_EQ(u"Hey …man", slicer_notrim.CutString(7, true)); + EXPECT_EQ(u"Hey … man", slicer_notrim.CutString(8, true)); + EXPECT_EQ(u"Hey … man", slicer_notrim.CutString(9, true)); + EXPECT_EQ(u"Hey … man", slicer_notrim.CutString(10, true)); + + // Eliding the middle of a string *should* result in whitespace being removed + // around the ellipsis in trim mode. + StringSlicer slicer_trim(text, ellipsis, true, false, true); + text = u"Hey U man"; + EXPECT_EQ(u"Hey…man", slicer_trim.CutString(6, true)); + EXPECT_EQ(u"Hey…man", slicer_trim.CutString(7, true)); + EXPECT_EQ(u"Hey…man", slicer_trim.CutString(8, true)); + EXPECT_EQ(u"Hey…man", slicer_trim.CutString(9, true)); + EXPECT_EQ(u"Hey…man", slicer_trim.CutString(10, true)); +} + +TEST(TextEliderTest, StringSlicerSurrogate) { + // The below is 'MUSICAL SYMBOL G CLEF' (U+1D11E), which is represented in + // UTF-16 as two code units forming a surrogate pair: 0xD834 0xDD1E. + const std::u16string kSurrogate = u"\U0001d11e"; + ASSERT_EQ(2u, kSurrogate.size()); + ASSERT_EQ(u'\xD834', kSurrogate[0]); + ASSERT_EQ(u'\xDD1E', kSurrogate[1]); + + std::u16string text(u"abc" + kSurrogate + u"xyz"); + std::u16string ellipsis(u"…"); + StringSlicer slicer(text, ellipsis, false, false); + + // Cut surrogate on the right. Should round left and exclude the surrogate. + EXPECT_EQ(u"…", slicer.CutString(0, true)); + EXPECT_EQ(u"abc…", slicer.CutString(4, true)); + EXPECT_EQ(text + u"…", slicer.CutString(text.length(), true)); + + // Cut surrogate on the left. Should round right and exclude the surrogate. + StringSlicer slicer_begin(text, ellipsis, false, true); + EXPECT_EQ(u"…xyz", slicer_begin.CutString(4, true)); + + // Cut surrogate in the middle. Should round right and exclude the surrogate. + std::u16string short_text(u"abc" + kSurrogate); + StringSlicer slicer_mid(short_text, ellipsis, true, false); + EXPECT_EQ(u"a…", slicer_mid.CutString(2, true)); + + // String that starts with a dangling trailing surrogate. + std::u16string dangling_trailing_text = kSurrogate.substr(1); + StringSlicer slicer_dangling_trailing(dangling_trailing_text, ellipsis, false, + false); + EXPECT_EQ(u"…", slicer_dangling_trailing.CutString(0, true)); + EXPECT_EQ(dangling_trailing_text + u"…", + slicer_dangling_trailing.CutString(1, true)); +} + +TEST(TextEliderTest, StringSlicerCombining) { + // The following string contains three combining character sequences (one for + // each category of combining mark): + // LATIN SMALL LETTER E + COMBINING ACUTE ACCENT + COMBINING CEDILLA + // LATIN SMALL LETTER X + COMBINING ENCLOSING KEYCAP + // DEVANAGARI LETTER DDA + DEVANAGARI VOWEL SIGN I + std::u16string text(u"e\u0301\u0327 x\u20e3 \u0921\u093f"); + std::u16string ellipsis(u"…"); + StringSlicer slicer(text, ellipsis, false, false); + + // Attempt to cut the string for all lengths. When a combining sequence is + // cut, it should always round left and exclude the combining sequence. + // Whitespace is also cut adjacent to the ellipsis. + + // First sequence: + EXPECT_EQ(u"…", slicer.CutString(0, true)); + EXPECT_EQ(u"…", slicer.CutString(1, true)); + EXPECT_EQ(u"…", slicer.CutString(2, true)); + EXPECT_EQ(text.substr(0, 3) + u"…", slicer.CutString(3, true)); + // Second sequence: + EXPECT_EQ(text.substr(0, 3) + u"…", slicer.CutString(4, true)); + EXPECT_EQ(text.substr(0, 3) + u"…", slicer.CutString(5, true)); + EXPECT_EQ(text.substr(0, 6) + u"…", slicer.CutString(6, true)); + // Third sequence: + EXPECT_EQ(text.substr(0, 6) + u"…", slicer.CutString(7, true)); + EXPECT_EQ(text.substr(0, 6) + u"…", slicer.CutString(8, true)); + EXPECT_EQ(text + u"…", slicer.CutString(9, true)); + + // Cut string in the middle, splitting the second sequence in half. Should + // round both left and right, excluding the second sequence. + StringSlicer slicer_mid(text, ellipsis, true, false); + EXPECT_EQ(text.substr(0, 4) + u"…" + text.substr(6), + slicer_mid.CutString(9, true)); + + // String that starts with a dangling combining mark. + char16_t dangling_mark_chars[] = {text[1], 0}; + std::u16string dangling_mark_text(dangling_mark_chars); + StringSlicer slicer_dangling_mark(dangling_mark_text, ellipsis, false, false); + EXPECT_EQ(u"…", slicer_dangling_mark.CutString(0, true)); + EXPECT_EQ(dangling_mark_text + u"…", slicer_dangling_mark.CutString(1, true)); +} + +TEST(TextEliderTest, StringSlicerCombiningSurrogate) { + // The ultimate test: combining sequences comprised of surrogate pairs. + // The following string contains a single combining character sequence: + // MUSICAL SYMBOL G CLEF (U+1D11E) + MUSICAL SYMBOL COMBINING FLAG-1 (U+1D16E) + // Represented as four UTF-16 code units. + std::u16string text(u"\U0001d11e\U0001d16e"); + std::u16string ellipsis(u"…"); + StringSlicer slicer(text, ellipsis, false, false); + + // Attempt to cut the string for all lengths. Should always round left and + // exclude the combining sequence. + EXPECT_EQ(u"…", slicer.CutString(0, true)); + EXPECT_EQ(u"…", slicer.CutString(1, true)); + EXPECT_EQ(u"…", slicer.CutString(2, true)); + EXPECT_EQ(u"…", slicer.CutString(3, true)); + EXPECT_EQ(text + u"…", slicer.CutString(4, true)); + + // Cut string in the middle. Should exclude the sequence. + StringSlicer slicer_mid(text, ellipsis, true, false); + EXPECT_EQ(u"…", slicer_mid.CutString(4, true)); +} + +TEST(TextEliderTest, ElideString) { + struct TestData { + const char16_t* input; + size_t max_len; + bool result; + const char16_t* output; + } cases[] = { + {u"Hello", 0, true, u""}, + {u"", 0, false, u""}, + {u"Hello, my name is Tom", 1, true, u"H"}, + {u"Hello, my name is Tom", 2, true, u"He"}, + {u"Hello, my name is Tom", 3, true, u"H.m"}, + {u"Hello, my name is Tom", 4, true, u"H..m"}, + {u"Hello, my name is Tom", 5, true, u"H...m"}, + {u"Hello, my name is Tom", 6, true, u"He...m"}, + {u"Hello, my name is Tom", 7, true, u"He...om"}, + {u"Hello, my name is Tom", 10, true, u"Hell...Tom"}, + {u"Hello, my name is Tom", 100, false, u"Hello, my name is Tom"}}; + for (size_t i = 0; i < base::size(cases); ++i) { + std::u16string output; + EXPECT_EQ(cases[i].result, + ElideString(cases[i].input, cases[i].max_len, &output)); + EXPECT_EQ(cases[i].output, output); + } +} + +TEST(TextEliderTest, ElideRectangleText) { + const FontList font_list; + const int line_height = font_list.GetHeight(); + const float test_width = GetStringWidthF(u"Test", font_list); + + struct TestData { + const char16_t* input; + float available_pixel_width; + int available_pixel_height; + bool truncated_y; + const char16_t* output; + } cases[] = { + {u"", 0, 0, false, nullptr}, + {u"", 1, 1, false, nullptr}, + {u"Test", test_width, 0, true, nullptr}, + {u"Test", test_width, 1, false, u"Test"}, + {u"Test", test_width, line_height, false, u"Test"}, + {u"Test Test", test_width, line_height, true, u"Test"}, + {u"Test Test", test_width, line_height + 1, false, u"Test|Test"}, + {u"Test Test", test_width, line_height * 2, false, u"Test|Test"}, + {u"Test Test", test_width, line_height * 3, false, u"Test|Test"}, + {u"Test Test", test_width * 2, line_height * 2, false, u"Test|Test"}, + {u"Test Test", test_width * 3, line_height, false, u"Test Test"}, + {u"Test\nTest", test_width * 3, line_height * 2, false, u"Test|Test"}, + {u"Te\nst Te", test_width, line_height * 3, false, u"Te|st|Te"}, + {u"\nTest", test_width, line_height * 2, false, u"|Test"}, + {u"\nTest", test_width, line_height, true, u""}, + {u"\n\nTest", test_width, line_height * 3, false, u"||Test"}, + {u"\n\nTest", test_width, line_height * 2, true, u"|"}, + {u"Test\n", 2 * test_width, line_height * 5, false, u"Test|"}, + {u"Test\n\n", 2 * test_width, line_height * 5, false, u"Test||"}, + {u"Test\n\n\n", 2 * test_width, line_height * 5, false, u"Test|||"}, + {u"Test\nTest\n\n", 2 * test_width, line_height * 5, false, + u"Test|Test||"}, + {u"Test\n\nTest\n", 2 * test_width, line_height * 5, false, + u"Test||Test|"}, + {u"Test\n\n\nTest", 2 * test_width, line_height * 5, false, + u"Test|||Test"}, + {u"Te ", test_width, line_height, false, u"Te"}, + {u"Te Te Test", test_width, 3 * line_height, false, u"Te|Te|Test"}, + }; + + for (size_t i = 0; i < base::size(cases); ++i) { + std::vector lines; + EXPECT_EQ(cases[i].truncated_y ? INSUFFICIENT_SPACE_VERTICAL : 0, + ElideRectangleText(cases[i].input, font_list, + cases[i].available_pixel_width, + cases[i].available_pixel_height, + TRUNCATE_LONG_WORDS, &lines)); + if (cases[i].output) { + const std::u16string result = base::JoinString(lines, u"|"); + EXPECT_EQ(cases[i].output, result) << "Case " << i << " failed!"; + } else { + EXPECT_TRUE(lines.empty()) << "Case " << i << " failed!"; + } + } +} + +TEST(TextEliderTest, ElideRectangleTextFirstWordTruncated) { + const FontList font_list; + const int line_height = font_list.GetHeight(); + + const float test_width = GetStringWidthF(u"Test", font_list); + const float tes_width = GetStringWidthF(u"Tes", font_list); + + std::vector lines; + + auto result_for_width = [&](const char16_t* input, float width) { + lines.clear(); + return ElideRectangleText(input, font_list, width, line_height * 4, + WRAP_LONG_WORDS, &lines); + }; + + // Test base case. + EXPECT_EQ(0, result_for_width(u"Test", test_width)); + EXPECT_EQ(1u, lines.size()); + EXPECT_EQ(u"Test", lines[0]); + + // First word truncated. + EXPECT_EQ(INSUFFICIENT_SPACE_FOR_FIRST_WORD, + result_for_width(u"Test", tes_width)); + EXPECT_EQ(2u, lines.size()); + EXPECT_EQ(u"Tes", lines[0]); + EXPECT_EQ(u"t", lines[1]); + + // Two words truncated. + EXPECT_EQ(INSUFFICIENT_SPACE_FOR_FIRST_WORD, + result_for_width(u"Test\nTest", tes_width)); + EXPECT_EQ(4u, lines.size()); + EXPECT_EQ(u"Tes", lines[0]); + EXPECT_EQ(u"t", lines[1]); + EXPECT_EQ(u"Tes", lines[2]); + EXPECT_EQ(u"t", lines[3]); + + // Word truncated, but not the first. + EXPECT_EQ(0, result_for_width(u"T Test", tes_width)); + EXPECT_EQ(3u, lines.size()); + EXPECT_EQ(u"T", lines[0]); + EXPECT_EQ(u"Tes", lines[1]); + EXPECT_EQ(u"t", lines[2]); + + // Leading \n. + EXPECT_EQ(0, result_for_width(u"\nTest", tes_width)); + EXPECT_EQ(3u, lines.size()); + EXPECT_EQ(u"", lines[0]); + EXPECT_EQ(u"Tes", lines[1]); + EXPECT_EQ(u"t", lines[2]); +} + +TEST(TextEliderTest, ElideRectangleTextPunctuation) { + const FontList font_list; + const int line_height = font_list.GetHeight(); + const float test_width = GetStringWidthF(u"Test", font_list); + const float test_t_width = GetStringWidthF(u"Test T", font_list); + constexpr int kResultMask = + INSUFFICIENT_SPACE_HORIZONTAL | INSUFFICIENT_SPACE_VERTICAL; + + struct TestData { + const char16_t* input; + float available_pixel_width; + int available_pixel_height; + bool wrap_words; + bool truncated_x; + const char16_t* output; + } cases[] = { + {u"Test T.", test_t_width, line_height * 2, false, false, u"Test|T."}, + {u"Test T ?", test_t_width, line_height * 2, false, false, u"Test|T ?"}, + {u"Test. Test", test_width, line_height * 3, false, true, u"Test|Test"}, + {u"Test. Test", test_width, line_height * 3, true, false, u"Test|.|Test"}, + }; + + for (size_t i = 0; i < base::size(cases); ++i) { + std::vector lines; + const WordWrapBehavior wrap_behavior = + (cases[i].wrap_words ? WRAP_LONG_WORDS : TRUNCATE_LONG_WORDS); + EXPECT_EQ(cases[i].truncated_x ? INSUFFICIENT_SPACE_HORIZONTAL : 0, + ElideRectangleText( + cases[i].input, font_list, cases[i].available_pixel_width, + cases[i].available_pixel_height, wrap_behavior, &lines) & + kResultMask); + if (cases[i].output) { + const std::u16string result = base::JoinString(lines, u"|"); + EXPECT_EQ(cases[i].output, result) << "Case " << i << " failed!"; + } else { + EXPECT_TRUE(lines.empty()) << "Case " << i << " failed!"; + } + } +} + +TEST(TextEliderTest, ElideRectangleTextLongWords) { + const FontList font_list; + const int kAvailableHeight = 1000; + const std::u16string kElidedTesting = u"Tes…"; + const float elided_width = GetStringWidthF(kElidedTesting, font_list); + const float test_width = GetStringWidthF(u"Test", font_list); + constexpr int kResultMask = + INSUFFICIENT_SPACE_HORIZONTAL | INSUFFICIENT_SPACE_VERTICAL; + + struct TestData { + const char16_t* input; + float available_pixel_width; + WordWrapBehavior wrap_behavior; + bool truncated_x; + const char16_t* output; + } cases[] = { + {u"Testing", test_width, IGNORE_LONG_WORDS, false, u"Testing"}, + {u"X Testing", test_width, IGNORE_LONG_WORDS, false, u"X|Testing"}, + {u"Test Testing", test_width, IGNORE_LONG_WORDS, false, u"Test|Testing"}, + {u"Test\nTesting", test_width, IGNORE_LONG_WORDS, false, u"Test|Testing"}, + {u"Test Tests ", test_width, IGNORE_LONG_WORDS, false, u"Test|Tests"}, + {u"Test Tests T", test_width, IGNORE_LONG_WORDS, false, u"Test|Tests|T"}, + + {u"Testing", elided_width, ELIDE_LONG_WORDS, true, u"Tes…"}, + {u"X Testing", elided_width, ELIDE_LONG_WORDS, true, u"X|Tes…"}, + {u"Test Testing", elided_width, ELIDE_LONG_WORDS, true, u"Test|Tes…"}, + {u"Test\nTesting", elided_width, ELIDE_LONG_WORDS, true, u"Test|Tes…"}, + + {u"Testing", test_width, TRUNCATE_LONG_WORDS, true, u"Test"}, + {u"X Testing", test_width, TRUNCATE_LONG_WORDS, true, u"X|Test"}, + {u"Test Testing", test_width, TRUNCATE_LONG_WORDS, true, u"Test|Test"}, + {u"Test\nTesting", test_width, TRUNCATE_LONG_WORDS, true, u"Test|Test"}, + {u"Test Tests ", test_width, TRUNCATE_LONG_WORDS, true, u"Test|Test"}, + {u"Test Tests T", test_width, TRUNCATE_LONG_WORDS, true, u"Test|Test|T"}, + + {u"Testing", test_width, WRAP_LONG_WORDS, false, u"Test|ing"}, + {u"X Testing", test_width, WRAP_LONG_WORDS, false, u"X|Test|ing"}, + {u"Test Testing", test_width, WRAP_LONG_WORDS, false, u"Test|Test|ing"}, + {u"Test\nTesting", test_width, WRAP_LONG_WORDS, false, u"Test|Test|ing"}, + {u"Test Tests ", test_width, WRAP_LONG_WORDS, false, u"Test|Test|s"}, + {u"Test Tests T", test_width, WRAP_LONG_WORDS, false, u"Test|Test|s T"}, + {u"TestTestTest", test_width, WRAP_LONG_WORDS, false, u"Test|Test|Test"}, + {u"TestTestTestT", test_width, WRAP_LONG_WORDS, false, + u"Test|Test|Test|T"}, + }; + + for (size_t i = 0; i < base::size(cases); ++i) { + std::vector lines; + EXPECT_EQ(cases[i].truncated_x ? INSUFFICIENT_SPACE_HORIZONTAL : 0, + ElideRectangleText( + cases[i].input, font_list, cases[i].available_pixel_width, + kAvailableHeight, cases[i].wrap_behavior, &lines) & + kResultMask); + std::u16string expected_output(cases[i].output); + const std::u16string result = base::JoinString(lines, u"|"); + EXPECT_EQ(expected_output, result) << "Case " << i << " failed!"; + } +} + +// This test is to make sure that the width of each wrapped line does not +// exceed the available width. On some platform like Mac, this test used to +// fail because the truncated integer width is returned for the string +// and the accumulation of the truncated values causes the elide function +// to wrap incorrectly. +TEST(TextEliderTest, ElideRectangleTextCheckLineWidth) { + FontList font_list; +#if defined(OS_MAC) + // Use a specific font to expose the line width exceeding problem. + font_list = FontList(Font("LucidaGrande", 12)); +#endif + const float kAvailableWidth = 235; + const int kAvailableHeight = 1000; + const char16_t text[] = u"that Russian place we used to go to after fencing"; + std::vector lines; + EXPECT_EQ(0, ElideRectangleText(text, font_list, kAvailableWidth, + kAvailableHeight, WRAP_LONG_WORDS, &lines)); + ASSERT_EQ(2u, lines.size()); + EXPECT_LE(GetStringWidthF(lines[0], font_list), kAvailableWidth); + EXPECT_LE(GetStringWidthF(lines[1], font_list), kAvailableWidth); +} + +#if BUILDFLAG(IS_CHROMEOS_ASH) +// This test was created specifically to test a message from crbug.com/415213. +// It tests that width of concatenation of words equals sum of widths of the +// words. +TEST(TextEliderTest, ElideRectangleTextCheckConcatWidthEqualsSumOfWidths) { + FontList font_list; + font_list = FontList("Noto Sans UI,ui-sans, 12px"); + SetFontRenderParamsDeviceScaleFactor(1.25f); +#define WIDTH(x) GetStringWidthF((x), font_list) + EXPECT_EQ(WIDTH(u"The administrator for this account has"), + WIDTH(u"The ") + WIDTH(u"administrator ") + WIDTH(u"for ") + + WIDTH(u"this ") + WIDTH(u"account ") + WIDTH(u"has")); +#undef WIDTH + SetFontRenderParamsDeviceScaleFactor(1.0f); +} +#endif // BUILDFLAG(IS_CHROMEOS_ASH) + +TEST(TextEliderTest, ElideRectangleString) { + struct TestData { + const char16_t* input; + int max_rows; + int max_cols; + bool result; + const char16_t* output; + } cases[] = { + {u"", 0, 0, false, u""}, + {u"", 1, 1, false, u""}, + {u"Hi, my name is\nTom", 0, 0, true, u"..."}, + {u"Hi, my name is\nTom", 1, 0, true, u"\n..."}, + {u"Hi, my name is\nTom", 0, 1, true, u"..."}, + {u"Hi, my name is\nTom", 1, 1, true, u"H\n..."}, + {u"Hi, my name is\nTom", 2, 1, true, u"H\ni\n..."}, + {u"Hi, my name is\nTom", 3, 1, true, u"H\ni\n,\n..."}, + {u"Hi, my name is\nTom", 4, 1, true, u"H\ni\n,\n \n..."}, + {u"Hi, my name is\nTom", 5, 1, true, u"H\ni\n,\n \nm\n..."}, + {u"Hi, my name is\nTom", 0, 2, true, u"..."}, + {u"Hi, my name is\nTom", 1, 2, true, u"Hi\n..."}, + {u"Hi, my name is\nTom", 2, 2, true, u"Hi\n, \n..."}, + {u"Hi, my name is\nTom", 3, 2, true, u"Hi\n, \nmy\n..."}, + {u"Hi, my name is\nTom", 4, 2, true, u"Hi\n, \nmy\n n\n..."}, + {u"Hi, my name is\nTom", 5, 2, true, u"Hi\n, \nmy\n n\nam\n..."}, + {u"Hi, my name is\nTom", 0, 3, true, u"..."}, + {u"Hi, my name is\nTom", 1, 3, true, u"Hi,\n..."}, + {u"Hi, my name is\nTom", 2, 3, true, u"Hi,\n my\n..."}, + {u"Hi, my name is\nTom", 3, 3, true, u"Hi,\n my\n na\n..."}, + {u"Hi, my name is\nTom", 4, 3, true, u"Hi,\n my\n na\nme \n..."}, + {u"Hi, my name is\nTom", 5, 3, true, u"Hi,\n my\n na\nme \nis\n..."}, + {u"Hi, my name is\nTom", 1, 4, true, u"Hi, \n..."}, + {u"Hi, my name is\nTom", 2, 4, true, u"Hi, \nmy n\n..."}, + {u"Hi, my name is\nTom", 3, 4, true, u"Hi, \nmy n\name \n..."}, + {u"Hi, my name is\nTom", 4, 4, true, u"Hi, \nmy n\name \nis\n..."}, + {u"Hi, my name is\nTom", 5, 4, false, u"Hi, \nmy n\name \nis\nTom"}, + {u"Hi, my name is\nTom", 1, 5, true, u"Hi, \n..."}, + {u"Hi, my name is\nTom", 2, 5, true, u"Hi, \nmy na\n..."}, + {u"Hi, my name is\nTom", 3, 5, true, u"Hi, \nmy na\nme \n..."}, + {u"Hi, my name is\nTom", 4, 5, true, u"Hi, \nmy na\nme \nis\n..."}, + {u"Hi, my name is\nTom", 5, 5, false, u"Hi, \nmy na\nme \nis\nTom"}, + {u"Hi, my name is\nTom", 1, 6, true, u"Hi, \n..."}, + {u"Hi, my name is\nTom", 2, 6, true, u"Hi, \nmy \n..."}, + {u"Hi, my name is\nTom", 3, 6, true, u"Hi, \nmy \nname \n..."}, + {u"Hi, my name is\nTom", 4, 6, true, u"Hi, \nmy \nname \nis\n..."}, + {u"Hi, my name is\nTom", 5, 6, false, u"Hi, \nmy \nname \nis\nTom"}, + {u"Hi, my name is\nTom", 1, 7, true, u"Hi, \n..."}, + {u"Hi, my name is\nTom", 2, 7, true, u"Hi, \nmy \n..."}, + {u"Hi, my name is\nTom", 3, 7, true, u"Hi, \nmy \nname \n..."}, + {u"Hi, my name is\nTom", 4, 7, true, u"Hi, \nmy \nname \nis\n..."}, + {u"Hi, my name is\nTom", 5, 7, false, u"Hi, \nmy \nname \nis\nTom"}, + {u"Hi, my name is\nTom", 1, 8, true, u"Hi, my \n..."}, + {u"Hi, my name is\nTom", 2, 8, true, u"Hi, my \nname \n..."}, + {u"Hi, my name is\nTom", 3, 8, true, u"Hi, my \nname \nis\n..."}, + {u"Hi, my name is\nTom", 4, 8, false, u"Hi, my \nname \nis\nTom"}, + {u"Hi, my name is\nTom", 1, 9, true, u"Hi, my \n..."}, + {u"Hi, my name is\nTom", 2, 9, true, u"Hi, my \nname is\n..."}, + {u"Hi, my name is\nTom", 3, 9, false, u"Hi, my \nname is\nTom"}, + {u"Hi, my name is\nTom", 1, 10, true, u"Hi, my \n..."}, + {u"Hi, my name is\nTom", 2, 10, true, u"Hi, my \nname is\n..."}, + {u"Hi, my name is\nTom", 3, 10, false, u"Hi, my \nname is\nTom"}, + {u"Hi, my name is\nTom", 1, 11, true, u"Hi, my \n..."}, + {u"Hi, my name is\nTom", 2, 11, true, u"Hi, my \nname is\n..."}, + {u"Hi, my name is\nTom", 3, 11, false, u"Hi, my \nname is\nTom"}, + {u"Hi, my name is\nTom", 1, 12, true, u"Hi, my \n..."}, + {u"Hi, my name is\nTom", 2, 12, true, u"Hi, my \nname is\n..."}, + {u"Hi, my name is\nTom", 3, 12, false, u"Hi, my \nname is\nTom"}, + {u"Hi, my name is\nTom", 1, 13, true, u"Hi, my name \n..."}, + {u"Hi, my name is\nTom", 2, 13, true, u"Hi, my name \nis\n..."}, + {u"Hi, my name is\nTom", 3, 13, false, u"Hi, my name \nis\nTom"}, + {u"Hi, my name is\nTom", 1, 20, true, u"Hi, my name is\n..."}, + {u"Hi, my name is\nTom", 2, 20, false, u"Hi, my name is\nTom"}, + {u"Hi, my name is Tom", 1, 40, false, u"Hi, my name is Tom"}, + }; + std::u16string output; + for (size_t i = 0; i < base::size(cases); ++i) { + EXPECT_EQ(cases[i].result, + ElideRectangleString(cases[i].input, cases[i].max_rows, + cases[i].max_cols, true, &output)); + EXPECT_EQ(cases[i].output, output); + } +} + +TEST(TextEliderTest, ElideRectangleStringNotStrict) { + struct TestData { + const char16_t* input; + int max_rows; + int max_cols; + bool result; + const char16_t* output; + } cases[] = { + {u"", 0, 0, false, u""}, + {u"", 1, 1, false, u""}, + {u"Hi, my name_is\nDick", 0, 0, true, u"..."}, + {u"Hi, my name_is\nDick", 1, 0, true, u"\n..."}, + {u"Hi, my name_is\nDick", 0, 1, true, u"..."}, + {u"Hi, my name_is\nDick", 1, 1, true, u"H\n..."}, + {u"Hi, my name_is\nDick", 2, 1, true, u"H\ni\n..."}, + {u"Hi, my name_is\nDick", 3, 1, true, u"H\ni\n,\n..."}, + {u"Hi, my name_is\nDick", 4, 1, true, u"H\ni\n,\n \n..."}, + {u"Hi, my name_is\nDick", 5, 1, true, u"H\ni\n,\n \nm\n..."}, + {u"Hi, my name_is\nDick", 0, 2, true, u"..."}, + {u"Hi, my name_is\nDick", 1, 2, true, u"Hi\n..."}, + {u"Hi, my name_is\nDick", 2, 2, true, u"Hi\n, \n..."}, + {u"Hi, my name_is\nDick", 3, 2, true, u"Hi\n, \nmy\n..."}, + {u"Hi, my name_is\nDick", 4, 2, true, u"Hi\n, \nmy\n n\n..."}, + {u"Hi, my name_is\nDick", 5, 2, true, u"Hi\n, \nmy\n n\nam\n..."}, + {u"Hi, my name_is\nDick", 0, 3, true, u"..."}, + {u"Hi, my name_is\nDick", 1, 3, true, u"Hi,\n..."}, + {u"Hi, my name_is\nDick", 2, 3, true, u"Hi,\n my\n..."}, + {u"Hi, my name_is\nDick", 3, 3, true, u"Hi,\n my\n na\n..."}, + {u"Hi, my name_is\nDick", 4, 3, true, u"Hi,\n my\n na\nme_\n..."}, + {u"Hi, my name_is\nDick", 5, 3, true, u"Hi,\n my\n na\nme_\nis\n..."}, + {u"Hi, my name_is\nDick", 1, 4, true, u"Hi, ..."}, + {u"Hi, my name_is\nDick", 2, 4, true, u"Hi, my n\n..."}, + {u"Hi, my name_is\nDick", 3, 4, true, u"Hi, my n\name_\n..."}, + {u"Hi, my name_is\nDick", 4, 4, true, u"Hi, my n\name_\nis\n..."}, + {u"Hi, my name_is\nDick", 5, 4, false, u"Hi, my n\name_\nis\nDick"}, + {u"Hi, my name_is\nDick", 1, 5, true, u"Hi, ..."}, + {u"Hi, my name_is\nDick", 2, 5, true, u"Hi, my na\n..."}, + {u"Hi, my name_is\nDick", 3, 5, true, u"Hi, my na\nme_is\n..."}, + {u"Hi, my name_is\nDick", 4, 5, true, u"Hi, my na\nme_is\n\n..."}, + {u"Hi, my name_is\nDick", 5, 5, false, u"Hi, my na\nme_is\n\nDick"}, + {u"Hi, my name_is\nDick", 1, 6, true, u"Hi, ..."}, + {u"Hi, my name_is\nDick", 2, 6, true, u"Hi, my nam\n..."}, + {u"Hi, my name_is\nDick", 3, 6, true, u"Hi, my nam\ne_is\n..."}, + {u"Hi, my name_is\nDick", 4, 6, false, u"Hi, my nam\ne_is\nDick"}, + {u"Hi, my name_is\nDick", 5, 6, false, u"Hi, my nam\ne_is\nDick"}, + {u"Hi, my name_is\nDick", 1, 7, true, u"Hi, ..."}, + {u"Hi, my name_is\nDick", 2, 7, true, u"Hi, my name\n..."}, + {u"Hi, my name_is\nDick", 3, 7, true, u"Hi, my name\n_is\n..."}, + {u"Hi, my name_is\nDick", 4, 7, false, u"Hi, my name\n_is\nDick"}, + {u"Hi, my name_is\nDick", 5, 7, false, u"Hi, my name\n_is\nDick"}, + {u"Hi, my name_is\nDick", 1, 8, true, u"Hi, my n\n..."}, + {u"Hi, my name_is\nDick", 2, 8, true, u"Hi, my n\name_is\n..."}, + {u"Hi, my name_is\nDick", 3, 8, false, u"Hi, my n\name_is\nDick"}, + {u"Hi, my name_is\nDick", 1, 9, true, u"Hi, my ..."}, + {u"Hi, my name_is\nDick", 2, 9, true, u"Hi, my name_is\n..."}, + {u"Hi, my name_is\nDick", 3, 9, false, u"Hi, my name_is\nDick"}, + {u"Hi, my name_is\nDick", 1, 10, true, u"Hi, my ..."}, + {u"Hi, my name_is\nDick", 2, 10, true, u"Hi, my name_is\n..."}, + {u"Hi, my name_is\nDick", 3, 10, false, u"Hi, my name_is\nDick"}, + {u"Hi, my name_is\nDick", 1, 11, true, u"Hi, my ..."}, + {u"Hi, my name_is\nDick", 2, 11, true, u"Hi, my name_is\n..."}, + {u"Hi, my name_is\nDick", 3, 11, false, u"Hi, my name_is\nDick"}, + {u"Hi, my name_is\nDick", 1, 12, true, u"Hi, my ..."}, + {u"Hi, my name_is\nDick", 2, 12, true, u"Hi, my name_is\n..."}, + {u"Hi, my name_is\nDick", 3, 12, false, u"Hi, my name_is\nDick"}, + {u"Hi, my name_is\nDick", 1, 13, true, u"Hi, my ..."}, + {u"Hi, my name_is\nDick", 2, 13, true, u"Hi, my name_is\n..."}, + {u"Hi, my name_is\nDick", 3, 13, false, u"Hi, my name_is\nDick"}, + {u"Hi, my name_is\nDick", 1, 20, true, u"Hi, my name_is\n..."}, + {u"Hi, my name_is\nDick", 2, 20, false, u"Hi, my name_is\nDick"}, + {u"Hi, my name_is Dick", 1, 40, false, u"Hi, my name_is Dick"}, + }; + std::u16string output; + for (size_t i = 0; i < base::size(cases); ++i) { + EXPECT_EQ(cases[i].result, + ElideRectangleString(cases[i].input, cases[i].max_rows, + cases[i].max_cols, false, &output)); + EXPECT_EQ(cases[i].output, output); + } +} + +TEST(TextEliderTest, ElideRectangleWide16) { + // Two greek words separated by space. + const std::u16string str(u"Παγκόσμιος Ιστός"); + const std::u16string out1(u"Παγκ\nόσμι\n..."); + const std::u16string out2(u"Παγκόσμιος \nΙστός"); + std::u16string output; + EXPECT_TRUE(ElideRectangleString(str, 2, 4, true, &output)); + EXPECT_EQ(out1, output); + EXPECT_FALSE(ElideRectangleString(str, 2, 12, true, &output)); + EXPECT_EQ(out2, output); +} + +TEST(TextEliderTest, ElideRectangleWide32) { + const std::u16string str(u"𝒜𝒜𝒜𝒜 aaaaa"); + const std::u16string out(u"𝒜𝒜𝒜\n𝒜 \naaa\n..."); + std::u16string output; + EXPECT_TRUE(ElideRectangleString(str, 3, 3, true, &output)); + EXPECT_EQ(out, output); +} + +TEST(TextEliderTest, TruncateString) { + std::u16string str = u"fooooey bxxxar baz "; + + // Test breaking at character 0. + EXPECT_EQ(std::u16string(), TruncateString(str, 0, WORD_BREAK)); + EXPECT_EQ(std::u16string(), TruncateString(str, 0, CHARACTER_BREAK)); + + // Test breaking at character 1. + EXPECT_EQ(u"…", TruncateString(str, 1, WORD_BREAK)); + EXPECT_EQ(u"…", TruncateString(str, 1, CHARACTER_BREAK)); + + // Test breaking in the middle of the first word. + EXPECT_EQ(u"f…", TruncateString(str, 2, WORD_BREAK)); + EXPECT_EQ(u"f…", TruncateString(str, 2, CHARACTER_BREAK)); + + // Test breaking in between words. + EXPECT_EQ(u"fooooey…", TruncateString(str, 9, WORD_BREAK)); + EXPECT_EQ(u"fooooey…", TruncateString(str, 9, CHARACTER_BREAK)); + + // Test breaking at the start of a later word. + EXPECT_EQ(u"fooooey…", TruncateString(str, 11, WORD_BREAK)); + EXPECT_EQ(u"fooooey…", TruncateString(str, 11, CHARACTER_BREAK)); + + // Test breaking in the middle of a word. + EXPECT_EQ(u"fooooey…", TruncateString(str, 12, WORD_BREAK)); + EXPECT_EQ(u"fooooey…", TruncateString(str, 12, CHARACTER_BREAK)); + EXPECT_EQ(u"fooooey…", TruncateString(str, 14, WORD_BREAK)); + EXPECT_EQ(u"fooooey bx…", TruncateString(str, 14, CHARACTER_BREAK)); + + // Test breaking in whitespace at the end of the string. + EXPECT_EQ(u"fooooey bxxxar baz…", TruncateString(str, 22, WORD_BREAK)); + EXPECT_EQ(u"fooooey bxxxar baz…", + TruncateString(str, 22, CHARACTER_BREAK)); + + // Test breaking at the end of the string. + EXPECT_EQ(str, TruncateString(str, str.length(), WORD_BREAK)); + EXPECT_EQ(str, TruncateString(str, str.length(), CHARACTER_BREAK)); + + // Test breaking past the end of the string. + EXPECT_EQ(str, TruncateString(str, str.length() + 10, WORD_BREAK)); + EXPECT_EQ(str, TruncateString(str, str.length() + 10, CHARACTER_BREAK)); + + + // Tests of strings with leading whitespace: + std::u16string str2 = u" foo"; + + // Test breaking in leading whitespace. + EXPECT_EQ(u"…", TruncateString(str2, 2, WORD_BREAK)); + EXPECT_EQ(u"…", TruncateString(str2, 2, CHARACTER_BREAK)); + + // Test breaking at the beginning of the first word, with leading whitespace. + EXPECT_EQ(u"…", TruncateString(str2, 3, WORD_BREAK)); + EXPECT_EQ(u"…", TruncateString(str2, 3, CHARACTER_BREAK)); + + // Test breaking in the middle of the first word, with leading whitespace. + EXPECT_EQ(u"…", TruncateString(str2, 4, WORD_BREAK)); + EXPECT_EQ(u"…", TruncateString(str2, 4, CHARACTER_BREAK)); + EXPECT_EQ(u" f…", TruncateString(str2, 5, WORD_BREAK)); + EXPECT_EQ(u" f…", TruncateString(str2, 5, CHARACTER_BREAK)); +} + +} // namespace gfx diff --git a/text_utils.cc b/text_utils.cc new file mode 100644 index 000000000000..2e73a65fc7a9 --- /dev/null +++ b/text_utils.cc @@ -0,0 +1,212 @@ +// Copyright 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/text_utils.h" + +#include + +#include "base/i18n/char_iterator.h" +#include "base/i18n/rtl.h" +#include "base/numerics/safe_conversions.h" +#include "third_party/icu/source/common/unicode/uchar.h" +#include "third_party/icu/source/common/unicode/utf16.h" +#include "ui/gfx/font_list.h" +#include "ui/gfx/geometry/insets.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/size.h" + +namespace gfx { + +using base::i18n::UTF16CharIterator; + +namespace { + +constexpr char16_t kAcceleratorChar = '&'; +constexpr char16_t kOpenParenthesisChar = '('; +constexpr char16_t kCloseParenthesisChar = ')'; + +// Returns true if the specified character must be elided from a string. +// Examples are combining marks and whitespace. +bool IsCombiningMark(UChar32 c) { + const int8_t char_type = u_charType(c); + return char_type == U_NON_SPACING_MARK || char_type == U_ENCLOSING_MARK || + char_type == U_COMBINING_SPACING_MARK; +} + +bool IsSpace(UChar32 c) { + // Ignore NUL character. + if (!c) + return false; + const int8_t char_type = u_charType(c); + return char_type == U_SPACE_SEPARATOR || char_type == U_LINE_SEPARATOR || + char_type == U_PARAGRAPH_SEPARATOR || char_type == U_CONTROL_CHAR; +} + +std::u16string RemoveAcceleratorChar(bool full_removal, + const std::u16string& s, + int* accelerated_char_pos, + int* accelerated_char_span) { + bool escaped = false; + ptrdiff_t last_char_pos = -1; + int last_char_span = 0; + UTF16CharIterator chars(s); + std::u16string accelerator_removed; + + // The states of a state machine looking for a CJK-style accelerator (i.e. + // "(&x)"). |cjk_state| proceeds up from |kFoundNothing| through these states, + // resetting either when it sees a complete accelerator, or gives up because + // the current character doesn't match. + enum { + kFoundNothing, + kFoundOpenParen, + kFoundAcceleratorChar, + kFoundAccelerator + } cjk_state = kFoundNothing; + size_t pre_cjk_size = 0; + + accelerator_removed.reserve(s.size()); + while (!chars.end()) { + int32_t c = chars.get(); + int array_pos = chars.array_pos(); + chars.Advance(); + + if (full_removal) { + if (cjk_state == kFoundNothing && c == kOpenParenthesisChar) { + pre_cjk_size = array_pos; + cjk_state = kFoundOpenParen; + } else if (cjk_state == kFoundOpenParen && c == kAcceleratorChar) { + cjk_state = kFoundAcceleratorChar; + } else if (cjk_state == kFoundAcceleratorChar) { + // Accept any character as the accelerator. + cjk_state = kFoundAccelerator; + } else if (cjk_state == kFoundAccelerator && c == kCloseParenthesisChar) { + cjk_state = kFoundNothing; + accelerator_removed.resize(pre_cjk_size); + pre_cjk_size = 0; + escaped = false; + continue; + } else { + cjk_state = kFoundNothing; + } + } + + if (c != kAcceleratorChar || escaped) { + int span = chars.array_pos() - array_pos; + if (escaped && c != kAcceleratorChar) { + last_char_pos = accelerator_removed.size(); + last_char_span = span; + } + for (int i = 0; i < span; i++) + accelerator_removed.push_back(s[array_pos + i]); + escaped = false; + } else { + escaped = true; + } + } + + if (accelerated_char_pos && !full_removal) + *accelerated_char_pos = last_char_pos; + if (accelerated_char_span && !full_removal) + *accelerated_char_span = last_char_span; + + return accelerator_removed; +} + +} // namespace + +std::u16string LocateAndRemoveAcceleratorChar(const std::u16string& s, + int* accelerated_char_pos, + int* accelerated_char_span) { + return RemoveAcceleratorChar(false, s, accelerated_char_pos, + accelerated_char_span); +} + +std::u16string RemoveAccelerator(const std::u16string& s) { + return RemoveAcceleratorChar(true, s, nullptr, nullptr); +} + +size_t FindValidBoundaryBefore(const std::u16string& text, + size_t index, + bool trim_whitespace) { + UTF16CharIterator it = UTF16CharIterator::LowerBound(text, index); + + // First, move left until we're positioned on a code point that is not a + // combining mark. + while (!it.start() && IsCombiningMark(it.get())) + it.Rewind(); + + // Next, maybe trim whitespace to the left of the current position. + if (trim_whitespace) { + while (!it.start() && IsSpace(it.PreviousCodePoint())) + it.Rewind(); + } + + return it.array_pos(); +} + +size_t FindValidBoundaryAfter(const std::u16string& text, + size_t index, + bool trim_whitespace) { + UTF16CharIterator it = UTF16CharIterator::UpperBound(text, index); + + // First, move right until we're positioned on a code point that is not a + // combining mark. + while (!it.end() && IsCombiningMark(it.get())) + it.Advance(); + + // Next, maybe trim space at the current position. + if (trim_whitespace) { + // A mark combining with a space is renderable, so we'll prevent + // trimming spaces with combining marks. + while (!it.end() && IsSpace(it.get()) && + !IsCombiningMark(it.NextCodePoint())) { + it.Advance(); + } + } + + return it.array_pos(); +} + +HorizontalAlignment MaybeFlipForRTL(HorizontalAlignment alignment) { + if (base::i18n::IsRTL() && + (alignment == gfx::ALIGN_LEFT || alignment == gfx::ALIGN_RIGHT)) { + alignment = + (alignment == gfx::ALIGN_LEFT) ? gfx::ALIGN_RIGHT : gfx::ALIGN_LEFT; + } + return alignment; +} + +Size GetStringSize(const std::u16string& text, const FontList& font_list) { + return Size(GetStringWidth(text, font_list), font_list.GetHeight()); +} + +Insets AdjustVisualBorderForFont(const FontList& font_list, + const Insets& desired_visual_padding) { + Insets result = desired_visual_padding; + const int baseline = font_list.GetBaseline(); + const int leading_space = baseline - font_list.GetCapHeight(); + const int descender = font_list.GetHeight() - baseline; + result.set_top(std::max(0, result.top() - leading_space)); + result.set_bottom(std::max(0, result.bottom() - descender)); + return result; +} + +int GetFontCapHeightCenterOffset(const gfx::FontList& original_font, + const gfx::FontList& to_center) { + const int original_cap_height = original_font.GetCapHeight(); + const int original_cap_leading = + original_font.GetBaseline() - original_cap_height; + const int to_center_cap_height = to_center.GetCapHeight(); + const int to_center_leading = to_center.GetBaseline() - to_center_cap_height; + + const int cap_height_diff = original_cap_height - to_center_cap_height; + const int new_cap_top = + original_cap_leading + std::lround(cap_height_diff / 2.0f); + const int new_top = new_cap_top - to_center_leading; + + // Since we assume the old font starts at zero, the new top is the adjustment. + return new_top; +} + +} // namespace gfx diff --git a/text_utils.h b/text_utils.h new file mode 100644 index 000000000000..b25a0013d225 --- /dev/null +++ b/text_utils.h @@ -0,0 +1,142 @@ +// Copyright 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_TEXT_UTILS_H_ +#define UI_GFX_TEXT_UTILS_H_ + +#include + +#include + +#include "ui/gfx/gfx_export.h" +#include "ui/gfx/text_constants.h" + +namespace gfx { + +class FontList; +class Insets; +class Size; + +// Strips the accelerator char ('&') from a menu string. Useful for platforms +// which use underlining to indicate accelerators. +// +// Single accelerator chars ('&') will be stripped from the string. Double +// accelerator chars ('&&') will be converted to a single '&'. The out params +// |accelerated_char_pos| and |accelerated_char_span| will be set to the index +// and span of the last accelerated character, respectively, or -1 and 0 if +// there was none. +GFX_EXPORT std::u16string LocateAndRemoveAcceleratorChar( + const std::u16string& s, + int* accelerated_char_pos, + int* accelerated_char_span); + +// Strips all accelerator notation from a menu string. Useful for platforms +// which use underlining to indicate accelerators, as well as situations where +// accelerators are not indicated. +// +// Single accelerator chars ('&') will be stripped from the string. Double +// accelerator chars ('&&') will be converted to a single '&'. CJK language +// accelerators, specified as "(&x)", will be entirely removed too. +GFX_EXPORT std::u16string RemoveAccelerator(const std::u16string& s); + +// Returns the number of horizontal pixels needed to display the specified +// |text| with |font_list|. |typesetter| indicates where the text will be +// displayed. +GFX_EXPORT int GetStringWidth(const std::u16string& text, + const FontList& font_list); + +// Returns the size required to render |text| in |font_list|. This includes all +// leading space, descender area, etc. even if the text to render does not +// contain characters with ascenders or descenders. +GFX_EXPORT Size GetStringSize(const std::u16string& text, + const FontList& font_list); + +// This is same as GetStringWidth except that fractional width is returned. +GFX_EXPORT float GetStringWidthF(const std::u16string& text, + const FontList& font_list); + +// Returns a valid cut boundary at or before |index|. The surrogate pair and +// combining characters should not be separated. +GFX_EXPORT size_t FindValidBoundaryBefore(const std::u16string& text, + size_t index, + bool trim_whitespace = false); + +// Returns a valid cut boundary at or after |index|. The surrogate pair and +// combining characters should not be separated. +GFX_EXPORT size_t FindValidBoundaryAfter(const std::u16string& text, + size_t index, + bool trim_whitespace = false); + +// If the UI layout is right-to-left, flip the alignment direction. +GFX_EXPORT HorizontalAlignment MaybeFlipForRTL(HorizontalAlignment alignment); + +// Returns insets that can be used to draw a highlight or border that appears to +// be distance |desired_visual_padding| from the body of a string of text +// rendered using |font_list|. The insets are adjusted based on the box used to +// render capital letters (or the bodies of most letters in non-capital fonts +// like Hebrew and Devanagari), in order to give the best visual appearance. +// +// That is, any portion of |desired_visual_padding| overlapping the font's +// leading space or descender area are truncated, to a minimum of zero. +// +// In this example, the text is rendered in a highlight that stretches above and +// below the height of the H as well as to the left and right of the text +// (|desired_visual_padding| = {2, 2, 2, 2}). Note that the descender of the 'y' +// overlaps with the padding, as it is outside the capital letter box. +// +// The resulting padding is {1, 2, 1, 2}. +// +// . . . . . . . . . . | actual top +// . . | | leading space +// . | | _ . | font | capital +// . |--| /_\ \ / . | height | height +// . | | \_ \/ . | | +// . / . | | descender +// . . . . . . . . . . | actual bottom +// ___ ___ +// actual actual +// left right +// +GFX_EXPORT Insets +AdjustVisualBorderForFont(const FontList& font_list, + const Insets& desired_visual_padding); + +// Returns the y adjustment necessary to align the center of the "cap size" box +// - the space between a capital letter's top and bottom - between two fonts. +// For non-capital scripts (e.g. Hebrew, Devanagari) the box containing the body +// of most letters is used. +// +// A positive return value means the font |to_center| needs to be moved down +// relative to the font |original_font|, while a negative value means it needs +// to be moved up. +// +// Illustration: +// +// original_font to_center +// ---------- ] - return value (+1) +// leading ---------- +// ---------- leading +// ---------- +// +// cap-height cap-height +// +// ---------- +// ---------- descent +// descent ---------- +// ---------- +// +// Visual result: Non-Latin example (Devanagari ऐ "ai"): +// \ +// |\ | ------ \ +// | \ | |\ | | | ---- +// | \ | | \| \ / \| +// | \| \ / +// / +// +GFX_EXPORT int GetFontCapHeightCenterOffset(const gfx::FontList& original_font, + const gfx::FontList& to_center); + +} // namespace gfx + +#endif // UI_GFX_TEXT_UTILS_H_ diff --git a/text_utils_ios.mm b/text_utils_ios.mm new file mode 100644 index 000000000000..1f7631601d73 --- /dev/null +++ b/text_utils_ios.mm @@ -0,0 +1,27 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/text_utils.h" + +#import + +#include + +#include "base/strings/sys_string_conversions.h" +#include "ui/gfx/font_list.h" + +namespace gfx { + +int GetStringWidth(const std::u16string& text, const FontList& font_list) { + return std::ceil(GetStringWidthF(text, font_list)); +} + +float GetStringWidthF(const std::u16string& text, const FontList& font_list) { + NSString* ns_text = base::SysUTF16ToNSString(text); + NativeFont native_font = font_list.GetPrimaryFont().GetNativeFont(); + NSDictionary* attributes = @{NSFontAttributeName : native_font}; + return [ns_text sizeWithAttributes:attributes].width; +} + +} // namespace gfx diff --git a/text_utils_skia.cc b/text_utils_skia.cc new file mode 100644 index 000000000000..2c8e96c3ab73 --- /dev/null +++ b/text_utils_skia.cc @@ -0,0 +1,19 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/text_utils.h" + +#include "ui/gfx/canvas.h" + +namespace gfx { + +int GetStringWidth(const std::u16string& text, const FontList& font_list) { + return Canvas::GetStringWidth(text, font_list); +} + +float GetStringWidthF(const std::u16string& text, const FontList& font_list) { + return Canvas::GetStringWidthF(text, font_list); +} + +} // namespace gfx diff --git a/text_utils_unittest.cc b/text_utils_unittest.cc new file mode 100644 index 000000000000..f1824dcb2a8f --- /dev/null +++ b/text_utils_unittest.cc @@ -0,0 +1,358 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/text_utils.h" + +#include + +#include + +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" +#include "build/build_config.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/font_list.h" +#include "ui/gfx/geometry/insets.h" +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/rect.h" + +namespace gfx { +namespace { + +TEST(TextUtilsTest, GetStringWidth) { + FontList font_list; + EXPECT_EQ(GetStringWidth(std::u16string(), font_list), 0); + EXPECT_GT(GetStringWidth(u"a", font_list), + GetStringWidth(std::u16string(), font_list)); + EXPECT_GT(GetStringWidth(u"ab", font_list), GetStringWidth(u"a", font_list)); + EXPECT_GT(GetStringWidth(u"abc", font_list), + GetStringWidth(u"ab", font_list)); +} + +TEST(TextUtilsTest, GetStringSize) { + std::vector strings{ + std::u16string(), + u"a", + u"abc", + }; + + FontList font_list; + for (std::u16string string : strings) { + gfx::Size size = GetStringSize(string, font_list); + EXPECT_EQ(GetStringWidth(string, font_list), size.width()) + << " input string is \"" << string << "\""; + EXPECT_EQ(font_list.GetHeight(), size.height()) + << " input string is \"" << string << "\""; + } +} + +TEST(TextUtilsTest, AdjustVisualBorderForFont_BorderLargerThanFont) { + FontList font_list; + + // We will make some assumptions about the default font - specifically that it + // has leading space and space for the descender. + DCHECK_GT(font_list.GetBaseline(), font_list.GetCapHeight()); + DCHECK_LT(font_list.GetBaseline(), font_list.GetHeight()); + + // Adjust a large border for the default font. Using a large number means that + // the border will extend outside the leading and descender area of the font. + constexpr gfx::Insets kOriginalBorder(20); + const gfx::Insets result = + AdjustVisualBorderForFont(font_list, kOriginalBorder); + EXPECT_EQ(result.left(), kOriginalBorder.left()); + EXPECT_EQ(result.right(), kOriginalBorder.right()); + EXPECT_LT(result.top(), kOriginalBorder.top()); + EXPECT_LT(result.bottom(), kOriginalBorder.bottom()); +} + +TEST(TextUtilsTest, AdjustVisualBorderForFont_BorderSmallerThanFont) { + FontList font_list; + + // We will make some assumptions about the default font - specifically that it + // has leading space and space for the descender. + DCHECK_GT(font_list.GetBaseline(), font_list.GetCapHeight()); + DCHECK_LT(font_list.GetBaseline(), font_list.GetHeight()); + + // Adjust a border with a small vertical component. The vertical component + // should go to zero because it overlaps the leading and descender areas of + // the font. + constexpr gfx::Insets kSmallVerticalInsets(1, 20); + const gfx::Insets result = + AdjustVisualBorderForFont(font_list, kSmallVerticalInsets); + EXPECT_EQ(result.left(), kSmallVerticalInsets.left()); + EXPECT_EQ(result.right(), kSmallVerticalInsets.right()); + EXPECT_EQ(result.top(), 0); + EXPECT_EQ(result.bottom(), 0); +} + +TEST(TextUtilsTest, GetFontCapHeightCenterOffset_SecondFontIsSmaller) { + FontList original_font; + FontList smaller_font = original_font.DeriveWithSizeDelta(-3); + DCHECK_LT(smaller_font.GetCapHeight(), original_font.GetCapHeight()); + EXPECT_GT(GetFontCapHeightCenterOffset(original_font, smaller_font), 0); +} + +TEST(TextUtilsTest, GetFontCapHeightCenterOffset_SecondFontIsLarger) { + FontList original_font; + FontList larger_font = original_font.DeriveWithSizeDelta(3); + DCHECK_GT(larger_font.GetCapHeight(), original_font.GetCapHeight()); + EXPECT_LT(GetFontCapHeightCenterOffset(original_font, larger_font), 0); +} + +TEST(TextUtilsTest, GetFontCapHeightCenterOffset_SameSize) { + FontList original_font; + EXPECT_EQ(0, GetFontCapHeightCenterOffset(original_font, original_font)); +} + +struct RemoveAcceleratorCharData { + const char* input; + int accelerated_char_pos; + int accelerated_char_span; + const char* output_locate_and_strip; + const char* output_full_strip; + const char* name; +}; + +class RemoveAcceleratorCharTest + : public testing::TestWithParam { + public: + static const RemoveAcceleratorCharData kCases[]; +}; + +const RemoveAcceleratorCharData RemoveAcceleratorCharTest::kCases[] = { + {"", -1, 0, "", "", "EmptyString"}, + {"&", -1, 0, "", "", "AcceleratorCharOnly"}, + {"no accelerator", -1, 0, "no accelerator", "no accelerator", + "NoAccelerator"}, + {"&one accelerator", 0, 1, "one accelerator", "one accelerator", + "OneAccelerator_Start"}, + {"one &accelerator", 4, 1, "one accelerator", "one accelerator", + "OneAccelerator_Middle"}, + {"one accelerator&", -1, 0, "one accelerator", "one accelerator", + "OneAccelerator_End"}, + {"&two &accelerators", 4, 1, "two accelerators", "two accelerators", + "TwoAccelerators_OneAtStart"}, + {"two &accelerators&", 4, 1, "two accelerators", "two accelerators", + "TwoAccelerators_OneAtEnd"}, + {"two& &accelerators", 4, 1, "two accelerators", "two accelerators", + "TwoAccelerators_SpaceBetween"}, + {"&&escaping", -1, 0, "&escaping", "&escaping", "Escape_Start"}, + {"escap&&ing", -1, 0, "escap&ing", "escap&ing", "Escape_Middle"}, + {"escaping&&", -1, 0, "escaping&", "escaping&", "Escape_End"}, + {"accelerator(&A)", 12, 1, "accelerator(A)", "accelerator", "CJK_Style"}, + {"accelerator(&A)...", 12, 1, "accelerator(A)...", "accelerator...", + "CJK_StyleEllipsis"}, + {"accelerator(paren", -1, 0, "accelerator(paren", "accelerator(paren", + "CJK_OpenParen"}, + {"accelerator(&paren", 12, 1, "accelerator(paren", "accelerator(paren", + "CJK_NoClosingParen"}, + {"accelerator(&paren)", 12, 1, "accelerator(paren)", "accelerator(paren)", + "CJK_LateClosingParen"}, + {"accelerator&paren)", 11, 1, "acceleratorparen)", "acceleratorparen)", + "CJK_NoOpeningParen"}, + {"accelerator(P)", -1, 0, "accelerator(P)", "accelerator(P)", + "CJK_JustParens"}, + {"accelerator(&)", 12, 1, "accelerator()", "accelerator()", + "CJK_MissingAccelerator"}, + {"&mix&&ed", 0, 1, "mix&ed", "mix&ed", "Mixed_EscapeAfterAccelerator"}, + {"&&m&ix&&e&d&", 6, 1, "&mix&ed", "&mix&ed", + "Mixed_MiddleAcceleratorSkipped"}, + {"&&m&&ix&ed&&", 5, 1, "&m&ixed&", "&m&ixed&", "Mixed_OneAccelerator"}, + {"&m&&ix&ed&&", 4, 1, "m&ixed&", "m&ixed&", + "Mixed_InitialAcceleratorSkipped"}, + // U+1D49C MATHEMATICAL SCRIPT CAPITAL A, which occupies two |char16|'s. + {"&\U0001D49C", 0, 2, "\U0001D49C", "\U0001D49C", + "MultibyteAccelerator_Start"}, + {"Test&\U0001D49Cing", 4, 2, "Test\U0001D49Cing", "Test\U0001D49Cing", + "MultibyteAccelerator_Middle"}, + {"Test\U0001D49C&ing", 6, 1, "Test\U0001D49Cing", "Test\U0001D49Cing", + "OneAccelerator_AfterMultibyte"}, + {"Test&\U0001D49C&ing", 6, 1, "Test\U0001D49Cing", "Test\U0001D49Cing", + "MultibyteAccelerator_Skipped"}, + {"Test&\U0001D49C&&ing", 4, 2, "Test\U0001D49C&ing", "Test\U0001D49C&ing", + "MultibyteAccelerator_EscapeAfter"}, + {"Test&\U0001D49C&\U0001D49Cing", 6, 2, "Test\U0001D49C\U0001D49Cing", + "Test\U0001D49C\U0001D49Cing", + "MultibyteAccelerator_AfterMultibyteAccelerator"}, + {"accelerator(&\U0001D49C)", 12, 2, "accelerator(\U0001D49C)", + "accelerator", "MultibyteAccelerator_CJK"}, +}; + +INSTANTIATE_TEST_SUITE_P( + All, + RemoveAcceleratorCharTest, + testing::ValuesIn(RemoveAcceleratorCharTest::kCases), + [](const testing::TestParamInfo& param_info) { + return param_info.param.name; + }); + +TEST_P(RemoveAcceleratorCharTest, RemoveAcceleratorChar) { + RemoveAcceleratorCharData data = GetParam(); + int accelerated_char_pos; + int accelerated_char_span; + std::u16string result_locate_and_strip = LocateAndRemoveAcceleratorChar( + base::UTF8ToUTF16(data.input), &accelerated_char_pos, + &accelerated_char_span); + std::u16string result_full_strip = + RemoveAccelerator(base::UTF8ToUTF16(data.input)); + EXPECT_EQ(result_locate_and_strip, + base::UTF8ToUTF16(data.output_locate_and_strip)); + EXPECT_EQ(result_full_strip, base::UTF8ToUTF16(data.output_full_strip)); + EXPECT_EQ(accelerated_char_pos, data.accelerated_char_pos); + EXPECT_EQ(accelerated_char_span, data.accelerated_char_span); +} + +struct FindValidBoundaryData { + const char16_t* input; + size_t index_in; + bool trim_whitespace; + size_t index_out; + const char* name; +}; + +class FindValidBoundaryBeforeTest + : public testing::TestWithParam { + public: + static const FindValidBoundaryData kCases[]; +}; + +const FindValidBoundaryData FindValidBoundaryBeforeTest::kCases[] = { + {u"", 0, false, 0, "Empty"}, + {u"word", 0, false, 0, "StartOfString"}, + {u"word", 4, false, 4, "EndOfString"}, + {u"word", 2, false, 2, "MiddleOfString_OnValidCharacter"}, + {u"w𐐷d", 2, false, 1, "MiddleOfString_OnSurrogatePair"}, + {u"w𐐷d", 1, false, 1, "MiddleOfString_BeforeSurrogatePair"}, + {u"w𐐷d", 3, false, 3, "MiddleOfString_AfterSurrogatePair"}, + {u"wo d", 3, false, 3, "MiddleOfString_OnSpace_NoTrim"}, + {u"wo d", 3, true, 2, "MiddleOfString_OnSpace_Trim"}, + {u"wo d", 2, false, 2, "MiddleOfString_LeftOfSpace_NoTrim"}, + {u"wo d", 2, true, 2, "MiddleOfString_LeftOfSpace_Trim"}, + {u"wo\td", 3, false, 3, "MiddleOfString_OnTab_NoTrim"}, + {u"wo\td", 3, true, 2, "MiddleOfString_OnTab_Trim"}, + {u"w d", 3, false, 3, "MiddleOfString_MultipleWhitespace_NoTrim"}, + {u"w d", 3, true, 1, "MiddleOfString_MultipleWhitespace_Trim"}, + {u"w d", 2, false, 2, "MiddleOfString_MiddleOfWhitespace_NoTrim"}, + {u"w d", 2, true, 1, "MiddleOfString_MiddleOfWhitespace_Trim"}, + {u"w ", 3, false, 3, "EndOfString_Whitespace_NoTrim"}, + {u"w ", 3, true, 1, "EndOfString_Whitespace_Trim"}, + {u" d", 2, false, 2, "MiddleOfString_Whitespace_NoTrim"}, + {u" d", 2, true, 0, "MiddleOfString_Whitespace_Trim"}, + // COMBINING GRAVE ACCENT (U+0300) + {u"wo\u0300d", 2, false, 1, "MiddleOfString_OnCombiningMark"}, + {u"wo\u0300d", 1, false, 1, "MiddleOfString_BeforeCombiningMark"}, + {u"wo\u0300d", 3, false, 3, "MiddleOfString_AfterCombiningMark"}, + {u"w o\u0300d", 3, true, 1, "MiddleOfString_SpaceAndCombinginMark_Trim"}, + {u"wo\u0300 d", 3, true, 3, "MiddleOfString_CombiningMarkAndSpace_Trim"}, + {u"w \u0300d", 3, true, 3, + "MiddleOfString_AfterSpaceWithCombiningMark_Trim"}, + {u"w \u0300d", 2, true, 1, "MiddleOfString_OnSpaceWithCombiningMark_Trim"}, + {u"w \u0300 d", 4, true, 3, + "MiddleOfString_AfterSpaceAfterSpaceWithCombiningMark_Trim"}, + // MUSICAL SYMBOL G CLEF (U+1D11E) + MUSICAL SYMBOL COMBINING FLAG-1 + // (U+1D16E) + {u"w\U0001D11E\U0001D16Ed", 1, false, 1, + "MiddleOfString_BeforeCombiningSurrogate"}, + {u"w\U0001D11E\U0001D16Ed", 2, false, 1, + "MiddleOfString_OnCombiningSurrogate_Pos1"}, + {u"w\U0001D11E\U0001D16Ed", 3, false, 1, + "MiddleOfString_OnCombiningSurrogate_Pos2"}, + {u"w\U0001D11E\U0001D16Ed", 4, false, 1, + "MiddleOfString_OnCombiningSurrogate_Pos3"}, + {u"w\U0001D11E\U0001D16Ed", 5, false, 5, + "MiddleOfString_AfterCombiningSurrogate"}, +}; + +INSTANTIATE_TEST_SUITE_P( + All, + FindValidBoundaryBeforeTest, + testing::ValuesIn(FindValidBoundaryBeforeTest::kCases), + [](const testing::TestParamInfo& param_info) { + return param_info.param.name; + }); + +TEST_P(FindValidBoundaryBeforeTest, FindValidBoundaryBefore) { + FindValidBoundaryData data = GetParam(); + const std::u16string::const_pointer input = + reinterpret_cast(data.input); + DLOG(INFO) << input; + size_t result = + FindValidBoundaryBefore(input, data.index_in, data.trim_whitespace); + EXPECT_EQ(data.index_out, result); +} + +class FindValidBoundaryAfterTest + : public testing::TestWithParam { + public: + static const FindValidBoundaryData kCases[]; +}; + +const FindValidBoundaryData FindValidBoundaryAfterTest::kCases[] = { + {u"", 0, false, 0, "Empty"}, + {u"word", 0, false, 0, "StartOfString"}, + {u"word", 4, false, 4, "EndOfString"}, + {u"word", 2, false, 2, "MiddleOfString_OnValidCharacter"}, + {u"w𐐷d", 2, false, 3, "MiddleOfString_OnSurrogatePair"}, + {u"w𐐷d", 1, false, 1, "MiddleOfString_BeforeSurrogatePair"}, + {u"w𐐷d", 3, false, 3, "MiddleOfString_AfterSurrogatePair"}, + {u"wo d", 2, false, 2, "MiddleOfString_OnSpace_NoTrim"}, + {u"wo d", 2, true, 3, "MiddleOfString_OnSpace_Trim"}, + {u"wo d", 3, false, 3, "MiddleOfString_RightOfSpace_NoTrim"}, + {u"wo d", 3, true, 3, "MiddleOfString_RightOfSpace_Trim"}, + {u"wo\td", 2, false, 2, "MiddleOfString_OnTab_NoTrim"}, + {u"wo\td", 2, true, 3, "MiddleOfString_OnTab_Trim"}, + {u"w d", 1, false, 1, "MiddleOfString_MultipleWhitespace_NoTrim"}, + {u"w d", 1, true, 3, "MiddleOfString_MultipleWhitespace_Trim"}, + {u"w d", 2, false, 2, "MiddleOfString_MiddleOfWhitespace_NoTrim"}, + {u"w d", 2, true, 3, "MiddleOfString_MiddleOfWhitespace_Trim"}, + {u"w ", 1, false, 1, "MiddleOfString_Whitespace_NoTrim"}, + {u"w ", 1, true, 3, "MiddleOfString_Whitespace_Trim"}, + {u" d", 0, false, 0, "StartOfString_Whitespace_NoTrim"}, + {u" d", 0, true, 2, "StartOfString_Whitespace_Trim"}, + // COMBINING GRAVE ACCENT (U+0300) + {u"wo\u0300d", 2, false, 3, "MiddleOfString_OnCombiningMark"}, + {u"wo\u0300d", 1, false, 1, "MiddleOfString_BeforeCombiningMark"}, + {u"wo\u0300d", 3, false, 3, "MiddleOfString_AfterCombiningMark"}, + {u"w o\u0300d", 1, true, 2, "MiddleOfString_SpaceAndCombinginMark_Trim"}, + {u"wo\u0300 d", 1, true, 1, + "MiddleOfString_BeforeCombiningMarkAndSpace_Trim"}, + {u"wo\u0300 d", 2, true, 4, "MiddleOfString_OnCombiningMarkAndSpace_Trim"}, + {u"w \u0300d", 1, true, 1, + "MiddleOfString_BeforeSpaceWithCombiningMark_Trim"}, + {u"w \u0300d", 2, true, 3, "MiddleOfString_OnSpaceWithCombiningMark_Trim"}, + {u"w \u0300d", 1, true, 2, + "MiddleOfString_BeforeSpaceBeforeSpaceWithCombiningMark_Trim"}, + // MUSICAL SYMBOL G CLEF (U+1D11E) + MUSICAL SYMBOL COMBINING FLAG-1 + // (U+1D16E) + {u"w\U0001D11E\U0001D16Ed", 1, false, 1, + "MiddleOfString_BeforeCombiningSurrogate"}, + {u"w\U0001D11E\U0001D16Ed", 2, false, 5, + "MiddleOfString_OnCombiningSurrogate_Pos1"}, + {u"w\U0001D11E\U0001D16Ed", 3, false, 5, + "MiddleOfString_OnCombiningSurrogate_Pos2"}, + {u"w\U0001D11E\U0001D16Ed", 4, false, 5, + "MiddleOfString_OnCombiningSurrogate_Pos3"}, + {u"w\U0001D11E\U0001D16Ed", 5, false, 5, + "MiddleOfString_AfterCombiningSurrogate"}, +}; + +INSTANTIATE_TEST_SUITE_P( + All, + FindValidBoundaryAfterTest, + testing::ValuesIn(FindValidBoundaryAfterTest::kCases), + [](const testing::TestParamInfo& param_info) { + return param_info.param.name; + }); + +TEST_P(FindValidBoundaryAfterTest, FindValidBoundaryAfter) { + FindValidBoundaryData data = GetParam(); + const std::u16string::const_pointer input = + reinterpret_cast(data.input); + size_t result = + FindValidBoundaryAfter(input, data.index_in, data.trim_whitespace); + EXPECT_EQ(data.index_out, result); +} + +} // namespace +} // namespace gfx diff --git a/ui_gfx_exports.cc b/ui_gfx_exports.cc new file mode 100644 index 000000000000..ca95eb331944 --- /dev/null +++ b/ui_gfx_exports.cc @@ -0,0 +1,10 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file is for including headers that are not included in any other .cc +// files contained with the ui/gfx module. We need to include these here so +// that linker will know to include the symbols, defined by these headers, in +// the resulting dynamic library (gfx.dll). + +#include "ui/gfx/vsync_provider.h" diff --git a/utf16_indexing.cc b/utf16_indexing.cc new file mode 100644 index 000000000000..164db80b3b1a --- /dev/null +++ b/utf16_indexing.cc @@ -0,0 +1,57 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/utf16_indexing.h" + +#include "base/check_op.h" +#include "base/third_party/icu/icu_utf.h" + +namespace gfx { + +bool IsValidCodePointIndex(const std::u16string& s, size_t index) { + return index == 0 || index == s.length() || + !(CBU16_IS_TRAIL(s[index]) && CBU16_IS_LEAD(s[index - 1])); +} + +ptrdiff_t UTF16IndexToOffset(const std::u16string& s, size_t base, size_t pos) { + // The indices point between UTF-16 words (range 0 to s.length() inclusive). + // In order to consistently handle indices that point to the middle of a + // surrogate pair, we count the first word in that surrogate pair and not + // the second. The test "s[i] is not the second half of a surrogate pair" is + // "IsValidCodePointIndex(s, i)". + DCHECK_LE(base, s.length()); + DCHECK_LE(pos, s.length()); + ptrdiff_t delta = 0; + while (base < pos) + delta += IsValidCodePointIndex(s, base++) ? 1 : 0; + while (pos < base) + delta -= IsValidCodePointIndex(s, pos++) ? 1 : 0; + return delta; +} + +size_t UTF16OffsetToIndex(const std::u16string& s, + size_t base, + ptrdiff_t offset) { + DCHECK_LE(base, s.length()); + // As in UTF16IndexToOffset, we count the first half of a surrogate pair, not + // the second. When stepping from pos to pos+1 we check s[pos:pos+1] == s[pos] + // (Python syntax), hence pos++. When stepping from pos to pos-1 we check + // s[pos-1], hence --pos. + size_t pos = base; + while (offset > 0 && pos < s.length()) + offset -= IsValidCodePointIndex(s, pos++) ? 1 : 0; + while (offset < 0 && pos > 0) + offset += IsValidCodePointIndex(s, --pos) ? 1 : 0; + // If offset != 0 then we ran off the edge of the string, which is a contract + // violation but is handled anyway (by clamping) in release for safety. + DCHECK_EQ(offset, 0); + // Since the second half of a surrogate pair has "length" zero, there is an + // ambiguity in the returned position. Resolve it by always returning a valid + // index. + if (!IsValidCodePointIndex(s, pos)) + ++pos; + return pos; +} + +} // namespace gfx diff --git a/utf16_indexing.h b/utf16_indexing.h new file mode 100644 index 000000000000..4831c412302d --- /dev/null +++ b/utf16_indexing.h @@ -0,0 +1,52 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_UTF16_INDEXING_H_ +#define UI_GFX_UTF16_INDEXING_H_ + +#include + +#include + +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +// Returns false if s[index-1] is a high surrogate and s[index] is a low +// surrogate, true otherwise. +GFX_EXPORT bool IsValidCodePointIndex(const std::u16string& s, size_t index); + +// |UTF16IndexToOffset| returns the number of code points between |base| and +// |pos| in the given string. |UTF16OffsetToIndex| returns the index that is +// |offset| code points away from the given |base| index. These functions are +// named after glib's |g_utf8_pointer_to_offset| and |g_utf8_offset_to_pointer|, +// which perform the same function for UTF-8. As in glib, it is an error to +// pass an |offset| that walks off the edge of the string. +// +// These functions attempt to deal with invalid use of UTF-16 surrogates in a +// way that makes as much sense as possible: unpaired surrogates are treated as +// single characters, and if an argument index points to the middle of a valid +// surrogate pair, it is treated as though it pointed to the end of that pair. +// The index returned by |UTF16OffsetToIndex| never points to the middle of a +// surrogate pair. +// +// The following identities hold: +// If |s| contains no surrogate pairs, then +// UTF16IndexToOffset(s, base, pos) == pos - base +// UTF16OffsetToIndex(s, base, offset) == base + offset +// If |pos| does not point to the middle of a surrogate pair, then +// UTF16OffsetToIndex(s, base, UTF16IndexToOffset(s, base, pos)) == pos +// Always, +// UTF16IndexToOffset(s, base, UTF16OffsetToIndex(s, base, ofs)) == ofs +// UTF16IndexToOffset(s, i, j) == -UTF16IndexToOffset(s, j, i) +GFX_EXPORT ptrdiff_t UTF16IndexToOffset(const std::u16string& s, + size_t base, + size_t pos); +GFX_EXPORT size_t UTF16OffsetToIndex(const std::u16string& s, + size_t base, + ptrdiff_t offset); + +} // namespace gfx + +#endif // UI_GFX_UTF16_INDEXING_H_ diff --git a/utf16_indexing_unittest.cc b/utf16_indexing_unittest.cc new file mode 100644 index 000000000000..289fd449f029 --- /dev/null +++ b/utf16_indexing_unittest.cc @@ -0,0 +1,34 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/utf16_indexing.h" + +namespace gfx { + +TEST(UTF16IndexingTest, IndexOffsetConversions) { + // Valid surrogate pair surrounded by unpaired surrogates + const char16_t foo[] = {0xDC00, 0xD800, 0xD800, 0xDFFF, 0xDFFF, 0xDBFF, 0}; + const std::u16string s(foo); + const size_t the_invalid_index = 3; + for (size_t i = 0; i <= s.length(); ++i) + EXPECT_EQ(i != the_invalid_index, IsValidCodePointIndex(s, i)); + for (size_t i = 0; i <= s.length(); ++i) { + for (size_t j = i; j <= s.length(); ++j) { + ptrdiff_t offset = static_cast(j - i); + if (i <= the_invalid_index && j > the_invalid_index) + --offset; + EXPECT_EQ(offset, UTF16IndexToOffset(s, i, j)); + EXPECT_EQ(-offset, UTF16IndexToOffset(s, j, i)); + size_t adjusted_j = (j == the_invalid_index) ? j + 1 : j; + EXPECT_EQ(adjusted_j, UTF16OffsetToIndex(s, i, offset)); + size_t adjusted_i = (i == the_invalid_index) ? i + 1 : i; + EXPECT_EQ(adjusted_i, UTF16OffsetToIndex(s, j, -offset)); + } + } +} + +} // namespace gfx diff --git a/vector_icon_types.h b/vector_icon_types.h new file mode 100644 index 000000000000..d4ffcdde0128 --- /dev/null +++ b/vector_icon_types.h @@ -0,0 +1,123 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_VECTOR_ICON_TYPES_H_ +#define UI_GFX_VECTOR_ICON_TYPES_H_ + +#include "base/macros.h" +#include "third_party/skia/include/core/SkScalar.h" +#include "ui/gfx/animation/tween.h" + +namespace gfx { + +// This macro allows defining the list of commands in this file, then pulling +// them each in to the template files via using-declarations. Files which want +// to do this should do the following: +// #define DECLARE_VECTOR_COMMAND(x) using gfx::x; +// DECLARE_VECTOR_COMMANDS +// The alternative would be to have the template files pull in the whole gfx +// namespace via using-directives, which is banned by the style guide. +#define DECLARE_VECTOR_COMMANDS \ + /* A new element. For the first path, this is assumed. */ \ + DECLARE_VECTOR_COMMAND(NEW_PATH) \ + /* Sets the alpha for the current path. */ \ + DECLARE_VECTOR_COMMAND(PATH_COLOR_ALPHA) \ + /* Sets the color for the current path. */ \ + DECLARE_VECTOR_COMMAND(PATH_COLOR_ARGB) \ + /* Sets the path to clear mode (Skia's kClear_Mode). */ \ + DECLARE_VECTOR_COMMAND(PATH_MODE_CLEAR) \ + /* By default, the path will be filled. This changes the paint action to */ \ + /* stroke at the given width. */ \ + DECLARE_VECTOR_COMMAND(STROKE) \ + /* By default, a stroke has a round cap. This sets it to square. */ \ + DECLARE_VECTOR_COMMAND(CAP_SQUARE) \ + /* These correspond to pathing commands. */ \ + DECLARE_VECTOR_COMMAND(MOVE_TO) \ + DECLARE_VECTOR_COMMAND(R_MOVE_TO) \ + DECLARE_VECTOR_COMMAND(ARC_TO) \ + DECLARE_VECTOR_COMMAND(R_ARC_TO) \ + DECLARE_VECTOR_COMMAND(LINE_TO) \ + DECLARE_VECTOR_COMMAND(R_LINE_TO) \ + DECLARE_VECTOR_COMMAND(H_LINE_TO) \ + DECLARE_VECTOR_COMMAND(R_H_LINE_TO) \ + DECLARE_VECTOR_COMMAND(V_LINE_TO) \ + DECLARE_VECTOR_COMMAND(R_V_LINE_TO) \ + DECLARE_VECTOR_COMMAND(CUBIC_TO) \ + DECLARE_VECTOR_COMMAND(R_CUBIC_TO) \ + DECLARE_VECTOR_COMMAND(CUBIC_TO_SHORTHAND) \ + DECLARE_VECTOR_COMMAND(QUADRATIC_TO) \ + DECLARE_VECTOR_COMMAND(R_QUADRATIC_TO) \ + DECLARE_VECTOR_COMMAND(QUADRATIC_TO_SHORTHAND) \ + DECLARE_VECTOR_COMMAND(R_QUADRATIC_TO_SHORTHAND) \ + DECLARE_VECTOR_COMMAND(CIRCLE) \ + DECLARE_VECTOR_COMMAND(OVAL) \ + DECLARE_VECTOR_COMMAND(ROUND_RECT) \ + DECLARE_VECTOR_COMMAND(CLOSE) \ + /* Sets the dimensions of the canvas in dip. */ \ + DECLARE_VECTOR_COMMAND(CANVAS_DIMENSIONS) \ + /* Sets a bounding rect for the path. This allows fine adjustment because */ \ + /* it can tweak edge anti-aliasing. Args are x, y, w, h. */ \ + DECLARE_VECTOR_COMMAND(CLIP) \ + /* Disables anti-aliasing for this path. */ \ + DECLARE_VECTOR_COMMAND(DISABLE_AA) \ + /* Flips the x-axis in RTL locales. Default is false, this command sets */ \ + /* it to true. */ \ + DECLARE_VECTOR_COMMAND(FLIPS_IN_RTL) + +#define DECLARE_VECTOR_COMMAND(x) x, + +// A command to Skia. +enum CommandType { DECLARE_VECTOR_COMMANDS }; + +#undef DECLARE_VECTOR_COMMAND + +// A POD that describes either a path command or an argument for it. +struct PathElement { + constexpr PathElement(CommandType value) : command(value) {} + constexpr PathElement(SkScalar value) : arg(value) {} + + union { + CommandType command; + SkScalar arg; + }; +}; + +// Describes the drawing commands for a single vector icon at a particular pixel +// size or range of sizes. +struct VectorIconRep { + VectorIconRep() = default; + + VectorIconRep(const VectorIconRep&) = delete; + VectorIconRep& operator=(const VectorIconRep&) = delete; + + const PathElement* path = nullptr; + + // The length of |path|. + size_t path_size = 0u; +}; + +// A vector icon that stores one or more representations to be used for various +// scale factors and pixel dimensions. +struct VectorIcon { + VectorIcon() = default; + + VectorIcon(const VectorIcon&) = delete; + VectorIcon& operator=(const VectorIcon&) = delete; + + bool is_empty() const { return !reps; } + + const VectorIconRep* const reps = nullptr; + size_t reps_size = 0u; + + // A human-readable name, useful for debugging, derived from the name of the + // icon file. This can also be used as an identifier, but vector icon targets + // should be careful to ensure this is unique. + const char* name = nullptr; + + bool operator<(const VectorIcon& other) const; +}; + +} // namespace gfx + +#endif // UI_GFX_VECTOR_ICON_TYPES_H_ diff --git a/vector_icon_utils.cc b/vector_icon_utils.cc new file mode 100644 index 000000000000..44b68fabcb84 --- /dev/null +++ b/vector_icon_utils.cc @@ -0,0 +1,24 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/vector_icon_utils.h" + +#include "base/check.h" +#include "ui/gfx/vector_icon_types.h" + +namespace gfx { + +int GetDefaultSizeOfVectorIcon(const VectorIcon& icon) { + if (icon.is_empty()) + return -1; + const PathElement* default_icon_path = icon.reps[icon.reps_size - 1].path; + DCHECK_EQ(default_icon_path[0].command, CANVAS_DIMENSIONS) + << " " << icon.name + << " has no size in its icon definition, and it seems unlikely you want " + "to display at the default of 48dip. Please specify a size in " + "CreateVectorIcon()."; + return default_icon_path[1].arg; +} + +} // namespace gfx diff --git a/vector_icon_utils.h b/vector_icon_utils.h new file mode 100644 index 000000000000..532f29f2b1cd --- /dev/null +++ b/vector_icon_utils.h @@ -0,0 +1,20 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_VECTOR_ICON_UTILS_H_ +#define UI_GFX_VECTOR_ICON_UTILS_H_ + +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +struct VectorIcon; + +// Calculates the size that will be default for |icon|, in dip. This will be the +// smallest icon size |icon| contains. +GFX_EXPORT int GetDefaultSizeOfVectorIcon(const gfx::VectorIcon& icon); + +} // namespace gfx + +#endif // UI_GFX_VECTOR_ICON_UTILS_H_ diff --git a/video_types.h b/video_types.h new file mode 100644 index 000000000000..e8865a682f9c --- /dev/null +++ b/video_types.h @@ -0,0 +1,19 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_VIDEO_TYPES_H_ +#define UI_GFX_VIDEO_TYPES_H_ + +namespace gfx { + +enum class ProtectedVideoType : uint32_t { + kClear = 0, + kSoftwareProtected = 1, + kHardwareProtected = 2, + kMaxValue = kHardwareProtected, +}; + +} // namespace gfx + +#endif // UI_GFX_VIDEO_TYPES_H_ diff --git a/vsync_provider.cc b/vsync_provider.cc new file mode 100644 index 000000000000..ff4b4a52ffa5 --- /dev/null +++ b/vsync_provider.cc @@ -0,0 +1,29 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/vsync_provider.h" + +namespace gfx { + +void FixedVSyncProvider::GetVSyncParameters(UpdateVSyncCallback callback) { + std::move(callback).Run(timebase_, interval_); +} + +bool FixedVSyncProvider::GetVSyncParametersIfAvailable( + base::TimeTicks* timebase, + base::TimeDelta* interval) { + *timebase = timebase_; + *interval = interval_; + return true; +} + +bool FixedVSyncProvider::SupportGetVSyncParametersIfAvailable() const { + return true; +} + +bool FixedVSyncProvider::IsHWClock() const { + return false; +} + +} // namespace gfx diff --git a/vsync_provider.h b/vsync_provider.h new file mode 100644 index 000000000000..87b0a15e38c4 --- /dev/null +++ b/vsync_provider.h @@ -0,0 +1,64 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_VSYNC_PROVIDER_H_ +#define UI_GFX_VSYNC_PROVIDER_H_ + +#include "base/callback.h" +#include "base/time/time.h" +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +class GFX_EXPORT VSyncProvider { + public: + virtual ~VSyncProvider() {} + + typedef base::OnceCallback + UpdateVSyncCallback; + + // Get the time of the most recent screen refresh, along with the time + // between consecutive refreshes. The callback is called as soon as + // the data is available: it could be immediately from this method, + // later via a PostTask to the current MessageLoop, or never (if we have + // no data source). We provide the strong guarantee that the callback will + // not be called once the instance of this class is destroyed. + virtual void GetVSyncParameters(UpdateVSyncCallback callback) = 0; + + // Similar to GetVSyncParameters(). It returns true, if the data is available. + // Otherwise false is returned. + virtual bool GetVSyncParametersIfAvailable(base::TimeTicks* timebase, + base::TimeDelta* interval) = 0; + + // Returns true, if GetVSyncParametersIfAvailable is supported. + virtual bool SupportGetVSyncParametersIfAvailable() const = 0; + + // Returns true, if VSyncProvider gets VSync timebase from HW. + virtual bool IsHWClock() const = 0; +}; + +// Provides a constant timebase and interval. +class GFX_EXPORT FixedVSyncProvider : public VSyncProvider { + public: + FixedVSyncProvider(base::TimeTicks timebase, base::TimeDelta interval) + : timebase_(timebase), interval_(interval) { + } + + ~FixedVSyncProvider() override {} + + void GetVSyncParameters(UpdateVSyncCallback callback) override; + bool GetVSyncParametersIfAvailable(base::TimeTicks* timebase, + base::TimeDelta* interval) override; + bool SupportGetVSyncParametersIfAvailable() const override; + bool IsHWClock() const override; + + private: + base::TimeTicks timebase_; + base::TimeDelta interval_; +}; + +} // namespace gfx + +#endif // UI_GFX_VSYNC_PROVIDER_H_ diff --git a/win/DEPS b/win/DEPS new file mode 100644 index 000000000000..99162b4c655a --- /dev/null +++ b/win/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+components/crash", +] diff --git a/win/OWNERS b/win/OWNERS new file mode 100644 index 000000000000..6ef3395c55c8 --- /dev/null +++ b/win/OWNERS @@ -0,0 +1,4 @@ +# For any other files, defer to ui/gfx/OWNERS. + +# Direct Write, fonts and related classes. +per-file direct_write*=etienneb@chromium.org diff --git a/win/crash_id_helper.cc b/win/crash_id_helper.cc new file mode 100644 index 000000000000..5cbf9201a018 --- /dev/null +++ b/win/crash_id_helper.cc @@ -0,0 +1,80 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/win/crash_id_helper.h" + +#include "base/memory/ptr_util.h" +#include "base/strings/string_util.h" + +namespace gfx { + +// static +CrashIdHelper* CrashIdHelper::Get() { + static base::NoDestructor helper; + return helper.get(); +} + +// static +void CrashIdHelper::RegisterMainThread(base::PlatformThreadId thread_id) { + main_thread_id_ = thread_id; +} + +CrashIdHelper::ScopedLogger::~ScopedLogger() { + CrashIdHelper::Get()->OnDidProcessMessages(); +} + +CrashIdHelper::ScopedLogger::ScopedLogger() = default; + +std::unique_ptr +CrashIdHelper::OnWillProcessMessages(const std::string& id) { + if (main_thread_id_ == base::kInvalidThreadId || + base::PlatformThread::CurrentId() != main_thread_id_) { + return nullptr; + } + + if (!ids_.empty()) + was_nested_ = true; + ids_.push_back(id.empty() ? "unspecified" : id); + debugging_crash_key_.Set(CurrentCrashId()); + // base::WrapUnique() as constructor is private. + return base::WrapUnique(new ScopedLogger); +} + +void CrashIdHelper::OnDidProcessMessages() { + DCHECK(!ids_.empty()); + ids_.pop_back(); + if (ids_.empty()) { + debugging_crash_key_.Clear(); + was_nested_ = false; + } else { + debugging_crash_key_.Set(CurrentCrashId()); + } +} + +CrashIdHelper::CrashIdHelper() = default; + +CrashIdHelper::~CrashIdHelper() = default; + +std::string CrashIdHelper::CurrentCrashId() const { + // This should only be called when there is at least one id. + DCHECK(!ids_.empty()); + // Common case is only one id. + if (ids_.size() == 1) { + // If the size of |ids_| is 1, then the message loop is not nested. If + // |was_nested_| is true, it means in processing the message corresponding + // to ids_[0] another message was processed, resulting in nested message + // loops. A nested message loop can lead to reentrancy and/or problems when + // the stack unravels. For example, it's entirely possible that when a + // nested message loop completes, objects further up in the stack have been + // deleted. "(N)" is added to signify that a nested message loop was run at + // some point during the current message loop. + return was_nested_ ? "(N) " + ids_[0] : ids_[0]; + } + return base::JoinString(ids_, ">"); +} + +// static +base::PlatformThreadId CrashIdHelper::main_thread_id_ = base::kInvalidThreadId; + +} // namespace gfx diff --git a/win/crash_id_helper.h b/win/crash_id_helper.h new file mode 100644 index 000000000000..649e2037beb1 --- /dev/null +++ b/win/crash_id_helper.h @@ -0,0 +1,90 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_WIN_CRASH_ID_HELPER_H_ +#define UI_GFX_WIN_CRASH_ID_HELPER_H_ + +#include +#include + +#include "base/macros.h" +#include "base/no_destructor.h" +#include "base/threading/platform_thread.h" +#include "components/crash/core/common/crash_key.h" +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +// CrashIdHelper is used to log (in crash dumps) the id(s) of the window/widget +// currently dispatching an event. Often times crashes occur purely in ui +// code, while the bug lies in client code. Logging an id helps better identify +// the client code that created the window/widget. +// +// This class only logs ids on the thread identified by RegisterMainThread(). +// +// Example usage: +// { +// auto logger = CrashIdHelper::Get()->OnWillProcessMessages(crash_id); +// +// } +class GFX_EXPORT CrashIdHelper { + public: + static CrashIdHelper* Get(); + + CrashIdHelper(const CrashIdHelper&) = delete; + CrashIdHelper& operator=(const CrashIdHelper&) = delete; + + // Registers the thread used for logging. + static void RegisterMainThread(base::PlatformThreadId thread_id); + + // RAII style class that unregisters in the destructor. + class GFX_EXPORT ScopedLogger { + public: + ScopedLogger(const ScopedLogger&) = delete; + ScopedLogger& operator=(const ScopedLogger&) = delete; + + ~ScopedLogger(); + + private: + friend class CrashIdHelper; + ScopedLogger(); + }; + + // Adds |id| to the list of active debugging ids. When the returned object + // is destroyed, |id| is removed from the list of active debugging ids. + // Returns null if logging is not enabled on the current thread. + std::unique_ptr OnWillProcessMessages(const std::string& id); + + private: + friend base::NoDestructor; + friend class CrashIdHelperTest; + + CrashIdHelper(); + ~CrashIdHelper(); + + // Called from ~ScopedLogger. Removes the most recently added id. + void OnDidProcessMessages(); + + // Returns the identifier to put in the crash key. + std::string CurrentCrashId() const; + + // Ordered list of debugging identifiers added. + std::vector ids_; + + // Set to true once |ids_| has more than one object, and false once |ids_| is + // empty. That is, this is true once processing the windows message resulted + // in processing another windows message (nested message loops). See comment + // in implementation of CurrentCrashId() as to why this is tracked. + bool was_nested_ = false; + + // This uses the name 'widget' as this code is most commonly triggered from + // views, which uses the term Widget. + crash_reporter::CrashKeyString<128> debugging_crash_key_{"widget-id"}; + + static base::PlatformThreadId main_thread_id_; +}; + +} // namespace gfx + +#endif // UI_GFX_WIN_CRASH_ID_HELPER_H_ diff --git a/win/crash_id_helper_unittest.cc b/win/crash_id_helper_unittest.cc new file mode 100644 index 000000000000..5e3e509e6925 --- /dev/null +++ b/win/crash_id_helper_unittest.cc @@ -0,0 +1,54 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/win/crash_id_helper.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace gfx { + +class CrashIdHelperTest : public testing::Test { + public: + CrashIdHelperTest() = default; + + CrashIdHelperTest(const CrashIdHelperTest&) = delete; + CrashIdHelperTest& operator=(const CrashIdHelperTest&) = delete; + + ~CrashIdHelperTest() override = default; + + std::string CurrentCrashId() { + return CrashIdHelper::Get()->CurrentCrashId(); + } +}; + +// This test verifies CurrentCrashId(). Ideally this would verify at +// crash_reporter::CrashKeyString, but that class isn't particularly test +// friendly (and the implementation varies depending upon compile time flags). +TEST_F(CrashIdHelperTest, Basic) { + CrashIdHelper::RegisterMainThread(base::PlatformThread::CurrentId()); + + const std::string id1 = "id"; + { + auto scoper = CrashIdHelper::Get()->OnWillProcessMessages(id1); + EXPECT_EQ(id1, CurrentCrashId()); + } + + // No assertions for empty as CurrentCrashId() DCHECKs there is at least + // one id. + + const std::string id2 = "id2"; + { + auto scoper = CrashIdHelper::Get()->OnWillProcessMessages(id2); + EXPECT_EQ(id2, CurrentCrashId()); + + { + auto scoper2 = CrashIdHelper::Get()->OnWillProcessMessages(id1); + EXPECT_EQ("id2>id", CurrentCrashId()); + } + EXPECT_EQ("(N) id2", CurrentCrashId()); + } + CrashIdHelper::RegisterMainThread(base::kInvalidThreadId); +} + +} // namespace gfx diff --git a/win/direct_write.cc b/win/direct_write.cc new file mode 100644 index 000000000000..fc7356b2f244 --- /dev/null +++ b/win/direct_write.cc @@ -0,0 +1,176 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/win/direct_write.h" + +#include + +#include + +#include "base/debug/alias.h" +#include "base/metrics/histogram_functions.h" +#include "base/metrics/histogram_macros.h" +#include "base/strings/utf_string_conversions.h" +#include "base/trace_event/trace_event.h" +#include "base/win/windows_version.h" +#include "skia/ext/fontmgr_default.h" +#include "third_party/skia/include/core/SkFontMgr.h" +#include "third_party/skia/include/ports/SkTypeface_win.h" + +namespace gfx { +namespace win { + +namespace { + +// Pointer to the global IDWriteFactory interface. +IDWriteFactory* g_direct_write_factory = nullptr; + +void SetDirectWriteFactory(IDWriteFactory* factory) { + DCHECK(!g_direct_write_factory); + // We grab a reference on the DirectWrite factory. This reference is + // leaked, which is ok because skia leaks it as well. + factory->AddRef(); + g_direct_write_factory = factory; +} + +} // anonymous namespace + +void CreateDWriteFactory(IDWriteFactory** factory) { + Microsoft::WRL::ComPtr factory_unknown; + HRESULT hr = + DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), + &factory_unknown); + if (FAILED(hr)) { + base::debug::Alias(&hr); + CHECK(false); + return; + } + factory_unknown.CopyTo(factory); +} + +void InitializeDirectWrite() { + static bool tried_dwrite_initialize = false; + DCHECK(!tried_dwrite_initialize); + tried_dwrite_initialize = true; + + TRACE_EVENT0("fonts", "gfx::InitializeDirectWrite"); + SCOPED_UMA_HISTOGRAM_LONG_TIMER("DirectWrite.Fonts.Gfx.InitializeTime"); + + Microsoft::WRL::ComPtr factory; + CreateDWriteFactory(&factory); + CHECK(!!factory); + SetDirectWriteFactory(factory.Get()); + + // The skia call to create a new DirectWrite font manager instance can fail + // if we are unable to get the system font collection from the DirectWrite + // factory. The GetSystemFontCollection method in the IDWriteFactory + // interface fails with E_INVALIDARG on certain Windows 7 gold versions + // (6.1.7600.*). + sk_sp direct_write_font_mgr = + SkFontMgr_New_DirectWrite(factory.Get()); + int iteration = 0; + if (!direct_write_font_mgr && + base::win::GetVersion() == base::win::Version::WIN7) { + // Windows (win7_rtm) may fail to map the service sections + // (crbug.com/956064). + constexpr int kMaxRetries = 5; + constexpr base::TimeDelta kRetrySleepTime = base::Microseconds(500); + while (iteration < kMaxRetries) { + base::PlatformThread::Sleep(kRetrySleepTime); + direct_write_font_mgr = SkFontMgr_New_DirectWrite(factory.Get()); + if (direct_write_font_mgr) + break; + ++iteration; + } + } + if (!direct_write_font_mgr) + iteration = -1; + base::UmaHistogramSparse("DirectWrite.Fonts.Gfx.InitializeLoopCount", + iteration); + // TODO(crbug.com/956064): Move to a CHECK when the cause of the crash is + // fixed and remove the if statement that fallback to GDI font manager. + DCHECK(!!direct_write_font_mgr); + if (!direct_write_font_mgr) + direct_write_font_mgr = SkFontMgr_New_GDI(); + + // Override the default skia font manager. This must be called before any + // use of the skia font manager is done (e.g. before any call to + // SkFontMgr::RefDefault()). + skia::OverrideDefaultSkFontMgr(std::move(direct_write_font_mgr)); +} + +IDWriteFactory* GetDirectWriteFactory() { + // Some unittests may access this accessor without any previous call to + // |InitializeDirectWrite|. A call to |InitializeDirectWrite| after this + // function being called is still invalid. + if (!g_direct_write_factory) + InitializeDirectWrite(); + return g_direct_write_factory; +} + +absl::optional RetrieveLocalizedString( + IDWriteLocalizedStrings* names, + const std::string& locale) { + std::wstring locale_wide = base::UTF8ToWide(locale); + + // If locale is empty, index 0 will be used. Otherwise, the locale name must + // be found and must exist. + UINT32 index = 0; + BOOL exists = false; + if (!locale.empty() && + (FAILED(names->FindLocaleName(locale_wide.c_str(), &index, &exists)) || + !exists)) { + return absl::nullopt; + } + + // Get the string length. + UINT32 length = 0; + if (FAILED(names->GetStringLength(index, &length))) + return absl::nullopt; + + // The output buffer length needs to be one larger to receive the NUL + // character. + std::wstring buffer; + buffer.resize(length + 1); + if (FAILED(names->GetString(index, &buffer[0], buffer.size()))) + return absl::nullopt; + + // Shrink the string to fit the actual length. + buffer.resize(length); + + return base::WideToUTF8(buffer); +} + +absl::optional RetrieveLocalizedFontName( + base::StringPiece font_name, + const std::string& locale) { + Microsoft::WRL::ComPtr factory; + CreateDWriteFactory(&factory); + + Microsoft::WRL::ComPtr font_collection; + if (FAILED(factory->GetSystemFontCollection(&font_collection))) { + return absl::nullopt; + } + + UINT32 index = 0; + BOOL exists; + std::wstring font_name_wide = base::UTF8ToWide(font_name); + if (FAILED(font_collection->FindFamilyName(font_name_wide.c_str(), &index, + &exists)) || + !exists) { + return absl::nullopt; + } + + Microsoft::WRL::ComPtr font_family; + Microsoft::WRL::ComPtr family_names; + if (FAILED(font_collection->GetFontFamily(index, &font_family)) || + FAILED(font_family->GetFamilyNames(&family_names))) { + return absl::nullopt; + } + + return RetrieveLocalizedString(family_names.Get(), locale); +} + +} // namespace win +} // namespace gfx diff --git a/win/direct_write.h b/win/direct_write.h new file mode 100644 index 000000000000..2cbb4a58e806 --- /dev/null +++ b/win/direct_write.h @@ -0,0 +1,40 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_WIN_DIRECT_WRITE_H_ +#define UI_GFX_WIN_DIRECT_WRITE_H_ + +#include + +#include "base/strings/string_piece.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/gfx/gfx_export.h" + +namespace gfx { +namespace win { + +GFX_EXPORT void InitializeDirectWrite(); + +// Creates a DirectWrite factory. +GFX_EXPORT void CreateDWriteFactory(IDWriteFactory** factory); + +// Returns the global DirectWrite factory. +GFX_EXPORT IDWriteFactory* GetDirectWriteFactory(); + +// Retrieves the localized string for a given locale. If locale is empty, +// retrieves the first element of |names|. +GFX_EXPORT absl::optional RetrieveLocalizedString( + IDWriteLocalizedStrings* names, + const std::string& locale); + +// Retrieves the localized font name for a given locale. If locale is empty, +// retrieves the default native font name. +GFX_EXPORT absl::optional RetrieveLocalizedFontName( + base::StringPiece font_name, + const std::string& locale); + +} // namespace win +} // namespace gfx + +#endif // UI_GFX_WIN_DIRECT_WRITE_H_ diff --git a/win/direct_write_unittest.cc b/win/direct_write_unittest.cc new file mode 100644 index 000000000000..1c136c878d55 --- /dev/null +++ b/win/direct_write_unittest.cc @@ -0,0 +1,28 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/win/direct_write.h" + +#include "base/i18n/rtl.h" +#include "testing/gtest/include/gtest/gtest.h" + +TEST(DirectWrite, RetrieveLocalizedFontName) { + // Retrieve the en-US localized names. + EXPECT_EQ(gfx::win::RetrieveLocalizedFontName("MS Gothic", "en-US"), + "MS Gothic"); + EXPECT_EQ(gfx::win::RetrieveLocalizedFontName("Malgun Gothic", "en-US"), + "Malgun Gothic"); + + // Retrieve the localized names. + EXPECT_EQ(gfx::win::RetrieveLocalizedFontName("MS Gothic", "ja-JP"), + "MS ゴシック"); + EXPECT_EQ(gfx::win::RetrieveLocalizedFontName("Malgun Gothic", "ko-KR"), + "맑은 고딕"); + + // Retrieve the default font name. + EXPECT_EQ(gfx::win::RetrieveLocalizedFontName("MS ゴシック", ""), + "MS Gothic"); + EXPECT_EQ(gfx::win::RetrieveLocalizedFontName("맑은 고딕", ""), + "Malgun Gothic"); +} diff --git a/win/hwnd_util.cc b/win/hwnd_util.cc new file mode 100644 index 000000000000..286b766a2a3d --- /dev/null +++ b/win/hwnd_util.cc @@ -0,0 +1,210 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/win/hwnd_util.h" + +#include + +#include "base/debug/gdi_debug_util_win.h" +#include "base/logging.h" +#include "base/notreached.h" +#include "base/strings/string_util.h" +#include "base/win/win_util.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/size.h" + +namespace gfx { + +namespace { + +// Adjust the window to fit. +void AdjustWindowToFit(HWND hwnd, const RECT& bounds, bool fit_to_monitor) { + if (fit_to_monitor) { + // Get the monitor. + HMONITOR hmon = MonitorFromRect(&bounds, MONITOR_DEFAULTTONEAREST); + if (hmon) { + MONITORINFO mi; + mi.cbSize = sizeof(mi); + GetMonitorInfo(hmon, &mi); + Rect window_rect(bounds); + Rect monitor_rect(mi.rcWork); + Rect new_window_rect = window_rect; + new_window_rect.AdjustToFit(monitor_rect); + if (new_window_rect != window_rect) { + // Window doesn't fit on monitor, move and possibly resize. + SetWindowPos(hwnd, 0, new_window_rect.x(), new_window_rect.y(), + new_window_rect.width(), new_window_rect.height(), + SWP_NOACTIVATE | SWP_NOZORDER); + return; + } + // Else fall through. + } else { + NOTREACHED() << "Unable to find default monitor"; + // Fall through. + } + } // Else fall through. + + // The window is not being fit to monitor, or the window fits on the monitor + // as is, or we have no monitor info; reset the bounds. + ::SetWindowPos(hwnd, 0, bounds.left, bounds.top, + bounds.right - bounds.left, bounds.bottom - bounds.top, + SWP_NOACTIVATE | SWP_NOZORDER); +} + +// Don't inline these functions so they show up in crash reports. + +NOINLINE void CrashAccessDenied(DWORD last_error) { + LOG(FATAL) << last_error; +} + +// Crash isn't one of the ones we commonly see. +NOINLINE void CrashOther(DWORD last_error) { + LOG(FATAL) << last_error; +} + +} // namespace + +std::wstring GetClassName(HWND window) { + // GetClassNameW will return a truncated result (properly null terminated) if + // the given buffer is not large enough. So, it is not possible to determine + // that we got the entire class name if the result is exactly equal to the + // size of the buffer minus one. + DWORD buffer_size = MAX_PATH; + while (true) { + std::wstring output; + DWORD size_ret = GetClassNameW( + window, base::WriteInto(&output, buffer_size), buffer_size); + if (size_ret == 0) + break; + if (size_ret < (buffer_size - 1)) { + output.resize(size_ret); + return output; + } + buffer_size *= 2; + } + return std::wstring(); // error +} + +#pragma warning(push) +#pragma warning(disable:4312 4244) + +WNDPROC SetWindowProc(HWND hwnd, WNDPROC proc) { + // The reason we don't return the SetwindowLongPtr() value is that it returns + // the orignal window procedure and not the current one. I don't know if it is + // a bug or an intended feature. + WNDPROC oldwindow_proc = + reinterpret_cast(GetWindowLongPtr(hwnd, GWLP_WNDPROC)); + SetWindowLongPtr(hwnd, GWLP_WNDPROC, reinterpret_cast(proc)); + return oldwindow_proc; +} + +void* SetWindowUserData(HWND hwnd, void* user_data) { + return + reinterpret_cast(SetWindowLongPtr(hwnd, GWLP_USERDATA, + reinterpret_cast(user_data))); +} + +void* GetWindowUserData(HWND hwnd) { + DWORD process_id = 0; + GetWindowThreadProcessId(hwnd, &process_id); + // A window outside the current process needs to be ignored. + if (process_id != ::GetCurrentProcessId()) + return NULL; + return reinterpret_cast(GetWindowLongPtr(hwnd, GWLP_USERDATA)); +} + +#pragma warning(pop) + +bool DoesWindowBelongToActiveWindow(HWND window) { + DCHECK(window); + HWND top_window = ::GetAncestor(window, GA_ROOT); + if (!top_window) + return false; + + HWND active_top_window = ::GetAncestor(::GetForegroundWindow(), GA_ROOT); + return (top_window == active_top_window); +} + +void CenterAndSizeWindow(HWND parent, + HWND window, + const Size& pref) { + DCHECK(window && pref.width() > 0 && pref.height() > 0); + + // Calculate the ideal bounds. + RECT window_bounds; + RECT center_bounds = {0}; + if (parent) { + // If there is a parent, center over the parents bounds. + ::GetWindowRect(parent, ¢er_bounds); + } + + if (::IsRectEmpty(¢er_bounds)) { + // No parent or no parent rect. Center over the monitor the window is on. + HMONITOR monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONEAREST); + if (monitor) { + MONITORINFO mi = {0}; + mi.cbSize = sizeof(mi); + GetMonitorInfo(monitor, &mi); + center_bounds = mi.rcWork; + } else { + NOTREACHED() << "Unable to get default monitor"; + } + } + + window_bounds.left = center_bounds.left; + if (pref.width() < (center_bounds.right - center_bounds.left)) { + window_bounds.left += + (center_bounds.right - center_bounds.left - pref.width()) / 2; + } + window_bounds.right = window_bounds.left + pref.width(); + + window_bounds.top = center_bounds.top; + if (pref.height() < (center_bounds.bottom - center_bounds.top)) { + window_bounds.top += + (center_bounds.bottom - center_bounds.top - pref.height()) / 2; + } + window_bounds.bottom = window_bounds.top + pref.height(); + + // If we're centering a child window, we are positioning in client + // coordinates, and as such we need to offset the target rectangle by the + // position of the parent window. + if (::GetWindowLong(window, GWL_STYLE) & WS_CHILD) { + DCHECK(parent && ::GetParent(window) == parent); + POINT topleft = { window_bounds.left, window_bounds.top }; + ::MapWindowPoints(HWND_DESKTOP, parent, &topleft, 1); + window_bounds.left = topleft.x; + window_bounds.top = topleft.y; + window_bounds.right = window_bounds.left + pref.width(); + window_bounds.bottom = window_bounds.top + pref.height(); + } + + AdjustWindowToFit(window, window_bounds, !parent); +} + +void CheckWindowCreated(HWND hwnd, DWORD last_error) { + if (!hwnd) { + switch (last_error) { + case ERROR_NOT_ENOUGH_MEMORY: + base::debug::CollectGDIUsageAndDie(); + break; + case ERROR_ACCESS_DENIED: + CrashAccessDenied(last_error); + break; + default: + CrashOther(last_error); + break; + } + LOG(FATAL) << last_error; + } +} + +extern "C" { + typedef HWND (*RootWindow)(); +} + +HWND GetWindowToParentTo(bool get_real_hwnd) { + return get_real_hwnd ? ::GetDesktopWindow() : HWND_DESKTOP; +} + +} // namespace gfx diff --git a/win/hwnd_util.h b/win/hwnd_util.h new file mode 100644 index 000000000000..d4d8eafc014a --- /dev/null +++ b/win/hwnd_util.h @@ -0,0 +1,50 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_WIN_HWND_UTIL_H_ +#define UI_GFX_WIN_HWND_UTIL_H_ + +#include + +#include + +#include "ui/gfx/gfx_export.h" + +namespace gfx { +class Size; + +// A version of the GetClassNameW API that returns the class name in an +// std::wstring. An empty result indicates a failure to get the class name. +GFX_EXPORT std::wstring GetClassName(HWND hwnd); + +// Useful for subclassing a HWND. Returns the previous window procedure. +GFX_EXPORT WNDPROC SetWindowProc(HWND hwnd, WNDPROC wndproc); + +// Pointer-friendly wrappers around Get/SetWindowLong(..., GWLP_USERDATA, ...) +// Returns the previously set value. +GFX_EXPORT void* SetWindowUserData(HWND hwnd, void* user_data); +GFX_EXPORT void* GetWindowUserData(HWND hwnd); + +// Returns true if the specified window is the current active top window or one +// of its children. +GFX_EXPORT bool DoesWindowBelongToActiveWindow(HWND window); + +// Sizes the window to have a window size of |pref|, then centers the window +// over |parent|, ensuring the window fits on screen. +GFX_EXPORT void CenterAndSizeWindow(HWND parent, + HWND window, + const gfx::Size& pref); + +// If |hwnd| is nullptr logs various thing and CHECKs. |last_error| must contain +// the result of ::GetLastError(), called immediately after CreateWindow(). +GFX_EXPORT void CheckWindowCreated(HWND hwnd, DWORD last_error); + +// Returns the window you can use to parent a top level window. +// Note that in some cases we create child windows not parented to its final +// container so in those cases you should pass true in |get_real_hwnd|. +GFX_EXPORT HWND GetWindowToParentTo(bool get_real_hwnd); + +} // namespace gfx + +#endif // UI_GFX_WIN_HWND_UTIL_H_ diff --git a/win/msg_util.h b/win/msg_util.h new file mode 100644 index 000000000000..7c6b42b23141 --- /dev/null +++ b/win/msg_util.h @@ -0,0 +1,2261 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_WIN_MSG_UTIL_H_ +#define UI_GFX_WIN_MSG_UTIL_H_ + +#include "base/memory/weak_ptr.h" +#include "base/notreached.h" +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/size.h" + +// Based on WTL version 8.0 atlcrack.h + +// This differs from the original atlcrack.h by removing usage of CPoint, +// CSize, etc. + +/////////////////////////////////////////////////////////////////////////////// +// Message map macro for cracked handlers + +// Note about message maps with cracked handlers: +// For ATL 3.0, a message map using cracked handlers MUST use +// CR_BEGIN_MSG_MAP_EX. For ATL 7.0 or higher, you can use CR_BEGIN_MSG_MAP for +// CWindowImpl/CDialogImpl derived classes, but must use CR_BEGIN_MSG_MAP_EX for +// classes that don't derive from CWindowImpl/CDialogImpl. +// Classes using the CR_BEGIN_MSG_MAP_EX/CR_END_MSG_MAP set of macros must +// also include a CR_MSG_MAP_CLASS_DECLARATIONS macro after all members in +// the class definition since the macros add a +// base::WeakPtrFactory which is only allowed if last in the class. + +#define CR_BEGIN_MSG_MAP_EX(theClass) \ + public: \ + /* "handled" management for cracked handlers */ \ + void SetMsgHandled(BOOL handled) { msg_handled_ = handled; } \ + BOOL ProcessWindowMessage(HWND hwnd, UINT msg, WPARAM w_param, \ + LPARAM l_param, LRESULT& l_result, \ + DWORD msg_map_id = 0) override { \ + auto ref(theClass::msg_handler_weak_factory_.GetWeakPtr()); \ + BOOL old_msg_handled = msg_handled_; \ + BOOL ret = _ProcessWindowMessage(hwnd, msg, w_param, l_param, l_result, \ + msg_map_id); \ + if (ref.get()) \ + msg_handled_ = old_msg_handled; \ + return ret; \ + } \ + BOOL _ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, \ + LPARAM lParam, LRESULT& lResult, \ + DWORD dwMsgMapID) { \ + auto ref(theClass::msg_handler_weak_factory_.GetWeakPtr()); \ + BOOL bHandled = TRUE; \ + hWnd; \ + uMsg; \ + wParam; \ + lParam; \ + lResult; \ + bHandled; \ + switch (dwMsgMapID) { \ + case 0: + +// Replacement for atlwin.h's END_MSG_MAP for removing ATL usage. +#define CR_END_MSG_MAP() \ + break; \ + default: \ + NOTREACHED() << "Invalid message map ID: " << dwMsgMapID; \ + break; \ + } \ + return FALSE; \ + } + +// This macro must be last in the class since it contains a +// base::WeakPtrFactory which must be last in the class. +#define CR_MSG_MAP_CLASS_DECLARATIONS(theClass) \ + private: \ + BOOL msg_handled_{false}; \ + base::WeakPtrFactory msg_handler_weak_factory_{this}; + +#define CR_GET_X_LPARAM(lParam) ((int)(short)LOWORD(lParam)) +#define CR_GET_Y_LPARAM(lParam) ((int)(short)HIWORD(lParam)) + +/////////////////////////////////////////////////////////////////////////////// +// Standard Windows message macros + +// int OnCreate(LPCREATESTRUCT lpCreateStruct) +#define CR_MSG_WM_CREATE(func) \ + if (uMsg == WM_CREATE) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func((LPCREATESTRUCT)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// BOOL OnInitDialog(CWindow wndFocus, LPARAM lInitParam) +#define CR_MSG_WM_INITDIALOG(func) \ + if (uMsg == WM_INITDIALOG) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func((HWND)wParam, lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// BOOL OnCopyData(CWindow wnd, PCOPYDATASTRUCT pCopyDataStruct) +#define CR_MSG_WM_COPYDATA(func) \ + if (uMsg == WM_COPYDATA) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func((HWND)wParam, (PCOPYDATASTRUCT)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnDestroy() +#define CR_MSG_WM_DESTROY(func) \ + if (uMsg == WM_DESTROY) { \ + SetMsgHandled(TRUE); \ + func(); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnMove(CPoint ptPos) +#define CR_MSG_WM_MOVE(func) \ + if (uMsg == WM_MOVE) { \ + SetMsgHandled(TRUE); \ + func(gfx::Point(CR_GET_X_LPARAM(lParam), CR_GET_Y_LPARAM(lParam))); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnSize(UINT nType, gfx::Size size) +#define CR_MSG_WM_SIZE(func) \ + if (uMsg == WM_SIZE) { \ + SetMsgHandled(TRUE); \ + func((UINT)wParam, \ + gfx::Size(CR_GET_X_LPARAM(lParam), CR_GET_Y_LPARAM(lParam))); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnActivate(UINT nState, BOOL bMinimized, CWindow wndOther) +#define CR_MSG_WM_ACTIVATE(func) \ + if (uMsg == WM_ACTIVATE) { \ + SetMsgHandled(TRUE); \ + func((UINT)LOWORD(wParam), (BOOL)HIWORD(wParam), (HWND)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnSetFocus(CWindow wndOld) +#define CR_MSG_WM_SETFOCUS(func) \ + if (uMsg == WM_SETFOCUS) { \ + SetMsgHandled(TRUE); \ + func((HWND)wParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnKillFocus(CWindow wndFocus) +#define CR_MSG_WM_KILLFOCUS(func) \ + if (uMsg == WM_KILLFOCUS) { \ + SetMsgHandled(TRUE); \ + func((HWND)wParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnEnable(BOOL bEnable) +#define CR_MSG_WM_ENABLE(func) \ + if (uMsg == WM_ENABLE) { \ + SetMsgHandled(TRUE); \ + func((BOOL)wParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnPaint(CDCHandle dc) +#define CR_MSG_WM_PAINT(func) \ + if (uMsg == WM_PAINT) { \ + SetMsgHandled(TRUE); \ + func((HDC)wParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnClose() +#define CR_MSG_WM_CLOSE(func) \ + if (uMsg == WM_CLOSE) { \ + SetMsgHandled(TRUE); \ + func(); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// BOOL OnQueryEndSession(UINT nSource, UINT uLogOff) +#define CR_MSG_WM_QUERYENDSESSION(func) \ + if (uMsg == WM_QUERYENDSESSION) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func((UINT)wParam, (UINT)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// BOOL OnQueryOpen() +#define CR_MSG_WM_QUERYOPEN(func) \ + if (uMsg == WM_QUERYOPEN) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func(); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// BOOL OnEraseBkgnd(CDCHandle dc) +#define CR_MSG_WM_ERASEBKGND(func) \ + if (uMsg == WM_ERASEBKGND) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func((HDC)wParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnSysColorChange() +#define CR_MSG_WM_SYSCOLORCHANGE(func) \ + if (uMsg == WM_SYSCOLORCHANGE) { \ + SetMsgHandled(TRUE); \ + func(); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnEndSession(BOOL bEnding, UINT uLogOff) +#define CR_MSG_WM_ENDSESSION(func) \ + if (uMsg == WM_ENDSESSION) { \ + SetMsgHandled(TRUE); \ + func((BOOL)wParam, (UINT)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnShowWindow(BOOL bShow, UINT nStatus) +#define CR_MSG_WM_SHOWWINDOW(func) \ + if (uMsg == WM_SHOWWINDOW) { \ + SetMsgHandled(TRUE); \ + func((BOOL)wParam, (int)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// HBRUSH OnCtlColorEdit(CDCHandle dc, CEdit edit) +#define CR_MSG_WM_CTLCOLOREDIT(func) \ + if (uMsg == WM_CTLCOLOREDIT) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func((HDC)wParam, (HWND)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// HBRUSH OnCtlColorListBox(CDCHandle dc, CListBox listBox) +#define CR_MSG_WM_CTLCOLORLISTBOX(func) \ + if (uMsg == WM_CTLCOLORLISTBOX) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func((HDC)wParam, (HWND)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// HBRUSH OnCtlColorBtn(CDCHandle dc, CButton button) +#define CR_MSG_WM_CTLCOLORBTN(func) \ + if (uMsg == WM_CTLCOLORBTN) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func((HDC)wParam, (HWND)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// HBRUSH OnCtlColorDlg(CDCHandle dc, CWindow wnd) +#define CR_MSG_WM_CTLCOLORDLG(func) \ + if (uMsg == WM_CTLCOLORDLG) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func((HDC)wParam, (HWND)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// HBRUSH OnCtlColorScrollBar(CDCHandle dc, CScrollBar scrollBar) +#define CR_MSG_WM_CTLCOLORSCROLLBAR(func) \ + if (uMsg == WM_CTLCOLORSCROLLBAR) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func((HDC)wParam, (HWND)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// HBRUSH OnCtlColorStatic(CDCHandle dc, CStatic wndStatic) +#define CR_MSG_WM_CTLCOLORSTATIC(func) \ + if (uMsg == WM_CTLCOLORSTATIC) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func((HDC)wParam, (HWND)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnSettingChange(UINT uFlags, LPCTSTR lpszSection) +#define CR_MSG_WM_SETTINGCHANGE(func) \ + if (uMsg == WM_SETTINGCHANGE) { \ + SetMsgHandled(TRUE); \ + func((UINT)wParam, (LPCTSTR)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnDevModeChange(LPCTSTR lpDeviceName) +#define CR_MSG_WM_DEVMODECHANGE(func) \ + if (uMsg == WM_DEVMODECHANGE) { \ + SetMsgHandled(TRUE); \ + func((LPCTSTR)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnActivateApp(BOOL bActive, DWORD dwThreadID) +#define CR_MSG_WM_ACTIVATEAPP(func) \ + if (uMsg == WM_ACTIVATEAPP) { \ + SetMsgHandled(TRUE); \ + func((BOOL)wParam, (DWORD)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnFontChange() +#define CR_MSG_WM_FONTCHANGE(func) \ + if (uMsg == WM_FONTCHANGE) { \ + SetMsgHandled(TRUE); \ + func(); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnTimeChange() +#define CR_MSG_WM_TIMECHANGE(func) \ + if (uMsg == WM_TIMECHANGE) { \ + SetMsgHandled(TRUE); \ + func(); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnCancelMode() +#define CR_MSG_WM_CANCELMODE(func) \ + if (uMsg == WM_CANCELMODE) { \ + SetMsgHandled(TRUE); \ + func(); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// BOOL OnSetCursor(CWindow wnd, UINT nHitTest, UINT message) +#define CR_MSG_WM_SETCURSOR(func) \ + if (uMsg == WM_SETCURSOR) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func((HWND)wParam, (UINT)LOWORD(lParam), \ + (UINT)HIWORD(lParam)); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// int OnMouseActivate(CWindow wndTopLevel, UINT nHitTest, UINT message) +#define CR_MSG_WM_MOUSEACTIVATE(func) \ + if (uMsg == WM_MOUSEACTIVATE) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func((HWND)wParam, (UINT)LOWORD(lParam), \ + (UINT)HIWORD(lParam)); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnChildActivate() +#define CR_MSG_WM_CHILDACTIVATE(func) \ + if (uMsg == WM_CHILDACTIVATE) { \ + SetMsgHandled(TRUE); \ + func(); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnGetMinMaxInfo(LPMINMAXINFO lpMMI) +#define CR_MSG_WM_GETMINMAXINFO(func) \ + if (uMsg == WM_GETMINMAXINFO) { \ + SetMsgHandled(TRUE); \ + func((LPMINMAXINFO)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnIconEraseBkgnd(CDCHandle dc) +#define CR_MSG_WM_ICONERASEBKGND(func) \ + if (uMsg == WM_ICONERASEBKGND) { \ + SetMsgHandled(TRUE); \ + func((HDC)wParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnSpoolerStatus(UINT nStatus, UINT nJobs) +#define CR_MSG_WM_SPOOLERSTATUS(func) \ + if (uMsg == WM_SPOOLERSTATUS) { \ + SetMsgHandled(TRUE); \ + func((UINT)wParam, (UINT)LOWORD(lParam)); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct) +#define CR_MSG_WM_DRAWITEM(func) \ + if (uMsg == WM_DRAWITEM) { \ + SetMsgHandled(TRUE); \ + func((UINT)wParam, (LPDRAWITEMSTRUCT)lParam); \ + lResult = TRUE; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct) +#define CR_MSG_WM_MEASUREITEM(func) \ + if (uMsg == WM_MEASUREITEM) { \ + SetMsgHandled(TRUE); \ + func((UINT)wParam, (LPMEASUREITEMSTRUCT)lParam); \ + lResult = TRUE; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnDeleteItem(int nIDCtl, LPDELETEITEMSTRUCT lpDeleteItemStruct) +#define CR_MSG_WM_DELETEITEM(func) \ + if (uMsg == WM_DELETEITEM) { \ + SetMsgHandled(TRUE); \ + func((UINT)wParam, (LPDELETEITEMSTRUCT)lParam); \ + lResult = TRUE; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// int OnCharToItem(UINT nChar, UINT nIndex, CListBox listBox) +#define CR_MSG_WM_CHARTOITEM(func) \ + if (uMsg == WM_CHARTOITEM) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func((UINT)LOWORD(wParam), (UINT)HIWORD(wParam), \ + (HWND)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// int OnVKeyToItem(UINT nKey, UINT nIndex, CListBox listBox) +#define CR_MSG_WM_VKEYTOITEM(func) \ + if (uMsg == WM_VKEYTOITEM) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func((UINT)LOWORD(wParam), (UINT)HIWORD(wParam), \ + (HWND)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// HCURSOR OnQueryDragIcon() +#define CR_MSG_WM_QUERYDRAGICON(func) \ + if (uMsg == WM_QUERYDRAGICON) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func(); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// int OnCompareItem(int nIDCtl, LPCOMPAREITEMSTRUCT lpCompareItemStruct) +#define CR_MSG_WM_COMPAREITEM(func) \ + if (uMsg == WM_COMPAREITEM) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func((UINT)wParam, (LPCOMPAREITEMSTRUCT)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnCompacting(UINT nCpuTime) +#define CR_MSG_WM_COMPACTING(func) \ + if (uMsg == WM_COMPACTING) { \ + SetMsgHandled(TRUE); \ + func((UINT)wParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// BOOL OnNcCreate(LPCREATESTRUCT lpCreateStruct) +#define CR_MSG_WM_NCCREATE(func) \ + if (uMsg == WM_NCCREATE) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func((LPCREATESTRUCT)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnNcDestroy() +#define CR_MSG_WM_NCDESTROY(func) \ + if (uMsg == WM_NCDESTROY) { \ + SetMsgHandled(TRUE); \ + func(); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// LRESULT OnNcCalcSize(BOOL bCalcValidRects, LPARAM lParam) +#define CR_MSG_WM_NCCALCSIZE(func) \ + if (uMsg == WM_NCCALCSIZE) { \ + SetMsgHandled(TRUE); \ + lResult = func((BOOL)wParam, lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// UINT OnNcHitTest(gfx::Point point) +#define CR_MSG_WM_NCHITTEST(func) \ + if (uMsg == WM_NCHITTEST) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func( \ + gfx::Point(CR_GET_X_LPARAM(lParam), CR_GET_Y_LPARAM(lParam))); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnNcPaint(CRgn rgn) +#define CR_MSG_WM_NCPAINT(func) \ + if (uMsg == WM_NCPAINT) { \ + SetMsgHandled(TRUE); \ + func((HRGN)wParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// BOOL OnNcActivate(BOOL bActive) +#define CR_MSG_WM_NCACTIVATE(func) \ + if (uMsg == WM_NCACTIVATE) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func((BOOL)wParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// UINT OnGetDlgCode(LPMSG lpMsg) +#define CR_MSG_WM_GETDLGCODE(func) \ + if (uMsg == WM_GETDLGCODE) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func((LPMSG)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnNcMouseMove(UINT nHitTest, gfx::Point point) +#define CR_MSG_WM_NCMOUSEMOVE(func) \ + if (uMsg == WM_NCMOUSEMOVE) { \ + SetMsgHandled(TRUE); \ + func((UINT)wParam, \ + gfx::Point(CR_GET_X_LPARAM(lParam), CR_GET_Y_LPARAM(lParam))); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnNcLButtonDown(UINT nHitTest, gfx::Point point) +#define CR_MSG_WM_NCLBUTTONDOWN(func) \ + if (uMsg == WM_NCLBUTTONDOWN) { \ + SetMsgHandled(TRUE); \ + func((UINT)wParam, \ + gfx::Point(CR_GET_X_LPARAM(lParam), CR_GET_Y_LPARAM(lParam))); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnNcLButtonUp(UINT nHitTest, gfx::Point point) +#define CR_MSG_WM_NCLBUTTONUP(func) \ + if (uMsg == WM_NCLBUTTONUP) { \ + SetMsgHandled(TRUE); \ + func((UINT)wParam, \ + gfx::Point(CR_GET_X_LPARAM(lParam), CR_GET_Y_LPARAM(lParam))); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnNcLButtonDblClk(UINT nHitTest, gfx::Point point) +#define CR_MSG_WM_NCLBUTTONDBLCLK(func) \ + if (uMsg == WM_NCLBUTTONDBLCLK) { \ + SetMsgHandled(TRUE); \ + func((UINT)wParam, \ + gfx::Point(CR_GET_X_LPARAM(lParam), CR_GET_Y_LPARAM(lParam))); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnNcRButtonDown(UINT nHitTest, gfx::Point point) +#define CR_MSG_WM_NCRBUTTONDOWN(func) \ + if (uMsg == WM_NCRBUTTONDOWN) { \ + SetMsgHandled(TRUE); \ + func((UINT)wParam, \ + gfx::Point(CR_GET_X_LPARAM(lParam), CR_GET_Y_LPARAM(lParam))); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnNcRButtonUp(UINT nHitTest, gfx::Point point) +#define CR_MSG_WM_NCRBUTTONUP(func) \ + if (uMsg == WM_NCRBUTTONUP) { \ + SetMsgHandled(TRUE); \ + func((UINT)wParam, \ + gfx::Point(CR_GET_X_LPARAM(lParam), CR_GET_Y_LPARAM(lParam))); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnNcRButtonDblClk(UINT nHitTest, CPoint point) +#define CR_MSG_WM_NCRBUTTONDBLCLK(func) \ + if (uMsg == WM_NCRBUTTONDBLCLK) { \ + SetMsgHandled(TRUE); \ + func((UINT)wParam, \ + gfx::Point(CR_GET_X_LPARAM(lParam), CR_GET_Y_LPARAM(lParam))); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnNcMButtonDown(UINT nHitTest, CPoint point) +#define CR_MSG_WM_NCMBUTTONDOWN(func) \ + if (uMsg == WM_NCMBUTTONDOWN) { \ + SetMsgHandled(TRUE); \ + func((UINT)wParam, \ + gfx::Point(CR_GET_X_LPARAM(lParam), CR_GET_Y_LPARAM(lParam))); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnNcMButtonUp(UINT nHitTest, CPoint point) +#define CR_MSG_WM_NCMBUTTONUP(func) \ + if (uMsg == WM_NCMBUTTONUP) { \ + SetMsgHandled(TRUE); \ + func((UINT)wParam, \ + gfx::Point(CR_GET_X_LPARAM(lParam), CR_GET_Y_LPARAM(lParam))); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnNcMButtonDblClk(UINT nHitTest, CPoint point) +#define CR_MSG_WM_NCMBUTTONDBLCLK(func) \ + if (uMsg == WM_NCMBUTTONDBLCLK) { \ + SetMsgHandled(TRUE); \ + func((UINT)wParam, \ + gfx::Point(CR_GET_X_LPARAM(lParam), CR_GET_Y_LPARAM(lParam))); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) +#define CR_MSG_WM_KEYDOWN(func) \ + if (uMsg == WM_KEYDOWN) { \ + SetMsgHandled(TRUE); \ + func((TCHAR)wParam, (UINT)lParam & 0xFFFF, \ + (UINT)((lParam & 0xFFFF0000) >> 16)); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags) +#define CR_MSG_WM_KEYUP(func) \ + if (uMsg == WM_KEYUP) { \ + SetMsgHandled(TRUE); \ + func((TCHAR)wParam, (UINT)lParam & 0xFFFF, \ + (UINT)((lParam & 0xFFFF0000) >> 16)); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) +#define CR_MSG_WM_CHAR(func) \ + if (uMsg == WM_CHAR) { \ + SetMsgHandled(TRUE); \ + func((TCHAR)wParam, (UINT)lParam & 0xFFFF, \ + (UINT)((lParam & 0xFFFF0000) >> 16)); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnDeadChar(UINT nChar, UINT nRepCnt, UINT nFlags) +#define CR_MSG_WM_DEADCHAR(func) \ + if (uMsg == WM_DEADCHAR) { \ + SetMsgHandled(TRUE); \ + func((TCHAR)wParam, (UINT)lParam & 0xFFFF, \ + (UINT)((lParam & 0xFFFF0000) >> 16)); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnSysKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) +#define CR_MSG_WM_SYSKEYDOWN(func) \ + if (uMsg == WM_SYSKEYDOWN) { \ + SetMsgHandled(TRUE); \ + func((TCHAR)wParam, (UINT)lParam & 0xFFFF, \ + (UINT)((lParam & 0xFFFF0000) >> 16)); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnSysKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags) +#define CR_MSG_WM_SYSKEYUP(func) \ + if (uMsg == WM_SYSKEYUP) { \ + SetMsgHandled(TRUE); \ + func((TCHAR)wParam, (UINT)lParam & 0xFFFF, \ + (UINT)((lParam & 0xFFFF0000) >> 16)); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnSysChar(UINT nChar, UINT nRepCnt, UINT nFlags) +#define CR_MSG_WM_SYSCHAR(func) \ + if (uMsg == WM_SYSCHAR) { \ + SetMsgHandled(TRUE); \ + func((TCHAR)wParam, (UINT)lParam & 0xFFFF, \ + (UINT)((lParam & 0xFFFF0000) >> 16)); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnSysDeadChar(UINT nChar, UINT nRepCnt, UINT nFlags) +#define CR_MSG_WM_SYSDEADCHAR(func) \ + if (uMsg == WM_SYSDEADCHAR) { \ + SetMsgHandled(TRUE); \ + func((TCHAR)wParam, (UINT)lParam & 0xFFFF, \ + (UINT)((lParam & 0xFFFF0000) >> 16)); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnSysCommand(UINT nID, LPARAM lParam) +#define CR_MSG_WM_SYSCOMMAND(func) \ + if (uMsg == WM_SYSCOMMAND) { \ + SetMsgHandled(TRUE); \ + func((UINT)wParam, \ + gfx::Point(CR_GET_X_LPARAM(lParam), CR_GET_Y_LPARAM(lParam))); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnTCard(UINT idAction, DWORD dwActionData) +#define CR_MSG_WM_TCARD(func) \ + if (uMsg == WM_TCARD) { \ + SetMsgHandled(TRUE); \ + func((UINT)wParam, (DWORD)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnTimer(UINT_PTR nIDEvent) +#define CR_MSG_WM_TIMER(func) \ + if (uMsg == WM_TIMER) { \ + SetMsgHandled(TRUE); \ + func((UINT_PTR)wParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnHScroll(UINT nSBCode, UINT nPos, CScrollBar pScrollBar) +#define CR_MSG_WM_HSCROLL(func) \ + if (uMsg == WM_HSCROLL) { \ + SetMsgHandled(TRUE); \ + func((int)LOWORD(wParam), (short)HIWORD(wParam), (HWND)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnVScroll(UINT nSBCode, UINT nPos, CScrollBar pScrollBar) +#define CR_MSG_WM_VSCROLL(func) \ + if (uMsg == WM_VSCROLL) { \ + SetMsgHandled(TRUE); \ + func((int)LOWORD(wParam), (short)HIWORD(wParam), (HWND)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnInitMenu(CMenu menu) +#define CR_MSG_WM_INITMENU(func) \ + if (uMsg == WM_INITMENU) { \ + SetMsgHandled(TRUE); \ + func((HMENU)wParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnInitMenuPopup(CMenu menuPopup, UINT nIndex, BOOL bSysMenu) +#define CR_MSG_WM_INITMENUPOPUP(func) \ + if (uMsg == WM_INITMENUPOPUP) { \ + SetMsgHandled(TRUE); \ + func((HMENU)wParam, (UINT)LOWORD(lParam), (BOOL)HIWORD(lParam)); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnMenuSelect(UINT nItemID, UINT nFlags, CMenu menu) +#define CR_MSG_WM_MENUSELECT(func) \ + if (uMsg == WM_MENUSELECT) { \ + SetMsgHandled(TRUE); \ + func((UINT)LOWORD(wParam), (UINT)HIWORD(wParam), (HMENU)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// LRESULT OnMenuChar(UINT nChar, UINT nFlags, CMenu menu) +#define CR_MSG_WM_MENUCHAR(func) \ + if (uMsg == WM_MENUCHAR) { \ + SetMsgHandled(TRUE); \ + lResult = \ + func((TCHAR)LOWORD(wParam), (UINT)HIWORD(wParam), (HMENU)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// LRESULT OnNotify(int idCtrl, LPNMHDR pnmh) +#define CR_MSG_WM_NOTIFY(func) \ + if (uMsg == WM_NOTIFY) { \ + SetMsgHandled(TRUE); \ + lResult = func((int)wParam, (LPNMHDR)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnEnterIdle(UINT nWhy, CWindow wndWho) +#define CR_MSG_WM_ENTERIDLE(func) \ + if (uMsg == WM_ENTERIDLE) { \ + SetMsgHandled(TRUE); \ + func((UINT)wParam, (HWND)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnMouseMove(UINT nFlags, CPoint point) +#define CR_MSG_WM_MOUSEMOVE(func) \ + if (uMsg == WM_MOUSEMOVE) { \ + SetMsgHandled(TRUE); \ + func((UINT)wParam, \ + gfx::Point(CR_GET_X_LPARAM(lParam), CR_GET_Y_LPARAM(lParam))); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// BOOL OnMouseWheel(UINT nFlags, short zDelta, CPoint pt) +#define CR_MSG_WM_MOUSEWHEEL(func) \ + if (uMsg == WM_MOUSEWHEEL) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func( \ + (UINT)LOWORD(wParam), (short)HIWORD(wParam), \ + gfx::Point(CR_GET_X_LPARAM(lParam), CR_GET_Y_LPARAM(lParam))); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnLButtonDown(UINT nFlags, CPoint point) +#define CR_MSG_WM_LBUTTONDOWN(func) \ + if (uMsg == WM_LBUTTONDOWN) { \ + SetMsgHandled(TRUE); \ + func((UINT)wParam, \ + gfx::Point(CR_GET_X_LPARAM(lParam), CR_GET_Y_LPARAM(lParam))); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnLButtonUp(UINT nFlags, CPoint point) +#define CR_MSG_WM_LBUTTONUP(func) \ + if (uMsg == WM_LBUTTONUP) { \ + SetMsgHandled(TRUE); \ + func((UINT)wParam, \ + gfx::Point(CR_GET_X_LPARAM(lParam), CR_GET_Y_LPARAM(lParam))); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnLButtonDblClk(UINT nFlags, CPoint point) +#define CR_MSG_WM_LBUTTONDBLCLK(func) \ + if (uMsg == WM_LBUTTONDBLCLK) { \ + SetMsgHandled(TRUE); \ + func((UINT)wParam, \ + gfx::Point(CR_GET_X_LPARAM(lParam), CR_GET_Y_LPARAM(lParam))); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnRButtonDown(UINT nFlags, CPoint point) +#define CR_MSG_WM_RBUTTONDOWN(func) \ + if (uMsg == WM_RBUTTONDOWN) { \ + SetMsgHandled(TRUE); \ + func((UINT)wParam, \ + gfx::Point(CR_GET_X_LPARAM(lParam), CR_GET_Y_LPARAM(lParam))); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnRButtonUp(UINT nFlags, CPoint point) +#define CR_MSG_WM_RBUTTONUP(func) \ + if (uMsg == WM_RBUTTONUP) { \ + SetMsgHandled(TRUE); \ + func((UINT)wParam, \ + gfx::Point(CR_GET_X_LPARAM(lParam), CR_GET_Y_LPARAM(lParam))); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnRButtonDblClk(UINT nFlags, CPoint point) +#define CR_MSG_WM_RBUTTONDBLCLK(func) \ + if (uMsg == WM_RBUTTONDBLCLK) { \ + SetMsgHandled(TRUE); \ + func((UINT)wParam, \ + gfx::Point(CR_GET_X_LPARAM(lParam), CR_GET_Y_LPARAM(lParam))); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnMButtonDown(UINT nFlags, CPoint point) +#define CR_MSG_WM_MBUTTONDOWN(func) \ + if (uMsg == WM_MBUTTONDOWN) { \ + SetMsgHandled(TRUE); \ + func((UINT)wParam, \ + gfx::Point(CR_GET_X_LPARAM(lParam), CR_GET_Y_LPARAM(lParam))); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnMButtonUp(UINT nFlags, CPoint point) +#define CR_MSG_WM_MBUTTONUP(func) \ + if (uMsg == WM_MBUTTONUP) { \ + SetMsgHandled(TRUE); \ + func((UINT)wParam, \ + gfx::Point(CR_GET_X_LPARAM(lParam), CR_GET_Y_LPARAM(lParam))); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnMButtonDblClk(UINT nFlags, CPoint point) +#define CR_MSG_WM_MBUTTONDBLCLK(func) \ + if (uMsg == WM_MBUTTONDBLCLK) { \ + SetMsgHandled(TRUE); \ + func((UINT)wParam, \ + gfx::Point(CR_GET_X_LPARAM(lParam), CR_GET_Y_LPARAM(lParam))); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnParentNotify(UINT message, UINT nChildID, LPARAM lParam) +#define CR_MSG_WM_PARENTNOTIFY(func) \ + if (uMsg == WM_PARENTNOTIFY) { \ + SetMsgHandled(TRUE); \ + func((UINT)LOWORD(wParam), (UINT)HIWORD(wParam), lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnMDIActivate(CWindow wndActivate, CWindow wndDeactivate) +#define CR_MSG_WM_MDIACTIVATE(func) \ + if (uMsg == WM_MDIACTIVATE) { \ + SetMsgHandled(TRUE); \ + func((HWND)wParam, (HWND)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnRenderFormat(UINT nFormat) +#define CR_MSG_WM_RENDERFORMAT(func) \ + if (uMsg == WM_RENDERFORMAT) { \ + SetMsgHandled(TRUE); \ + func((UINT)wParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnRenderAllFormats() +#define CR_MSG_WM_RENDERALLFORMATS(func) \ + if (uMsg == WM_RENDERALLFORMATS) { \ + SetMsgHandled(TRUE); \ + func(); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnDestroyClipboard() +#define CR_MSG_WM_DESTROYCLIPBOARD(func) \ + if (uMsg == WM_DESTROYCLIPBOARD) { \ + SetMsgHandled(TRUE); \ + func(); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnDrawClipboard() +#define CR_MSG_WM_DRAWCLIPBOARD(func) \ + if (uMsg == WM_DRAWCLIPBOARD) { \ + SetMsgHandled(TRUE); \ + func(); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnPaintClipboard(CWindow wndViewer, const LPPAINTSTRUCT lpPaintStruct) +#define CR_MSG_WM_PAINTCLIPBOARD(func) \ + if (uMsg == WM_PAINTCLIPBOARD) { \ + SetMsgHandled(TRUE); \ + func((HWND)wParam, (const LPPAINTSTRUCT)::GlobalLock((HGLOBAL)lParam)); \ + ::GlobalUnlock((HGLOBAL)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnVScrollClipboard(CWindow wndViewer, UINT nSBCode, UINT nPos) +#define CR_MSG_WM_VSCROLLCLIPBOARD(func) \ + if (uMsg == WM_VSCROLLCLIPBOARD) { \ + SetMsgHandled(TRUE); \ + func((HWND)wParam, (UINT)LOWORD(lParam), (UINT)HIWORD(lParam)); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnContextMenu(CWindow wnd, CPoint point) +#define CR_MSG_WM_CONTEXTMENU(func) \ + if (uMsg == WM_CONTEXTMENU) { \ + SetMsgHandled(TRUE); \ + func((HWND)wParam, \ + gfx::Point(CR_GET_X_LPARAM(lParam), CR_GET_Y_LPARAM(lParam))); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnSizeClipboard(CWindow wndViewer, const LPRECT lpRect) +#define CR_MSG_WM_SIZECLIPBOARD(func) \ + if (uMsg == WM_SIZECLIPBOARD) { \ + SetMsgHandled(TRUE); \ + func((HWND)wParam, (const LPRECT)::GlobalLock((HGLOBAL)lParam)); \ + ::GlobalUnlock((HGLOBAL)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnAskCbFormatName(UINT nMaxCount, LPTSTR lpszString) +#define CR_MSG_WM_ASKCBFORMATNAME(func) \ + if (uMsg == WM_ASKCBFORMATNAME) { \ + SetMsgHandled(TRUE); \ + func((DWORD)wParam, (LPTSTR)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnChangeCbChain(CWindow wndRemove, CWindow wndAfter) +#define CR_MSG_WM_CHANGECBCHAIN(func) \ + if (uMsg == WM_CHANGECBCHAIN) { \ + SetMsgHandled(TRUE); \ + func((HWND)wParam, (HWND)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnHScrollClipboard(CWindow wndViewer, UINT nSBCode, UINT nPos) +#define CR_MSG_WM_HSCROLLCLIPBOARD(func) \ + if (uMsg == WM_HSCROLLCLIPBOARD) { \ + SetMsgHandled(TRUE); \ + func((HWND)wParam, (UINT)LOWORD(lParam), (UINT)HIWORD(lParam)); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// BOOL OnQueryNewPalette() +#define CR_MSG_WM_QUERYNEWPALETTE(func) \ + if (uMsg == WM_QUERYNEWPALETTE) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func(); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnPaletteChanged(CWindow wndFocus) +#define CR_MSG_WM_PALETTECHANGED(func) \ + if (uMsg == WM_PALETTECHANGED) { \ + SetMsgHandled(TRUE); \ + func((HWND)wParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnPaletteIsChanging(CWindow wndPalChg) +#define CR_MSG_WM_PALETTEISCHANGING(func) \ + if (uMsg == WM_PALETTEISCHANGING) { \ + SetMsgHandled(TRUE); \ + func((HWND)wParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnDropFiles(HDROP hDropInfo) +#define CR_MSG_WM_DROPFILES(func) \ + if (uMsg == WM_DROPFILES) { \ + SetMsgHandled(TRUE); \ + func((HDROP)wParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnWindowPosChanging(LPWINDOWPOS lpWndPos) +#define CR_MSG_WM_WINDOWPOSCHANGING(func) \ + if (uMsg == WM_WINDOWPOSCHANGING) { \ + SetMsgHandled(TRUE); \ + func((LPWINDOWPOS)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnWindowPosChanged(LPWINDOWPOS lpWndPos) +#define CR_MSG_WM_WINDOWPOSCHANGED(func) \ + if (uMsg == WM_WINDOWPOSCHANGED) { \ + SetMsgHandled(TRUE); \ + func((LPWINDOWPOS)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnExitMenuLoop(BOOL fIsTrackPopupMenu) +#define CR_MSG_WM_EXITMENULOOP(func) \ + if (uMsg == WM_EXITMENULOOP) { \ + SetMsgHandled(TRUE); \ + func((BOOL)wParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnEnterMenuLoop(BOOL fIsTrackPopupMenu) +#define CR_MSG_WM_ENTERMENULOOP(func) \ + if (uMsg == WM_ENTERMENULOOP) { \ + SetMsgHandled(TRUE); \ + func((BOOL)wParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnStyleChanged(int nStyleType, LPSTYLESTRUCT lpStyleStruct) +#define CR_MSG_WM_STYLECHANGED(func) \ + if (uMsg == WM_STYLECHANGED) { \ + SetMsgHandled(TRUE); \ + func((UINT)wParam, (LPSTYLESTRUCT)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnStyleChanging(int nStyleType, LPSTYLESTRUCT lpStyleStruct) +#define CR_MSG_WM_STYLECHANGING(func) \ + if (uMsg == WM_STYLECHANGING) { \ + SetMsgHandled(TRUE); \ + func((UINT)wParam, (LPSTYLESTRUCT)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnSizing(UINT fwSide, LPRECT pRect) +#define CR_MSG_WM_SIZING(func) \ + if (uMsg == WM_SIZING) { \ + SetMsgHandled(TRUE); \ + func((UINT)wParam, (LPRECT)lParam); \ + lResult = TRUE; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnMoving(UINT fwSide, LPRECT pRect) +#define CR_MSG_WM_MOVING(func) \ + if (uMsg == WM_MOVING) { \ + SetMsgHandled(TRUE); \ + func((UINT)wParam, (LPRECT)lParam); \ + lResult = TRUE; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnCaptureChanged(CWindow wnd) +#define CR_MSG_WM_CAPTURECHANGED(func) \ + if (uMsg == WM_CAPTURECHANGED) { \ + SetMsgHandled(TRUE); \ + func((HWND)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// BOOL OnDeviceChange(UINT nEventType, DWORD dwData) +#define CR_MSG_WM_DEVICECHANGE(func) \ + if (uMsg == WM_DEVICECHANGE) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func((UINT)wParam, (DWORD)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnCommand(UINT uNotifyCode, int nID, CWindow wndCtl) +#define CR_MSG_WM_COMMAND(func) \ + if (uMsg == WM_COMMAND) { \ + SetMsgHandled(TRUE); \ + func((UINT)HIWORD(wParam), (int)LOWORD(wParam), (HWND)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnDisplayChange(UINT uBitsPerPixel, gfx::Size sizeScreen) +#define CR_MSG_WM_DISPLAYCHANGE(func) \ + if (uMsg == WM_DISPLAYCHANGE) { \ + SetMsgHandled(TRUE); \ + func((UINT)wParam, \ + gfx::Size(CR_GET_X_LPARAM(lParam), CR_GET_Y_LPARAM(lParam))); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnEnterSizeMove() +#define CR_MSG_WM_ENTERSIZEMOVE(func) \ + if (uMsg == WM_ENTERSIZEMOVE) { \ + SetMsgHandled(TRUE); \ + func(); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnExitSizeMove() +#define CR_MSG_WM_EXITSIZEMOVE(func) \ + if (uMsg == WM_EXITSIZEMOVE) { \ + SetMsgHandled(TRUE); \ + func(); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// HFONT OnGetFont() +#define CR_MSG_WM_GETFONT(func) \ + if (uMsg == WM_GETFONT) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func(); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// LRESULT OnGetHotKey() +#define CR_MSG_WM_GETHOTKEY(func) \ + if (uMsg == WM_GETHOTKEY) { \ + SetMsgHandled(TRUE); \ + lResult = func(); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// HICON OnGetIcon() +#define CR_MSG_WM_GETICON(func) \ + if (uMsg == WM_GETICON) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func((UINT)wParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// int OnGetText(int cchTextMax, LPTSTR lpszText) +#define CR_MSG_WM_GETTEXT(func) \ + if (uMsg == WM_GETTEXT) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func((int)wParam, (LPTSTR)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// int OnGetTextLength() +#define CR_MSG_WM_GETTEXTLENGTH(func) \ + if (uMsg == WM_GETTEXTLENGTH) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func(); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnHelp(LPHELPINFO lpHelpInfo) +#define CR_MSG_WM_HELP(func) \ + if (uMsg == WM_HELP) { \ + SetMsgHandled(TRUE); \ + func((LPHELPINFO)lParam); \ + lResult = TRUE; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnHotKey(int nHotKeyID, UINT uModifiers, UINT uVirtKey) +#define CR_MSG_WM_HOTKEY(func) \ + if (uMsg == WM_HOTKEY) { \ + SetMsgHandled(TRUE); \ + func((int)wParam, (UINT)LOWORD(lParam), (UINT)HIWORD(lParam)); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnInputLangChange(DWORD dwCharSet, HKL hKbdLayout) +#define CR_MSG_WM_INPUTLANGCHANGE(func) \ + if (uMsg == WM_INPUTLANGCHANGE) { \ + SetMsgHandled(TRUE); \ + func((DWORD)wParam, (HKL)lParam); \ + lResult = TRUE; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnInputLangChangeRequest(BOOL bSysCharSet, HKL hKbdLayout) +#define CR_MSG_WM_INPUTLANGCHANGEREQUEST(func) \ + if (uMsg == WM_INPUTLANGCHANGEREQUEST) { \ + SetMsgHandled(TRUE); \ + func((BOOL)wParam, (HKL)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnNextDlgCtl(BOOL bHandle, WPARAM wCtlFocus) +#define CR_MSG_WM_NEXTDLGCTL(func) \ + if (uMsg == WM_NEXTDLGCTL) { \ + SetMsgHandled(TRUE); \ + func((BOOL)LOWORD(lParam), wParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnNextMenu(int nVirtKey, LPMDINEXTMENU lpMdiNextMenu) +#define CR_MSG_WM_NEXTMENU(func) \ + if (uMsg == WM_NEXTMENU) { \ + SetMsgHandled(TRUE); \ + func((int)wParam, (LPMDINEXTMENU)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// int OnNotifyFormat(CWindow wndFrom, int nCommand) +#define CR_MSG_WM_NOTIFYFORMAT(func) \ + if (uMsg == WM_NOTIFYFORMAT) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func((HWND)wParam, (int)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// BOOL OnPowerBroadcast(DWORD dwPowerEvent, DWORD dwData) +#define CR_MSG_WM_POWERBROADCAST(func) \ + if (uMsg == WM_POWERBROADCAST) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func((DWORD)wParam, (DWORD)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnPrint(CDCHandle dc, UINT uFlags) +#define CR_MSG_WM_PRINT(func) \ + if (uMsg == WM_PRINT) { \ + SetMsgHandled(TRUE); \ + func((HDC)wParam, (UINT)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnPrintClient(CDCHandle dc, UINT uFlags) +#define CR_MSG_WM_PRINTCLIENT(func) \ + if (uMsg == WM_PRINTCLIENT) { \ + SetMsgHandled(TRUE); \ + func((HDC)wParam, (UINT)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnRasDialEvent(RASCONNSTATE rasconnstate, DWORD dwError) +#define CR_MSG_WM_RASDIALEVENT(func) \ + if (uMsg == WM_RASDIALEVENT) { \ + SetMsgHandled(TRUE); \ + func((RASCONNSTATE)wParam, (DWORD)lParam); \ + lResult = TRUE; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnSetFont(CFont font, BOOL bRedraw) +#define CR_MSG_WM_SETFONT(func) \ + if (uMsg == WM_SETFONT) { \ + SetMsgHandled(TRUE); \ + func((HFONT)wParam, (BOOL)LOWORD(lParam)); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// int OnSetHotKey(int nVirtKey, UINT uFlags) +#define CR_MSG_WM_SETHOTKEY(func) \ + if (uMsg == WM_SETHOTKEY) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func((int)LOBYTE(LOWORD(wParam)), \ + (UINT)HIBYTE(LOWORD(wParam))); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// HICON OnSetIcon(UINT uType, HICON hIcon) +#define CR_MSG_WM_SETICON(func) \ + if (uMsg == WM_SETICON) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func((UINT)wParam, (HICON)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnSetRedraw(BOOL bRedraw) +#define CR_MSG_WM_SETREDRAW(func) \ + if (uMsg == WM_SETREDRAW) { \ + SetMsgHandled(TRUE); \ + func((BOOL)wParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// int OnSetText(LPCTSTR lpstrText) +#define CR_MSG_WM_SETTEXT(func) \ + if (uMsg == WM_SETTEXT) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func((LPCTSTR)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnUserChanged() +#define CR_MSG_WM_USERCHANGED(func) \ + if (uMsg == WM_USERCHANGED) { \ + SetMsgHandled(TRUE); \ + func(); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +/////////////////////////////////////////////////////////////////////////////// +// New NT4 & NT5 messages + +#if (_WIN32_WINNT >= 0x0400) + +// void OnMouseHover(WPARAM wParam, CPoint ptPos) +#define CR_MSG_WM_MOUSEHOVER(func) \ + if (uMsg == WM_MOUSEHOVER) { \ + SetMsgHandled(TRUE); \ + func(wParam, \ + gfx::Point(CR_GET_X_LPARAM(lParam), CR_GET_Y_LPARAM(lParam))); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnMouseLeave() +#define CR_MSG_WM_MOUSELEAVE(func) \ + if (uMsg == WM_MOUSELEAVE) { \ + SetMsgHandled(TRUE); \ + func(); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +#endif /* _WIN32_WINNT >= 0x0400 */ + +#if (WINVER >= 0x0500) + +// void OnMenuRButtonUp(WPARAM wParam, CMenu menu) +#define CR_MSG_WM_MENURBUTTONUP(func) \ + if (uMsg == WM_MENURBUTTONUP) { \ + SetMsgHandled(TRUE); \ + func(wParam, (HMENU)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// LRESULT OnMenuDrag(WPARAM wParam, CMenu menu) +#define CR_MSG_WM_MENUDRAG(func) \ + if (uMsg == WM_MENUDRAG) { \ + SetMsgHandled(TRUE); \ + lResult = func(wParam, (HMENU)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// LRESULT OnMenuGetObject(PMENUGETOBJECTINFO info) +#define CR_MSG_WM_MENUGETOBJECT(func) \ + if (uMsg == WM_MENUGETOBJECT) { \ + SetMsgHandled(TRUE); \ + lResult = func((PMENUGETOBJECTINFO)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnUnInitMenuPopup(UINT nID, CMenu menu) +#define CR_MSG_WM_UNINITMENUPOPUP(func) \ + if (uMsg == WM_UNINITMENUPOPUP) { \ + SetMsgHandled(TRUE); \ + func((UINT)HIWORD(lParam), (HMENU)wParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnMenuCommand(WPARAM nIndex, CMenu menu) +#define CR_MSG_WM_MENUCOMMAND(func) \ + if (uMsg == WM_MENUCOMMAND) { \ + SetMsgHandled(TRUE); \ + func(wParam, (HMENU)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +#endif /* WINVER >= 0x0500 */ + +#if (_WIN32_WINNT >= 0x0500) + +// BOOL OnAppCommand(CWindow wndFocus, short cmd, WORD uDevice, int dwKeys) +#define CR_MSG_WM_APPCOMMAND(func) \ + if (uMsg == WM_APPCOMMAND) { \ + SetMsgHandled(TRUE); \ + lResult = \ + (LRESULT)func((HWND)wParam, GET_APPCOMMAND_LPARAM(lParam), \ + GET_DEVICE_LPARAM(lParam), GET_KEYSTATE_LPARAM(lParam)); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnNCXButtonDown(int fwButton, short nHittest, CPoint ptPos) +#define CR_MSG_WM_NCXBUTTONDOWN(func) \ + if (uMsg == WM_NCXBUTTONDOWN) { \ + SetMsgHandled(TRUE); \ + func(GET_XBUTTON_WPARAM(wParam), GET_NCHITTEST_WPARAM(wParam), \ + gfx::Point(CR_GET_X_LPARAM(lParam), CR_GET_Y_LPARAM(lParam))); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnNCXButtonUp(int fwButton, short nHittest, CPoint ptPos) +#define CR_MSG_WM_NCXBUTTONUP(func) \ + if (uMsg == WM_NCXBUTTONUP) { \ + SetMsgHandled(TRUE); \ + func(GET_XBUTTON_WPARAM(wParam), GET_NCHITTEST_WPARAM(wParam), \ + gfx::Point(CR_GET_X_LPARAM(lParam), CR_GET_Y_LPARAM(lParam))); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnNCXButtonDblClk(int fwButton, short nHittest, CPoint ptPos) +#define CR_MSG_WM_NCXBUTTONDBLCLK(func) \ + if (uMsg == WM_NCXBUTTONDBLCLK) { \ + SetMsgHandled(TRUE); \ + func(GET_XBUTTON_WPARAM(wParam), GET_NCHITTEST_WPARAM(wParam), \ + gfx::Point(CR_GET_X_LPARAM(lParam), CR_GET_Y_LPARAM(lParam))); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnXButtonDown(int fwButton, int dwKeys, CPoint ptPos) +#define CR_MSG_WM_XBUTTONDOWN(func) \ + if (uMsg == WM_XBUTTONDOWN) { \ + SetMsgHandled(TRUE); \ + func(GET_XBUTTON_WPARAM(wParam), GET_KEYSTATE_WPARAM(wParam), \ + gfx::Point(CR_GET_X_LPARAM(lParam), CR_GET_Y_LPARAM(lParam))); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnXButtonUp(int fwButton, int dwKeys, CPoint ptPos) +#define CR_MSG_WM_XBUTTONUP(func) \ + if (uMsg == WM_XBUTTONUP) { \ + SetMsgHandled(TRUE); \ + func(GET_XBUTTON_WPARAM(wParam), GET_KEYSTATE_WPARAM(wParam), \ + gfx::Point(CR_GET_X_LPARAM(lParam), CR_GET_Y_LPARAM(lParam))); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnXButtonDblClk(int fwButton, int dwKeys, CPoint ptPos) +#define CR_MSG_WM_XBUTTONDBLCLK(func) \ + if (uMsg == WM_XBUTTONDBLCLK) { \ + SetMsgHandled(TRUE); \ + func(GET_XBUTTON_WPARAM(wParam), GET_KEYSTATE_WPARAM(wParam), \ + gfx::Point(CR_GET_X_LPARAM(lParam), CR_GET_Y_LPARAM(lParam))); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnChangeUIState(WORD nAction, WORD nState) +#define CR_MSG_WM_CHANGEUISTATE(func) \ + if (uMsg == WM_CHANGEUISTATE) { \ + SetMsgHandled(TRUE); \ + func(LOWORD(wParam), HIWORD(wParam)); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnUpdateUIState(WORD nAction, WORD nState) +#define CR_MSG_WM_UPDATEUISTATE(func) \ + if (uMsg == WM_UPDATEUISTATE) { \ + SetMsgHandled(TRUE); \ + func(LOWORD(wParam), HIWORD(wParam)); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// LRESULT OnQueryUIState() +#define CR_MSG_WM_QUERYUISTATE(func) \ + if (uMsg == WM_QUERYUISTATE) { \ + SetMsgHandled(TRUE); \ + lResult = func(); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +#endif // (_WIN32_WINNT >= 0x0500) + +#if (_WIN32_WINNT >= 0x0501) + +// void OnInput(WPARAM RawInputCode, HRAWINPUT hRawInput) +#define CR_MSG_WM_INPUT(func) \ + if (uMsg == WM_INPUT) { \ + SetMsgHandled(TRUE); \ + func(GET_RAWINPUT_CODE_WPARAM(wParam), (HRAWINPUT)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnUniChar(TCHAR nChar, UINT nRepCnt, UINT nFlags) +#define CR_MSG_WM_UNICHAR(func) \ + if (uMsg == WM_UNICHAR) { \ + SetMsgHandled(TRUE); \ + func((TCHAR)wParam, (UINT)lParam & 0xFFFF, \ + (UINT)((lParam & 0xFFFF0000) >> 16)); \ + if (!ref.get() || msg_handled_) { \ + lResult = (wParam == UNICODE_NOCHAR) ? TRUE : FALSE; \ + return TRUE; \ + } \ + } + +// OnThemeChanged() +#define CR_MSG_WM_THEMECHANGED(func) \ + if (uMsg == WM_THEMECHANGED) { \ + SetMsgHandled(TRUE); \ + func(); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +#endif /* _WIN32_WINNT >= 0x0501 */ + +/////////////////////////////////////////////////////////////////////////////// +// ATL defined messages + +// BOOL OnForwardMsg(LPMSG Msg, DWORD nUserData) +#define CR_MSG_WM_FORWARDMSG(func) \ + if (uMsg == WM_FORWARDMSG) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func((LPMSG)lParam, (DWORD)wParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +/////////////////////////////////////////////////////////////////////////////// +// Dialog specific messages + +// LRESULT OnDMGetDefID() +#define MSG_DM_GETDEFID(func) \ + if (uMsg == DM_GETDEFID) { \ + SetMsgHandled(TRUE); \ + lResult = func(); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnDMSetDefID(UINT DefID) +#define MSG_DM_SETDEFID(func) \ + if (uMsg == DM_SETDEFID) { \ + SetMsgHandled(TRUE); \ + func((UINT)wParam); \ + lResult = TRUE; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnDMReposition() +#define MSG_DM_REPOSITION(func) \ + if (uMsg == DM_REPOSITION) { \ + SetMsgHandled(TRUE); \ + func(); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +/////////////////////////////////////////////////////////////////////////////// +// Reflected messages + +// void OnReflectedCommand(UINT uNotifyCode, int nID, CWindow wndCtl) +#define MSG_OCM_COMMAND(func) \ + if (uMsg == OCM_COMMAND) { \ + SetMsgHandled(TRUE); \ + func((UINT)HIWORD(wParam), (int)LOWORD(wParam), (HWND)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// LRESULT OnReflectedNotify(int idCtrl, LPNMHDR pnmh) +#define MSG_OCM_NOTIFY(func) \ + if (uMsg == OCM_NOTIFY) { \ + SetMsgHandled(TRUE); \ + lResult = func((int)wParam, (LPNMHDR)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnReflectedParentNotify(UINT message, UINT nChildID, LPARAM lParam) +#define MSG_OCM_PARENTNOTIFY(func) \ + if (uMsg == OCM_PARENTNOTIFY) { \ + SetMsgHandled(TRUE); \ + func((UINT)LOWORD(wParam), (UINT)HIWORD(wParam), lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnReflectedDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct) +#define MSG_OCM_DRAWITEM(func) \ + if (uMsg == OCM_DRAWITEM) { \ + SetMsgHandled(TRUE); \ + func((UINT)wParam, (LPDRAWITEMSTRUCT)lParam); \ + lResult = TRUE; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnReflectedMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT +// lpMeasureItemStruct) +#define MSG_OCM_MEASUREITEM(func) \ + if (uMsg == OCM_MEASUREITEM) { \ + SetMsgHandled(TRUE); \ + func((UINT)wParam, (LPMEASUREITEMSTRUCT)lParam); \ + lResult = TRUE; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// int OnReflectedCompareItem(int nIDCtl, LPCOMPAREITEMSTRUCT +// lpCompareItemStruct) +#define MSG_OCM_COMPAREITEM(func) \ + if (uMsg == OCM_COMPAREITEM) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func((UINT)wParam, (LPCOMPAREITEMSTRUCT)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnReflectedDeleteItem(int nIDCtl, LPDELETEITEMSTRUCT lpDeleteItemStruct) +#define MSG_OCM_DELETEITEM(func) \ + if (uMsg == OCM_DELETEITEM) { \ + SetMsgHandled(TRUE); \ + func((UINT)wParam, (LPDELETEITEMSTRUCT)lParam); \ + lResult = TRUE; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// int OnReflectedVKeyToItem(UINT nKey, UINT nIndex, CListBox listBox) +#define MSG_OCM_VKEYTOITEM(func) \ + if (uMsg == OCM_VKEYTOITEM) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func((UINT)LOWORD(wParam), (UINT)HIWORD(wParam), \ + (HWND)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// int OnReflectedCharToItem(UINT nChar, UINT nIndex, CListBox listBox) +#define MSG_OCM_CHARTOITEM(func) \ + if (uMsg == OCM_CHARTOITEM) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func((UINT)LOWORD(wParam), (UINT)HIWORD(wParam), \ + (HWND)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnReflectedHScroll(UINT nSBCode, UINT nPos, CScrollBar pScrollBar) +#define MSG_OCM_HSCROLL(func) \ + if (uMsg == OCM_HSCROLL) { \ + SetMsgHandled(TRUE); \ + func((int)LOWORD(wParam), (short)HIWORD(wParam), (HWND)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnReflectedVScroll(UINT nSBCode, UINT nPos, CScrollBar pScrollBar) +#define MSG_OCM_VSCROLL(func) \ + if (uMsg == OCM_VSCROLL) { \ + SetMsgHandled(TRUE); \ + func((int)LOWORD(wParam), (short)HIWORD(wParam), (HWND)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// HBRUSH OnReflectedCtlColorEdit(CDCHandle dc, CEdit edit) +#define MSG_OCM_CTLCOLOREDIT(func) \ + if (uMsg == OCM_CTLCOLOREDIT) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func((HDC)wParam, (HWND)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// HBRUSH OnReflectedCtlColorListBox(CDCHandle dc, CListBox listBox) +#define MSG_OCM_CTLCOLORLISTBOX(func) \ + if (uMsg == OCM_CTLCOLORLISTBOX) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func((HDC)wParam, (HWND)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// HBRUSH OnReflectedCtlColorBtn(CDCHandle dc, CButton button) +#define MSG_OCM_CTLCOLORBTN(func) \ + if (uMsg == OCM_CTLCOLORBTN) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func((HDC)wParam, (HWND)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// HBRUSH OnReflectedCtlColorDlg(CDCHandle dc, CWindow wnd) +#define MSG_OCM_CTLCOLORDLG(func) \ + if (uMsg == OCM_CTLCOLORDLG) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func((HDC)wParam, (HWND)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// HBRUSH OnReflectedCtlColorScrollBar(CDCHandle dc, CScrollBar scrollBar) +#define MSG_OCM_CTLCOLORSCROLLBAR(func) \ + if (uMsg == OCM_CTLCOLORSCROLLBAR) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func((HDC)wParam, (HWND)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// HBRUSH OnReflectedCtlColorStatic(CDCHandle dc, CStatic wndStatic) +#define MSG_OCM_CTLCOLORSTATIC(func) \ + if (uMsg == OCM_CTLCOLORSTATIC) { \ + SetMsgHandled(TRUE); \ + lResult = (LRESULT)func((HDC)wParam, (HWND)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +/////////////////////////////////////////////////////////////////////////////// +// Edit specific messages + +// void OnClear() +#define CR_MSG_WM_CLEAR(func) \ + if (uMsg == WM_CLEAR) { \ + SetMsgHandled(TRUE); \ + func(); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnCopy() +#define CR_MSG_WM_COPY(func) \ + if (uMsg == WM_COPY) { \ + SetMsgHandled(TRUE); \ + func(); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnCut() +#define CR_MSG_WM_CUT(func) \ + if (uMsg == WM_CUT) { \ + SetMsgHandled(TRUE); \ + func(); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnPaste() +#define CR_MSG_WM_PASTE(func) \ + if (uMsg == WM_PASTE) { \ + SetMsgHandled(TRUE); \ + func(); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnUndo() +#define CR_MSG_WM_UNDO(func) \ + if (uMsg == WM_UNDO) { \ + SetMsgHandled(TRUE); \ + func(); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +/////////////////////////////////////////////////////////////////////////////// +// Generic message handlers + +// LRESULT OnMessageHandlerEX(UINT uMsg, WPARAM wParam, LPARAM lParam) +#define CR_MESSAGE_HANDLER_EX(msg, func) \ + if (uMsg == msg) { \ + SetMsgHandled(TRUE); \ + lResult = func(uMsg, wParam, lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// LRESULT OnMessageRangeHandlerEX(UINT uMsg, WPARAM wParam, LPARAM lParam) +#define CR_MESSAGE_RANGE_HANDLER_EX(msgFirst, msgLast, func) \ + if (uMsg >= msgFirst && uMsg <= msgLast) { \ + SetMsgHandled(TRUE); \ + lResult = func(uMsg, wParam, lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +/////////////////////////////////////////////////////////////////////////////// +// Commands and notifications + +// void OnCommandHandlerEX(UINT uNotifyCode, int nID, CWindow wndCtl) +#define CR_COMMAND_HANDLER_EX(id, code, func) \ + if (uMsg == WM_COMMAND && code == HIWORD(wParam) && id == LOWORD(wParam)) { \ + SetMsgHandled(TRUE); \ + func((UINT)HIWORD(wParam), (int)LOWORD(wParam), (HWND)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnCommandIDHandlerEX(UINT uNotifyCode, int nID, CWindow wndCtl) +#define CR_COMMAND_ID_HANDLER_EX(id, func) \ + if (uMsg == WM_COMMAND && id == LOWORD(wParam)) { \ + SetMsgHandled(TRUE); \ + func((UINT)HIWORD(wParam), (int)LOWORD(wParam), (HWND)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnCommandCodeHandlerEX(UINT uNotifyCode, int nID, CWindow wndCtl) +#define CR_COMMAND_CODE_HANDLER_EX(code, func) \ + if (uMsg == WM_COMMAND && code == HIWORD(wParam)) { \ + SetMsgHandled(TRUE); \ + func((UINT)HIWORD(wParam), (int)LOWORD(wParam), (HWND)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// LRESULT OnNotifyHandlerEX(LPNMHDR pnmh) +#define CR_NOTIFY_HANDLER_EX(id, cd, func) \ + if (uMsg == WM_NOTIFY && cd == ((LPNMHDR)lParam)->code && \ + id == ((LPNMHDR)lParam)->idFrom) { \ + SetMsgHandled(TRUE); \ + lResult = func((LPNMHDR)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// LRESULT OnNotifyIDHandlerEX(LPNMHDR pnmh) +#define CR_NOTIFY_ID_HANDLER_EX(id, func) \ + if (uMsg == WM_NOTIFY && id == ((LPNMHDR)lParam)->idFrom) { \ + SetMsgHandled(TRUE); \ + lResult = func((LPNMHDR)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// LRESULT OnNotifyCodeHandlerEX(LPNMHDR pnmh) +#define CR_NOTIFY_CODE_HANDLER_EX(cd, func) \ + if (uMsg == WM_NOTIFY && cd == ((LPNMHDR)lParam)->code) { \ + SetMsgHandled(TRUE); \ + lResult = func((LPNMHDR)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnCommandRangeHandlerEX(UINT uNotifyCode, int nID, CWindow wndCtl) +#define CR_COMMAND_RANGE_HANDLER_EX(idFirst, idLast, func) \ + if (uMsg == WM_COMMAND && LOWORD(wParam) >= idFirst && \ + LOWORD(wParam) <= idLast) { \ + SetMsgHandled(TRUE); \ + func((UINT)HIWORD(wParam), (int)LOWORD(wParam), (HWND)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnCommandRangeCodeHandlerEX(UINT uNotifyCode, int nID, CWindow wndCtl) +#define CR_COMMAND_RANGE_CODE_HANDLER_EX(idFirst, idLast, code, func) \ + if (uMsg == WM_COMMAND && code == HIWORD(wParam) && \ + LOWORD(wParam) >= idFirst && LOWORD(wParam) <= idLast) { \ + SetMsgHandled(TRUE); \ + func((UINT)HIWORD(wParam), (int)LOWORD(wParam), (HWND)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// LRESULT OnNotifyRangeHandlerEX(LPNMHDR pnmh) +#define CR_NOTIFY_RANGE_HANDLER_EX(idFirst, idLast, func) \ + if (uMsg == WM_NOTIFY && ((LPNMHDR)lParam)->idFrom >= idFirst && \ + ((LPNMHDR)lParam)->idFrom <= idLast) { \ + SetMsgHandled(TRUE); \ + lResult = func((LPNMHDR)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// LRESULT OnNotifyRangeCodeHandlerEX(LPNMHDR pnmh) +#define CR_NOTIFY_RANGE_CODE_HANDLER_EX(idFirst, idLast, cd, func) \ + if (uMsg == WM_NOTIFY && cd == ((LPNMHDR)lParam)->code && \ + ((LPNMHDR)lParam)->idFrom >= idFirst && \ + ((LPNMHDR)lParam)->idFrom <= idLast) { \ + SetMsgHandled(TRUE); \ + lResult = func((LPNMHDR)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// LRESULT OnReflectedCommandHandlerEX(UINT uNotifyCode, int nID, CWindow +// wndCtl) +#define CR_REFLECTED_COMMAND_HANDLER_EX(id, code, func) \ + if (uMsg == OCM_COMMAND && code == HIWORD(wParam) && id == LOWORD(wParam)) { \ + SetMsgHandled(TRUE); \ + func((UINT)HIWORD(wParam), (int)LOWORD(wParam), (HWND)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// LRESULT OnReflectedCommandIDHandlerEX(UINT uNotifyCode, int nID, CWindow +// wndCtl) +#define CR_REFLECTED_COMMAND_ID_HANDLER_EX(id, func) \ + if (uMsg == OCM_COMMAND && id == LOWORD(wParam)) { \ + SetMsgHandled(TRUE); \ + func((UINT)HIWORD(wParam), (int)LOWORD(wParam), (HWND)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// LRESULT OnReflectedCommandCodeHandlerEX(UINT uNotifyCode, int nID, CWindow +// wndCtl) +#define CR_REFLECTED_COMMAND_CODE_HANDLER_EX(code, func) \ + if (uMsg == OCM_COMMAND && code == HIWORD(wParam)) { \ + SetMsgHandled(TRUE); \ + func((UINT)HIWORD(wParam), (int)LOWORD(wParam), (HWND)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// LRESULT OnReflectedNotifyHandlerEX(LPNMHDR pnmh) +#define CR_REFLECTED_NOTIFY_HANDLER_EX(id, cd, func) \ + if (uMsg == OCM_NOTIFY && cd == ((LPNMHDR)lParam)->code && \ + id == ((LPNMHDR)lParam)->idFrom) { \ + SetMsgHandled(TRUE); \ + lResult = func((LPNMHDR)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// LRESULT OnReflectedNotifyIDHandlerEX(LPNMHDR pnmh) +#define CR_REFLECTED_NOTIFY_ID_HANDLER_EX(id, func) \ + if (uMsg == OCM_NOTIFY && id == ((LPNMHDR)lParam)->idFrom) { \ + SetMsgHandled(TRUE); \ + lResult = func((LPNMHDR)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// LRESULT OnReflectedNotifyCodeHandlerEX(LPNMHDR pnmh) +#define CR_REFLECTED_NOTIFY_CODE_HANDLER_EX(cd, func) \ + if (uMsg == OCM_NOTIFY && cd == ((LPNMHDR)lParam)->code) { \ + SetMsgHandled(TRUE); \ + lResult = func((LPNMHDR)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnReflectedCommandRangeHandlerEX(UINT uNotifyCode, int nID, CWindow +// wndCtl) +#define CR_REFLECTED_COMMAND_RANGE_HANDLER_EX(idFirst, idLast, func) \ + if (uMsg == OCM_COMMAND && LOWORD(wParam) >= idFirst && \ + LOWORD(wParam) <= idLast) { \ + SetMsgHandled(TRUE); \ + func((UINT)HIWORD(wParam), (int)LOWORD(wParam), (HWND)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// void OnReflectedCommandRangeCodeHandlerEX(UINT uNotifyCode, int nID, CWindow +// wndCtl) +#define CR_REFLECTED_COMMAND_RANGE_CODE_HANDLER_EX(idFirst, idLast, code, \ + func) \ + if (uMsg == OCM_COMMAND && code == HIWORD(wParam) && \ + LOWORD(wParam) >= idFirst && LOWORD(wParam) <= idLast) { \ + SetMsgHandled(TRUE); \ + func((UINT)HIWORD(wParam), (int)LOWORD(wParam), (HWND)lParam); \ + lResult = 0; \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// LRESULT OnReflectedNotifyRangeHandlerEX(LPNMHDR pnmh) +#define CR_REFLECTED_NOTIFY_RANGE_HANDLER_EX(idFirst, idLast, func) \ + if (uMsg == OCM_NOTIFY && ((LPNMHDR)lParam)->idFrom >= idFirst && \ + ((LPNMHDR)lParam)->idFrom <= idLast) { \ + SetMsgHandled(TRUE); \ + lResult = func((LPNMHDR)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +// LRESULT OnReflectedNotifyRangeCodeHandlerEX(LPNMHDR pnmh) +#define CR_REFLECTED_NOTIFY_RANGE_CODE_HANDLER_EX(idFirst, idLast, cd, func) \ + if (uMsg == OCM_NOTIFY && cd == ((LPNMHDR)lParam)->code && \ + ((LPNMHDR)lParam)->idFrom >= idFirst && \ + ((LPNMHDR)lParam)->idFrom <= idLast) { \ + SetMsgHandled(TRUE); \ + lResult = func((LPNMHDR)lParam); \ + if (!ref.get() || msg_handled_) \ + return TRUE; \ + } + +#define CR_DEFLATE_RECT(rect, by) \ + { \ + (rect)->left += (by)->left; \ + (rect)->top += (by)->top; \ + (rect)->right -= (by)->right; \ + (rect)->bottom -= (by)->bottom; \ + } + +#define CR_POINT_INITIALIZER_FROM_LPARAM(lparam) \ + { LOWORD(lparam), HIWORD(lparam) } + +#endif // UI_GFX_WIN_MSG_UTIL_H_ diff --git a/win/physical_size.cc b/win/physical_size.cc new file mode 100644 index 000000000000..39978f739398 --- /dev/null +++ b/win/physical_size.cc @@ -0,0 +1,163 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/win/physical_size.h" + +#include +#include + +#include +#include + +#include "base/check_op.h" +#include "base/memory/free_deleter.h" +#include "base/scoped_generic.h" +#include "base/strings/utf_string_conversions.h" +#include "base/win/registry.h" + +// This GUID {E6F07B5F-EE97-4A90-B076-33F57BF4EAA7} was taken from +// https://msdn.microsoft.com/en-us/library/windows/hardware/ff545901.aspx +const GUID GUID_DEVICEINTERFACE_MONITOR = { + 0xE6F07B5F, + 0xEE97, + 0x4A90, + {0xB0, 0x76, 0x33, 0xF5, 0x7B, 0xF4, 0xEA, 0xA7}}; + +namespace { + +struct DeviceInfoListScopedTraits { + static HDEVINFO InvalidValue() { return INVALID_HANDLE_VALUE; } + + static void Free(HDEVINFO h) { SetupDiDestroyDeviceInfoList(h); } +}; + +bool GetSizeFromRegistry(HDEVINFO device_info_list, + SP_DEVINFO_DATA* device_info, + int* width_mm, + int* height_mm) { + base::win::RegKey reg_key(SetupDiOpenDevRegKey( + device_info_list, device_info, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ)); + if (!reg_key.Valid()) + return false; + + BYTE data[128]; // EDID block is exactly 128 bytes long. + ZeroMemory(&data[0], sizeof(data)); + DWORD data_length = sizeof(data); + LONG return_value = + reg_key.ReadValue(L"EDID", &data[0], &data_length, nullptr); + if (return_value != ERROR_SUCCESS) + return false; + + // Byte 54 is the start of the first descriptor block, which contains the + // required timing information with the highest preference, and 12 bytes + // into that block is the size information. + // 66: width least significant bits + // 67: height least significant bits + // 68: 4 bits for each of width and height most significant bits + if (data[54] == 0) + return false; + const int w = ((data[68] & 0xF0) << 4) + data[66]; + const int h = ((data[68] & 0x0F) << 8) + data[67]; + + if (w <= 0 || h <= 0) + return false; + + *width_mm = w; + *height_mm = h; + + return true; +} + +bool GetInterfaceDetailAndDeviceInfo( + HDEVINFO device_info_list, + SP_DEVICE_INTERFACE_DATA* interface_data, + std::unique_ptr* + interface_detail, + SP_DEVINFO_DATA* device_info) { + DCHECK_EQ(sizeof(*device_info), device_info->cbSize); + DWORD buffer_size; + // This call populates device_info. It will also fail, but if the error is + // "insufficient buffer" then it will set buffer_size and we can call again + // with an allocated buffer. + SetupDiGetDeviceInterfaceDetail(device_info_list, interface_data, nullptr, 0, + &buffer_size, device_info); + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) + return false; + + interface_detail->reset( + reinterpret_cast(malloc(buffer_size))); + (*interface_detail)->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); + return SetupDiGetDeviceInterfaceDetail(device_info_list, interface_data, + interface_detail->get(), buffer_size, + nullptr, nullptr) != 0; +} + +} // namespace + +namespace gfx { + +// The physical size information is only available by looking in the EDID block +// via setup. However setup has the device path and not the device name that we +// use to identify displays. Therefore after looking up a device via setup we +// need to find the display again via EnumDisplayDevices (matching device path +// to the device ID of the display's interface) so we can return the device name +// (available from the interface's attached monitor). +std::vector GetPhysicalSizeForDisplays() { + std::vector out; + + base::ScopedGeneric device_info_list( + SetupDiGetClassDevs(&GUID_DEVICEINTERFACE_MONITOR, nullptr, nullptr, + DIGCF_PRESENT | DIGCF_DEVICEINTERFACE)); + + if (!device_info_list.is_valid()) + return out; + + SP_DEVICE_INTERFACE_DATA interface_data = {}; + interface_data.cbSize = sizeof(interface_data); + int interface_index = 0; + while (SetupDiEnumDeviceInterfaces(device_info_list.get(), nullptr, + &GUID_DEVICEINTERFACE_MONITOR, + interface_index++, &interface_data)) { + std::unique_ptr + interface_detail; + SP_DEVINFO_DATA device_info = {}; + device_info.cbSize = sizeof(device_info); + bool get_info_succeeded = + GetInterfaceDetailAndDeviceInfo(device_info_list.get(), &interface_data, + &interface_detail, &device_info); + if (!get_info_succeeded) + continue; + + DISPLAY_DEVICE display_device = {}; + display_device.cb = sizeof(display_device); + int display_index = 0; + while (EnumDisplayDevices(nullptr, display_index++, &display_device, + EDD_GET_DEVICE_INTERFACE_NAME)) { + DISPLAY_DEVICE attached_device = {}; + attached_device.cb = sizeof(attached_device); + int attached_index = 0; + while (EnumDisplayDevices(display_device.DeviceName, attached_index++, + &attached_device, + EDD_GET_DEVICE_INTERFACE_NAME)) { + wchar_t* attached_device_id = attached_device.DeviceID; + wchar_t* setup_device_path = interface_detail->DevicePath; + if (wcsicmp(attached_device_id, setup_device_path) == 0) { + int width_mm; + int height_mm; + bool found = GetSizeFromRegistry(device_info_list.get(), &device_info, + &width_mm, &height_mm); + if (found) { + out.push_back( + PhysicalDisplaySize(base::WideToUTF8(display_device.DeviceName), + width_mm, height_mm)); + } + break; + } + } + } + } + return out; +} + +} // namespace gfx diff --git a/win/physical_size.h b/win/physical_size.h new file mode 100644 index 000000000000..5b768adba6e6 --- /dev/null +++ b/win/physical_size.h @@ -0,0 +1,31 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_WIN_PHYSICAL_SIZE_H_ +#define UI_GFX_WIN_PHYSICAL_SIZE_H_ + +#include +#include + +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +struct PhysicalDisplaySize { + PhysicalDisplaySize(const std::string& display_name, + int width_mm, + int height_mm) + : display_name(display_name), width_mm(width_mm), height_mm(height_mm) {} + + std::string display_name; + int width_mm; + int height_mm; +}; + +// Gets the physical size for all displays. +GFX_EXPORT std::vector GetPhysicalSizeForDisplays(); + +} // namespace gfx + +#endif // UI_GFX_WIN_PHYSICAL_SIZE_H_ diff --git a/win/rendering_window_manager.cc b/win/rendering_window_manager.cc new file mode 100644 index 000000000000..853688286820 --- /dev/null +++ b/win/rendering_window_manager.cc @@ -0,0 +1,80 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/win/rendering_window_manager.h" + +#include "base/bind.h" +#include "base/logging.h" +#include "base/no_destructor.h" +#include "base/single_thread_task_runner.h" +#include "base/threading/thread_task_runner_handle.h" + +namespace gfx { + +// static +RenderingWindowManager* RenderingWindowManager::GetInstance() { + static base::NoDestructor instance; + return instance.get(); +} + +void RenderingWindowManager::RegisterParent(HWND parent) { + DCHECK(task_runner_->BelongsToCurrentThread()); + registered_hwnds_.emplace(parent, nullptr); +} + +void RenderingWindowManager::RegisterChild(HWND parent, + HWND child, + DWORD expected_child_process_id) { + if (!child) + return; + + // This can be called from any thread, if we're not on the correct thread then + // PostTask back to the UI thread before doing anything. + if (!task_runner_->BelongsToCurrentThread()) { + task_runner_->PostTask( + FROM_HERE, base::BindOnce(&RenderingWindowManager::RegisterChild, + base::Unretained(this), parent, child, + expected_child_process_id)); + return; + } + + // Check that |parent| was registered as a HWND that could have a child HWND. + auto it = registered_hwnds_.find(parent); + if (it == registered_hwnds_.end()) + return; + + // Check that |child| belongs to the GPU process. + DWORD child_process_id = 0; + DWORD child_thread_id = GetWindowThreadProcessId(child, &child_process_id); + if (!child_thread_id || child_process_id != expected_child_process_id) { + DLOG(ERROR) << "Child HWND not owned by GPU process."; + return; + } + + it->second = child; + + ::SetParent(child, parent); + // Move D3D window behind Chrome's window to avoid losing some messages. + ::SetWindowPos(child, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); +} + +void RenderingWindowManager::UnregisterParent(HWND parent) { + DCHECK(task_runner_->BelongsToCurrentThread()); + registered_hwnds_.erase(parent); +} + +bool RenderingWindowManager::HasValidChildWindow(HWND parent) { + DCHECK(task_runner_->BelongsToCurrentThread()); + auto it = registered_hwnds_.find(parent); + if (it == registered_hwnds_.end()) + return false; + return !!it->second && ::IsWindow(it->second); +} + +RenderingWindowManager::RenderingWindowManager() + : task_runner_(base::ThreadTaskRunnerHandle::Get()) {} + +RenderingWindowManager::~RenderingWindowManager() = default; + +} // namespace gfx diff --git a/win/rendering_window_manager.h b/win/rendering_window_manager.h new file mode 100644 index 000000000000..dd729b1aeca8 --- /dev/null +++ b/win/rendering_window_manager.h @@ -0,0 +1,59 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_WIN_RENDERING_WINDOW_MANAGER_H_ +#define UI_GFX_WIN_RENDERING_WINDOW_MANAGER_H_ + +#include + +#include "base/containers/flat_map.h" +#include "base/memory/scoped_refptr.h" +#include "base/no_destructor.h" +#include "ui/gfx/gfx_export.h" + +namespace base { +class SingleThreadTaskRunner; +} + +namespace gfx { + +// This keeps track of whether a given HWND has a child window which the GPU +// process renders into. This should only be used from the UI thread unless +// otherwise noted. +class GFX_EXPORT RenderingWindowManager { + public: + // The first call to GetInstance() should happen on the UI thread. + static RenderingWindowManager* GetInstance(); + + RenderingWindowManager(const RenderingWindowManager&) = delete; + RenderingWindowManager& operator=(const RenderingWindowManager&) = delete; + + void RegisterParent(HWND parent); + // Registers |child| as child window for |parent|. Allows the GPU process to + // draw into the |child| HWND instead of |parent|. This will fail and do + // nothing if: + // 1. |parent| isn't registered. + // 2. |child| doesn't belong to |expected_child_process_id|. + // + // Can be called from any thread, as long GetInstance() has already been + // called on the UI thread at least once. + void RegisterChild(HWND parent, HWND child, DWORD expected_child_process_id); + void UnregisterParent(HWND parent); + bool HasValidChildWindow(HWND parent); + + private: + friend class base::NoDestructor; + + RenderingWindowManager(); + ~RenderingWindowManager(); + + // UI thread task runner. + scoped_refptr task_runner_; + // Map from registered parent HWND to child HWND. + base::flat_map registered_hwnds_; +}; + +} // namespace gfx + +#endif // UI_GFX_WIN_RENDERING_WINDOW_MANAGER_H_ diff --git a/win/scoped_set_map_mode.h b/win/scoped_set_map_mode.h new file mode 100644 index 000000000000..f5669fe44c52 --- /dev/null +++ b/win/scoped_set_map_mode.h @@ -0,0 +1,41 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_WIN_SCOPED_SET_MAP_MODE_H_ +#define UI_GFX_WIN_SCOPED_SET_MAP_MODE_H_ + +#include + +#include "base/check_op.h" +#include "base/macros.h" + +namespace gfx { + +// Helper class for setting and restore the map mode on a DC. +class ScopedSetMapMode { + public: + ScopedSetMapMode(HDC hdc, int map_mode) + : hdc_(hdc), + old_map_mode_(SetMapMode(hdc, map_mode)) { + DCHECK(hdc_); + DCHECK_NE(map_mode, 0); + DCHECK_NE(old_map_mode_, 0); + } + + ScopedSetMapMode(const ScopedSetMapMode&) = delete; + ScopedSetMapMode& operator=(const ScopedSetMapMode&) = delete; + + ~ScopedSetMapMode() { + const int mode = SetMapMode(hdc_, old_map_mode_); + DCHECK_NE(mode, 0); + } + + private: + HDC hdc_; + int old_map_mode_; +}; + +} // namespace gfx + +#endif // UI_GFX_WIN_SCOPED_SET_MAP_MODE_H_ diff --git a/win/singleton_hwnd.cc b/win/singleton_hwnd.cc new file mode 100644 index 000000000000..1476b11bfd3a --- /dev/null +++ b/win/singleton_hwnd.cc @@ -0,0 +1,66 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/win/singleton_hwnd.h" + +#include "base/memory/singleton.h" +#include "base/task/current_thread.h" +#include "ui/gfx/win/singleton_hwnd_observer.h" + +namespace gfx { + +// static +SingletonHwnd* SingletonHwnd::GetInstance() { + return base::Singleton::get(); +} + +BOOL SingletonHwnd::ProcessWindowMessage(HWND window, + UINT message, + WPARAM wparam, + LPARAM lparam, + LRESULT& result, + DWORD msg_map_id) { + if (!base::CurrentUIThread::IsSet()) { + // If there is no MessageLoop and SingletonHwnd is receiving messages, this + // means it is receiving messages via an external message pump such as COM + // uninitialization. + // + // It is unsafe to forward these messages as observers may depend on the + // existence of a MessageLoop to proceed. + return false; + } + + for (SingletonHwndObserver& observer : observer_list_) + observer.OnWndProc(window, message, wparam, lparam); + return false; +} + +SingletonHwnd::SingletonHwnd() { + if (!base::CurrentUIThread::IsSet()) { + // Creating this window in (e.g.) a renderer inhibits shutdown on + // Windows. See http://crbug.com/230122 and http://crbug.com/236039. + return; + } + WindowImpl::Init(NULL, Rect()); +} + +SingletonHwnd::~SingletonHwnd() { + // WindowImpl will clean up the hwnd value on WM_NCDESTROY. + if (hwnd()) + DestroyWindow(hwnd()); + + // Tell all of our current observers to clean themselves up. + for (SingletonHwndObserver& observer : observer_list_) + observer.ClearWndProc(); +} + +void SingletonHwnd::AddObserver(SingletonHwndObserver* observer) { + observer_list_.AddObserver(observer); +} + +void SingletonHwnd::RemoveObserver(SingletonHwndObserver* observer) { + observer_list_.RemoveObserver(observer); +} + +} // namespace gfx diff --git a/win/singleton_hwnd.h b/win/singleton_hwnd.h new file mode 100644 index 000000000000..36daf552c9e7 --- /dev/null +++ b/win/singleton_hwnd.h @@ -0,0 +1,57 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_WIN_SINGLETON_HWND_H_ +#define UI_GFX_WIN_SINGLETON_HWND_H_ + +#include + +#include "base/macros.h" +#include "base/observer_list.h" +#include "ui/gfx/gfx_export.h" +#include "ui/gfx/win/window_impl.h" + +namespace base { +template struct DefaultSingletonTraits; +} + +namespace gfx { + +class SingletonHwndObserver; + +// Singleton message-only HWND that allows interested clients to receive WM_* +// notifications. +class GFX_EXPORT SingletonHwnd : public WindowImpl { + public: + static SingletonHwnd* GetInstance(); + + SingletonHwnd(const SingletonHwnd&) = delete; + SingletonHwnd& operator=(const SingletonHwnd&) = delete; + + // Windows callback for WM_* notifications. + BOOL ProcessWindowMessage(HWND window, + UINT message, + WPARAM wparam, + LPARAM lparam, + LRESULT& result, + DWORD msg_map_id) override; + + private: + friend class SingletonHwndObserver; + friend struct base::DefaultSingletonTraits; + + SingletonHwnd(); + ~SingletonHwnd() override; + + // Add/remove SingletonHwndObserver to forward WM_* notifications. + void AddObserver(SingletonHwndObserver* observer); + void RemoveObserver(SingletonHwndObserver* observer); + + // List of registered observers. + base::ObserverList::Unchecked observer_list_; +}; + +} // namespace gfx + +#endif // UI_GFX_WIN_SINGLETON_HWND_H_ diff --git a/win/singleton_hwnd_hot_key_observer.cc b/win/singleton_hwnd_hot_key_observer.cc new file mode 100644 index 000000000000..be3d7c2fed76 --- /dev/null +++ b/win/singleton_hwnd_hot_key_observer.cc @@ -0,0 +1,96 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/win/singleton_hwnd_hot_key_observer.h" + +#include "base/bind.h" +#include "base/containers/flat_set.h" +#include "base/memory/ptr_util.h" +#include "base/no_destructor.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/gfx/win/singleton_hwnd.h" + +namespace gfx { + +namespace { + +base::flat_set& GetUsedHotKeyIDs() { + static base::NoDestructor> used_hot_key_ids; + return *used_hot_key_ids; +} + +absl::optional GetAvailableHotKeyID() { + // Valid hot key IDs are in the range 0x0000 to 0xBFFF. See + // https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-registerhotkey + for (int i = 0x0000; i < 0xBFFF; i++) { + if (!GetUsedHotKeyIDs().contains(i)) + return i; + } + return absl::nullopt; +} + +void SetHotKeyIDUsed(int id) { + DCHECK(!GetUsedHotKeyIDs().contains(id)); + GetUsedHotKeyIDs().insert(id); +} + +void SetHotKeyIDAvailable(int id) { + DCHECK(GetUsedHotKeyIDs().contains(id)); + GetUsedHotKeyIDs().erase(id); +} + +} // anonymous namespace + +std::unique_ptr +SingletonHwndHotKeyObserver::Create( + const SingletonHwndObserver::WndProc& wnd_proc, + UINT key_code, + int modifiers) { + absl::optional hot_key_id = GetAvailableHotKeyID(); + + // If there are no available hot key IDs, return null. + if (!hot_key_id.has_value()) + return nullptr; + + // If we fail to register the hot key, return null. Most likely reason for + // failure is that another application has already registered the hot key. + if (!RegisterHotKey(gfx::SingletonHwnd::GetInstance()->hwnd(), *hot_key_id, + modifiers, key_code)) { + return nullptr; + } + + return base::WrapUnique( + new SingletonHwndHotKeyObserver(wnd_proc, *hot_key_id)); +} + +SingletonHwndHotKeyObserver::SingletonHwndHotKeyObserver( + const SingletonHwndObserver::WndProc& wnd_proc, + int hot_key_id) + : observer_(base::BindRepeating(&SingletonHwndHotKeyObserver::OnWndProc, + base::Unretained(this))), + wnd_proc_(wnd_proc), + hot_key_id_(hot_key_id) { + SetHotKeyIDUsed(hot_key_id); +} + +SingletonHwndHotKeyObserver::~SingletonHwndHotKeyObserver() { + bool success = !!UnregisterHotKey(gfx::SingletonHwnd::GetInstance()->hwnd(), + hot_key_id_); + // This call should always succeed, as long as we pass in the right HWND and + // an id we've used to register before. + DCHECK(success); + SetHotKeyIDAvailable(hot_key_id_); +} + +void SingletonHwndHotKeyObserver::OnWndProc(HWND hwnd, + UINT message, + WPARAM wparam, + LPARAM lparam) { + // Only propagate WM_HOTKEY messages for this particular hot key to the owner + // of this observer. + if (message == WM_HOTKEY && static_cast(wparam) == hot_key_id_) + wnd_proc_.Run(hwnd, message, wparam, lparam); +} + +} // namespace gfx diff --git a/win/singleton_hwnd_hot_key_observer.h b/win/singleton_hwnd_hot_key_observer.h new file mode 100644 index 000000000000..5b003ce2c433 --- /dev/null +++ b/win/singleton_hwnd_hot_key_observer.h @@ -0,0 +1,53 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_WIN_SINGLETON_HWND_HOT_KEY_OBSERVER_H_ +#define UI_GFX_WIN_SINGLETON_HWND_HOT_KEY_OBSERVER_H_ + +#include "base/win/windows_types.h" +#include "ui/gfx/gfx_export.h" +#include "ui/gfx/win/singleton_hwnd_observer.h" + +namespace gfx { + +// We need to avoid duplicate hot key IDs for SingletonHwndObservers that call +// RegisterHotKey for SingletonHwnd. This class properly handles getting a +// unique hot key ID, registers the hotkey on construction, and unregisters the +// hot key on destruction. +// +// This class should always be used instead of directly registering hot keys on +// the SingletonHwnd with a SingletonHwndObserver in order to prevent duplicate +// hot key IDs. +class GFX_EXPORT SingletonHwndHotKeyObserver { + public: + // Registers a hot key with the given |key_code| and |modifiers| and returns + // a SingletonHwndHotKeyObserver if successful. Returns null if the hot key + // fails to register, which can happen if another application has already + // registered the hot key. + static std::unique_ptr Create( + const SingletonHwndObserver::WndProc& wnd_proc, + UINT key_code, + int modifiers); + + SingletonHwndHotKeyObserver(const SingletonHwndHotKeyObserver&) = delete; + SingletonHwndHotKeyObserver& operator=(const SingletonHwndHotKeyObserver&) = + delete; + + ~SingletonHwndHotKeyObserver(); + + private: + SingletonHwndHotKeyObserver(const SingletonHwndObserver::WndProc& wnd_proc, + int hot_key_id); + + // Called by SingletonHwndObserver. + void OnWndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam); + + SingletonHwndObserver observer_; + SingletonHwndObserver::WndProc wnd_proc_; + const int hot_key_id_; +}; + +} // namespace gfx + +#endif // UI_GFX_WIN_SINGLETON_HWND_HOT_KEY_OBSERVER_H_ diff --git a/win/singleton_hwnd_observer.cc b/win/singleton_hwnd_observer.cc new file mode 100644 index 000000000000..f439105e659d --- /dev/null +++ b/win/singleton_hwnd_observer.cc @@ -0,0 +1,35 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/win/singleton_hwnd_observer.h" + +#include "ui/gfx/win/singleton_hwnd.h" + +namespace gfx { + +SingletonHwndObserver::SingletonHwndObserver(const WndProc& wnd_proc) + : wnd_proc_(wnd_proc) { + DCHECK(!wnd_proc.is_null()); + SingletonHwnd::GetInstance()->AddObserver(this); +} + +SingletonHwndObserver::~SingletonHwndObserver() { + ClearWndProc(); +} + +void SingletonHwndObserver::ClearWndProc() { + if (!wnd_proc_.is_null()) { + SingletonHwnd::GetInstance()->RemoveObserver(this); + wnd_proc_.Reset(); + } +} + +void SingletonHwndObserver::OnWndProc(HWND hwnd, + UINT message, + WPARAM wparam, + LPARAM lparam) { + wnd_proc_.Run(hwnd, message, wparam, lparam); +} + +} // namespace gfx diff --git a/win/singleton_hwnd_observer.h b/win/singleton_hwnd_observer.h new file mode 100644 index 000000000000..cc2ef8aa0b19 --- /dev/null +++ b/win/singleton_hwnd_observer.h @@ -0,0 +1,44 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_WIN_SINGLETON_HWND_OBSERVER_H_ +#define UI_GFX_WIN_SINGLETON_HWND_OBSERVER_H_ + +#include + +#include "base/callback.h" +#include "base/macros.h" +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +class SingletonHwnd; + +// Singleton lifetime management is tricky. This observer handles the correct +// cleanup if either the SingletonHwnd or forwarded object is destroyed first. +// Note that if you want to register a hot key on the SingletonHwnd, you need to +// use a SingletonHwndHotKeyObserver instead for each hot key. +class GFX_EXPORT SingletonHwndObserver { + public: + using WndProc = base::RepeatingCallback; + + explicit SingletonHwndObserver(const WndProc& wnd_proc); + + SingletonHwndObserver(const SingletonHwndObserver&) = delete; + SingletonHwndObserver& operator=(const SingletonHwndObserver&) = delete; + + ~SingletonHwndObserver(); + + private: + friend class SingletonHwnd; + + void ClearWndProc(); + void OnWndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam); + + WndProc wnd_proc_; +}; + +} // namespace gfx + +#endif // UI_GFX_WIN_SINGLETON_HWND_OBSERVER_H_ diff --git a/win/text_analysis_source.cc b/win/text_analysis_source.cc new file mode 100644 index 000000000000..13fef96bab4b --- /dev/null +++ b/win/text_analysis_source.cc @@ -0,0 +1,95 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/win/text_analysis_source.h" + +#include "base/check.h" + +namespace gfx { +namespace win { + +HRESULT TextAnalysisSource::Create( + IDWriteTextAnalysisSource** text_analysis_out, + const std::wstring& text, + const std::wstring& locale_name, + IDWriteNumberSubstitution* number_substitution, + DWRITE_READING_DIRECTION reading_direction) { + return Microsoft::WRL::MakeAndInitialize( + text_analysis_out, text, locale_name, number_substitution, + reading_direction); +} + +TextAnalysisSource::TextAnalysisSource() = default; +TextAnalysisSource::~TextAnalysisSource() = default; + +HRESULT TextAnalysisSource::GetLocaleName(UINT32 text_position, + UINT32* text_length, + const WCHAR** locale_name) { + if (text_position >= text_.length() || !text_length || !locale_name) + return E_INVALIDARG; + *text_length = text_.length() - text_position; + *locale_name = locale_name_.c_str(); + return S_OK; +} + +HRESULT TextAnalysisSource::GetNumberSubstitution( + UINT32 text_position, + UINT32* text_length, + IDWriteNumberSubstitution** number_substitution) { + if (text_position >= text_.length() || !text_length || !number_substitution) + return E_INVALIDARG; + *text_length = text_.length() - text_position; + number_substitution_.CopyTo(number_substitution); + return S_OK; +} + +DWRITE_READING_DIRECTION TextAnalysisSource::GetParagraphReadingDirection() { + return reading_direction_; +} + +HRESULT TextAnalysisSource::GetTextAtPosition(UINT32 text_position, + const WCHAR** text_string, + UINT32* text_length) { + if (!text_length || !text_string) + return E_INVALIDARG; + if (text_position >= text_.length()) { + *text_string = nullptr; + *text_length = 0; + return S_OK; + } + *text_string = text_.c_str() + text_position; + *text_length = text_.length() - text_position; + return S_OK; +} + +HRESULT TextAnalysisSource::GetTextBeforePosition(UINT32 text_position, + const WCHAR** text_string, + UINT32* text_length) { + if (!text_length || !text_string) + return E_INVALIDARG; + if (text_position < 1 || text_position > text_.length()) { + *text_string = nullptr; + *text_length = 0; + return S_OK; + } + *text_string = text_.c_str(); + *text_length = text_position; + return S_OK; +} + +HRESULT TextAnalysisSource::RuntimeClassInitialize( + const std::wstring& text, + const std::wstring& locale_name, + IDWriteNumberSubstitution* number_substitution, + DWRITE_READING_DIRECTION reading_direction) { + DCHECK(number_substitution); + text_ = text; + locale_name_ = locale_name; + number_substitution_ = number_substitution; + reading_direction_ = reading_direction; + return S_OK; +} + +} // namespace win +} // namespace gfx diff --git a/win/text_analysis_source.h b/win/text_analysis_source.h new file mode 100644 index 000000000000..93ce417ce561 --- /dev/null +++ b/win/text_analysis_source.h @@ -0,0 +1,78 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_WIN_TEXT_ANALYSIS_SOURCE_H_ +#define UI_GFX_WIN_TEXT_ANALYSIS_SOURCE_H_ + +#include +#include + +#include + +#include "base/macros.h" +#include "ui/gfx/gfx_export.h" + +namespace gfx { +namespace win { + +// Implements an IDWriteTextAnalysisSource, describing a single pre-defined +// chunk of text with a uniform locale, reading direction, and number +// substitution. +class TextAnalysisSource + : public Microsoft::WRL::RuntimeClass< + Microsoft::WRL::RuntimeClassFlags, + IDWriteTextAnalysisSource> { + public: + // Factory method to avoid exporting the class and all it derives from. + static GFX_EXPORT HRESULT + Create(IDWriteTextAnalysisSource** text_analysis_out, + const std::wstring& text, + const std::wstring& locale_name, + IDWriteNumberSubstitution* number_substitution, + DWRITE_READING_DIRECTION reading_direction); + + // Use Create() to construct these objects. Direct calls to the constructor + // are an error - it is only public because a WRL helper function creates the + // objects. + TextAnalysisSource(); + + TextAnalysisSource& operator=(const TextAnalysisSource&) = delete; + + // IDWriteTextAnalysisSource: + HRESULT STDMETHODCALLTYPE GetLocaleName(UINT32 text_position, + UINT32* text_length, + const WCHAR** locale_name) override; + HRESULT STDMETHODCALLTYPE GetNumberSubstitution( + UINT32 text_position, + UINT32* text_length, + IDWriteNumberSubstitution** number_substitution) override; + DWRITE_READING_DIRECTION STDMETHODCALLTYPE + GetParagraphReadingDirection() override; + HRESULT STDMETHODCALLTYPE GetTextAtPosition(UINT32 text_position, + const WCHAR** text_string, + UINT32* text_length) override; + HRESULT STDMETHODCALLTYPE GetTextBeforePosition(UINT32 text_position, + const WCHAR** text_string, + UINT32* text_length) override; + + HRESULT STDMETHODCALLTYPE + RuntimeClassInitialize(const std::wstring& text, + const std::wstring& locale_name, + IDWriteNumberSubstitution* number_substitution, + DWRITE_READING_DIRECTION reading_direction); + + protected: + ~TextAnalysisSource() override; + + private: + std::wstring text_; + std::wstring locale_name_; + Microsoft::WRL::ComPtr number_substitution_; + DWRITE_READING_DIRECTION reading_direction_; +}; + +} // namespace win +} // namespace gfx + +#endif // UI_GFX_WIN_TEXT_ANALYSIS_SOURCE_H_ diff --git a/win/text_analysis_source_unittest.cc b/win/text_analysis_source_unittest.cc new file mode 100644 index 000000000000..788621801ea8 --- /dev/null +++ b/win/text_analysis_source_unittest.cc @@ -0,0 +1,133 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/win/text_analysis_source.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/win/direct_write.h" + +namespace mswr = Microsoft::WRL; + +namespace gfx { +namespace win { + +namespace { + +DWRITE_READING_DIRECTION kReadingDirection = + DWRITE_READING_DIRECTION_TOP_TO_BOTTOM; +const wchar_t* kLocale = L"hi-in"; +const wchar_t kText[] = L"sample text"; +const size_t kTextLength = _countof(kText) - 1; + +} // namespace + +class TextAnalysisSourceTest : public testing::Test { + public: + TextAnalysisSourceTest() { + mswr::ComPtr factory; + CreateDWriteFactory(&factory); + + mswr::ComPtr number_substitution; + factory->CreateNumberSubstitution(DWRITE_NUMBER_SUBSTITUTION_METHOD_NONE, + kLocale, true /* ignoreUserOverride */, + &number_substitution); + + TextAnalysisSource::Create(&source_, kText, kLocale, + number_substitution.Get(), kReadingDirection); + } + + TextAnalysisSourceTest(const TextAnalysisSourceTest&) = delete; + TextAnalysisSourceTest& operator=(const TextAnalysisSourceTest&) = delete; + + protected: + mswr::ComPtr source_; +}; + +TEST_F(TextAnalysisSourceTest, TestGetLocaleName) { + UINT32 length = 0; + const wchar_t* locale_name = nullptr; + HRESULT hr = E_FAIL; + hr = source_->GetLocaleName(0, &length, &locale_name); + + EXPECT_TRUE(SUCCEEDED(hr)); + EXPECT_EQ(kTextLength, length); + EXPECT_STREQ(kLocale, locale_name); + + // Attempting to get a locale for a location that does not have a text chunk + // should fail. + hr = source_->GetLocaleName(kTextLength, &length, &locale_name); + + EXPECT_TRUE(FAILED(hr)); +} + +TEST_F(TextAnalysisSourceTest, TestGetNumberSubstitution) { + UINT32 length = 0; + mswr::ComPtr number_substitution; + HRESULT hr = E_FAIL; + hr = source_->GetNumberSubstitution(0, &length, &number_substitution); + + EXPECT_TRUE(SUCCEEDED(hr)); + EXPECT_EQ(kTextLength, length); + EXPECT_NE(nullptr, number_substitution.Get()); + + // Attempting to get a number substitution for a location that does not have + // a text chunk should fail. + hr = source_->GetNumberSubstitution(kTextLength, &length, + &number_substitution); + + EXPECT_TRUE(FAILED(hr)); +} + +TEST_F(TextAnalysisSourceTest, TestGetParagraphReadingDirection) { + EXPECT_EQ(kReadingDirection, source_->GetParagraphReadingDirection()); +} + +TEST_F(TextAnalysisSourceTest, TestGetTextAtPosition) { + UINT32 length = 0; + const wchar_t* text = nullptr; + HRESULT hr = E_FAIL; + hr = source_->GetTextAtPosition(0, &text, &length); + + EXPECT_TRUE(SUCCEEDED(hr)); + EXPECT_EQ(kTextLength, length); + EXPECT_STREQ(kText, text); + + hr = source_->GetTextAtPosition(5, &text, &length); + + EXPECT_TRUE(SUCCEEDED(hr)); + EXPECT_EQ(kTextLength - 5, length); + EXPECT_STREQ(kText + 5, text); + + // Trying to get a text chunk past the end should return null. + hr = source_->GetTextAtPosition(kTextLength, &text, &length); + + EXPECT_TRUE(SUCCEEDED(hr)); + EXPECT_EQ(nullptr, text); +} + +TEST_F(TextAnalysisSourceTest, TestGetTextBeforePosition) { + UINT32 length = 0; + const wchar_t* text = nullptr; + HRESULT hr = E_FAIL; + // Trying to get a text chunk before the beginning should return null. + hr = source_->GetTextBeforePosition(0, &text, &length); + + EXPECT_TRUE(SUCCEEDED(hr)); + EXPECT_EQ(nullptr, text); + + hr = source_->GetTextBeforePosition(5, &text, &length); + + EXPECT_TRUE(SUCCEEDED(hr)); + EXPECT_EQ(5u, length); + EXPECT_STREQ(kText, text); + + hr = source_->GetTextBeforePosition(kTextLength, &text, &length); + + EXPECT_TRUE(SUCCEEDED(hr)); + EXPECT_EQ(kTextLength, length); + EXPECT_STREQ(kText, text); +} + +} // namespace win +} // namespace gfx diff --git a/win/window_impl.cc b/win/window_impl.cc new file mode 100644 index 000000000000..0fdf80b4c327 --- /dev/null +++ b/win/window_impl.cc @@ -0,0 +1,317 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/win/window_impl.h" + +#include + +#include "base/bind.h" +#include "base/cxx17_backports.h" +#include "base/debug/alias.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/singleton.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/synchronization/lock.h" +#include "base/win/win_util.h" +#include "base/win/wrapped_window_proc.h" +#include "ui/gfx/win/crash_id_helper.h" +#include "ui/gfx/win/hwnd_util.h" + +namespace gfx { + +static const DWORD kWindowDefaultChildStyle = + WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS; +static const DWORD kWindowDefaultStyle = WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN; + +/////////////////////////////////////////////////////////////////////////////// +// WindowImpl class tracking. + +// Several external scripts rely explicitly on this base class name for +// acquiring the window handle and will break if this is modified! +// static +const wchar_t* const WindowImpl::kBaseClassName = L"Chrome_WidgetWin_"; + +// WindowImpl class information used for registering unique windows. +struct ClassInfo { + ClassInfo(int style, HICON icon, HICON small_icon) + : style(style), icon(icon), small_icon(small_icon) {} + + // Compares two ClassInfos. Returns true if all members match. + bool Equals(const ClassInfo& other) const { + return (other.style == style && other.icon == icon && + other.small_icon == small_icon); + } + + UINT style; + HICON icon; + HICON small_icon; +}; + +// WARNING: this class may be used on multiple threads. +class ClassRegistrar { + public: + ClassRegistrar(const ClassRegistrar&) = delete; + ClassRegistrar& operator=(const ClassRegistrar&) = delete; + + ~ClassRegistrar(); + + static ClassRegistrar* GetInstance(); + + void UnregisterClasses(); + + // Returns the atom identifying the class matching |class_info|, + // creating and registering a new class if the class is not yet known. + ATOM RetrieveClassAtom(const ClassInfo& class_info); + + private: + // Represents a registered window class. + struct RegisteredClass { + RegisteredClass(const ClassInfo& info, + const std::wstring& name, + ATOM atom, + HINSTANCE instance); + + // Info used to create the class. + ClassInfo info; + + // The name given to the window class + std::wstring name; + + // The atom identifying the window class. + ATOM atom; + + // The handle of the module containing the window proceedure. + HMODULE instance; + }; + + ClassRegistrar(); + friend struct base::DefaultSingletonTraits; + + typedef std::list RegisteredClasses; + RegisteredClasses registered_classes_; + + // Counter of how many classes have been registered so far. + int registered_count_; + + base::Lock lock_; +}; + +ClassRegistrar::~ClassRegistrar() {} + +// static +ClassRegistrar* ClassRegistrar::GetInstance() { + return base::Singleton>::get(); +} + +void ClassRegistrar::UnregisterClasses() { + for (RegisteredClasses::iterator i = registered_classes_.begin(); + i != registered_classes_.end(); ++i) { + if (UnregisterClass(MAKEINTATOM(i->atom), i->instance)) { + registered_classes_.erase(i); + } else { + LOG(ERROR) << "Failed to unregister class " << i->name + << ". Error = " << GetLastError(); + } + } +} + +ATOM ClassRegistrar::RetrieveClassAtom(const ClassInfo& class_info) { + base::AutoLock auto_lock(lock_); + for (RegisteredClasses::const_iterator i = registered_classes_.begin(); + i != registered_classes_.end(); ++i) { + if (class_info.Equals(i->info)) + return i->atom; + } + + // No class found, need to register one. + std::wstring name = std::wstring(WindowImpl::kBaseClassName) + + base::NumberToWString(registered_count_++); + + WNDCLASSEX window_class; + base::win::InitializeWindowClass( + name.c_str(), &base::win::WrappedWindowProc, + class_info.style, 0, 0, NULL, + reinterpret_cast(GetStockObject(BLACK_BRUSH)), NULL, + class_info.icon, class_info.small_icon, &window_class); + HMODULE instance = window_class.hInstance; + ATOM atom = RegisterClassEx(&window_class); + if (!atom) { + // Perhaps the Window session has run out of atoms; see + // https://crbug.com/653493. + auto last_error = ::GetLastError(); + base::debug::Alias(&last_error); + wchar_t name_copy[64]; + base::wcslcpy(name_copy, name.c_str(), base::size(name_copy)); + base::debug::Alias(name_copy); + PCHECK(atom); + } + + registered_classes_.push_back(RegisteredClass( + class_info, name, atom, instance)); + + return atom; +} + +ClassRegistrar::RegisteredClass::RegisteredClass(const ClassInfo& info, + const std::wstring& name, + ATOM atom, + HMODULE instance) + : info(info), name(name), atom(atom), instance(instance) {} + +ClassRegistrar::ClassRegistrar() : registered_count_(0) {} + + +/////////////////////////////////////////////////////////////////////////////// +// WindowImpl, public + +WindowImpl::WindowImpl(const std::string& debugging_id) + : debugging_id_(debugging_id), class_style_(CS_DBLCLKS) {} + +WindowImpl::~WindowImpl() { + ClearUserData(); +} + +// static +void WindowImpl::UnregisterClassesAtExit() { + base::AtExitManager::RegisterTask( + base::BindOnce(&ClassRegistrar::UnregisterClasses, + base::Unretained(ClassRegistrar::GetInstance()))); +} + +void WindowImpl::Init(HWND parent, const Rect& bounds) { + if (window_style_ == 0) + window_style_ = parent ? kWindowDefaultChildStyle : kWindowDefaultStyle; + + if (parent == HWND_DESKTOP) { + // Only non-child windows can have HWND_DESKTOP (0) as their parent. + CHECK((window_style_ & WS_CHILD) == 0); + parent = GetWindowToParentTo(false); + } else if (parent == ::GetDesktopWindow()) { + // Any type of window can have the "Desktop Window" as their parent. + parent = GetWindowToParentTo(true); + } else if (parent != HWND_MESSAGE) { + CHECK(::IsWindow(parent)); + } + + int x, y, width, height; + if (bounds.IsEmpty()) { + x = y = width = height = CW_USEDEFAULT; + } else { + x = bounds.x(); + y = bounds.y(); + width = bounds.width(); + height = bounds.height(); + } + + ATOM atom = GetWindowClassAtom(); + auto weak_this = weak_factory_.GetWeakPtr(); + HWND hwnd = CreateWindowEx(window_ex_style_, + reinterpret_cast(atom), NULL, + window_style_, x, y, width, height, + parent, NULL, NULL, this); + const DWORD create_window_error = ::GetLastError(); + + // First nccalcszie (during CreateWindow) for captioned windows is + // deliberately ignored so force a second one here to get the right + // non-client set up. + if (hwnd && (window_style_ & WS_CAPTION)) { + SetWindowPos(hwnd, NULL, 0, 0, 0, 0, + SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | + SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREDRAW); + } + + if (!hwnd_ && create_window_error == 0) { + bool still_alive = !!weak_this; + base::debug::Alias(&still_alive); + base::debug::Alias(&hwnd); + base::debug::Alias(&atom); + bool got_create = got_create_; + base::debug::Alias(&got_create); + bool got_valid_hwnd = got_valid_hwnd_; + base::debug::Alias(&got_valid_hwnd); + WNDCLASSEX class_info; + memset(&class_info, 0, sizeof(WNDCLASSEX)); + class_info.cbSize = sizeof(WNDCLASSEX); + BOOL got_class = GetClassInfoEx(GetModuleHandle(NULL), + reinterpret_cast(atom), + &class_info); + base::debug::Alias(&got_class); + bool procs_match = got_class && class_info.lpfnWndProc == + base::win::WrappedWindowProc<&WindowImpl::WndProc>; + base::debug::Alias(&procs_match); + CHECK(false); + } + + CheckWindowCreated(hwnd_, create_window_error); + + // The window procedure should have set the data for us. + CHECK_EQ(this, GetWindowUserData(hwnd)); +} + +HICON WindowImpl::GetDefaultWindowIcon() const { + return nullptr; +} + +HICON WindowImpl::GetSmallWindowIcon() const { + return nullptr; +} + +LRESULT WindowImpl::OnWndProc(UINT message, WPARAM w_param, LPARAM l_param) { + LRESULT result = 0; + + HWND hwnd = hwnd_; + if (message == WM_NCDESTROY) + hwnd_ = nullptr; + + // Handle the message if it's in our message map; otherwise, let the system + // handle it. + if (!ProcessWindowMessage(hwnd, message, w_param, l_param, result)) + result = DefWindowProc(hwnd, message, w_param, l_param); + + return result; +} + +void WindowImpl::ClearUserData() { + if (::IsWindow(hwnd_)) + gfx::SetWindowUserData(hwnd_, nullptr); +} + +// static +LRESULT CALLBACK WindowImpl::WndProc(HWND hwnd, + UINT message, + WPARAM w_param, + LPARAM l_param) { + WindowImpl* window = nullptr; + if (message == WM_NCCREATE) { + CREATESTRUCT* cs = reinterpret_cast(l_param); + window = reinterpret_cast(cs->lpCreateParams); + DCHECK(window); + gfx::SetWindowUserData(hwnd, window); + window->hwnd_ = hwnd; + window->got_create_ = true; + if (hwnd) + window->got_valid_hwnd_ = true; + } else { + window = reinterpret_cast(GetWindowUserData(hwnd)); + } + + if (!window) + return 0; + + auto logger = + CrashIdHelper::Get()->OnWillProcessMessages(window->debugging_id_); + return window->OnWndProc(message, w_param, l_param); +} + +ATOM WindowImpl::GetWindowClassAtom() { + HICON icon = GetDefaultWindowIcon(); + HICON small_icon = GetSmallWindowIcon(); + ClassInfo class_info(initial_class_style(), icon, small_icon); + return ClassRegistrar::GetInstance()->RetrieveClassAtom(class_info); +} + +} // namespace gfx diff --git a/win/window_impl.h b/win/window_impl.h new file mode 100644 index 000000000000..2a5683bebd56 --- /dev/null +++ b/win/window_impl.h @@ -0,0 +1,136 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_WIN_WINDOW_IMPL_H_ +#define UI_GFX_WIN_WINDOW_IMPL_H_ + +#include + +#include "base/check_op.h" +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/gfx_export.h" +#include "ui/gfx/native_widget_types.h" +#include "ui/gfx/win/msg_util.h" + +namespace gfx { + +// An interface implemented by classes that use message maps. +// ProcessWindowMessage is implemented by the BEGIN_MESSAGE_MAP_EX macro. +class MessageMapInterface { + public: + // Processes one message from the window's message queue. + virtual BOOL ProcessWindowMessage(HWND window, + UINT message, + WPARAM w_param, + LPARAM l_param, + LRESULT& result, + DWORD msg_map_id = 0) = 0; +}; + +/////////////////////////////////////////////////////////////////////////////// +// +// WindowImpl +// A convenience class that encapsulates the details of creating and +// destroying a HWND. This class also hosts the windows procedure used by all +// Windows. +// +/////////////////////////////////////////////////////////////////////////////// +class GFX_EXPORT WindowImpl : public MessageMapInterface { + public: + // |debugging_id| is reported with crashes to help attribute the code that + // created the WindowImpl. + explicit WindowImpl(const std::string& debugging_id = std::string()); + + WindowImpl(const WindowImpl&) = delete; + WindowImpl& operator=(const WindowImpl&) = delete; + + virtual ~WindowImpl(); + + // Causes all generated windows classes to be unregistered at exit. + // This can cause result in errors for tests that don't destroy all instances + // of windows, but is necessary if the tests unload the classes WndProc. + static void UnregisterClassesAtExit(); + + // Initializes the Window with a parent and an initial desired size. + void Init(HWND parent, const gfx::Rect& bounds); + + // Returns the default window icon to use for windows of this type. + virtual HICON GetDefaultWindowIcon() const; + virtual HICON GetSmallWindowIcon() const; + + // Returns the HWND associated with this Window. + HWND hwnd() const { return hwnd_; } + + // Sets the window styles. This is ONLY used when the window is created. + // In other words, if you invoke this after invoking Init, nothing happens. + void set_window_style(DWORD style) { window_style_ = style; } + DWORD window_style() const { return window_style_; } + + // Sets the extended window styles. See comment about |set_window_style|. + void set_window_ex_style(DWORD style) { window_ex_style_ = style; } + DWORD window_ex_style() const { return window_ex_style_; } + + // Sets the class style to use. The default is CS_DBLCLKS. + void set_initial_class_style(UINT class_style) { + // We dynamically generate the class name, so don't register it globally! + DCHECK_EQ((class_style & CS_GLOBALCLASS), 0u); + class_style_ = class_style; + } + UINT initial_class_style() const { return class_style_; } + + const std::string& debugging_id() const { return debugging_id_; } + + protected: + // Handles the WndProc callback for this object. + virtual LRESULT OnWndProc(UINT message, WPARAM w_param, LPARAM l_param); + + // Subclasses must call this method from their destructors to ensure that + // this object is properly disassociated from the HWND during destruction, + // otherwise it's possible this object may still exist while a subclass is + // destroyed. + void ClearUserData(); + + private: + friend class ClassRegistrar; + + // The window procedure used by all Windows. + static LRESULT CALLBACK WndProc(HWND window, + UINT message, + WPARAM w_param, + LPARAM l_param); + + // Gets the window class atom to use when creating the corresponding HWND. + // If necessary, this registers the window class. + ATOM GetWindowClassAtom(); + + // All classes registered by WindowImpl start with this name. + static const wchar_t* const kBaseClassName; + + const std::string debugging_id_; + + // Window Styles used when creating the window. + DWORD window_style_ = 0; + + // Window Extended Styles used when creating the window. + DWORD window_ex_style_ = 0; + + // Style of the class to use. + UINT class_style_; + + // Our hwnd. + HWND hwnd_ = nullptr; + + // For debugging. + // TODO(sky): nuke this when get crash data. + bool got_create_ = false; + bool got_valid_hwnd_ = false; + // For tracking whether this object has been destroyed. Must be last. + base::WeakPtrFactory weak_factory_{this}; +}; + +} // namespace gfx + +#endif // UI_GFX_WIN_WINDOW_IMPL_H_ diff --git a/x/.style.yapf b/x/.style.yapf new file mode 100644 index 000000000000..557fa7bf84c0 --- /dev/null +++ b/x/.style.yapf @@ -0,0 +1,2 @@ +[style] +based_on_style = pep8 diff --git a/x/BUILD.gn b/x/BUILD.gn new file mode 100644 index 000000000000..87ce467eda01 --- /dev/null +++ b/x/BUILD.gn @@ -0,0 +1,226 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//build/config/ozone.gni") +import("//build/config/ui.gni") +import("//tools/generate_library_loader/generate_library_loader.gni") + +assert(use_x11 || ozone_platform_x11) + +declare_args() { + regenerate_x11_protos = false +} + +config("x11_private_config") { + visibility = [ ":*" ] + defines = [ "IS_X11_IMPL" ] +} + +config("build_xprotos_config") { + cflags = [ + # Generated proto files pull all fields from a struct into scope + # even if they aren't used. Rather than adding logic in the + # generator to determine which fields are used and keeping only + # those, simply ignore unused variable warnings. + "-Wno-unused-variable", + ] +} + +generate_library_loader("xlib_loader") { + name = "XlibLoader" + output_h = "xlib_loader.h" + output_cc = "xlib_loader.cc" + header = "\"ui/gfx/x/xlib.h\"" + + functions = [ + "XInitThreads", + "XOpenDisplay", + "XCloseDisplay", + "XFlush", + "XSynchronize", + "XSetErrorHandler", + "XFree", + "XPending", + ] +} + +generate_library_loader("xlib_xcb_loader") { + name = "XlibXcbLoader" + output_h = "xlib_xcb_loader.h" + output_cc = "xlib_xcb_loader.cc" + header = "\"ui/gfx/x/xlib_xcb.h\"" + + functions = [ "XGetXCBConnection" ] +} + +protos = [ + "bigreq", + "composite", + "damage", + "dpms", + "dri2", + "dri3", + "ge", + "glx", + "present", + "randr", + "record", + "render", + "res", + "screensaver", + "shape", + "shm", + "sync", + "xc_misc", + "xevie", + "xf86dri", + "xf86vidmode", + "xfixes", + "xinerama", + "xinput", + "xkb", + "xprint", + "xproto", + "xselinux", + "xtest", + "xv", + "xvmc", +] +proto_generated_files = [ + "read_event.cc", + "read_error.cc", + "extension_manager.h", + "extension_manager.cc", +] +foreach(proto, protos) { + proto_generated_files += [ + "${proto}.h", + "${proto}.cc", + ] +} + +if (regenerate_x11_protos) { + xcbproto_path = "//third_party/xcbproto/src" + + action("gen_xprotos") { + visibility = [ ":build_xprotos" ] + script = "gen_xproto.py" + + sources = [] + foreach(proto, protos) { + sources += [ "$xcbproto_path/src/${proto}.xml" ] + } + + outputs = [] + foreach(proto_generated_file, proto_generated_files) { + outputs += [ "$target_gen_dir/$proto_generated_file" ] + } + + args = rebase_path([ + xcbproto_path, + target_gen_dir, + ], + root_build_dir) + protos + } +} else { + copy("gen_xprotos") { + sources = [] + foreach(proto_generated_file, proto_generated_files) { + sources += [ "generated_protos/$proto_generated_file" ] + } + + outputs = [ "$target_gen_dir/{{source_file_part}}" ] + } +} + +source_set("build_xprotos") { + visibility = [ ":xproto" ] + deps = [ + ":gen_xprotos", + "//base", + ] + sources = get_target_outputs(":gen_xprotos") + configs += [ + ":build_xprotos_config", + ":x11_private_config", + ] +} + +source_set("xproto") { + visibility = [ ":x" ] + sources = [ + "connection.cc", + "connection.h", + "error.cc", + "error.h", + "event.cc", + "event.h", + "future.h", + "keyboard_state.cc", + "keyboard_state.h", + "ref_counted_fd.cc", + "ref_counted_fd.h", + "scoped_ignore_errors.cc", + "scoped_ignore_errors.h", + "x11_switches.cc", + "x11_switches.h", + "xlib_support.cc", + "xlib_support.h", + "xproto_internal.cc", + "xproto_internal.h", + "xproto_types.cc", + "xproto_types.h", + "xproto_util.cc", + "xproto_util.h", + ] + deps = [ + ":xlib_loader", + ":xlib_xcb_loader", + "//base", + "//base:i18n", + "//ui/events/platform", + ] + public_deps = [ + ":build_xprotos", + "//ui/gfx/x/keysyms", + ] + configs += [ ":x11_private_config" ] + libs = [ "xcb" ] +} + +component("x") { + output_name = "gfx_x11" + + sources = [ + "property_cache.cc", + "property_cache.h", + "x11_atom_cache.cc", + "x11_atom_cache.h", + "x11_path.cc", + "x11_path.h", + "x11_window_event_manager.cc", + "x11_window_event_manager.h", + ] + + configs += [ ":x11_private_config" ] + + deps = [ + "//base", + "//skia", + ] + public_deps = [ ":xproto" ] +} + +source_set("unit_test") { + testonly = true + sources = [ + "connection_unittest.cc", + "property_cache_unittest.cc", + ] + deps = [ + "//base", + "//testing/gtest", + "//ui/gfx/x", + ] +} diff --git a/x/DEPS b/x/DEPS new file mode 100644 index 000000000000..eccf1f068669 --- /dev/null +++ b/x/DEPS @@ -0,0 +1,6 @@ +include_rules = [ + "+ui/events/platform/platform_event_source.h", + "+third_party/libx11", + "+third_party/libxcb-keysyms", + "+third_party/x11proto", +] diff --git a/x/OWNERS b/x/OWNERS new file mode 100644 index 000000000000..c5442ff23565 --- /dev/null +++ b/x/OWNERS @@ -0,0 +1,4 @@ +thomasanderson@chromium.org + +# Adding new atoms is allowed without OWNERS review. +per-file x11_atom_cache.cc=* diff --git a/x/connection.cc b/x/connection.cc new file mode 100644 index 000000000000..cf033f9126a3 --- /dev/null +++ b/x/connection.cc @@ -0,0 +1,773 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/x/connection.h" + +#include +#include + +#include +#include + +#include "base/auto_reset.h" +#include "base/command_line.h" +#include "base/memory/ptr_util.h" +#include "base/memory/scoped_refptr.h" +#include "base/no_destructor.h" +#include "base/threading/thread_local.h" +#include "base/trace_event/trace_event.h" +#include "ui/gfx/x/bigreq.h" +#include "ui/gfx/x/event.h" +#include "ui/gfx/x/keyboard_state.h" +#include "ui/gfx/x/randr.h" +#include "ui/gfx/x/x11_switches.h" +#include "ui/gfx/x/xkb.h" +#include "ui/gfx/x/xproto.h" +#include "ui/gfx/x/xproto_internal.h" +#include "ui/gfx/x/xproto_types.h" + +namespace x11 { + +namespace { + +// On the wire, sequence IDs are 16 bits. In xcb, they're usually extended to +// 32 and sometimes 64 bits. In Xlib, they're extended to unsigned long, which +// may be 32 or 64 bits depending on the platform. This function is intended to +// prevent bugs caused by comparing two differently sized sequences. Also +// handles rollover. To use, compare the result of this function with 0. For +// example, to compare seq1 <= seq2, use CompareSequenceIds(seq1, seq2) <= 0. +template +auto CompareSequenceIds(T t, U u) { + static_assert(std::is_unsigned::value, ""); + static_assert(std::is_unsigned::value, ""); + // Cast to the smaller of the two types so that comparisons will always work. + // If we casted to the larger type, then the smaller type will be zero-padded + // and may incorrectly compare less than the other value. + using SmallerType = + typename std::conditional::type; + SmallerType t0 = static_cast(t); + SmallerType u0 = static_cast(u); + using SignedType = typename std::make_signed::type; + return static_cast(t0 - u0); +} + +base::ThreadLocalOwnedPointer& GetConnectionTLS() { + static base::NoDestructor> tls; + return *tls; +} + +void DefaultErrorHandler(const Error* error, const char* request_name) { + LOG(WARNING) << "X error received. Request: " << request_name + << "Request, Error: " << error->ToString(); +} + +void DefaultIOErrorHandler() { + LOG(ERROR) << "X connection error received."; +} + +class UnknownError : public Error { + public: + explicit UnknownError(Connection::RawError error_bytes) + : error_bytes_(error_bytes) {} + + ~UnknownError() override = default; + + std::string ToString() const override { + std::stringstream ss; + ss << "UnknownError{"; + // Errors are always a fixed 32 bytes. + for (size_t i = 0; i < 32; i++) { + char buf[3]; + sprintf(buf, "%02x", error_bytes_->data()[i]); + ss << "0x" << buf; + if (i != 31) + ss << ", "; + } + ss << "}"; + return ss.str(); + } + + private: + Connection::RawError error_bytes_; +}; + +} // namespace + +// static +Connection* Connection::Get() { + auto& tls = GetConnectionTLS(); + if (Connection* connection = tls.Get()) + return connection; + auto connection = std::make_unique(); + auto* p_connection = connection.get(); + tls.Set(std::move(connection)); + return p_connection; +} + +// static +void Connection::Set(std::unique_ptr connection) { + DCHECK_CALLED_ON_VALID_SEQUENCE(connection->sequence_checker_); + auto& tls = GetConnectionTLS(); + DCHECK(!tls.Get()); + tls.Set(std::move(connection)); +} + +Connection::Connection(const std::string& address) + : XProto(this), + display_string_( + address.empty() + ? base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( + switches::kX11Display) + : address), + error_handler_(base::BindRepeating(DefaultErrorHandler)), + io_error_handler_(base::BindOnce(DefaultIOErrorHandler)) { + connection_ = + xcb_connect(display_string_.empty() ? nullptr : display_string_.c_str(), + &default_screen_id_); + DCHECK(connection_); + if (Ready()) { + auto buf = ReadBuffer(base::MakeRefCounted( + xcb_get_setup(XcbConnection())), + true); + setup_ = Read(&buf); + default_screen_ = &setup_.roots[DefaultScreenId()]; + InitRootDepthAndVisual(); + } else { + // Default-initialize the setup data so we always have something to return. + setup_.roots.emplace_back(); + default_screen_ = &setup_.roots[0]; + default_screen_->allowed_depths.emplace_back(); + default_root_depth_ = &default_screen_->allowed_depths[0]; + default_root_depth_->visuals.emplace_back(); + default_root_visual_ = &default_root_depth_->visuals[0]; + } + + ExtensionManager::Init(this); + auto enable_bigreq = bigreq().Enable(); + // Xlib enables XKB on display creation, so we do that here to maintain + // compatibility. + xkb() + .UseExtension({Xkb::major_version, Xkb::minor_version}) + .OnResponse(base::BindOnce([](Xkb::UseExtensionResponse response) { + if (!response || !response->supported) + DVLOG(1) << "Xkb extension not available."; + })); + Flush(); + if (auto response = enable_bigreq.Sync()) + extended_max_request_length_ = response->maximum_request_length; + + const Format* formats[256]; + memset(formats, 0, sizeof(formats)); + for (const auto& format : setup_.pixmap_formats) + formats[format.depth] = &format; + + std::vector> default_screen_visuals; + for (const auto& depth : default_screen().allowed_depths) { + const Format* format = formats[depth.depth]; + for (const auto& visual : depth.visuals) { + default_screen_visuals.emplace_back(visual.visual_id, + VisualInfo{format, &visual}); + } + } + default_screen_visuals_ = + base::flat_map(std::move(default_screen_visuals)); + + keyboard_state_ = CreateKeyboardState(this); + + InitErrorParsers(); +} + +Connection::~Connection() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + platform_event_source.reset(); + xcb_disconnect(connection_); +} + +size_t Connection::MaxRequestSizeInBytes() const { + return 4 * std::max(extended_max_request_length_, + setup_.maximum_request_length); +} + +XlibDisplayWrapper Connection::GetXlibDisplay(XlibDisplayType type) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (!xlib_display_) + xlib_display_ = base::WrapUnique(new XlibDisplay(display_string_)); + return XlibDisplayWrapper(xlib_display_->display_, type); +} + +Connection::FutureImpl::FutureImpl(Connection* connection, + SequenceType sequence, + bool generates_reply, + const char* request_name_for_tracing) + : connection(connection), + sequence(sequence), + generates_reply(generates_reply), + request_name_for_tracing(request_name_for_tracing) {} + +void Connection::FutureImpl::Wait() { + connection->WaitForResponse(this); + ProcessResponse(); +} + +void Connection::FutureImpl::Sync(RawReply* raw_reply, + std::unique_ptr* error) { + connection->WaitForResponse(this); + TakeResponse(raw_reply, error); +} + +void Connection::FutureImpl::OnResponse(ResponseCallback callback) { + UpdateRequestHandler(std::move(callback)); +} + +void Connection::FutureImpl::UpdateRequestHandler(ResponseCallback callback) { + DCHECK_CALLED_ON_VALID_SEQUENCE(connection->sequence_checker_); + DCHECK(callback); + + auto* request = connection->GetRequestForFuture(this); + // Make sure we haven't processed this request yet. + DCHECK(request->callback); + + request->callback = std::move(callback); +} + +void Connection::FutureImpl::ProcessResponse() { + DCHECK_CALLED_ON_VALID_SEQUENCE(connection->sequence_checker_); + + auto* request = connection->GetRequestForFuture(this); + DCHECK(request->callback); + DCHECK(request->have_response); + + std::move(request->callback) + .Run(std::move(request->reply), std::move(request->error)); +} + +void Connection::FutureImpl::TakeResponse(RawReply* raw_reply, + std::unique_ptr* error) { + DCHECK_CALLED_ON_VALID_SEQUENCE(connection->sequence_checker_); + + auto* request = connection->GetRequestForFuture(this); + DCHECK(request->callback); + DCHECK(request->have_response); + + *raw_reply = std::move(request->reply); + *error = std::move(request->error); + request->callback.Reset(); +} + +Connection::Request::Request(ResponseCallback callback) + : callback(std::move(callback)) { + DCHECK(this->callback); +} + +Connection::Request::Request(Request&& other) = default; + +Connection::Request::~Request() = default; + +void Connection::Request::SetResponse(Connection* connection, + void* raw_reply, + void* raw_error) { + have_response = true; + if (raw_reply) + reply = base::MakeRefCounted(raw_reply); + if (raw_error) { + error = connection->ParseError( + base::MakeRefCounted(raw_error)); + } +} + +bool Connection::HasNextResponse() { + if (requests_.empty()) + return false; + auto& request = requests_.front(); + if (request.have_response) + return true; + + void* reply = nullptr; + xcb_generic_error_t* error = nullptr; + if (!xcb_poll_for_reply(XcbConnection(), first_request_id_, &reply, &error)) + return false; + + request.SetResponse(this, reply, error); + return true; +} + +bool Connection::HasNextEvent() { + while (!events_.empty()) { + if (events_.front().Initialized()) + return true; + events_.pop_front(); + } + return false; +} + +int Connection::GetFd() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return Ready() ? xcb_get_file_descriptor(XcbConnection()) : -1; +} + +const std::string& Connection::DisplayString() const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return display_string_; +} + +std::string Connection::GetConnectionHostname() const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + char* host = nullptr; + int display_id = 0; + int screen = 0; + if (xcb_parse_display(display_string_.c_str(), &host, &display_id, &screen)) { + std::string name = host; + free(host); + return name; + } + return std::string(); +} + +int Connection::DefaultScreenId() const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + // This is not part of the setup data as the server has no concept of a + // default screen. Instead, it's part of the display name. Eg in + // "localhost:0.0", the screen ID is the second "0". + return default_screen_id_; +} + +bool Connection::Ready() const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return !xcb_connection_has_error(connection_); +} + +void Connection::Flush() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + xcb_flush(connection_); +} + +void Connection::Sync() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (syncing_) + return; + { + base::AutoReset auto_reset(&syncing_, true); + GetInputFocus().Sync(); + } +} + +void Connection::SynchronizeForTest(bool synchronous) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + synchronous_ = synchronous; + if (synchronous_) + Sync(); +} + +void Connection::ReadResponses() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + while (ReadResponse(false)) { + } +} + +bool Connection::ReadResponse(bool queued) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + auto* event = queued ? xcb_poll_for_queued_event(XcbConnection()) + : xcb_poll_for_event(XcbConnection()); + if (event) { + events_.emplace_back(base::MakeRefCounted(event), + this); + } + return event; +} + +Event Connection::WaitForNextEvent() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (HasNextEvent()) { + Event event = std::move(events_.front()); + events_.pop_front(); + return event; + } + if (auto* xcb_event = xcb_wait_for_event(XcbConnection())) { + return Event(base::MakeRefCounted(xcb_event), + this); + } + return Event(); +} + +bool Connection::HasPendingResponses() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return HasNextEvent() || HasNextResponse(); +} + +const Connection::VisualInfo* Connection::GetVisualInfoFromId( + VisualId id) const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + auto it = default_screen_visuals_.find(id); + if (it != default_screen_visuals_.end()) + return &it->second; + return nullptr; +} + +KeyCode Connection::KeysymToKeycode(uint32_t keysym) const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return keyboard_state_->KeysymToKeycode(keysym); +} + +uint32_t Connection::KeycodeToKeysym(KeyCode keycode, + uint32_t modifiers) const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return keyboard_state_->KeycodeToKeysym(keycode, modifiers); +} + +std::unique_ptr Connection::Clone() const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return std::make_unique(display_string_); +} + +void Connection::DetachFromSequence() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DETACH_FROM_SEQUENCE(sequence_checker_); +} + +bool Connection::Dispatch() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (HasNextResponse() && HasNextEvent()) { + auto next_response_sequence = first_request_id_; + auto next_event_sequence = events_.front().sequence(); + + // All events have the sequence number of the last processed request + // included in them. So if a reply and an event have the same sequence, + // the reply must have been received first. + if (CompareSequenceIds(next_event_sequence, next_response_sequence) <= 0) + ProcessNextResponse(); + else + ProcessNextEvent(); + } else if (HasNextResponse()) { + ProcessNextResponse(); + } else if (HasNextEvent()) { + ProcessNextEvent(); + } else { + return false; + } + return true; +} + +void Connection::DispatchAll() { + do { + Flush(); + ReadResponses(); + } while (Dispatch()); +} + +void Connection::DispatchEvent(const Event& event) { + PreDispatchEvent(event); + + // NB: The event should be reset to nullptr when this function + // returns, not to its initial value, otherwise nested message loops + // will incorrectly think that the current event being dispatched is + // an old event. This means base::AutoReset should not be used. + dispatching_event_ = &event; + for (auto& observer : event_observers_) + observer.OnEvent(event); + dispatching_event_ = nullptr; +} + +Connection::ErrorHandler Connection::SetErrorHandler(ErrorHandler new_handler) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + return std::exchange(error_handler_, new_handler); +} + +void Connection::SetIOErrorHandler(IOErrorHandler new_handler) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + io_error_handler_ = std::move(new_handler); +} + +void Connection::AddEventObserver(EventObserver* observer) { + event_observers_.AddObserver(observer); +} + +void Connection::RemoveEventObserver(EventObserver* observer) { + event_observers_.RemoveObserver(observer); +} + +xcb_connection_t* Connection::XcbConnection() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (io_error_handler_ && xcb_connection_has_error(connection_)) + std::move(io_error_handler_).Run(); + return connection_; +} + +void Connection::InitRootDepthAndVisual() { + for (auto& depth : default_screen_->allowed_depths) { + for (auto& visual : depth.visuals) { + if (visual.visual_id == default_screen_->root_visual) { + default_root_depth_ = &depth; + default_root_visual_ = &visual; + return; + } + } + } + NOTREACHED(); +} + +void Connection::ProcessNextEvent() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK(HasNextEvent()); + + Event event = std::move(events_.front()); + events_.pop_front(); + + DispatchEvent(event); +} + +void Connection::ProcessNextResponse() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK(!requests_.empty()); + DCHECK(requests_.front().have_response); + + Request request = std::move(requests_.front()); + requests_.pop_front(); + if (last_non_void_request_id_.has_value() && + last_non_void_request_id_.value() == first_request_id_) { + last_non_void_request_id_ = absl::nullopt; + } + first_request_id_++; + if (request.callback) { + std::move(request.callback) + .Run(std::move(request.reply), std::move(request.error)); + } +} + +std::unique_ptr Connection::SendRequest( + WriteBuffer* buf, + const char* request_name_for_tracing, + bool generates_reply, + bool reply_has_fds) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + xcb_protocol_request_t xpr{ + .ext = nullptr, + .isvoid = !generates_reply, + }; + + struct RequestHeader { + uint8_t major_opcode; + uint8_t minor_opcode; + uint16_t length; + }; + + struct ExtendedRequestHeader { + RequestHeader header; + uint32_t long_length; + }; + static_assert(sizeof(ExtendedRequestHeader) == 8, ""); + + auto& first_buffer = buf->GetBuffers()[0]; + DCHECK_GE(first_buffer->size(), sizeof(RequestHeader)); + auto* old_header = reinterpret_cast( + const_cast(first_buffer->data())); + ExtendedRequestHeader new_header{*old_header, 0}; + + // Requests are always a multiple of 4 bytes on the wire. Because of this, + // the length field represents the size in chunks of 4 bytes. + DCHECK_EQ(buf->offset() % 4, 0UL); + size_t size32 = buf->offset() / 4; + + // XCB requires 2 iovecs for its own internal usage. + std::vector io{{nullptr, 0}, {nullptr, 0}}; + if (size32 < setup_.maximum_request_length) { + // Regular request + old_header->length = size32; + } else if (size32 < extended_max_request_length_) { + // BigRequests extension request + DCHECK_EQ(new_header.header.length, 0U); + new_header.long_length = size32 + 1; + + io.push_back({&new_header, sizeof(ExtendedRequestHeader)}); + first_buffer = base::MakeRefCounted( + first_buffer, sizeof(RequestHeader), + first_buffer->size() - sizeof(RequestHeader)); + } else { + LOG(ERROR) << "Cannot send request of length " << buf->offset(); + return nullptr; + } + + for (auto& buffer : buf->GetBuffers()) + io.push_back({const_cast(buffer->data()), buffer->size()}); + xpr.count = io.size() - 2; + + xcb_connection_t* conn = XcbConnection(); + auto flags = XCB_REQUEST_CHECKED | XCB_REQUEST_RAW; + if (reply_has_fds) + flags |= XCB_REQUEST_REPLY_FDS; + + for (int fd : buf->fds()) + xcb_send_fd(conn, fd); + SequenceType sequence = xcb_send_request(conn, flags, &io[2], &xpr); + + if (xcb_connection_has_error(conn)) + return nullptr; + + SequenceType next_request_id = first_request_id_ + requests_.size(); + DCHECK_EQ(CompareSequenceIds(next_request_id, sequence), 0); + + // If we ever reach 2^32 outstanding requests, then bail because sequence IDs + // would no longer be unique. + next_request_id++; + CHECK_NE(next_request_id, first_request_id_); + + // Install a default response-handler that throws away the reply and prints + // the error if there is one. This handler may be overridden by clients. + auto callback = base::BindOnce( + [](const char* request_name, Connection::ErrorHandler error_handler, + RawReply raw_reply, std::unique_ptr error) { + if (error) + error_handler.Run(error.get(), request_name); + }, + request_name_for_tracing, error_handler_); + requests_.emplace_back(std::move(callback)); + if (generates_reply) + last_non_void_request_id_ = sequence; + if (synchronous_) + Sync(); + + return std::make_unique(this, sequence, generates_reply, + request_name_for_tracing); +} + +void Connection::WaitForResponse(FutureImpl* future) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + auto* request = GetRequestForFuture(future); + DCHECK(request->callback); + if (request->have_response) + return; + + xcb_generic_error_t* error = nullptr; + void* reply = nullptr; + if (future->generates_reply) { + if (!xcb_poll_for_reply(XcbConnection(), future->sequence, &reply, + &error)) { + TRACE_EVENT1("ui", "xcb_wait_for_reply", "request", + future->request_name_for_tracing); + reply = xcb_wait_for_reply(XcbConnection(), future->sequence, &error); + } + } else { + // There's a special case here. This request doesn't generate a reply, and + // may not generate an error, so the only way to know if it finished is to + // send another request that we know will generate a reply or error. Once + // the new request finishes, we know this request has finished, since the + // server is guaranteed to process requests in order. Normally, the + // xcb_request_check() below would do this for us automatically, but we need + // to keep track of the sequence count ourselves, so we explicitly make a + // GetInputFocus request if necessary (which is the request xcb would have + // made -- GetInputFocus is chosen since it has the minimum size request and + // reply, and can be made at any time). + bool needs_extra_request_for_check = false; + if (!last_non_void_request_id_.has_value()) { + needs_extra_request_for_check = true; + } else { + SequenceType last_non_void_offset = + last_non_void_request_id_.value() - first_request_id_; + SequenceType sequence_offset = future->sequence - first_request_id_; + needs_extra_request_for_check = sequence_offset > last_non_void_offset; + } + if (needs_extra_request_for_check) { + GetInputFocus().IgnoreError(); + // The circular_deque may have swapped buffers, so we need to get a fresh + // pointer to the request. + request = GetRequestForFuture(future); + } + + // libxcb has a bug where it doesn't flush in xcb_request_check() under some + // circumstances, leading to deadlock [1], so always perform a manual flush. + // [1] https://gitlab.freedesktop.org/xorg/lib/libxcb/-/issues/53 + Flush(); + + { + TRACE_EVENT1("ui", "xcb_request_check", "request", + future->request_name_for_tracing); + error = xcb_request_check(XcbConnection(), {future->sequence}); + } + } + request->SetResponse(this, reply, error); +} + +Connection::Request* Connection::GetRequestForFuture(FutureImpl* future) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + SequenceType offset = future->sequence - first_request_id_; + DCHECK_LT(offset, requests_.size()); + return &requests_[offset]; +} + +void Connection::PreDispatchEvent(const Event& event) { + if (auto* mapping = event.As()) { + if (mapping->request == Mapping::Modifier || + mapping->request == Mapping::Keyboard) { + setup_.min_keycode = mapping->first_keycode; + setup_.max_keycode = static_cast( + static_cast(mapping->first_keycode) + mapping->count - 1); + keyboard_state_->UpdateMapping(); + } + } + if (auto* notify = event.As()) { + setup_.min_keycode = notify->minKeyCode; + setup_.max_keycode = notify->maxKeyCode; + keyboard_state_->UpdateMapping(); + } + + // This is adapted from XRRUpdateConfiguration. + if (auto* configure = event.As()) { + int index = ScreenIndexFromRootWindow(configure->window); + if (index != -1) { + setup_.roots[index].width_in_pixels = configure->width; + setup_.roots[index].height_in_pixels = configure->height; + } + } else if (auto* screen = event.As()) { + int index = ScreenIndexFromRootWindow(screen->root); + DCHECK_GE(index, 0); + bool portrait = + static_cast(screen->rotation & (RandR::Rotation::Rotate_90 | + RandR::Rotation::Rotate_270)); + if (portrait) { + setup_.roots[index].width_in_pixels = screen->height; + setup_.roots[index].height_in_pixels = screen->width; + setup_.roots[index].width_in_millimeters = screen->mheight; + setup_.roots[index].height_in_millimeters = screen->mwidth; + } else { + setup_.roots[index].width_in_pixels = screen->width; + setup_.roots[index].height_in_pixels = screen->height; + setup_.roots[index].width_in_millimeters = screen->mwidth; + setup_.roots[index].height_in_millimeters = screen->mheight; + } + } +} + +int Connection::ScreenIndexFromRootWindow(Window root) const { + for (size_t i = 0; i < setup_.roots.size(); i++) { + if (setup_.roots[i].root == root) + return i; + } + return -1; +} + +std::unique_ptr Connection::ParseError(RawError error_bytes) { + if (!error_bytes) + return nullptr; + struct ErrorHeader { + uint8_t response_type; + uint8_t error_code; + uint16_t sequence; + }; + auto error_code = error_bytes->front_as()->error_code; + if (auto parser = error_parsers_[error_code]) + return parser(error_bytes); + return std::make_unique(error_bytes); +} + +uint32_t Connection::GenerateIdImpl() { + return xcb_generate_id(connection_); +} + +} // namespace x11 diff --git a/x/connection.h b/x/connection.h new file mode 100644 index 000000000000..4281481678ee --- /dev/null +++ b/x/connection.h @@ -0,0 +1,347 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_X_CONNECTION_H_ +#define UI_GFX_X_CONNECTION_H_ + +#include "base/callback.h" +#include "base/component_export.h" +#include "base/containers/circular_deque.h" +#include "base/containers/flat_map.h" +#include "base/sequence_checker.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/events/platform/platform_event_source.h" +#include "ui/gfx/x/extension_manager.h" +#include "ui/gfx/x/xlib_support.h" +#include "ui/gfx/x/xproto.h" + +typedef struct xcb_connection_t xcb_connection_t; + +namespace x11 { + +class Event; +class KeyboardState; +class WriteBuffer; + +// This interface is used by classes wanting to receive +// Events directly. For input events (mouse, keyboard, touch), a +// PlatformEventObserver should be used instead. +class EVENTS_EXPORT EventObserver { + public: + virtual void OnEvent(const Event& xevent) = 0; + + protected: + virtual ~EventObserver() = default; +}; + +// Represents a socket to the X11 server. +class COMPONENT_EXPORT(X11) Connection : public XProto, + public ExtensionManager { + public: + using ErrorHandler = base::RepeatingCallback; + using IOErrorHandler = base::OnceClosure; + using RawReply = scoped_refptr; + using RawError = scoped_refptr; + using ResponseCallback = + base::OnceCallback error)>; + + // xcb returns unsigned int when making requests. This may be updated to + // uint16_t if/when we stop using xcb for socket IO. + using SequenceType = unsigned int; + + struct VisualInfo { + const Format* format; + const VisualType* visual_type; + }; + + // Gets or creates the thread local connection instance. + static Connection* Get(); + + // Sets the thread local connection instance. + static void Set(std::unique_ptr connection); + + template + T GenerateId() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return static_cast(GenerateIdImpl()); + } + + template + Future SendRequest(WriteBuffer* buf, + const char* request_name, + bool reply_has_fds) { + bool generates_reply = !std::is_void::value; + return Future( + SendRequest(buf, request_name, generates_reply, reply_has_fds)); + } + + explicit Connection(const std::string& address = ""); + ~Connection(); + + Connection(const Connection&) = delete; + Connection(Connection&&) = delete; + + // Obtain an Xlib display that's connected to the same server as |this|. This + // is meant to be used only for compatibility with components like GLX, + // Vulkan, and VAAPI. The underlying socket is not shared, so synchronization + // with |this| may be necessary. The |type| parameter can be used to achieve + // synchronization. The returned wrapper should not be saved. + XlibDisplayWrapper GetXlibDisplay( + XlibDisplayType type = XlibDisplayType::kNormal); + + size_t MaxRequestSizeInBytes() const; + + const Setup& setup() const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return setup_; + } + const Screen& default_screen() const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return *default_screen_; + } + Window default_root() const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return default_screen().root; + } + const Depth& default_root_depth() const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return *default_root_depth_; + } + const VisualType& default_root_visual() const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return *default_root_visual_; + } + + const Event* dispatching_event() const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return dispatching_event_; + } + + // Returns the underlying socket's FD if the connection is valid, or -1 + // otherwise. + int GetFd(); + + const std::string& DisplayString() const; + + std::string GetConnectionHostname() const; + + int DefaultScreenId() const; + + // Is the connection up and error-free? + bool Ready() const; + + // Write all requests to the socket. + void Flush(); + + // Flush and block until the server has responded to all requests. + void Sync(); + + // If |synchronous| is true, this makes all requests Sync(). + void SynchronizeForTest(bool synchronous); + + // Read all responses from the socket without blocking. This function will + // make non-blocking read() syscalls. + void ReadResponses(); + + // Read a single response. If |queued| is true, no read() will be done; a + // response may only be translated from buffered socket data. If |queued| is + // false, a non-blocking read() will only be done if no response is buffered. + // Returns true if an event was read. + bool ReadResponse(bool queued); + + Event WaitForNextEvent(); + + // Are there any events, errors, or replies already buffered? + bool HasPendingResponses(); + + // Dispatches one event, reply, or error from the server; or returns false + // if there's none available. This function doesn't read or write any data on + // the socket. + bool Dispatch(); + + // Dispatches all available events, replies, and errors. This function + // ensures the read and write buffers on the socket are empty upon returning. + void DispatchAll(); + + // Directly dispatch an event, bypassing the event queue. + void DispatchEvent(const Event& event); + + // Returns the old error handler. + ErrorHandler SetErrorHandler(ErrorHandler new_handler); + + void SetIOErrorHandler(IOErrorHandler new_handler); + + void AddEventObserver(EventObserver* observer); + + void RemoveEventObserver(EventObserver* observer); + + // Returns the visual data for |id|, or nullptr if the visual with that ID + // doesn't exist or only exists on a non-default screen. + const VisualInfo* GetVisualInfoFromId(VisualId id) const; + + KeyCode KeysymToKeycode(uint32_t keysym) const; + + uint32_t KeycodeToKeysym(KeyCode keycode, uint32_t modifiers) const; + + // Access the event buffer. Clients may modify the queue, including + // "deleting" events by setting events[i] = x11::Event(), which will + // guarantee all calls to x11::Event::As() will return nullptr. + base::circular_deque& events() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return events_; + } + + std::unique_ptr Clone() const; + + // Releases ownership of this connection to a different thread. + void DetachFromSequence(); + + // The viz compositor thread hangs a PlatformEventSource off the connection so + // that it gets destroyed at the appropriate time. + // TODO(thomasanderson): This is a layering violation and this should be moved + // somewhere else. + std::unique_ptr platform_event_source; + + private: + template + friend class Future; + + class COMPONENT_EXPORT(X11) FutureImpl { + public: + FutureImpl(Connection* connection, + SequenceType sequence, + bool generates_reply, + const char* request_name_for_tracing); + + void Wait(); + + void Sync(RawReply* raw_reply, std::unique_ptr* error); + + void OnResponse(ResponseCallback callback); + + // Update an existing Request with a new handler. |sequence| must + // correspond to a request in the queue that has not already been processed + // out-of-order. + void UpdateRequestHandler(ResponseCallback callback); + + // Call the response handler for request |sequence| now (out-of-order). The + // response must already have been obtained from a call to + // WaitForResponse(). + void ProcessResponse(); + + // Clear the response handler for request |sequence| and take the response. + // The response must already have been obtained using WaitForResponse(). + void TakeResponse(RawReply* reply, std::unique_ptr* error); + + Connection* connection = nullptr; + SequenceType sequence = 0; + bool generates_reply = false; + const char* request_name_for_tracing = nullptr; + }; + + struct Request { + explicit Request(ResponseCallback callback); + Request(Request&& other); + ~Request(); + + // Takes ownership of |reply| and |error|. + void SetResponse(Connection* connection, void* raw_reply, void* raw_error); + + // If |callback| is nullptr, then this request has already been processed + // out-of-order. + ResponseCallback callback; + + // Indicates if |reply| and |error| are available. A separate + // |have_response| flag is necessary to distinguish the case where a request + // hasn't finished yet from the case where a request finished but didn't + // generate a reply or error. + bool have_response = false; + RawReply reply; + std::unique_ptr error; + }; + + xcb_connection_t* XcbConnection(); + + void InitRootDepthAndVisual(); + + void ProcessNextEvent(); + + void ProcessNextResponse(); + + bool HasNextResponse(); + + bool HasNextEvent(); + + // Creates a new Request and adds it to the end of the queue. + // |request_name_for_tracing| must be valid until the response is + // dispatched; currently the string values are only stored in .rodata, so + // this constraint is satisfied. + std::unique_ptr SendRequest(WriteBuffer* buf, + const char* request_name_for_tracing, + bool generates_reply, + bool reply_has_fds); + + // Block until the reply or error for request |sequence| is received. + void WaitForResponse(FutureImpl* future); + + Request* GetRequestForFuture(FutureImpl* future); + + void PreDispatchEvent(const Event& event); + + int ScreenIndexFromRootWindow(Window root) const; + + // This function is implemented in the generated read_error.cc. + void InitErrorParsers(); + + std::unique_ptr ParseError(RawError error_bytes); + + uint32_t GenerateIdImpl(); + + xcb_connection_t* connection_ = nullptr; + std::unique_ptr xlib_display_; + + bool synchronous_ = false; + bool syncing_ = false; + + uint32_t extended_max_request_length_ = 0; + + std::string display_string_; + int default_screen_id_ = 0; + Setup setup_; + Screen* default_screen_ = nullptr; + Depth* default_root_depth_ = nullptr; + VisualType* default_root_visual_ = nullptr; + + base::flat_map default_screen_visuals_; + + std::unique_ptr keyboard_state_; + + base::circular_deque events_; + + base::ObserverList::Unchecked event_observers_; + + // The Event currently being dispatched, or nullptr if there is none. + const Event* dispatching_event_ = nullptr; + + base::circular_deque requests_; + // The sequence ID of requests_.front(), or if |requests_| is empty, then the + // ID of the next request that will go in the queue. This starts at 1 because + // the 0'th request is handled internally by XCB when opening the connection. + SequenceType first_request_id_ = 1; + // If any request in |requests_| will generate a reply, this is the ID of the + // latest one, otherwise this is absl::nullopt. + absl::optional last_non_void_request_id_; + + using ErrorParser = std::unique_ptr (*)(RawError error_bytes); + std::array error_parsers_{}; + + ErrorHandler error_handler_; + IOErrorHandler io_error_handler_; + + SEQUENCE_CHECKER(sequence_checker_); +}; + +} // namespace x11 + +#endif // UI_GFX_X_CONNECTION_H_ diff --git a/x/connection_unittest.cc b/x/connection_unittest.cc new file mode 100644 index 000000000000..46928a735b02 --- /dev/null +++ b/x/connection_unittest.cc @@ -0,0 +1,105 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/x/connection.h" + +#include "base/memory/ref_counted_memory.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/x/event.h" +#include "ui/gfx/x/future.h" +#include "ui/gfx/x/xproto.h" + +namespace x11 { + +namespace { + +Window CreateWindow(Connection* connection) { + Window window = connection->GenerateId(); + auto create_window_future = connection->CreateWindow({ + .depth = connection->default_root_depth().depth, + .wid = window, + .parent = connection->default_screen().root, + .width = 1, + .height = 1, + .override_redirect = Bool32(true), + }); + auto create_window_response = create_window_future.Sync(); + EXPECT_FALSE(create_window_response.error); + return window; +} + +} // namespace + +// Connection setup and teardown. +TEST(X11ConnectionTest, Basic) { + Connection connection; + ASSERT_TRUE(connection.Ready()); +} + +TEST(X11ConnectionTest, Request) { + Connection connection; + ASSERT_TRUE(connection.Ready()); + + Window window = CreateWindow(&connection); + + auto attributes = connection.GetWindowAttributes({window}).Sync(); + ASSERT_TRUE(attributes); + EXPECT_EQ(attributes->map_state, MapState::Unmapped); + EXPECT_TRUE(attributes->override_redirect); + + auto geometry = connection.GetGeometry(window).Sync(); + ASSERT_TRUE(geometry); + EXPECT_EQ(geometry->x, 0); + EXPECT_EQ(geometry->y, 0); + EXPECT_EQ(geometry->width, 1u); + EXPECT_EQ(geometry->height, 1u); +} + +TEST(X11ConnectionTest, Event) { + Connection connection; + ASSERT_TRUE(connection.Ready()); + + Window window = CreateWindow(&connection); + + auto cwa_future = connection.ChangeWindowAttributes({ + .window = window, + .event_mask = EventMask::PropertyChange, + }); + EXPECT_FALSE(cwa_future.Sync().error); + + std::vector data{0}; + auto prop_future = connection.ChangeProperty({ + .window = static_cast(window), + .property = Atom::WM_NAME, + .type = Atom::STRING, + .format = CHAR_BIT, + .data_len = 1, + .data = base::RefCountedBytes::TakeVector(&data), + }); + EXPECT_FALSE(prop_future.Sync().error); + + connection.ReadResponses(); + ASSERT_EQ(connection.events().size(), 1u); + auto* prop = connection.events().front().As(); + ASSERT_TRUE(prop); + EXPECT_EQ(prop->atom, Atom::WM_NAME); + EXPECT_EQ(prop->state, Property::NewValue); +} + +TEST(X11ConnectionTest, Error) { + Connection connection; + ASSERT_TRUE(connection.Ready()); + + Window invalid_window = connection.GenerateId(); + + auto geometry = connection.GetGeometry(invalid_window).Sync(); + ASSERT_FALSE(geometry); + auto* error = geometry.error.get(); + ASSERT_TRUE(error); + // TODO(thomasanderson): Implement As<> for errors, similar to events. + auto* drawable_error = reinterpret_cast(error); + EXPECT_EQ(drawable_error->bad_value, static_cast(invalid_window)); +} + +} // namespace x11 diff --git a/x/error.cc b/x/error.cc new file mode 100644 index 000000000000..f33de1a474aa --- /dev/null +++ b/x/error.cc @@ -0,0 +1,13 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/x/error.h" + +namespace x11 { + +Error::Error() = default; + +Error::~Error() = default; + +} // namespace x11 diff --git a/x/error.h b/x/error.h new file mode 100644 index 000000000000..a9de90bfab55 --- /dev/null +++ b/x/error.h @@ -0,0 +1,26 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_X_ERROR_H_ +#define UI_GFX_X_ERROR_H_ + +#include + +#include "base/component_export.h" + +namespace x11 { + +// This class is a generic interface for X11 errors. Currently the only +// functionality is printing the error as a human-readable string. +class COMPONENT_EXPORT(X11) Error { + public: + Error(); + virtual ~Error(); + + virtual std::string ToString() const = 0; +}; + +} // namespace x11 + +#endif // UI_GFX_X_ERROR_H_ diff --git a/x/event.cc b/x/event.cc new file mode 100644 index 000000000000..68d6946df088 --- /dev/null +++ b/x/event.cc @@ -0,0 +1,83 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/x/event.h" + +#include + +#include + +#include "base/check_op.h" +#include "base/memory/scoped_refptr.h" +#include "ui/gfx/x/connection.h" +#include "ui/gfx/x/xproto.h" +#include "ui/gfx/x/xproto_internal.h" +#include "ui/gfx/x/xproto_types.h" + +namespace x11 { + +Event::Event() = default; + +Event::Event(scoped_refptr event_bytes, + Connection* connection) { + auto* xcb_event = reinterpret_cast( + const_cast(event_bytes->data())); + sequence_ = xcb_event->full_sequence; + // KeymapNotify events are the only events that don't have a sequence. + if ((xcb_event->response_type & ~kSendEventMask) != + KeymapNotifyEvent::opcode) { + // On the wire, events are 32 bytes except for generic events which are + // trailed by additional data. XCB inserts an extended 4-byte sequence + // between the 32-byte event and the additional data, so we need to shift + // the additional data over by 4 bytes so the event is back in its wire + // format, which is what Xlib and XProto are expecting. + if ((xcb_event->response_type & ~kSendEventMask) == + GeGenericEvent::opcode) { + auto* ge = reinterpret_cast(xcb_event); + constexpr size_t ge_length = sizeof(xcb_raw_generic_event_t); + constexpr size_t offset = sizeof(ge->full_sequence); + size_t extended_length = ge->length * 4; + if (extended_length < ge_length) { + // If the additional data is smaller than the fixed size event, shift + // the additional data to the left. + memmove(&ge->full_sequence, &ge[1], extended_length); + } else { + // Otherwise shift the fixed size event to the right. + char* addr = reinterpret_cast(xcb_event); + memmove(addr + offset, addr, ge_length); + event_bytes = base::MakeRefCounted( + event_bytes, offset, ge_length + extended_length); + xcb_event = reinterpret_cast(addr + offset); + } + } + } + + // Xlib sometimes modifies |xcb_event|, so let it handle the event after + // we parse it with ReadEvent(). + ReadBuffer buf(event_bytes); + ReadEvent(this, connection, &buf); +} + +Event::Event(Event&& event) { + memcpy(this, &event, sizeof(Event)); + memset(&event, 0, sizeof(Event)); +} + +Event& Event::operator=(Event&& event) { + Dealloc(); + memcpy(this, &event, sizeof(Event)); + memset(&event, 0, sizeof(Event)); + return *this; +} + +Event::~Event() { + Dealloc(); +} + +void Event::Dealloc() { + if (deleter_) + deleter_(event_); +} + +} // namespace x11 diff --git a/x/event.h b/x/event.h new file mode 100644 index 000000000000..ed4f9b0c0fee --- /dev/null +++ b/x/event.h @@ -0,0 +1,96 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_X_EVENT_H_ +#define UI_GFX_X_EVENT_H_ + +#include +#include + +#include "base/component_export.h" +#include "base/memory/ref_counted_memory.h" +#include "base/memory/scoped_refptr.h" +#include "ui/gfx/x/xproto.h" + +namespace x11 { + +class Connection; +class Event; +struct ReadBuffer; + +COMPONENT_EXPORT(X11) +void ReadEvent(Event* event, Connection* connection, ReadBuffer* buffer); + +class COMPONENT_EXPORT(X11) Event { + public: + template + explicit Event(T&& xproto_event) { + using DecayT = std::decay_t; + sequence_ = xproto_event.sequence; + type_id_ = DecayT::type_id; + deleter_ = [](void* event) { delete reinterpret_cast(event); }; + auto* event = new DecayT(std::forward(xproto_event)); + event_ = event; + window_ = event->GetWindow(); + } + + Event(); + + // |event_bytes| is modified and will not be valid after this call. + // A copy is necessary if the original data is still needed. + Event(scoped_refptr event_bytes, + Connection* connection); + + Event(const Event&) = delete; + Event& operator=(const Event&) = delete; + + Event(Event&& event); + Event& operator=(Event&& event); + + ~Event(); + + template + T* As() { + if (type_id_ == T::type_id) + return reinterpret_cast(event_); + return nullptr; + } + + template + const T* As() const { + return const_cast(this)->As(); + } + + uint32_t sequence() const { return sequence_; } + + Window window() const { return window_ ? *window_ : Window::None; } + void set_window(Window window) { + if (window_) + *window_ = window; + } + + bool Initialized() const { return deleter_; } + + private: + friend void ReadEvent(Event* event, + Connection* connection, + ReadBuffer* buffer); + + void Dealloc(); + + uint16_t sequence_ = 0; + + // XProto event state. + int type_id_ = 0; + void (*deleter_)(void*) = nullptr; + void* event_ = nullptr; + + // This member points to a field in |event_|, or may be nullptr if there's no + // associated window for the event. It's owned by |event_|, not us. + Window* window_ = nullptr; +}; + +} // namespace x11 + +#endif // UI_GFX_X_EVENT_H_ diff --git a/x/future.h b/x/future.h new file mode 100644 index 000000000000..fd2009357b60 --- /dev/null +++ b/x/future.h @@ -0,0 +1,114 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_X_FUTURE_H_ +#define UI_GFX_X_FUTURE_H_ + +#include "ui/gfx/x/connection.h" +#include "ui/gfx/x/xproto_types.h" + +namespace x11 { + +// An Future wraps an asynchronous response from the X11 server. The +// response may be waited-for with Sync(), or asynchronously handled by +// installing a response handler using OnResponse(). +template +class Future { + public: + using Callback = base::OnceCallback response)>; + + Future() = default; + + explicit Future(std::unique_ptr impl) + : impl_(std::move(impl)) {} + + // Blocks until we receive the response from the server. Returns the response. + Response Sync() { + if (!impl_) + return {nullptr, nullptr}; + + Connection::RawReply raw_reply; + std::unique_ptr error; + impl_->Sync(&raw_reply, &error); + + std::unique_ptr reply; + if (raw_reply) { + auto buf = ReadBuffer(raw_reply); + reply = detail::ReadReply(&buf); + } + + return {std::move(reply), std::move(error)}; + } + + // Block until this request is handled by the server. Unlike Sync(), this + // method doesn't return the response. Rather, it calls the response + // handler installed for this request out-of-order. + void Wait() { + if (impl_) + impl_->Wait(); + } + + // Installs |callback| to be run when the response is received. + void OnResponse(Callback callback) { + if (!impl_) + return; + + // This intermediate callback handles the conversion from |raw_reply| to a + // real Reply object before feeding the result to |callback|. This means + // |callback| must be bound as the first argument of the intermediate + // function. + auto wrapper = [](Callback callback, Connection::RawReply raw_reply, + std::unique_ptr error) { + std::unique_ptr reply; + if (raw_reply) { + ReadBuffer buf(raw_reply); + reply = detail::ReadReply(&buf); + } + std::move(callback).Run({std::move(reply), std::move(error)}); + }; + impl_->OnResponse(base::BindOnce(wrapper, std::move(callback))); + } + + void IgnoreError() { + OnResponse(base::BindOnce([](Response) {})); + } + + private: + std::unique_ptr impl_; +}; + +// Sync() specialization for requests that don't generate replies. The returned +// response will only contain an error if there was one. +template <> +inline Response Future::Sync() { + if (!impl_) + return Response{nullptr}; + + Connection::RawReply raw_reply; + std::unique_ptr error; + impl_->Sync(&raw_reply, &error); + DCHECK(!raw_reply); + return Response(std::move(error)); +} + +// OnResponse() specialization for requests that don't generate replies. The +// response argument to |callback| will only contain an error if there was one. +template <> +inline void Future::OnResponse(Callback callback) { + if (!impl_) + return; + + // See Future::OnResponse() for an explanation of why + // this wrapper is necessary. + auto wrapper = [](Callback callback, Connection::RawReply reply, + std::unique_ptr error) { + DCHECK(!reply); + std::move(callback).Run(Response{std::move(error)}); + }; + impl_->OnResponse(base::BindOnce(wrapper, std::move(callback))); +} + +} // namespace x11 + +#endif // UI_GFX_X_FUTURE_H_ diff --git a/x/gen_xproto.py b/x/gen_xproto.py new file mode 100644 index 000000000000..715d80f9b003 --- /dev/null +++ b/x/gen_xproto.py @@ -0,0 +1,1661 @@ +# Copyright 2020 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# This script generates header/source files with C++ language bindings +# for the X11 protocol and its extensions. The protocol information +# is obtained from xcbproto which provides XML files describing the +# wire format. However, we don't parse the XML here; xcbproto ships +# with xcbgen, a python library that parses the files into python data +# structures for us. + +from __future__ import print_function + +import argparse +import collections +import itertools +import os +import re +import sys +import types + +# __main__.output must be defined before importing xcbgen, +# so this global is unavoidable. +output = collections.defaultdict(int) + +RENAME = { + 'ANIMCURSORELT': 'AnimationCursorElement', + 'CA': 'ChangeAlarmAttribute', + 'CHAR2B': 'Char16', + 'CHARINFO': 'CharInfo', + 'COLORITEM': 'ColorItem', + 'COLORMAP': 'ColorMap', + 'Connection': 'RandRConnection', + 'CP': 'CreatePictureAttribute', + 'CS': 'ClientSpec', + 'CW': 'CreateWindowAttribute', + 'DAMAGE': 'DamageId', + 'DIRECTFORMAT': 'DirectFormat', + 'DOTCLOCK': 'DotClock', + 'FBCONFIG': 'FbConfig', + 'FONTPROP': 'FontProperty', + 'GC': 'GraphicsContextAttribute', + 'GCONTEXT': 'GraphicsContext', + 'GLYPHINFO': 'GlyphInfo', + 'GLYPHSET': 'GlyphSet', + 'INDEXVALUE': 'IndexValue', + 'KB': 'Keyboard', + 'KEYCODE': 'KeyCode', + 'KEYCODE32': 'KeyCode32', + 'KEYSYM': 'KeySym', + 'LINEFIX': 'LineFix', + 'OP': 'Operation', + 'PBUFFER': 'PBuffer', + 'PCONTEXT': 'PContext', + 'PICTDEPTH': 'PictDepth', + 'PICTFORMAT': 'PictFormat', + 'PICTFORMINFO': 'PictFormInfo', + 'PICTSCREEN': 'PictScreen', + 'PICTVISUAL': 'PictVisual', + 'POINTFIX': 'PointFix', + 'SPANFIX': 'SpanFix', + 'SUBPICTURE': 'SubPicture', + 'SYSTEMCOUNTER': 'SystemCounter', + 'TIMECOORD': 'TimeCoord', + 'TIMESTAMP': 'Time', + 'VISUALID': 'VisualId', + 'VISUALTYPE': 'VisualType', + 'WAITCONDITION': 'WaitCondition', +} + +READ_SPECIAL = set([ + ('xcb', 'Setup'), +]) + +WRITE_SPECIAL = set([ + ('xcb', 'ClientMessage'), + ('xcb', 'Expose'), + ('xcb', 'UnmapNotify'), + ('xcb', 'SelectionNotify'), + ('xcb', 'MotionNotify'), + ('xcb', 'Key'), + ('xcb', 'Button'), + ('xcb', 'PropertyNotify'), +]) + +FILE_HEADER = \ +'''// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// %s +''' % ' \\\n// '.join(sys.argv) + + +def adjust_type_name(name): + if name in RENAME: + return RENAME[name] + # If there's an underscore, then this is either snake case or upper case. + if '_' in name: + return ''.join([ + token[0].upper() + token[1:].lower() for token in name.split('_') + ]) + if name.isupper(): + name = name.lower() + # Now the only possibilities are caml case and pascal case. It could also + # be snake case with a single word, but that would be same as caml case. + # To convert all of these, just capitalize the first letter. + return name[0].upper() + name[1:] + + +# Given a list of event names like ["KeyPress", "KeyRelease"], returns a name +# suitable for use as a base event like "Key". +def event_base_name(names): + # If there's only one event in this group, the "common name" is just + # the event name. + if len(names) == 1: + return names[0] + + # Handle a few special cases where the longest common prefix is empty: eg. + # EnterNotify/LeaveNotify/FocusIn/FocusOut -> Crossing. + EVENT_NAMES = [ + ('TouchBegin', 'Device'), + ('RawTouchBegin', 'RawDevice'), + ('Enter', 'Crossing'), + ('EnterNotify', 'Crossing'), + ('DeviceButtonPress', 'LegacyDevice'), + ] + for name, rename in EVENT_NAMES: + if name in names: + return rename + + # Use the longest common prefix of the event names as the base name. + name = ''.join( + chars[0] + for chars in itertools.takewhile(lambda chars: len(set(chars)) == 1, + zip(*names))) + assert name + return name + + +def list_size(name, list_type): + separator = '->' if list_type.is_ref_counted_memory else '.' + return '%s%ssize()' % (name, separator) + + +# Left-pad with 2 spaces while this class is alive. +class Indent: + def __init__(self, xproto, opening_line, closing_line): + self.xproto = xproto + self.opening_line = opening_line + self.closing_line = closing_line + + def __enter__(self): + self.xproto.write(self.opening_line) + self.xproto.indent += 1 + + def __exit__(self, exc_type, exc_value, exc_traceback): + self.xproto.indent -= 1 + self.xproto.write(self.closing_line) + + +# Make all members of |obj|, given by |fields|, visible in +# the local scope while this class is alive. +class ScopedFields: + def __init__(self, xproto, obj, fields): + self.xproto = xproto + self.obj = obj + self.fields = fields + self.n_pushed = 0 + + def __enter__(self): + for field in self.fields: + self.n_pushed += self.xproto.add_field_to_scope(field, self.obj) + + if self.n_pushed: + self.xproto.write() + + def __exit__(self, exc_type, exc_value, exc_traceback): + for _ in range(self.n_pushed): + self.xproto.scope.pop() + + +# Ensures |name| is usable as a C++ field by avoiding keywords and +# symbols that start with numbers. +def safe_name(name): + RESERVED = [ + 'and', + 'xor', + 'or', + 'class', + 'explicit', + 'new', + 'delete', + 'default', + 'private', + ] + if name[0].isdigit() or name in RESERVED: + return 'c_' + name + return name + + +class FileWriter: + def __init__(self): + self.indent = 0 + + # Write a line to the current file. + def write(self, line=''): + indent = self.indent if line and not line.startswith('#') else 0 + print((' ' * indent) + line, file=self.file) + + def write_header(self): + for header_line in FILE_HEADER.split('\n'): + self.write(header_line) + + +class GenXproto(FileWriter): + def __init__(self, proto, proto_dir, gen_dir, xcbgen, all_types): + FileWriter.__init__(self) + + # Command line arguments + self.proto = proto + self.xml_filename = os.path.join(proto_dir, '%s.xml' % proto) + self.header_file = open(os.path.join(gen_dir, '%s.h' % proto), 'w') + self.source_file = open(os.path.join(gen_dir, '%s.cc' % proto), 'w') + + # Top-level xcbgen python module + self.xcbgen = xcbgen + + # Types for every module including this one + self.all_types = all_types + + # The last used UID for making unique names + self.prev_id = -1 + + # Current file to write to + self.file = None + + # Flag to indicate if we're generating code to serialize or + # deserialize data. + self.is_read = False + + # List of the fields in scope + self.scope = [] + + # Current place in C++ namespace hierarchy (including classes) + self.namespace = [] + + # Map from type names to a set of types. Certain types + # like enums and simple types can alias each other. + self.types = collections.defaultdict(list) + + # Set of names of simple types to be replaced with enums + self.replace_with_enum = set() + + # Map of enums to their underlying types + self.enum_types = collections.defaultdict(set) + + # Map from (XML tag, XML name) to XML element + self.module_names = {} + + # Enums that represent bit masks. + self.bitenums = [] + + # Geenerate an ID suitable for use in temporary variable names. + def new_uid(self, ): + self.prev_id += 1 + return self.prev_id + + def type_suffix(self, t): + if isinstance(t, self.xcbgen.xtypes.Error): + return 'Error' + elif isinstance(t, self.xcbgen.xtypes.Request): + return 'Request' + elif t.is_reply: + return 'Reply' + elif t.is_event: + return 'Event' + return '' + + def rename_type(self, t, name): + name = list(name) + + if name[0] == 'xcb': + # Use namespace x11 instead of xcb. + name[0] = 'x11' + + for i in range(1, len(name)): + name[i] = adjust_type_name(name[i]) + name[-1] += self.type_suffix(t) + return name + + # Given an unqualified |name| like ('Window') and a namespace like ['x11'], + # returns a fully qualified name like ('x11', 'Window'). + def qualify_type(self, name, namespace): + if tuple(namespace + name) in self.all_types: + return namespace + name + return self.qualify_type(name, namespace[:-1]) + + # Given an xcbgen.xtypes.Type, returns a C++-namespace-qualified + # string that looks like Input::InputClass::Key. + def qualtype(self, t, name): + name = self.rename_type(t, name) + + # Try to avoid adding namespace qualifiers if they're not necessary. + chop = 0 + for t1, t2 in zip(name, self.namespace): + if t1 != t2: + break + if self.qualify_type(name[chop + 1:], self.namespace) != name: + break + chop += 1 + return '::'.join(name[chop:]) + + def fieldtype(self, field): + if field.isfd: + return 'RefCountedFD' + return self.qualtype(field.type, + field.enum if field.enum else field.field_type) + + def switch_fields(self, switch): + fields = [] + for case in switch.bitcases: + if case.field_name: + fields.append(case) + else: + fields.extend(case.type.fields) + return fields + + def add_field_to_scope(self, field, obj): + if not field.visible or (not field.wire and not field.isfd): + return 0 + + field_name = safe_name(field.field_name) + + if field.type.is_switch: + self.write('auto& %s = %s;' % (field_name, obj)) + return 0 + + self.scope.append(field) + + if field.for_list or field.for_switch: + self.write('%s %s{};' % (self.fieldtype(field), field_name)) + else: + self.write('auto& %s = %s.%s;' % (field_name, obj, field_name)) + + if field.type.is_list: + len_name = field_name + '_len' + if not self.field_from_scope(len_name): + len_expr = list_size(field_name, field.type) + if field.type.is_ref_counted_memory: + len_expr = '%s ? %s : 0' % (field_name, len_expr) + self.write('size_t %s = %s;' % (len_name, len_expr)) + + return 1 + + # Lookup |name| in the current scope. Returns the deepest + # (most local) occurrence of |name|. + def field_from_scope(self, name): + for field in reversed(self.scope): + if field.field_name == name: + return field + return None + + def expr(self, expr): + if expr.op == 'popcount': + return 'PopCount(%s)' % self.expr(expr.rhs) + if expr.op == '~': + return 'BitNot(%s)' % self.expr(expr.rhs) + if expr.op == '&': + return 'BitAnd(%s, %s)' % (self.expr(expr.lhs), self.expr( + expr.rhs)) + if expr.op in ('+', '-', '*', '/', '|'): + return ('(%s) %s (%s)' % + (self.expr(expr.lhs), expr.op, self.expr(expr.rhs))) + if expr.op == 'calculate_len': + return expr.lenfield_name + if expr.op == 'sumof': + tmp_id = self.new_uid() + lenfield = self.field_from_scope(expr.lenfield_name) + elem_type = lenfield.type.member + fields = elem_type.fields if elem_type.is_container else [] + header = 'auto sum%d_ = SumOf([](%sauto& listelem_ref) {' % ( + tmp_id, '' if self.is_read else 'const ') + footer = '}, %s);' % expr.lenfield_name + with Indent(self, header, + footer), ScopedFields(self, 'listelem_ref', fields): + body = self.expr(expr.rhs) if expr.rhs else 'listelem_ref' + self.write('return %s;' % body) + return 'sum%d_' % tmp_id + if expr.op == 'listelement-ref': + return 'listelem_ref' + if expr.op == 'enumref': + return '%s::%s' % (self.qualtype( + expr.lenfield_type, + expr.lenfield_type.name), safe_name(expr.lenfield_name)) + + assert expr.op == None + if expr.nmemb: + return str(expr.nmemb) + + assert expr.lenfield_name + return expr.lenfield_name + + def get_xidunion_element(self, name): + key = ('xidunion', name[-1]) + return self.module_names.get(key, None) + + def declare_xidunion(self, xidunion, xidname): + names = [type_element.text for type_element in xidunion] + types = list(set([self.module.get_type(name) for name in names])) + assert len(types) == 1 + value_type = types[0] + value_typename = self.qualtype(value_type, value_type.name) + with Indent(self, 'struct %s {' % xidname, '};'): + self.write('%s() : value{} {}' % xidname) + self.write() + for name in names: + cpp_name = self.module.get_type_name(name) + typename = self.qualtype(value_type, cpp_name) + self.write('%s(%s value) : value{static_cast<%s>(value)} {}' % + (xidname, typename, value_typename)) + self.write( + 'operator %s() const { return static_cast<%s>(value); }' % + (typename, typename)) + self.write() + self.write('%s value{};' % value_typename) + + def declare_simple(self, item, name): + renamed = tuple(self.rename_type(item, name)) + if renamed in self.replace_with_enum: + return + + xidunion = self.get_xidunion_element(name) + if xidunion: + self.declare_xidunion(xidunion, renamed[-1]) + else: + self.write('enum class %s : %s {};' % + (renamed[-1], self.qualtype(item, item.name))) + self.write() + + def copy_primitive(self, name): + if self.is_read: + self.write('Read(&%s, &buf);' % name) + else: + self.write('buf.Write(&%s);' % name) + + def copy_fd(self, field, name): + if self.is_read: + self.write('%s = RefCountedFD(buf.TakeFd());' % name) + else: + # We take the request struct as const&, so dup() the fd to preserve + # const-correctness because XCB close()s it after writing it. + self.write('buf.fds().push_back(HANDLE_EINTR(dup(%s.get())));' % + name) + + def copy_special_field(self, field): + type_name = self.fieldtype(field) + name = safe_name(field.field_name) + + def copy_basic(): + self.write('%s %s;' % (type_name, name)) + self.copy_primitive(name) + + if name in ('major_opcode', 'minor_opcode'): + assert not self.is_read + is_ext = self.module.namespace.is_ext + self.write( + '%s %s = %s;' % + (type_name, name, 'info_.major_opcode' if is_ext + and name == 'major_opcode' else field.parent[0].opcode)) + self.copy_primitive(name) + elif name == 'response_type': + if self.is_read: + copy_basic() + else: + container_type, container_name = field.parent + assert container_type.is_event + # Extension events require offsetting the opcode, so make + # sure this path is only hit for non-extension events for now. + assert not self.module.namespace.is_ext + opcode = container_type.opcodes.get(container_name, + 'obj.opcode') + self.write('%s %s = %s;' % (type_name, name, opcode)) + self.copy_primitive(name) + elif name in ('extension', 'error_code', 'event_type'): + assert self.is_read + copy_basic() + elif name == 'length': + if not self.is_read: + self.write('// Caller fills in length for writes.') + self.write('Pad(&buf, sizeof(%s));' % type_name) + else: + copy_basic() + else: + assert field.type.is_expr + assert (not isinstance(field.type, self.xcbgen.xtypes.Enum)) + self.write('%s %s = %s;' % + (type_name, name, self.expr(field.type.expr))) + self.copy_primitive(name) + + def declare_case(self, case): + assert case.type.is_case != case.type.is_bitcase + + fields = [ + field for case_field in case.type.fields + for field in self.declare_field(case_field) + ] + if not case.field_name: + return fields + name = safe_name(case.field_name) + typename = adjust_type_name(name) + with Indent(self, 'struct %s {' % typename, '};'): + for field in fields: + self.write('%s %s{};' % field) + return [(typename, name)] + + def copy_case(self, case, switch_name): + op = 'CaseEq' if case.type.is_case else 'CaseAnd' + condition = ' || '.join([ + '%s(%s_expr, %s)' % (op, switch_name, self.expr(expr)) + for expr in case.type.expr + ]) + + with Indent(self, 'if (%s) {' % condition, '}'): + if case.field_name: + fields = [case] + obj = '(*%s.%s)' % (switch_name, safe_name(case.field_name)) + else: + fields = case.type.fields + obj = '*' + switch_name + for case_field in fields: + name = safe_name(case_field.field_name) + if case_field.visible and self.is_read: + self.write('%s.%s.emplace();' % (switch_name, name)) + with ScopedFields(self, obj, case.type.fields): + for case_field in case.type.fields: + self.copy_field(case_field) + + def declare_switch(self, field): + return [('absl::optional<%s>' % field_type, field_name) + for case in field.type.bitcases + for field_type, field_name in self.declare_case(case)] + + def copy_switch(self, field): + t = field.type + name = safe_name(field.field_name) + + self.write('auto %s_expr = %s;' % (name, self.expr(t.expr))) + for case in t.bitcases: + self.copy_case(case, name) + + def declare_list(self, field): + t = field.type + type_name = self.fieldtype(field) + name = safe_name(field.field_name) + + assert (t.nmemb not in (0, 1)) + if t.is_ref_counted_memory: + type_name = 'scoped_refptr' + elif t.nmemb: + type_name = 'std::array<%s, %d>' % (type_name, t.nmemb) + elif type_name == 'char': + type_name = 'std::string' + else: + type_name = 'std::vector<%s>' % type_name + return [(type_name, name)] + + def copy_list(self, field): + t = field.type + name = safe_name(field.field_name) + size = self.expr(t.expr) + + if t.is_ref_counted_memory: + if self.is_read: + self.write('%s = buffer->ReadAndAdvance(%s);' % (name, size)) + else: + self.write('buf.AppendBuffer(%s, %s);' % (name, size)) + return + + if not t.nmemb: + if self.is_read: + self.write('%s.resize(%s);' % (name, size)) + else: + left = 'static_cast(%s)' % size + self.write('DCHECK_EQ(%s, %s.size());' % (left, name)) + with Indent(self, 'for (auto& %s_elem : %s) {' % (name, name), '}'): + elem_name = name + '_elem' + elem_type = t.member + elem_field = self.xcbgen.expr.Field(elem_type, field.field_type, + elem_name, field.visible, + field.wire, field.auto, + field.enum, field.isfd) + elem_field.for_list = None + elem_field.for_switch = None + self.copy_field(elem_field) + + def generate_switch_var(self, field): + name = safe_name(field.field_name) + for case in field.for_switch.type.bitcases: + case_field = case if case.field_name else case.type.fields[0] + self.write('SwitchVar(%s, %s.%s.has_value(), %s, &%s);' % + (self.expr(case.type.expr[0]), + safe_name(field.for_switch.field_name), + safe_name(case_field.field_name), + 'true' if case.type.is_bitcase else 'false', name)) + + def is_field_hidden_from_api(self, field): + return not field.visible or getattr( + field, 'for_list', False) or getattr(field, 'for_switch', False) + + def declare_field(self, field): + t = field.type + name = safe_name(field.field_name) + + if self.is_field_hidden_from_api(field): + return [] + + if t.is_switch: + return self.declare_switch(field) + if t.is_list: + return self.declare_list(field) + return [(self.fieldtype(field), name)] + + def copy_field(self, field): + if not field.wire and not field.isfd: + return + + t = field.type + renamed = tuple(self.rename_type(field.type, field.field_type)) + if t.is_list: + t.member = self.all_types.get(renamed, t.member) + else: + t = self.all_types.get(renamed, t) + name = safe_name(field.field_name) + + self.write('// ' + name) + + # If this is a generated field, initialize the value of the field + # variable from the given context. + if not self.is_read: + if field.for_list: + size = list_size(safe_name(field.for_list.field_name), + field.for_list.type) + self.write('%s = %s;' % (name, size)) + if field.for_switch: + self.generate_switch_var(field) + + if t.is_pad: + if t.align > 1: + assert t.nmemb == 1 + assert t.align in (2, 4) + self.write('Align(&buf, %d);' % t.align) + else: + self.write('Pad(&buf, %d);' % t.nmemb) + elif not field.visible: + self.copy_special_field(field) + elif t.is_switch: + self.copy_switch(field) + elif t.is_list: + self.copy_list(field) + elif t.is_union: + self.copy_primitive(name) + elif t.is_container: + with Indent(self, '{', '}'): + self.copy_container(t, name) + else: + assert t.is_simple + if field.isfd: + self.copy_fd(field, name) + elif field.enum: + self.copy_enum(field) + else: + self.copy_primitive(name) + + self.write() + + def declare_enum(self, enum): + def declare_enum_entry(name, value): + name = safe_name(name) + self.write('%s = %s,' % (name, value)) + + with Indent( + self, 'enum class %s : %s {' % + (adjust_type_name(enum.name[-1]), self.enum_types[enum.name][0] + if enum.name in self.enum_types else 'int'), '};'): + bitnames = set([name for name, _ in enum.bits]) + for name, value in enum.values: + if name not in bitnames: + declare_enum_entry(name, value) + for name, value in enum.bits: + declare_enum_entry(name, '1 << ' + value) + self.write() + + def copy_enum(self, field): + # The size of enum types may be different depending on the + # context, so they should always be casted to the contextual + # underlying type before calling Read() or Write(). + underlying_type = self.qualtype(field.type, field.type.name) + tmp_name = 'tmp%d' % self.new_uid() + real_name = safe_name(field.field_name) + self.write('%s %s;' % (underlying_type, tmp_name)) + if not self.is_read: + self.write('%s = static_cast<%s>(%s);' % + (tmp_name, underlying_type, real_name)) + self.copy_primitive(tmp_name) + if self.is_read: + enum_type = self.qualtype(field.type, field.enum) + self.write('%s = static_cast<%s>(%s);' % + (real_name, enum_type, tmp_name)) + + def declare_fields(self, fields): + for field in fields: + for field_type_name in self.declare_field(field): + self.write('%s %s{};' % field_type_name) + + # This tries to match XEvent.xany.window, except the window will be + # Window::None for events that don't have a window, unlike the XEvent + # union which will get whatever data happened to be at the offset of + # xany.window. + def get_window_field(self, event): + # The window field is not stored at any particular offset in the event, + # so get a list of all the window fields. + WINDOW_TYPES = set([ + ('xcb', 'WINDOW'), + ('xcb', 'DRAWABLE'), + ('xcb', 'Glx', 'DRAWABLE'), + ]) + # The window we want may not be the first in the list if there are + # multiple windows. This is a list of all possible window names, + # ordered from highest to lowest priority. + WINDOW_NAMES = [ + 'event', + 'window', + 'request_window', + 'owner', + ] + windows = set([ + field.field_name for field in event.fields + if field.field_type in WINDOW_TYPES + ]) + if len(windows) == 0: + return '' + if len(windows) == 1: + return list(windows)[0] + for name in WINDOW_NAMES: + if name in windows: + return name + assert False + + def declare_event(self, event, name): + event_name = name[-1] + 'Event' + with Indent(self, 'struct %s {' % adjust_type_name(event_name), '};'): + self.write('static constexpr int type_id = %d;' % event.type_id) + if len(event.opcodes) == 1: + self.write('static constexpr uint8_t opcode = %s;' % + event.opcodes[name]) + else: + with Indent(self, 'enum Opcode {', '} opcode{};'): + items = [(int(x), y) + for (y, x) in event.enum_opcodes.items()] + for opcode, opname in sorted(items): + self.write('%s = %s,' % (opname, opcode)) + self.write('bool send_event{};') + self.declare_fields(event.fields) + self.write() + window_field = self.get_window_field(event) + ret = ('reinterpret_cast(&%s)' % + window_field if window_field else 'nullptr') + self.write('x11::Window* GetWindow() { return %s; }' % ret) + self.write() + + def declare_error(self, error, name): + name = adjust_type_name(name[-1] + 'Error') + with Indent(self, 'struct %s : public x11::Error {' % name, '};'): + self.declare_fields(error.fields) + self.write() + self.write('std::string ToString() const override;') + self.write() + + def declare_container(self, struct, struct_name): + name = struct_name[-1] + self.type_suffix(struct) + with Indent(self, 'struct %s {' % adjust_type_name(name), '};'): + self.declare_fields(struct.fields) + self.write() + + def copy_container(self, struct, name): + assert not struct.is_union + with ScopedFields(self, name, struct.fields): + for field in struct.fields: + self.copy_field(field) + + def read_special_container(self, struct, name): + self.namespace = ['x11'] + name = self.qualtype(struct, name) + self.write('template <> COMPONENT_EXPORT(X11)') + self.write('%s Read<%s>(' % (name, name)) + with Indent(self, ' ReadBuffer* buffer) {', '}'): + self.write('auto& buf = *buffer;') + self.write('%s obj;' % name) + self.write() + self.is_read = True + self.copy_container(struct, 'obj') + self.write('return obj;') + self.write() + + def write_special_container(self, struct, name): + self.namespace = ['x11'] + name = self.qualtype(struct, name) + self.write('template <> COMPONENT_EXPORT(X11)') + self.write('WriteBuffer Write<%s>(' % name) + with Indent(self, ' const %s& obj) {' % name, '}'): + self.write('WriteBuffer buf;') + self.write() + self.is_read = False + self.copy_container(struct, 'obj') + self.write('return buf;') + self.write() + + def declare_union(self, union): + name = union.name[-1] + if union.elt.tag == 'eventstruct': + # There's only one of these in all of the protocol descriptions. + # It's just used to represent any 32-byte event for XInput. + self.write('using %s = std::array;' % name) + return + with Indent(self, 'union %s {' % name, '};'): + self.write('%s() { memset(this, 0, sizeof(*this)); }' % name) + self.write() + for field in union.fields: + field_type_names = self.declare_field(field) + assert len(field_type_names) == 1 + self.write('%s %s;' % field_type_names[0]) + self.write( + 'static_assert(std::is_trivially_copyable<%s>::value, "");' % name) + self.write() + + # Returns a list of strings suitable for use as a default-initializer for + # |field|. There may be 0 strings (if the field is hidden from the public + # API), 1 string (for normal cases), or many strings (for switch fields). + def get_initializer(self, field): + if self.is_field_hidden_from_api(field): + return [] + + if field.type.is_switch: + return ['absl::nullopt'] * len(self.declare_switch(field)) + if field.type.is_list or not field.type.is_container: + return ['{}'] + + # While using {} as an initializer for structs is fine when nested + # in other structs, it causes compiler errors when used as a default + # argument initializer, so explicitly initialize each field. + return [ + '{%s}' % ', '.join([ + init for subfield in field.type.fields + if not self.is_field_hidden_from_api(subfield) + for init in self.get_initializer(subfield) + ]) + ] + + def declare_request(self, request): + method_name = request.name[-1] + request_name = method_name + 'Request' + reply_name = method_name + 'Reply' if request.reply else 'void' + + in_class = self.namespace == ['x11', self.class_name] + + if not in_class or self.module.namespace.is_ext: + self.declare_container(request, request.name) + if request.reply: + self.declare_container(request.reply, request.reply.name) + + self.write('using %sResponse = Response<%s>;' % + (method_name, reply_name)) + self.write() + + if in_class: + # Generate a request method that takes a Request object. + self.write('Future<%s> %s(' % (reply_name, method_name)) + self.write(' const %s& request);' % request_name) + self.write() + + # Generate a request method that takes fields as arguments and + # forwards them as a Request object to the above implementation. + field_type_names = [ + field_type_name for field in request.fields + for field_type_name in self.declare_field(field) + ] + inits = [ + init for field in request.fields + for init in self.get_initializer(field) + ] + assert len(field_type_names) == len(inits) + args = [ + 'const %s& %s = %s' % (field_type_name + (init, )) + for (field_type_name, init) in zip(field_type_names, inits) + ] + self.write('Future<%s> %s(%s);' % + (reply_name, method_name, ', '.join(args))) + self.write() + + def define_request(self, request): + method_name = '%s::%s' % (self.class_name, request.name[-1]) + prefix = (method_name + if self.module.namespace.is_ext else request.name[-1]) + request_name = prefix + 'Request' + reply_name = prefix + 'Reply' + + reply = request.reply + if not reply: + reply_name = 'void' + + # Generate a request method that takes a Request object. + self.write('Future<%s>' % reply_name) + self.write('%s(' % method_name) + with Indent(self, ' const %s& request) {' % request_name, '}'): + cond = '!connection_->Ready()' + if self.module.namespace.is_ext: + cond += ' || !present()' + self.write('if (%s)' % cond) + self.write(' return {};') + self.write() + self.namespace = ['x11', self.class_name] + self.write('WriteBuffer buf;') + self.write() + self.is_read = False + self.copy_container(request, 'request') + self.write('Align(&buf, 4);') + self.write() + reply_has_fds = reply and any(field.isfd for field in reply.fields) + self.write( + 'return connection_->SendRequest<%s>(&buf, "%s", %s);' % + (reply_name, prefix, 'true' if reply_has_fds else 'false')) + self.write() + + # Generate a request method that takes fields as arguments and + # forwards them as a Request object to the above implementation. + self.write('Future<%s>' % reply_name) + self.write('%s(' % method_name) + args = [ + 'const %s& %s' % field_type_name for field in request.fields + for field_type_name in self.declare_field(field) + ] + with Indent(self, '%s) {' % ', '.join(args), '}'): + self.write('return %s(%s{%s});' % + (method_name, request_name, ', '.join([ + field_name for field in request.fields + for (_, field_name) in self.declare_field(field) + ]))) + self.write() + + if not reply: + return + + self.write('template<> COMPONENT_EXPORT(X11)') + self.write('std::unique_ptr<%s>' % reply_name) + sig = 'detail::ReadReply<%s>(ReadBuffer* buffer) {' % reply_name + with Indent(self, sig, '}'): + self.namespace = ['x11'] + self.write('auto& buf = *buffer;') + self.write('auto reply = std::make_unique<%s>();' % reply_name) + self.write() + self.is_read = True + self.copy_container(reply, '(*reply)') + self.write('Align(&buf, 4);') + offset = 'buf.offset < 32 ? 0 : buf.offset - 32' + self.write('DCHECK_EQ(%s, 4 * length);' % offset) + self.write() + self.write('return reply;') + self.write() + + def define_event(self, event, name): + self.namespace = ['x11'] + name = self.qualtype(event, name) + self.write('template <> COMPONENT_EXPORT(X11)') + self.write('void ReadEvent<%s>(' % name) + with Indent(self, ' %s* event_, ReadBuffer* buffer) {' % name, '}'): + self.write('auto& buf = *buffer;') + self.write() + self.is_read = True + self.copy_container(event, '(*event_)') + if event.is_ge_event: + self.write('Align(&buf, 4);') + self.write('DCHECK_EQ(buf.offset, 32 + 4 * length);') + else: + self.write('DCHECK_LE(buf.offset, 32ul);') + self.write() + + def define_error(self, error, name): + self.namespace = ['x11'] + name = self.qualtype(error, name) + with Indent(self, 'std::string %s::ToString() const {' % name, '}'): + self.write('std::stringstream ss_;') + self.write('ss_ << "%s{";' % name) + fields = [field for field in error.fields if field.visible] + for i, field in enumerate(fields): + terminator = '' if i == len(fields) - 1 else ' << ", "' + self.write('ss_ << ".%s = " << static_cast(%s)%s;' % + (field.field_name, field.field_name, terminator)) + self.write('ss_ << "}";') + self.write('return ss_.str();') + self.write() + self.write('template <>') + self.write('void ReadError<%s>(' % name) + with Indent(self, ' %s* error_, ReadBuffer* buffer) {' % name, '}'): + self.write('auto& buf = *buffer;') + self.write() + self.is_read = True + self.copy_container(error, '(*error_)') + self.write('DCHECK_LE(buf.offset, 32ul);') + + def define_type(self, item, name): + if name in READ_SPECIAL: + self.read_special_container(item, name) + if name in WRITE_SPECIAL: + self.write_special_container(item, name) + if isinstance(item, self.xcbgen.xtypes.Request): + self.define_request(item) + elif item.is_event: + self.define_event(item, name) + elif isinstance(item, self.xcbgen.xtypes.Error): + self.define_error(item, name) + + def declare_type(self, item, name): + if item.is_union: + self.declare_union(item) + elif isinstance(item, self.xcbgen.xtypes.Request): + self.declare_request(item) + elif item.is_event: + self.declare_event(item, name) + elif isinstance(item, self.xcbgen.xtypes.Error): + self.declare_error(item, name) + elif item.is_container: + self.declare_container(item, name) + elif isinstance(item, self.xcbgen.xtypes.Enum): + self.declare_enum(item) + else: + assert item.is_simple + self.declare_simple(item, name) + + # Additional type information identifying the enum/mask is present in the + # XML data, but xcbgen doesn't make use of it: it only uses the underlying + # type, as it appears on the wire. We want additional type safety, so + # extract this information the from XML directly. + def resolve_element(self, xml_element, fields): + for child in xml_element: + if 'name' not in child.attrib: + if child.tag == 'case' or child.tag == 'bitcase': + self.resolve_element(child, fields) + continue + name = child.attrib['name'] + field = fields[name] + field.elt = child + enums = [ + child.attrib[attr] for attr in ['enum', 'mask'] + if attr in child.attrib + ] + if enums: + assert len(enums) == 1 + enum = enums[0] + field.enum = self.module.get_type(enum).name + self.enum_types[enum].add(field.type.name) + else: + field.enum = None + + def resolve_type(self, t, name): + renamed = tuple(self.rename_type(t, name)) + assert renamed[0] == 'x11' + assert t not in self.types[renamed] + self.types[renamed].append(t) + self.all_types[renamed] = t + + if isinstance(t, self.xcbgen.xtypes.Enum): + self.bitenums.append((t, name)) + + if not t.is_container: + return + + fields = { + field.field_name: field + for field in (self.switch_fields(t) if t.is_switch else t.fields) + } + + self.resolve_element(t.elt, fields) + + for field in fields.values(): + if field.field_name == 'sequence': + field.visible = True + field.parent = (t, name) + + if field.type.is_list: + # xcb uses void* in some places to represent arbitrary data. + field.type.is_ref_counted_memory = ( + not field.type.nmemb and field.field_type[0] == 'void') + + # |for_list| and |for_switch| may have already been set when + # processing other fields in this structure. + field.for_list = getattr(field, 'for_list', None) + field.for_switch = getattr(field, 'for_switch', None) + + for is_type, for_type in ((field.type.is_list, 'for_list'), + (field.type.is_switch, 'for_switch')): + if not is_type: + continue + expr = field.type.expr + field_name = expr.lenfield_name + if (expr.op in (None, 'calculate_len') + and field_name in fields): + setattr(fields[field_name], for_type, field) + + if field.type.is_switch or field.type.is_case_or_bitcase: + self.resolve_type(field.type, field.field_type) + + if isinstance(t, self.xcbgen.xtypes.Request) and t.reply: + self.resolve_type(t.reply, t.reply.name) + + # Multiple event names may map to the same underlying event. For these + # cases, we want to avoid duplicating the event structure. Instead, put + # all of these events under one structure with an additional opcode field + # to indicate the type of event. + def uniquify_events(self): + types = [] + events = set() + for name, t in self.module.all: + if not t.is_event or len(t.opcodes) == 1: + types.append((name, t)) + continue + + renamed = tuple(self.rename_type(t, name)) + self.all_types[renamed] = t + if t in events: + continue + events.add(t) + + names = [name[-1] for name in t.opcodes.keys()] + name = name[:-1] + (event_base_name(names), ) + types.append((name, t)) + + t.enum_opcodes = {} + for opname in t.opcodes: + opcode = t.opcodes[opname] + opname = opname[-1] + if opname.startswith(name[-1]): + opname = opname[len(name[-1]):] + t.enum_opcodes[opname] = opcode + self.module.all = types + + # Perform preprocessing like renaming, reordering, and adding additional + # data fields. + def resolve(self): + self.class_name = (adjust_type_name(self.module.namespace.ext_name) + if self.module.namespace.is_ext else 'XProto') + + self.uniquify_events() + + for name, t in self.module.all: + self.resolve_type(t, name) + + for enum, types in list(self.enum_types.items()): + if len(types) == 1: + self.enum_types[enum] = list(types)[0] + else: + del self.enum_types[enum] + + for t in self.types: + l = self.types[t] + if len(l) == 1: + continue + + # Allow simple types and enums to alias each other after renaming. + # This is done because we want strong typing even for simple types. + # If the types were not merged together, then a cast would be + # necessary to convert from eg. AtomEnum to AtomSimple. + assert len(l) == 2 + if isinstance(l[0], self.xcbgen.xtypes.Enum): + enum = l[0] + simple = l[1] + elif isinstance(l[1], self.xcbgen.xtypes.Enum): + enum = l[1] + simple = l[0] + assert simple.is_simple + assert enum and simple + self.replace_with_enum.add(t) + self.enum_types[enum.name] = simple.name + + for node in self.module.namespace.root: + if 'name' in node.attrib: + key = (node.tag, node.attrib['name']) + assert key not in self.module_names + self.module_names[key] = node + + # The order of types in xcbproto's xml files are inconsistent, so sort + # them in the order {type aliases, enums, xidunions, structs, + # requests/replies}. + def type_order_priority(module_type): + name, item = module_type + if item.is_simple: + return 2 if self.get_xidunion_element(name) else 0 + if isinstance(item, self.xcbgen.xtypes.Enum): + return 1 + if isinstance(item, self.xcbgen.xtypes.Request): + return 4 + return 3 + + # sort() is guaranteed to be stable. + self.module.all.sort(key=type_order_priority) + + def gen_header(self): + self.file = self.header_file + self.write_header() + include_guard = 'UI_GFX_X_GENERATED_PROTOS_%s_' % ( + self.header_file.name.split('/')[-1].upper().replace('.', '_')) + self.write('#ifndef ' + include_guard) + self.write('#define ' + include_guard) + self.write() + self.write('#include ') + self.write('#include ') + self.write('#include ') + self.write('#include ') + self.write('#include ') + self.write() + self.write('#include "base/component_export.h"') + self.write('#include "base/memory/ref_counted_memory.h"') + self.write('#include "base/memory/scoped_refptr.h"') + self.write('#include "third_party/abseil-cpp/absl/types/optional.h"') + self.write('#include "base/files/scoped_file.h"') + self.write('#include "ui/gfx/x/ref_counted_fd.h"') + self.write('#include "ui/gfx/x/error.h"') + imports = set(self.module.direct_imports) + if self.module.namespace.is_ext: + imports.add(('xproto', 'xproto')) + for direct_import in sorted(list(imports)): + self.write('#include "%s.h"' % direct_import[-1]) + self.write() + self.write('namespace x11 {') + self.write() + self.write('class Connection;') + self.write() + self.write('template ') + self.write('struct Response;') + self.write() + self.write('template ') + self.write('class Future;') + self.write() + + self.namespace = ['x11'] + if not self.module.namespace.is_ext: + for (name, item) in self.module.all: + self.declare_type(item, name) + + name = self.class_name + with Indent(self, 'class COMPONENT_EXPORT(X11) %s {' % name, '};'): + self.namespace = ['x11', self.class_name] + self.write('public:') + if self.module.namespace.is_ext: + self.write('static constexpr unsigned major_version = %s;' % + self.module.namespace.major_version) + self.write('static constexpr unsigned minor_version = %s;' % + self.module.namespace.minor_version) + self.write() + self.write(name + '(Connection* connection,') + self.write(' const x11::QueryExtensionReply& info);') + self.write() + with Indent(self, 'uint8_t present() const {', '}'): + self.write('return info_.present;') + with Indent(self, 'uint8_t major_opcode() const {', '}'): + self.write('return info_.major_opcode;') + with Indent(self, 'uint8_t first_event() const {', '}'): + self.write('return info_.first_event;') + with Indent(self, 'uint8_t first_error() const {', '}'): + self.write('return info_.first_error;') + else: + self.write('explicit %s(Connection* connection);' % name) + self.write() + self.write( + 'Connection* connection() const { return connection_; }') + self.write() + for (name, item) in self.module.all: + if self.module.namespace.is_ext: + self.declare_type(item, name) + elif isinstance(item, self.xcbgen.xtypes.Request): + self.declare_request(item) + self.write('private:') + self.write('Connection* const connection_;') + if self.module.namespace.is_ext: + self.write('x11::QueryExtensionReply info_{};') + + self.write() + self.write('} // namespace x11') + self.write() + self.namespace = [] + + def binop(op, name): + self.write('inline constexpr %s operator%s(' % (name, op)) + with Indent(self, ' {0} l, {0} r)'.format(name) + ' {', '}'): + self.write('using T = std::underlying_type_t<%s>;' % name) + self.write('return static_cast<%s>(' % name) + self.write(' static_cast(l) %s static_cast(r));' % op) + self.write() + + for enum, name in self.bitenums: + name = self.qualtype(enum, name) + binop('|', name) + binop('&', name) + + self.write() + self.write('#endif // ' + include_guard) + + def gen_source(self): + self.file = self.source_file + self.write_header() + self.write('#include "%s.h"' % self.module.namespace.header) + self.write() + self.write('#include ') + self.write('#include ') + self.write() + self.write('#include "base/logging.h"') + self.write('#include "base/posix/eintr_wrapper.h"') + self.write('#include "ui/gfx/x/xproto_internal.h"') + self.write() + self.write('namespace x11 {') + self.write() + ctor = '%s::%s' % (self.class_name, self.class_name) + if self.module.namespace.is_ext: + self.write(ctor + '(Connection* connection,') + self.write(' const x11::QueryExtensionReply& info)') + self.write(' : connection_(connection), info_(info) {}') + else: + self.write(ctor + + '(Connection* connection) : connection_(connection) {}') + self.write() + for (name, item) in self.module.all: + self.define_type(item, name) + self.write('} // namespace x11') + + def parse(self): + self.module = self.xcbgen.state.Module(self.xml_filename, None) + self.module.register() + self.module.resolve() + + def generate(self): + self.gen_header() + self.gen_source() + + +class GenExtensionManager(FileWriter): + def __init__(self, gen_dir, genprotos): + FileWriter.__init__(self) + + self.gen_dir = gen_dir + self.genprotos = genprotos + self.extensions = [ + proto for proto in genprotos if proto.module.namespace.is_ext + ] + + def gen_header(self): + self.file = open(os.path.join(self.gen_dir, 'extension_manager.h'), + 'w') + self.write_header() + self.write('#ifndef UI_GFX_X_GENERATED_PROTOS_EXTENSION_MANAGER_H_') + self.write('#define UI_GFX_X_GENERATED_PROTOS_EXTENSION_MANAGER_H_') + self.write() + self.write('#include ') + self.write() + self.write('#include "base/component_export.h"') + self.write() + self.write('namespace x11 {') + self.write() + self.write('class Connection;') + self.write() + for genproto in self.genprotos: + self.write('class %s;' % genproto.class_name) + self.write() + with Indent(self, 'class COMPONENT_EXPORT(X11) ExtensionManager {', + '};'): + self.write('public:') + self.write('ExtensionManager();') + self.write('~ExtensionManager();') + self.write() + for extension in self.extensions: + name = extension.proto + self.write('%s& %s() { return *%s_; }' % + (extension.class_name, name, name)) + self.write() + self.write('protected:') + self.write('void Init(Connection* conn);') + self.write() + self.write('private:') + for extension in self.extensions: + self.write('std::unique_ptr<%s> %s_;' % + (extension.class_name, extension.proto)) + self.write() + self.write('} // namespace x11') + self.write() + self.write('#endif // UI_GFX_X_GENERATED_PROTOS_EXTENSION_MANAGER_H_') + + def gen_source(self): + self.file = open(os.path.join(self.gen_dir, 'extension_manager.cc'), + 'w') + self.write_header() + self.write('#include "ui/gfx/x/extension_manager.h"') + self.write() + self.write('#include "ui/gfx/x/connection.h"') + self.write('#include "ui/gfx/x/xproto_internal.h"') + for genproto in self.genprotos: + self.write('#include "ui/gfx/x/%s.h"' % genproto.proto) + self.write() + self.write('namespace x11 {') + self.write() + init = 'void ExtensionManager::Init' + with Indent(self, init + '(Connection* conn) {', '}'): + for extension in self.extensions: + self.write( + 'auto %s_future = conn->QueryExtension("%s");' % + (extension.proto, extension.module.namespace.ext_xname)) + # Flush so all requests are sent before waiting on any replies. + self.write('conn->Flush();') + self.write() + for extension in self.extensions: + name = extension.proto + self.write( + '%s_ = MakeExtension<%s>(conn, std::move(%s_future));' % + (name, extension.class_name, name)) + self.write() + self.write('ExtensionManager::ExtensionManager() = default;') + self.write('ExtensionManager::~ExtensionManager() = default;') + self.write() + self.write('} // namespace x11') + + +class GenReadEvent(FileWriter): + def __init__(self, gen_dir, genprotos): + FileWriter.__init__(self) + + self.gen_dir = gen_dir + self.genprotos = genprotos + + self.events = [] + for proto in self.genprotos: + for name, item in proto.module.all: + if item.is_event: + self.events.append((name, item, proto)) + + def event_condition(self, event, typename, proto): + ext = 'conn->%s()' % proto.proto + + conds = [] + if not proto.module.namespace.is_ext: + # Core protocol event + opcode = 'evtype' + elif event.is_ge_event: + # GenericEvent extension event + conds.extend([ + 'evtype == GeGenericEvent::opcode', + '%s.present()' % ext, + 'ge->extension == %s.major_opcode()' % ext, + ]) + opcode = 'ge->event_type' + else: + # Extension event + opcode = 'evtype - %s.first_event()' % ext + conds.append('%s.present()' % ext) + + if len(event.opcodes) == 1: + conds.append('%s == %s::opcode' % (opcode, typename)) + else: + conds.append('(%s)' % ' || '.join([ + '%s == %s::%s' % (opcode, typename, opname) + for opname in event.enum_opcodes.keys() + ])) + + return ' && '.join(conds), opcode + + def gen_event(self, name, event, proto): + # We can't ever have a plain generic event. It must be a concrete + # event provided by an extension. + if name == ('xcb', 'GeGeneric'): + return + + name = [adjust_type_name(part) for part in name[1:]] + typename = '::'.join(name) + 'Event' + + cond, opcode = self.event_condition(event, typename, proto) + with Indent(self, 'if (%s) {' % cond, '}'): + self.write('event->type_id_ = %d;' % event.type_id) + with Indent(self, 'event->deleter_ = [](void* event) {', '};'): + self.write('delete reinterpret_cast<%s*>(event);' % typename) + self.write('auto* event_ = new %s;' % typename) + self.write('ReadEvent(event_, buffer);') + if len(event.opcodes) > 1: + self.write('{0} = static_cast({1});'.format( + 'event_->opcode', opcode)) + self.write('event_->send_event = send_event;') + self.write('event->event_ = event_;') + self.write('event->window_ = event_->GetWindow();') + self.write('return;') + self.write() + + def gen_source(self): + self.file = open(os.path.join(self.gen_dir, 'read_event.cc'), 'w') + self.write_header() + self.write('#include "ui/gfx/x/event.h"') + self.write() + self.write('#include ') + self.write() + self.write('#include "ui/gfx/x/connection.h"') + self.write('#include "ui/gfx/x/xproto_types.h"') + for genproto in self.genprotos: + self.write('#include "ui/gfx/x/%s.h"' % genproto.proto) + self.write() + self.write('namespace x11 {') + self.write() + self.write('void ReadEvent(') + args = 'Event* event, Connection* conn, ReadBuffer* buffer' + with Indent(self, ' %s) {' % args, '}'): + self.write('auto* buf = buffer->data->data();') + cast = 'auto* %s = reinterpret_cast(buf);' + self.write(cast % ('ev', 'xcb_generic_event_t')) + self.write(cast % ('ge', 'xcb_ge_generic_event_t')) + self.write('auto evtype = ev->response_type & ~kSendEventMask;') + self.write('bool send_event = ev->response_type & kSendEventMask;') + self.write() + for name, event, proto in self.events: + self.gen_event(name, event, proto) + self.write('NOTREACHED();') + self.write() + self.write('} // namespace x11') + + +class GenReadError(FileWriter): + def __init__(self, gen_dir, genprotos, xcbgen): + FileWriter.__init__(self) + + self.gen_dir = gen_dir + self.genprotos = genprotos + self.xcbgen = xcbgen + + def get_errors_for_proto(self, proto): + errors = {} + for _, item in proto.module.all: + if isinstance(item, self.xcbgen.xtypes.Error): + for name in item.opcodes: + id = int(item.opcodes[name]) + if id < 0: + continue + name = [adjust_type_name(part) for part in name[1:]] + typename = '::'.join(name) + 'Error' + errors[id] = typename + return errors + + def gen_errors_for_proto(self, errors, proto): + if proto.module.namespace.is_ext: + cond = 'if (%s().present()) {' % proto.proto + first_error = '%s().first_error()' % proto.proto + else: + cond = '{' + first_error = '0' + with Indent(self, cond, '}'): + self.write('uint8_t first_error = %s;' % first_error) + for id, name in sorted(errors.items()): + with Indent(self, '{', '}'): + self.write('auto error_code = first_error + %d;' % id) + self.write('auto parse = MakeError<%s>;' % name) + self.write('add_parser(error_code, first_error, parse);') + self.write() + + def gen_init_error_parsers(self): + self.write('uint8_t first_errors[256];') + self.write('memset(first_errors, 0, sizeof(first_errors));') + self.write() + args = 'uint8_t error_code, uint8_t first_error, ErrorParser parser' + with Indent(self, 'auto add_parser = [&](%s) {' % args, '};'): + cond = ('!error_parsers_[error_code] || ' + + 'first_error > first_errors[error_code]') + with Indent(self, 'if (%s) {' % cond, '}'): + self.write('first_errors[error_code] = error_code;') + self.write('error_parsers_[error_code] = parser;') + self.write() + for proto in self.genprotos: + errors = self.get_errors_for_proto(proto) + if errors: + self.gen_errors_for_proto(errors, proto) + + def gen_source(self): + self.file = open(os.path.join(self.gen_dir, 'read_error.cc'), 'w') + self.write_header() + self.write('#include "ui/gfx/x/connection.h"') + self.write('#include "ui/gfx/x/error.h"') + self.write('#include "ui/gfx/x/xproto_internal.h"') + self.write() + for genproto in self.genprotos: + self.write('#include "ui/gfx/x/%s.h"' % genproto.proto) + self.write() + self.write('namespace x11 {') + self.write() + self.write('namespace {') + self.write() + self.write('template ') + sig = 'std::unique_ptr MakeError(Connection::RawError error_)' + with Indent(self, '%s {' % sig, '}'): + self.write('ReadBuffer buf(error_);') + self.write('auto error = std::make_unique();') + self.write('ReadError(error.get(), &buf);') + self.write('return error;') + self.write() + self.write('} // namespace') + self.write() + with Indent(self, 'void Connection::InitErrorParsers() {', '}'): + self.gen_init_error_parsers() + + self.write() + self.write('} // namespace x11') + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('xcbproto_dir', type=str) + parser.add_argument('gen_dir', type=str) + parser.add_argument('protos', type=str, nargs='*') + args = parser.parse_args() + + sys.path.insert(1, args.xcbproto_dir) + import xcbgen.xtypes + import xcbgen.state + + all_types = {} + proto_src_dir = os.path.join(args.xcbproto_dir, 'src') + genprotos = [ + GenXproto(proto, proto_src_dir, args.gen_dir, xcbgen, all_types) + for proto in args.protos + ] + for genproto in genprotos: + genproto.parse() + for genproto in genprotos: + genproto.resolve() + + # Give each event a unique type ID. This is used by Event to + # implement downcasting for events. + type_id = 1 + for proto in genprotos: + for _, item in proto.module.all: + if item.is_event: + item.type_id = type_id + type_id += 1 + + for genproto in genprotos: + genproto.generate() + + gen_extension_manager = GenExtensionManager(args.gen_dir, genprotos) + gen_extension_manager.gen_header() + gen_extension_manager.gen_source() + + GenReadEvent(args.gen_dir, genprotos).gen_source() + + GenReadError(args.gen_dir, genprotos, xcbgen).gen_source() + + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/x/generated_protos/README.txt b/x/generated_protos/README.txt new file mode 100644 index 000000000000..aea329bd416b --- /dev/null +++ b/x/generated_protos/README.txt @@ -0,0 +1,10 @@ +The files in this directory are generated by gen_xproto.py. To regenerate these +files, add "regenerate_x11_protos=true" to your gn args, then run the following +(assuming your build directory is out/Release): + +$ rm -f ui/gfx/x/generated_protos/*.h +$ rm -f ui/gfx/x/generated_protos/*.cc +$ ninja -C out/Release ui/gfx/x:gen_xprotos +$ cp out/Release/gen/ui/gfx/x/*.h ui/gfx/x/generated_protos/ +$ cp out/Release/gen/ui/gfx/x/*.cc ui/gfx/x/generated_protos/ +$ git cl format diff --git a/x/generated_protos/bigreq.cc b/x/generated_protos/bigreq.cc new file mode 100644 index 000000000000..8ad0c82247ff --- /dev/null +++ b/x/generated_protos/bigreq.cc @@ -0,0 +1,118 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#include "bigreq.h" + +#include +#include + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "ui/gfx/x/xproto_internal.h" + +namespace x11 { + +BigRequests::BigRequests(Connection* connection, + const x11::QueryExtensionReply& info) + : connection_(connection), info_(info) {} + +Future BigRequests::Enable( + const BigRequests::EnableRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 0; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "BigRequests::Enable", false); +} + +Future BigRequests::Enable() { + return BigRequests::Enable(BigRequests::EnableRequest{}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + BigRequests::EnableReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& maximum_request_length = (*reply).maximum_request_length; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // maximum_request_length + Read(&maximum_request_length, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +} // namespace x11 diff --git a/x/generated_protos/bigreq.h b/x/generated_protos/bigreq.h new file mode 100644 index 000000000000..46d12de250ef --- /dev/null +++ b/x/generated_protos/bigreq.h @@ -0,0 +1,101 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#ifndef UI_GFX_X_GENERATED_PROTOS_BIGREQ_H_ +#define UI_GFX_X_GENERATED_PROTOS_BIGREQ_H_ + +#include +#include +#include + +#include "base/component_export.h" +#include "base/files/scoped_file.h" +#include "base/memory/ref_counted_memory.h" +#include "base/memory/scoped_refptr.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/gfx/x/error.h" +#include "ui/gfx/x/ref_counted_fd.h" +#include "xproto.h" + +namespace x11 { + +class Connection; + +template +struct Response; + +template +class Future; + +class COMPONENT_EXPORT(X11) BigRequests { + public: + static constexpr unsigned major_version = 0; + static constexpr unsigned minor_version = 0; + + BigRequests(Connection* connection, const x11::QueryExtensionReply& info); + + uint8_t present() const { return info_.present; } + uint8_t major_opcode() const { return info_.major_opcode; } + uint8_t first_event() const { return info_.first_event; } + uint8_t first_error() const { return info_.first_error; } + + Connection* connection() const { return connection_; } + + struct EnableRequest {}; + + struct EnableReply { + uint16_t sequence{}; + uint32_t maximum_request_length{}; + }; + + using EnableResponse = Response; + + Future Enable(const EnableRequest& request); + + Future Enable(); + + private: + Connection* const connection_; + x11::QueryExtensionReply info_{}; +}; + +} // namespace x11 + +#endif // UI_GFX_X_GENERATED_PROTOS_BIGREQ_H_ diff --git a/x/generated_protos/composite.cc b/x/generated_protos/composite.cc new file mode 100644 index 000000000000..5fda7238462d --- /dev/null +++ b/x/generated_protos/composite.cc @@ -0,0 +1,504 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#include "composite.h" + +#include +#include + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "ui/gfx/x/xproto_internal.h" + +namespace x11 { + +Composite::Composite(Connection* connection, + const x11::QueryExtensionReply& info) + : connection_(connection), info_(info) {} + +Future Composite::QueryVersion( + const Composite::QueryVersionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& client_major_version = request.client_major_version; + auto& client_minor_version = request.client_minor_version; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 0; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // client_major_version + buf.Write(&client_major_version); + + // client_minor_version + buf.Write(&client_minor_version); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Composite::QueryVersion", false); +} + +Future Composite::QueryVersion( + const uint32_t& client_major_version, + const uint32_t& client_minor_version) { + return Composite::QueryVersion(Composite::QueryVersionRequest{ + client_major_version, client_minor_version}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Composite::QueryVersionReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& major_version = (*reply).major_version; + auto& minor_version = (*reply).minor_version; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // major_version + Read(&major_version, &buf); + + // minor_version + Read(&minor_version, &buf); + + // pad1 + Pad(&buf, 16); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Composite::RedirectWindow( + const Composite::RedirectWindowRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + auto& update = request.update; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 1; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + // update + uint8_t tmp0; + tmp0 = static_cast(update); + buf.Write(&tmp0); + + // pad0 + Pad(&buf, 3); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Composite::RedirectWindow", + false); +} + +Future Composite::RedirectWindow(const Window& window, + const Redirect& update) { + return Composite::RedirectWindow( + Composite::RedirectWindowRequest{window, update}); +} + +Future Composite::RedirectSubwindows( + const Composite::RedirectSubwindowsRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + auto& update = request.update; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 2; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + // update + uint8_t tmp1; + tmp1 = static_cast(update); + buf.Write(&tmp1); + + // pad0 + Pad(&buf, 3); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Composite::RedirectSubwindows", + false); +} + +Future Composite::RedirectSubwindows(const Window& window, + const Redirect& update) { + return Composite::RedirectSubwindows( + Composite::RedirectSubwindowsRequest{window, update}); +} + +Future Composite::UnredirectWindow( + const Composite::UnredirectWindowRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + auto& update = request.update; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 3; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + // update + uint8_t tmp2; + tmp2 = static_cast(update); + buf.Write(&tmp2); + + // pad0 + Pad(&buf, 3); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Composite::UnredirectWindow", + false); +} + +Future Composite::UnredirectWindow(const Window& window, + const Redirect& update) { + return Composite::UnredirectWindow( + Composite::UnredirectWindowRequest{window, update}); +} + +Future Composite::UnredirectSubwindows( + const Composite::UnredirectSubwindowsRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + auto& update = request.update; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 4; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + // update + uint8_t tmp3; + tmp3 = static_cast(update); + buf.Write(&tmp3); + + // pad0 + Pad(&buf, 3); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Composite::UnredirectSubwindows", + false); +} + +Future Composite::UnredirectSubwindows(const Window& window, + const Redirect& update) { + return Composite::UnredirectSubwindows( + Composite::UnredirectSubwindowsRequest{window, update}); +} + +Future Composite::CreateRegionFromBorderClip( + const Composite::CreateRegionFromBorderClipRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& region = request.region; + auto& window = request.window; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 5; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // region + buf.Write(®ion); + + // window + buf.Write(&window); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Composite::CreateRegionFromBorderClip", false); +} + +Future Composite::CreateRegionFromBorderClip(const XFixes::Region& region, + const Window& window) { + return Composite::CreateRegionFromBorderClip( + Composite::CreateRegionFromBorderClipRequest{region, window}); +} + +Future Composite::NameWindowPixmap( + const Composite::NameWindowPixmapRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + auto& pixmap = request.pixmap; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 6; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + // pixmap + buf.Write(&pixmap); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Composite::NameWindowPixmap", + false); +} + +Future Composite::NameWindowPixmap(const Window& window, + const Pixmap& pixmap) { + return Composite::NameWindowPixmap( + Composite::NameWindowPixmapRequest{window, pixmap}); +} + +Future Composite::GetOverlayWindow( + const Composite::GetOverlayWindowRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 7; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Composite::GetOverlayWindow", false); +} + +Future Composite::GetOverlayWindow( + const Window& window) { + return Composite::GetOverlayWindow( + Composite::GetOverlayWindowRequest{window}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Composite::GetOverlayWindowReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& overlay_win = (*reply).overlay_win; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // overlay_win + Read(&overlay_win, &buf); + + // pad1 + Pad(&buf, 20); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Composite::ReleaseOverlayWindow( + const Composite::ReleaseOverlayWindowRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 8; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Composite::ReleaseOverlayWindow", + false); +} + +Future Composite::ReleaseOverlayWindow(const Window& window) { + return Composite::ReleaseOverlayWindow( + Composite::ReleaseOverlayWindowRequest{window}); +} + +} // namespace x11 diff --git a/x/generated_protos/composite.h b/x/generated_protos/composite.h new file mode 100644 index 000000000000..76e8be6f10f1 --- /dev/null +++ b/x/generated_protos/composite.h @@ -0,0 +1,230 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#ifndef UI_GFX_X_GENERATED_PROTOS_COMPOSITE_H_ +#define UI_GFX_X_GENERATED_PROTOS_COMPOSITE_H_ + +#include +#include +#include +#include +#include + +#include "base/component_export.h" +#include "base/files/scoped_file.h" +#include "base/memory/ref_counted_memory.h" +#include "base/memory/scoped_refptr.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/gfx/x/error.h" +#include "ui/gfx/x/ref_counted_fd.h" +#include "xfixes.h" +#include "xproto.h" + +namespace x11 { + +class Connection; + +template +struct Response; + +template +class Future; + +class COMPONENT_EXPORT(X11) Composite { + public: + static constexpr unsigned major_version = 0; + static constexpr unsigned minor_version = 4; + + Composite(Connection* connection, const x11::QueryExtensionReply& info); + + uint8_t present() const { return info_.present; } + uint8_t major_opcode() const { return info_.major_opcode; } + uint8_t first_event() const { return info_.first_event; } + uint8_t first_error() const { return info_.first_error; } + + Connection* connection() const { return connection_; } + + enum class Redirect : int { + Automatic = 0, + Manual = 1, + }; + + struct QueryVersionRequest { + uint32_t client_major_version{}; + uint32_t client_minor_version{}; + }; + + struct QueryVersionReply { + uint16_t sequence{}; + uint32_t major_version{}; + uint32_t minor_version{}; + }; + + using QueryVersionResponse = Response; + + Future QueryVersion(const QueryVersionRequest& request); + + Future QueryVersion( + const uint32_t& client_major_version = {}, + const uint32_t& client_minor_version = {}); + + struct RedirectWindowRequest { + Window window{}; + Redirect update{}; + }; + + using RedirectWindowResponse = Response; + + Future RedirectWindow(const RedirectWindowRequest& request); + + Future RedirectWindow(const Window& window = {}, + const Redirect& update = {}); + + struct RedirectSubwindowsRequest { + Window window{}; + Redirect update{}; + }; + + using RedirectSubwindowsResponse = Response; + + Future RedirectSubwindows(const RedirectSubwindowsRequest& request); + + Future RedirectSubwindows(const Window& window = {}, + const Redirect& update = {}); + + struct UnredirectWindowRequest { + Window window{}; + Redirect update{}; + }; + + using UnredirectWindowResponse = Response; + + Future UnredirectWindow(const UnredirectWindowRequest& request); + + Future UnredirectWindow(const Window& window = {}, + const Redirect& update = {}); + + struct UnredirectSubwindowsRequest { + Window window{}; + Redirect update{}; + }; + + using UnredirectSubwindowsResponse = Response; + + Future UnredirectSubwindows(const UnredirectSubwindowsRequest& request); + + Future UnredirectSubwindows(const Window& window = {}, + const Redirect& update = {}); + + struct CreateRegionFromBorderClipRequest { + XFixes::Region region{}; + Window window{}; + }; + + using CreateRegionFromBorderClipResponse = Response; + + Future CreateRegionFromBorderClip( + const CreateRegionFromBorderClipRequest& request); + + Future CreateRegionFromBorderClip(const XFixes::Region& region = {}, + const Window& window = {}); + + struct NameWindowPixmapRequest { + Window window{}; + Pixmap pixmap{}; + }; + + using NameWindowPixmapResponse = Response; + + Future NameWindowPixmap(const NameWindowPixmapRequest& request); + + Future NameWindowPixmap(const Window& window = {}, + const Pixmap& pixmap = {}); + + struct GetOverlayWindowRequest { + Window window{}; + }; + + struct GetOverlayWindowReply { + uint16_t sequence{}; + Window overlay_win{}; + }; + + using GetOverlayWindowResponse = Response; + + Future GetOverlayWindow( + const GetOverlayWindowRequest& request); + + Future GetOverlayWindow(const Window& window = {}); + + struct ReleaseOverlayWindowRequest { + Window window{}; + }; + + using ReleaseOverlayWindowResponse = Response; + + Future ReleaseOverlayWindow(const ReleaseOverlayWindowRequest& request); + + Future ReleaseOverlayWindow(const Window& window = {}); + + private: + Connection* const connection_; + x11::QueryExtensionReply info_{}; +}; + +} // namespace x11 + +inline constexpr x11::Composite::Redirect operator|( + x11::Composite::Redirect l, + x11::Composite::Redirect r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::Composite::Redirect operator&( + x11::Composite::Redirect l, + x11::Composite::Redirect r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +#endif // UI_GFX_X_GENERATED_PROTOS_COMPOSITE_H_ diff --git a/x/generated_protos/damage.cc b/x/generated_protos/damage.cc new file mode 100644 index 000000000000..bfd9c665ee71 --- /dev/null +++ b/x/generated_protos/damage.cc @@ -0,0 +1,400 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#include "damage.h" + +#include +#include + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "ui/gfx/x/xproto_internal.h" + +namespace x11 { + +Damage::Damage(Connection* connection, const x11::QueryExtensionReply& info) + : connection_(connection), info_(info) {} + +std::string Damage::BadDamageError::ToString() const { + std::stringstream ss_; + ss_ << "Damage::BadDamageError{"; + ss_ << ".sequence = " << static_cast(sequence); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError(Damage::BadDamageError* error_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + DCHECK_LE(buf.offset, 32ul); +} +template <> +COMPONENT_EXPORT(X11) +void ReadEvent(Damage::NotifyEvent* event_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& level = (*event_).level; + auto& sequence = (*event_).sequence; + auto& drawable = (*event_).drawable; + auto& damage = (*event_).damage; + auto& timestamp = (*event_).timestamp; + auto& area = (*event_).area; + auto& geometry = (*event_).geometry; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // level + uint8_t tmp0; + Read(&tmp0, &buf); + level = static_cast(tmp0); + + // sequence + Read(&sequence, &buf); + + // drawable + Read(&drawable, &buf); + + // damage + Read(&damage, &buf); + + // timestamp + Read(×tamp, &buf); + + // area + { + auto& x = area.x; + auto& y = area.y; + auto& width = area.width; + auto& height = area.height; + + // x + Read(&x, &buf); + + // y + Read(&y, &buf); + + // width + Read(&width, &buf); + + // height + Read(&height, &buf); + } + + // geometry + { + auto& x = geometry.x; + auto& y = geometry.y; + auto& width = geometry.width; + auto& height = geometry.height; + + // x + Read(&x, &buf); + + // y + Read(&y, &buf); + + // width + Read(&width, &buf); + + // height + Read(&height, &buf); + } + + DCHECK_LE(buf.offset, 32ul); +} + +Future Damage::QueryVersion( + const Damage::QueryVersionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& client_major_version = request.client_major_version; + auto& client_minor_version = request.client_minor_version; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 0; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // client_major_version + buf.Write(&client_major_version); + + // client_minor_version + buf.Write(&client_minor_version); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Damage::QueryVersion", false); +} + +Future Damage::QueryVersion( + const uint32_t& client_major_version, + const uint32_t& client_minor_version) { + return Damage::QueryVersion( + Damage::QueryVersionRequest{client_major_version, client_minor_version}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Damage::QueryVersionReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& major_version = (*reply).major_version; + auto& minor_version = (*reply).minor_version; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // major_version + Read(&major_version, &buf); + + // minor_version + Read(&minor_version, &buf); + + // pad1 + Pad(&buf, 16); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Damage::Create(const Damage::CreateRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& damage = request.damage; + auto& drawable = request.drawable; + auto& level = request.level; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 1; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // damage + buf.Write(&damage); + + // drawable + buf.Write(&drawable); + + // level + uint8_t tmp1; + tmp1 = static_cast(level); + buf.Write(&tmp1); + + // pad0 + Pad(&buf, 3); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Damage::Create", false); +} + +Future Damage::Create(const DamageId& damage, + const Drawable& drawable, + const ReportLevel& level) { + return Damage::Create(Damage::CreateRequest{damage, drawable, level}); +} + +Future Damage::Destroy(const Damage::DestroyRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& damage = request.damage; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 2; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // damage + buf.Write(&damage); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Damage::Destroy", false); +} + +Future Damage::Destroy(const DamageId& damage) { + return Damage::Destroy(Damage::DestroyRequest{damage}); +} + +Future Damage::Subtract(const Damage::SubtractRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& damage = request.damage; + auto& repair = request.repair; + auto& parts = request.parts; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 3; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // damage + buf.Write(&damage); + + // repair + buf.Write(&repair); + + // parts + buf.Write(&parts); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Damage::Subtract", false); +} + +Future Damage::Subtract(const DamageId& damage, + const XFixes::Region& repair, + const XFixes::Region& parts) { + return Damage::Subtract(Damage::SubtractRequest{damage, repair, parts}); +} + +Future Damage::Add(const Damage::AddRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& drawable = request.drawable; + auto& region = request.region; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 4; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // drawable + buf.Write(&drawable); + + // region + buf.Write(®ion); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Damage::Add", false); +} + +Future Damage::Add(const Drawable& drawable, + const XFixes::Region& region) { + return Damage::Add(Damage::AddRequest{drawable, region}); +} + +} // namespace x11 diff --git a/x/generated_protos/damage.h b/x/generated_protos/damage.h new file mode 100644 index 000000000000..bcfd8c4ee402 --- /dev/null +++ b/x/generated_protos/damage.h @@ -0,0 +1,208 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#ifndef UI_GFX_X_GENERATED_PROTOS_DAMAGE_H_ +#define UI_GFX_X_GENERATED_PROTOS_DAMAGE_H_ + +#include +#include +#include +#include +#include + +#include "base/component_export.h" +#include "base/files/scoped_file.h" +#include "base/memory/ref_counted_memory.h" +#include "base/memory/scoped_refptr.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/gfx/x/error.h" +#include "ui/gfx/x/ref_counted_fd.h" +#include "xfixes.h" +#include "xproto.h" + +namespace x11 { + +class Connection; + +template +struct Response; + +template +class Future; + +class COMPONENT_EXPORT(X11) Damage { + public: + static constexpr unsigned major_version = 1; + static constexpr unsigned minor_version = 1; + + Damage(Connection* connection, const x11::QueryExtensionReply& info); + + uint8_t present() const { return info_.present; } + uint8_t major_opcode() const { return info_.major_opcode; } + uint8_t first_event() const { return info_.first_event; } + uint8_t first_error() const { return info_.first_error; } + + Connection* connection() const { return connection_; } + + enum class DamageId : uint32_t {}; + + enum class ReportLevel : int { + RawRectangles = 0, + DeltaRectangles = 1, + BoundingBox = 2, + NonEmpty = 3, + }; + + struct BadDamageError : public x11::Error { + uint16_t sequence{}; + + std::string ToString() const override; + }; + + struct NotifyEvent { + static constexpr int type_id = 1; + static constexpr uint8_t opcode = 0; + bool send_event{}; + ReportLevel level{}; + uint16_t sequence{}; + Drawable drawable{}; + DamageId damage{}; + Time timestamp{}; + Rectangle area{}; + Rectangle geometry{}; + + x11::Window* GetWindow() { + return reinterpret_cast(&drawable); + } + }; + + struct QueryVersionRequest { + uint32_t client_major_version{}; + uint32_t client_minor_version{}; + }; + + struct QueryVersionReply { + uint16_t sequence{}; + uint32_t major_version{}; + uint32_t minor_version{}; + }; + + using QueryVersionResponse = Response; + + Future QueryVersion(const QueryVersionRequest& request); + + Future QueryVersion( + const uint32_t& client_major_version = {}, + const uint32_t& client_minor_version = {}); + + struct CreateRequest { + DamageId damage{}; + Drawable drawable{}; + ReportLevel level{}; + }; + + using CreateResponse = Response; + + Future Create(const CreateRequest& request); + + Future Create(const DamageId& damage = {}, + const Drawable& drawable = {}, + const ReportLevel& level = {}); + + struct DestroyRequest { + DamageId damage{}; + }; + + using DestroyResponse = Response; + + Future Destroy(const DestroyRequest& request); + + Future Destroy(const DamageId& damage = {}); + + struct SubtractRequest { + DamageId damage{}; + XFixes::Region repair{}; + XFixes::Region parts{}; + }; + + using SubtractResponse = Response; + + Future Subtract(const SubtractRequest& request); + + Future Subtract(const DamageId& damage = {}, + const XFixes::Region& repair = {}, + const XFixes::Region& parts = {}); + + struct AddRequest { + Drawable drawable{}; + XFixes::Region region{}; + }; + + using AddResponse = Response; + + Future Add(const AddRequest& request); + + Future Add(const Drawable& drawable = {}, + const XFixes::Region& region = {}); + + private: + Connection* const connection_; + x11::QueryExtensionReply info_{}; +}; + +} // namespace x11 + +inline constexpr x11::Damage::ReportLevel operator|( + x11::Damage::ReportLevel l, + x11::Damage::ReportLevel r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::Damage::ReportLevel operator&( + x11::Damage::ReportLevel l, + x11::Damage::ReportLevel r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +#endif // UI_GFX_X_GENERATED_PROTOS_DAMAGE_H_ diff --git a/x/generated_protos/dpms.cc b/x/generated_protos/dpms.cc new file mode 100644 index 000000000000..dd007a3fbecd --- /dev/null +++ b/x/generated_protos/dpms.cc @@ -0,0 +1,470 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#include "dpms.h" + +#include +#include + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "ui/gfx/x/xproto_internal.h" + +namespace x11 { + +Dpms::Dpms(Connection* connection, const x11::QueryExtensionReply& info) + : connection_(connection), info_(info) {} + +Future Dpms::GetVersion( + const Dpms::GetVersionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& client_major_version = request.client_major_version; + auto& client_minor_version = request.client_minor_version; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 0; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // client_major_version + buf.Write(&client_major_version); + + // client_minor_version + buf.Write(&client_minor_version); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Dpms::GetVersion", false); +} + +Future Dpms::GetVersion( + const uint16_t& client_major_version, + const uint16_t& client_minor_version) { + return Dpms::GetVersion( + Dpms::GetVersionRequest{client_major_version, client_minor_version}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& server_major_version = (*reply).server_major_version; + auto& server_minor_version = (*reply).server_minor_version; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // server_major_version + Read(&server_major_version, &buf); + + // server_minor_version + Read(&server_minor_version, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Dpms::Capable(const Dpms::CapableRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 1; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Dpms::Capable", + false); +} + +Future Dpms::Capable() { + return Dpms::Capable(Dpms::CapableRequest{}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& capable = (*reply).capable; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // capable + Read(&capable, &buf); + + // pad1 + Pad(&buf, 23); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Dpms::GetTimeouts( + const Dpms::GetTimeoutsRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 2; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Dpms::GetTimeouts", false); +} + +Future Dpms::GetTimeouts() { + return Dpms::GetTimeouts(Dpms::GetTimeoutsRequest{}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Dpms::GetTimeoutsReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& standby_timeout = (*reply).standby_timeout; + auto& suspend_timeout = (*reply).suspend_timeout; + auto& off_timeout = (*reply).off_timeout; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // standby_timeout + Read(&standby_timeout, &buf); + + // suspend_timeout + Read(&suspend_timeout, &buf); + + // off_timeout + Read(&off_timeout, &buf); + + // pad1 + Pad(&buf, 18); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Dpms::SetTimeouts(const Dpms::SetTimeoutsRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& standby_timeout = request.standby_timeout; + auto& suspend_timeout = request.suspend_timeout; + auto& off_timeout = request.off_timeout; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 3; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // standby_timeout + buf.Write(&standby_timeout); + + // suspend_timeout + buf.Write(&suspend_timeout); + + // off_timeout + buf.Write(&off_timeout); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Dpms::SetTimeouts", false); +} + +Future Dpms::SetTimeouts(const uint16_t& standby_timeout, + const uint16_t& suspend_timeout, + const uint16_t& off_timeout) { + return Dpms::SetTimeouts( + Dpms::SetTimeoutsRequest{standby_timeout, suspend_timeout, off_timeout}); +} + +Future Dpms::Enable(const Dpms::EnableRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 4; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Dpms::Enable", false); +} + +Future Dpms::Enable() { + return Dpms::Enable(Dpms::EnableRequest{}); +} + +Future Dpms::Disable(const Dpms::DisableRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 5; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Dpms::Disable", false); +} + +Future Dpms::Disable() { + return Dpms::Disable(Dpms::DisableRequest{}); +} + +Future Dpms::ForceLevel(const Dpms::ForceLevelRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& power_level = request.power_level; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 6; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // power_level + uint16_t tmp0; + tmp0 = static_cast(power_level); + buf.Write(&tmp0); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Dpms::ForceLevel", false); +} + +Future Dpms::ForceLevel(const DPMSMode& power_level) { + return Dpms::ForceLevel(Dpms::ForceLevelRequest{power_level}); +} + +Future Dpms::Info(const Dpms::InfoRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 7; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Dpms::Info", false); +} + +Future Dpms::Info() { + return Dpms::Info(Dpms::InfoRequest{}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& power_level = (*reply).power_level; + auto& state = (*reply).state; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // power_level + uint16_t tmp1; + Read(&tmp1, &buf); + power_level = static_cast(tmp1); + + // state + Read(&state, &buf); + + // pad1 + Pad(&buf, 21); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +} // namespace x11 diff --git a/x/generated_protos/dpms.h b/x/generated_protos/dpms.h new file mode 100644 index 000000000000..f81f656db0e1 --- /dev/null +++ b/x/generated_protos/dpms.h @@ -0,0 +1,211 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#ifndef UI_GFX_X_GENERATED_PROTOS_DPMS_H_ +#define UI_GFX_X_GENERATED_PROTOS_DPMS_H_ + +#include +#include +#include +#include +#include + +#include "base/component_export.h" +#include "base/files/scoped_file.h" +#include "base/memory/ref_counted_memory.h" +#include "base/memory/scoped_refptr.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/gfx/x/error.h" +#include "ui/gfx/x/ref_counted_fd.h" +#include "xproto.h" + +namespace x11 { + +class Connection; + +template +struct Response; + +template +class Future; + +class COMPONENT_EXPORT(X11) Dpms { + public: + static constexpr unsigned major_version = 0; + static constexpr unsigned minor_version = 0; + + Dpms(Connection* connection, const x11::QueryExtensionReply& info); + + uint8_t present() const { return info_.present; } + uint8_t major_opcode() const { return info_.major_opcode; } + uint8_t first_event() const { return info_.first_event; } + uint8_t first_error() const { return info_.first_error; } + + Connection* connection() const { return connection_; } + + enum class DPMSMode : int { + On = 0, + Standby = 1, + Suspend = 2, + Off = 3, + }; + + struct GetVersionRequest { + uint16_t client_major_version{}; + uint16_t client_minor_version{}; + }; + + struct GetVersionReply { + uint16_t sequence{}; + uint16_t server_major_version{}; + uint16_t server_minor_version{}; + }; + + using GetVersionResponse = Response; + + Future GetVersion(const GetVersionRequest& request); + + Future GetVersion(const uint16_t& client_major_version = {}, + const uint16_t& client_minor_version = {}); + + struct CapableRequest {}; + + struct CapableReply { + uint16_t sequence{}; + uint8_t capable{}; + }; + + using CapableResponse = Response; + + Future Capable(const CapableRequest& request); + + Future Capable(); + + struct GetTimeoutsRequest {}; + + struct GetTimeoutsReply { + uint16_t sequence{}; + uint16_t standby_timeout{}; + uint16_t suspend_timeout{}; + uint16_t off_timeout{}; + }; + + using GetTimeoutsResponse = Response; + + Future GetTimeouts(const GetTimeoutsRequest& request); + + Future GetTimeouts(); + + struct SetTimeoutsRequest { + uint16_t standby_timeout{}; + uint16_t suspend_timeout{}; + uint16_t off_timeout{}; + }; + + using SetTimeoutsResponse = Response; + + Future SetTimeouts(const SetTimeoutsRequest& request); + + Future SetTimeouts(const uint16_t& standby_timeout = {}, + const uint16_t& suspend_timeout = {}, + const uint16_t& off_timeout = {}); + + struct EnableRequest {}; + + using EnableResponse = Response; + + Future Enable(const EnableRequest& request); + + Future Enable(); + + struct DisableRequest {}; + + using DisableResponse = Response; + + Future Disable(const DisableRequest& request); + + Future Disable(); + + struct ForceLevelRequest { + DPMSMode power_level{}; + }; + + using ForceLevelResponse = Response; + + Future ForceLevel(const ForceLevelRequest& request); + + Future ForceLevel(const DPMSMode& power_level = {}); + + struct InfoRequest {}; + + struct InfoReply { + uint16_t sequence{}; + DPMSMode power_level{}; + uint8_t state{}; + }; + + using InfoResponse = Response; + + Future Info(const InfoRequest& request); + + Future Info(); + + private: + Connection* const connection_; + x11::QueryExtensionReply info_{}; +}; + +} // namespace x11 + +inline constexpr x11::Dpms::DPMSMode operator|(x11::Dpms::DPMSMode l, + x11::Dpms::DPMSMode r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::Dpms::DPMSMode operator&(x11::Dpms::DPMSMode l, + x11::Dpms::DPMSMode r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +#endif // UI_GFX_X_GENERATED_PROTOS_DPMS_H_ diff --git a/x/generated_protos/dri2.cc b/x/generated_protos/dri2.cc new file mode 100644 index 000000000000..0e5eca28f067 --- /dev/null +++ b/x/generated_protos/dri2.cc @@ -0,0 +1,1316 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#include "dri2.h" + +#include +#include + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "ui/gfx/x/xproto_internal.h" + +namespace x11 { + +Dri2::Dri2(Connection* connection, const x11::QueryExtensionReply& info) + : connection_(connection), info_(info) {} + +template <> +COMPONENT_EXPORT(X11) +void ReadEvent( + Dri2::BufferSwapCompleteEvent* event_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*event_).sequence; + auto& event_type = (*event_).event_type; + auto& drawable = (*event_).drawable; + auto& ust_hi = (*event_).ust_hi; + auto& ust_lo = (*event_).ust_lo; + auto& msc_hi = (*event_).msc_hi; + auto& msc_lo = (*event_).msc_lo; + auto& sbc = (*event_).sbc; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // event_type + uint16_t tmp0; + Read(&tmp0, &buf); + event_type = static_cast(tmp0); + + // pad1 + Pad(&buf, 2); + + // drawable + Read(&drawable, &buf); + + // ust_hi + Read(&ust_hi, &buf); + + // ust_lo + Read(&ust_lo, &buf); + + // msc_hi + Read(&msc_hi, &buf); + + // msc_lo + Read(&msc_lo, &buf); + + // sbc + Read(&sbc, &buf); + + DCHECK_LE(buf.offset, 32ul); +} + +template <> +COMPONENT_EXPORT(X11) +void ReadEvent( + Dri2::InvalidateBuffersEvent* event_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*event_).sequence; + auto& drawable = (*event_).drawable; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // drawable + Read(&drawable, &buf); + + DCHECK_LE(buf.offset, 32ul); +} + +Future Dri2::QueryVersion( + const Dri2::QueryVersionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& major_version = request.major_version; + auto& minor_version = request.minor_version; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 0; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // major_version + buf.Write(&major_version); + + // minor_version + buf.Write(&minor_version); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Dri2::QueryVersion", false); +} + +Future Dri2::QueryVersion( + const uint32_t& major_version, + const uint32_t& minor_version) { + return Dri2::QueryVersion( + Dri2::QueryVersionRequest{major_version, minor_version}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Dri2::QueryVersionReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& major_version = (*reply).major_version; + auto& minor_version = (*reply).minor_version; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // major_version + Read(&major_version, &buf); + + // minor_version + Read(&minor_version, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Dri2::Connect(const Dri2::ConnectRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + auto& driver_type = request.driver_type; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 1; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + // driver_type + uint32_t tmp1; + tmp1 = static_cast(driver_type); + buf.Write(&tmp1); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Dri2::Connect", + false); +} + +Future Dri2::Connect(const Window& window, + const DriverType& driver_type) { + return Dri2::Connect(Dri2::ConnectRequest{window, driver_type}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t driver_name_length{}; + uint32_t device_name_length{}; + auto& driver_name = (*reply).driver_name; + size_t driver_name_len = driver_name.size(); + auto& alignment_pad = (*reply).alignment_pad; + size_t alignment_pad_len = alignment_pad ? alignment_pad->size() : 0; + auto& device_name = (*reply).device_name; + size_t device_name_len = device_name.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // driver_name_length + Read(&driver_name_length, &buf); + + // device_name_length + Read(&device_name_length, &buf); + + // pad1 + Pad(&buf, 16); + + // driver_name + driver_name.resize(driver_name_length); + for (auto& driver_name_elem : driver_name) { + // driver_name_elem + Read(&driver_name_elem, &buf); + } + + // alignment_pad + alignment_pad = buffer->ReadAndAdvance( + (BitAnd((driver_name_length) + (3), BitNot(3))) - (driver_name_length)); + + // device_name + device_name.resize(device_name_length); + for (auto& device_name_elem : device_name) { + // device_name_elem + Read(&device_name_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Dri2::Authenticate( + const Dri2::AuthenticateRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + auto& magic = request.magic; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 2; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + // magic + buf.Write(&magic); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Dri2::Authenticate", false); +} + +Future Dri2::Authenticate(const Window& window, + const uint32_t& magic) { + return Dri2::Authenticate(Dri2::AuthenticateRequest{window, magic}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Dri2::AuthenticateReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& authenticated = (*reply).authenticated; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // authenticated + Read(&authenticated, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Dri2::CreateDrawable(const Dri2::CreateDrawableRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& drawable = request.drawable; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 3; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // drawable + buf.Write(&drawable); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Dri2::CreateDrawable", false); +} + +Future Dri2::CreateDrawable(const Drawable& drawable) { + return Dri2::CreateDrawable(Dri2::CreateDrawableRequest{drawable}); +} + +Future Dri2::DestroyDrawable( + const Dri2::DestroyDrawableRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& drawable = request.drawable; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 4; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // drawable + buf.Write(&drawable); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Dri2::DestroyDrawable", false); +} + +Future Dri2::DestroyDrawable(const Drawable& drawable) { + return Dri2::DestroyDrawable(Dri2::DestroyDrawableRequest{drawable}); +} + +Future Dri2::GetBuffers( + const Dri2::GetBuffersRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& drawable = request.drawable; + auto& count = request.count; + auto& attachments = request.attachments; + size_t attachments_len = attachments.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 5; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // drawable + buf.Write(&drawable); + + // count + buf.Write(&count); + + // attachments + DCHECK_EQ(static_cast(attachments_len), attachments.size()); + for (auto& attachments_elem : attachments) { + // attachments_elem + buf.Write(&attachments_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Dri2::GetBuffers", false); +} + +Future Dri2::GetBuffers( + const Drawable& drawable, + const uint32_t& count, + const std::vector& attachments) { + return Dri2::GetBuffers( + Dri2::GetBuffersRequest{drawable, count, attachments}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& width = (*reply).width; + auto& height = (*reply).height; + uint32_t count{}; + auto& buffers = (*reply).buffers; + size_t buffers_len = buffers.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // width + Read(&width, &buf); + + // height + Read(&height, &buf); + + // count + Read(&count, &buf); + + // pad1 + Pad(&buf, 12); + + // buffers + buffers.resize(count); + for (auto& buffers_elem : buffers) { + // buffers_elem + { + auto& attachment = buffers_elem.attachment; + auto& name = buffers_elem.name; + auto& pitch = buffers_elem.pitch; + auto& cpp = buffers_elem.cpp; + auto& flags = buffers_elem.flags; + + // attachment + uint32_t tmp2; + Read(&tmp2, &buf); + attachment = static_cast(tmp2); + + // name + Read(&name, &buf); + + // pitch + Read(&pitch, &buf); + + // cpp + Read(&cpp, &buf); + + // flags + Read(&flags, &buf); + } + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Dri2::CopyRegion( + const Dri2::CopyRegionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& drawable = request.drawable; + auto& region = request.region; + auto& dest = request.dest; + auto& src = request.src; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 6; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // drawable + buf.Write(&drawable); + + // region + buf.Write(®ion); + + // dest + buf.Write(&dest); + + // src + buf.Write(&src); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Dri2::CopyRegion", false); +} + +Future Dri2::CopyRegion(const Drawable& drawable, + const uint32_t& region, + const uint32_t& dest, + const uint32_t& src) { + return Dri2::CopyRegion(Dri2::CopyRegionRequest{drawable, region, dest, src}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Dri2::GetBuffersWithFormat( + const Dri2::GetBuffersWithFormatRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& drawable = request.drawable; + auto& count = request.count; + auto& attachments = request.attachments; + size_t attachments_len = attachments.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 7; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // drawable + buf.Write(&drawable); + + // count + buf.Write(&count); + + // attachments + DCHECK_EQ(static_cast(attachments_len), attachments.size()); + for (auto& attachments_elem : attachments) { + // attachments_elem + { + auto& attachment = attachments_elem.attachment; + auto& format = attachments_elem.format; + + // attachment + uint32_t tmp3; + tmp3 = static_cast(attachment); + buf.Write(&tmp3); + + // format + buf.Write(&format); + } + } + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Dri2::GetBuffersWithFormat", false); +} + +Future Dri2::GetBuffersWithFormat( + const Drawable& drawable, + const uint32_t& count, + const std::vector& attachments) { + return Dri2::GetBuffersWithFormat( + Dri2::GetBuffersWithFormatRequest{drawable, count, attachments}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Dri2::GetBuffersWithFormatReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& width = (*reply).width; + auto& height = (*reply).height; + uint32_t count{}; + auto& buffers = (*reply).buffers; + size_t buffers_len = buffers.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // width + Read(&width, &buf); + + // height + Read(&height, &buf); + + // count + Read(&count, &buf); + + // pad1 + Pad(&buf, 12); + + // buffers + buffers.resize(count); + for (auto& buffers_elem : buffers) { + // buffers_elem + { + auto& attachment = buffers_elem.attachment; + auto& name = buffers_elem.name; + auto& pitch = buffers_elem.pitch; + auto& cpp = buffers_elem.cpp; + auto& flags = buffers_elem.flags; + + // attachment + uint32_t tmp4; + Read(&tmp4, &buf); + attachment = static_cast(tmp4); + + // name + Read(&name, &buf); + + // pitch + Read(&pitch, &buf); + + // cpp + Read(&cpp, &buf); + + // flags + Read(&flags, &buf); + } + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Dri2::SwapBuffers( + const Dri2::SwapBuffersRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& drawable = request.drawable; + auto& target_msc_hi = request.target_msc_hi; + auto& target_msc_lo = request.target_msc_lo; + auto& divisor_hi = request.divisor_hi; + auto& divisor_lo = request.divisor_lo; + auto& remainder_hi = request.remainder_hi; + auto& remainder_lo = request.remainder_lo; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 8; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // drawable + buf.Write(&drawable); + + // target_msc_hi + buf.Write(&target_msc_hi); + + // target_msc_lo + buf.Write(&target_msc_lo); + + // divisor_hi + buf.Write(&divisor_hi); + + // divisor_lo + buf.Write(&divisor_lo); + + // remainder_hi + buf.Write(&remainder_hi); + + // remainder_lo + buf.Write(&remainder_lo); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Dri2::SwapBuffers", false); +} + +Future Dri2::SwapBuffers(const Drawable& drawable, + const uint32_t& target_msc_hi, + const uint32_t& target_msc_lo, + const uint32_t& divisor_hi, + const uint32_t& divisor_lo, + const uint32_t& remainder_hi, + const uint32_t& remainder_lo) { + return Dri2::SwapBuffers(Dri2::SwapBuffersRequest{ + drawable, target_msc_hi, target_msc_lo, divisor_hi, divisor_lo, + remainder_hi, remainder_lo}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Dri2::SwapBuffersReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& swap_hi = (*reply).swap_hi; + auto& swap_lo = (*reply).swap_lo; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // swap_hi + Read(&swap_hi, &buf); + + // swap_lo + Read(&swap_lo, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Dri2::GetMSC(const Dri2::GetMSCRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& drawable = request.drawable; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 9; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // drawable + buf.Write(&drawable); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Dri2::GetMSC", + false); +} + +Future Dri2::GetMSC(const Drawable& drawable) { + return Dri2::GetMSC(Dri2::GetMSCRequest{drawable}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& ust_hi = (*reply).ust_hi; + auto& ust_lo = (*reply).ust_lo; + auto& msc_hi = (*reply).msc_hi; + auto& msc_lo = (*reply).msc_lo; + auto& sbc_hi = (*reply).sbc_hi; + auto& sbc_lo = (*reply).sbc_lo; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // ust_hi + Read(&ust_hi, &buf); + + // ust_lo + Read(&ust_lo, &buf); + + // msc_hi + Read(&msc_hi, &buf); + + // msc_lo + Read(&msc_lo, &buf); + + // sbc_hi + Read(&sbc_hi, &buf); + + // sbc_lo + Read(&sbc_lo, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Dri2::WaitMSC(const Dri2::WaitMSCRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& drawable = request.drawable; + auto& target_msc_hi = request.target_msc_hi; + auto& target_msc_lo = request.target_msc_lo; + auto& divisor_hi = request.divisor_hi; + auto& divisor_lo = request.divisor_lo; + auto& remainder_hi = request.remainder_hi; + auto& remainder_lo = request.remainder_lo; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 10; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // drawable + buf.Write(&drawable); + + // target_msc_hi + buf.Write(&target_msc_hi); + + // target_msc_lo + buf.Write(&target_msc_lo); + + // divisor_hi + buf.Write(&divisor_hi); + + // divisor_lo + buf.Write(&divisor_lo); + + // remainder_hi + buf.Write(&remainder_hi); + + // remainder_lo + buf.Write(&remainder_lo); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Dri2::WaitMSC", + false); +} + +Future Dri2::WaitMSC(const Drawable& drawable, + const uint32_t& target_msc_hi, + const uint32_t& target_msc_lo, + const uint32_t& divisor_hi, + const uint32_t& divisor_lo, + const uint32_t& remainder_hi, + const uint32_t& remainder_lo) { + return Dri2::WaitMSC( + Dri2::WaitMSCRequest{drawable, target_msc_hi, target_msc_lo, divisor_hi, + divisor_lo, remainder_hi, remainder_lo}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& ust_hi = (*reply).ust_hi; + auto& ust_lo = (*reply).ust_lo; + auto& msc_hi = (*reply).msc_hi; + auto& msc_lo = (*reply).msc_lo; + auto& sbc_hi = (*reply).sbc_hi; + auto& sbc_lo = (*reply).sbc_lo; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // ust_hi + Read(&ust_hi, &buf); + + // ust_lo + Read(&ust_lo, &buf); + + // msc_hi + Read(&msc_hi, &buf); + + // msc_lo + Read(&msc_lo, &buf); + + // sbc_hi + Read(&sbc_hi, &buf); + + // sbc_lo + Read(&sbc_lo, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Dri2::WaitSBC(const Dri2::WaitSBCRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& drawable = request.drawable; + auto& target_sbc_hi = request.target_sbc_hi; + auto& target_sbc_lo = request.target_sbc_lo; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 11; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // drawable + buf.Write(&drawable); + + // target_sbc_hi + buf.Write(&target_sbc_hi); + + // target_sbc_lo + buf.Write(&target_sbc_lo); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Dri2::WaitSBC", + false); +} + +Future Dri2::WaitSBC(const Drawable& drawable, + const uint32_t& target_sbc_hi, + const uint32_t& target_sbc_lo) { + return Dri2::WaitSBC( + Dri2::WaitSBCRequest{drawable, target_sbc_hi, target_sbc_lo}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& ust_hi = (*reply).ust_hi; + auto& ust_lo = (*reply).ust_lo; + auto& msc_hi = (*reply).msc_hi; + auto& msc_lo = (*reply).msc_lo; + auto& sbc_hi = (*reply).sbc_hi; + auto& sbc_lo = (*reply).sbc_lo; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // ust_hi + Read(&ust_hi, &buf); + + // ust_lo + Read(&ust_lo, &buf); + + // msc_hi + Read(&msc_hi, &buf); + + // msc_lo + Read(&msc_lo, &buf); + + // sbc_hi + Read(&sbc_hi, &buf); + + // sbc_lo + Read(&sbc_lo, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Dri2::SwapInterval(const Dri2::SwapIntervalRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& drawable = request.drawable; + auto& interval = request.interval; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 12; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // drawable + buf.Write(&drawable); + + // interval + buf.Write(&interval); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Dri2::SwapInterval", false); +} + +Future Dri2::SwapInterval(const Drawable& drawable, + const uint32_t& interval) { + return Dri2::SwapInterval(Dri2::SwapIntervalRequest{drawable, interval}); +} + +Future Dri2::GetParam( + const Dri2::GetParamRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& drawable = request.drawable; + auto& param = request.param; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 13; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // drawable + buf.Write(&drawable); + + // param + buf.Write(¶m); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Dri2::GetParam", + false); +} + +Future Dri2::GetParam(const Drawable& drawable, + const uint32_t& param) { + return Dri2::GetParam(Dri2::GetParamRequest{drawable, param}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& is_param_recognized = (*reply).is_param_recognized; + auto& sequence = (*reply).sequence; + auto& value_hi = (*reply).value_hi; + auto& value_lo = (*reply).value_lo; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // is_param_recognized + Read(&is_param_recognized, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // value_hi + Read(&value_hi, &buf); + + // value_lo + Read(&value_lo, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +} // namespace x11 diff --git a/x/generated_protos/dri2.h b/x/generated_protos/dri2.h new file mode 100644 index 000000000000..5d4a6bdf5f45 --- /dev/null +++ b/x/generated_protos/dri2.h @@ -0,0 +1,474 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#ifndef UI_GFX_X_GENERATED_PROTOS_DRI2_H_ +#define UI_GFX_X_GENERATED_PROTOS_DRI2_H_ + +#include +#include +#include +#include +#include + +#include "base/component_export.h" +#include "base/files/scoped_file.h" +#include "base/memory/ref_counted_memory.h" +#include "base/memory/scoped_refptr.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/gfx/x/error.h" +#include "ui/gfx/x/ref_counted_fd.h" +#include "xproto.h" + +namespace x11 { + +class Connection; + +template +struct Response; + +template +class Future; + +class COMPONENT_EXPORT(X11) Dri2 { + public: + static constexpr unsigned major_version = 1; + static constexpr unsigned minor_version = 4; + + Dri2(Connection* connection, const x11::QueryExtensionReply& info); + + uint8_t present() const { return info_.present; } + uint8_t major_opcode() const { return info_.major_opcode; } + uint8_t first_event() const { return info_.first_event; } + uint8_t first_error() const { return info_.first_error; } + + Connection* connection() const { return connection_; } + + enum class Attachment : int { + BufferFrontLeft = 0, + BufferBackLeft = 1, + BufferFrontRight = 2, + BufferBackRight = 3, + BufferDepth = 4, + BufferStencil = 5, + BufferAccum = 6, + BufferFakeFrontLeft = 7, + BufferFakeFrontRight = 8, + BufferDepthStencil = 9, + BufferHiz = 10, + }; + + enum class DriverType : int { + DRI = 0, + VDPAU = 1, + }; + + enum class EventType : int { + ExchangeComplete = 1, + BlitComplete = 2, + FlipComplete = 3, + }; + + struct DRI2Buffer { + Attachment attachment{}; + uint32_t name{}; + uint32_t pitch{}; + uint32_t cpp{}; + uint32_t flags{}; + }; + + struct AttachFormat { + Attachment attachment{}; + uint32_t format{}; + }; + + struct BufferSwapCompleteEvent { + static constexpr int type_id = 2; + static constexpr uint8_t opcode = 0; + bool send_event{}; + uint16_t sequence{}; + EventType event_type{}; + Drawable drawable{}; + uint32_t ust_hi{}; + uint32_t ust_lo{}; + uint32_t msc_hi{}; + uint32_t msc_lo{}; + uint32_t sbc{}; + + x11::Window* GetWindow() { + return reinterpret_cast(&drawable); + } + }; + + struct InvalidateBuffersEvent { + static constexpr int type_id = 3; + static constexpr uint8_t opcode = 1; + bool send_event{}; + uint16_t sequence{}; + Drawable drawable{}; + + x11::Window* GetWindow() { + return reinterpret_cast(&drawable); + } + }; + + struct QueryVersionRequest { + uint32_t major_version{}; + uint32_t minor_version{}; + }; + + struct QueryVersionReply { + uint16_t sequence{}; + uint32_t major_version{}; + uint32_t minor_version{}; + }; + + using QueryVersionResponse = Response; + + Future QueryVersion(const QueryVersionRequest& request); + + Future QueryVersion(const uint32_t& major_version = {}, + const uint32_t& minor_version = {}); + + struct ConnectRequest { + Window window{}; + DriverType driver_type{}; + }; + + struct ConnectReply { + uint16_t sequence{}; + std::string driver_name{}; + scoped_refptr alignment_pad{}; + std::string device_name{}; + }; + + using ConnectResponse = Response; + + Future Connect(const ConnectRequest& request); + + Future Connect(const Window& window = {}, + const DriverType& driver_type = {}); + + struct AuthenticateRequest { + Window window{}; + uint32_t magic{}; + }; + + struct AuthenticateReply { + uint16_t sequence{}; + uint32_t authenticated{}; + }; + + using AuthenticateResponse = Response; + + Future Authenticate(const AuthenticateRequest& request); + + Future Authenticate(const Window& window = {}, + const uint32_t& magic = {}); + + struct CreateDrawableRequest { + Drawable drawable{}; + }; + + using CreateDrawableResponse = Response; + + Future CreateDrawable(const CreateDrawableRequest& request); + + Future CreateDrawable(const Drawable& drawable = {}); + + struct DestroyDrawableRequest { + Drawable drawable{}; + }; + + using DestroyDrawableResponse = Response; + + Future DestroyDrawable(const DestroyDrawableRequest& request); + + Future DestroyDrawable(const Drawable& drawable = {}); + + struct GetBuffersRequest { + Drawable drawable{}; + uint32_t count{}; + std::vector attachments{}; + }; + + struct GetBuffersReply { + uint16_t sequence{}; + uint32_t width{}; + uint32_t height{}; + std::vector buffers{}; + }; + + using GetBuffersResponse = Response; + + Future GetBuffers(const GetBuffersRequest& request); + + Future GetBuffers( + const Drawable& drawable = {}, + const uint32_t& count = {}, + const std::vector& attachments = {}); + + struct CopyRegionRequest { + Drawable drawable{}; + uint32_t region{}; + uint32_t dest{}; + uint32_t src{}; + }; + + struct CopyRegionReply { + uint16_t sequence{}; + }; + + using CopyRegionResponse = Response; + + Future CopyRegion(const CopyRegionRequest& request); + + Future CopyRegion(const Drawable& drawable = {}, + const uint32_t& region = {}, + const uint32_t& dest = {}, + const uint32_t& src = {}); + + struct GetBuffersWithFormatRequest { + Drawable drawable{}; + uint32_t count{}; + std::vector attachments{}; + }; + + struct GetBuffersWithFormatReply { + uint16_t sequence{}; + uint32_t width{}; + uint32_t height{}; + std::vector buffers{}; + }; + + using GetBuffersWithFormatResponse = Response; + + Future GetBuffersWithFormat( + const GetBuffersWithFormatRequest& request); + + Future GetBuffersWithFormat( + const Drawable& drawable = {}, + const uint32_t& count = {}, + const std::vector& attachments = {}); + + struct SwapBuffersRequest { + Drawable drawable{}; + uint32_t target_msc_hi{}; + uint32_t target_msc_lo{}; + uint32_t divisor_hi{}; + uint32_t divisor_lo{}; + uint32_t remainder_hi{}; + uint32_t remainder_lo{}; + }; + + struct SwapBuffersReply { + uint16_t sequence{}; + uint32_t swap_hi{}; + uint32_t swap_lo{}; + }; + + using SwapBuffersResponse = Response; + + Future SwapBuffers(const SwapBuffersRequest& request); + + Future SwapBuffers(const Drawable& drawable = {}, + const uint32_t& target_msc_hi = {}, + const uint32_t& target_msc_lo = {}, + const uint32_t& divisor_hi = {}, + const uint32_t& divisor_lo = {}, + const uint32_t& remainder_hi = {}, + const uint32_t& remainder_lo = {}); + + struct GetMSCRequest { + Drawable drawable{}; + }; + + struct GetMSCReply { + uint16_t sequence{}; + uint32_t ust_hi{}; + uint32_t ust_lo{}; + uint32_t msc_hi{}; + uint32_t msc_lo{}; + uint32_t sbc_hi{}; + uint32_t sbc_lo{}; + }; + + using GetMSCResponse = Response; + + Future GetMSC(const GetMSCRequest& request); + + Future GetMSC(const Drawable& drawable = {}); + + struct WaitMSCRequest { + Drawable drawable{}; + uint32_t target_msc_hi{}; + uint32_t target_msc_lo{}; + uint32_t divisor_hi{}; + uint32_t divisor_lo{}; + uint32_t remainder_hi{}; + uint32_t remainder_lo{}; + }; + + struct WaitMSCReply { + uint16_t sequence{}; + uint32_t ust_hi{}; + uint32_t ust_lo{}; + uint32_t msc_hi{}; + uint32_t msc_lo{}; + uint32_t sbc_hi{}; + uint32_t sbc_lo{}; + }; + + using WaitMSCResponse = Response; + + Future WaitMSC(const WaitMSCRequest& request); + + Future WaitMSC(const Drawable& drawable = {}, + const uint32_t& target_msc_hi = {}, + const uint32_t& target_msc_lo = {}, + const uint32_t& divisor_hi = {}, + const uint32_t& divisor_lo = {}, + const uint32_t& remainder_hi = {}, + const uint32_t& remainder_lo = {}); + + struct WaitSBCRequest { + Drawable drawable{}; + uint32_t target_sbc_hi{}; + uint32_t target_sbc_lo{}; + }; + + struct WaitSBCReply { + uint16_t sequence{}; + uint32_t ust_hi{}; + uint32_t ust_lo{}; + uint32_t msc_hi{}; + uint32_t msc_lo{}; + uint32_t sbc_hi{}; + uint32_t sbc_lo{}; + }; + + using WaitSBCResponse = Response; + + Future WaitSBC(const WaitSBCRequest& request); + + Future WaitSBC(const Drawable& drawable = {}, + const uint32_t& target_sbc_hi = {}, + const uint32_t& target_sbc_lo = {}); + + struct SwapIntervalRequest { + Drawable drawable{}; + uint32_t interval{}; + }; + + using SwapIntervalResponse = Response; + + Future SwapInterval(const SwapIntervalRequest& request); + + Future SwapInterval(const Drawable& drawable = {}, + const uint32_t& interval = {}); + + struct GetParamRequest { + Drawable drawable{}; + uint32_t param{}; + }; + + struct GetParamReply { + uint8_t is_param_recognized{}; + uint16_t sequence{}; + uint32_t value_hi{}; + uint32_t value_lo{}; + }; + + using GetParamResponse = Response; + + Future GetParam(const GetParamRequest& request); + + Future GetParam(const Drawable& drawable = {}, + const uint32_t& param = {}); + + private: + Connection* const connection_; + x11::QueryExtensionReply info_{}; +}; + +} // namespace x11 + +inline constexpr x11::Dri2::Attachment operator|(x11::Dri2::Attachment l, + x11::Dri2::Attachment r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::Dri2::Attachment operator&(x11::Dri2::Attachment l, + x11::Dri2::Attachment r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +inline constexpr x11::Dri2::DriverType operator|(x11::Dri2::DriverType l, + x11::Dri2::DriverType r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::Dri2::DriverType operator&(x11::Dri2::DriverType l, + x11::Dri2::DriverType r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +inline constexpr x11::Dri2::EventType operator|(x11::Dri2::EventType l, + x11::Dri2::EventType r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::Dri2::EventType operator&(x11::Dri2::EventType l, + x11::Dri2::EventType r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +#endif // UI_GFX_X_GENERATED_PROTOS_DRI2_H_ diff --git a/x/generated_protos/dri3.cc b/x/generated_protos/dri3.cc new file mode 100644 index 000000000000..158cfdf36ccf --- /dev/null +++ b/x/generated_protos/dri3.cc @@ -0,0 +1,855 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#include "dri3.h" + +#include +#include + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "ui/gfx/x/xproto_internal.h" + +namespace x11 { + +Dri3::Dri3(Connection* connection, const x11::QueryExtensionReply& info) + : connection_(connection), info_(info) {} + +Future Dri3::QueryVersion( + const Dri3::QueryVersionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& major_version = request.major_version; + auto& minor_version = request.minor_version; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 0; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // major_version + buf.Write(&major_version); + + // minor_version + buf.Write(&minor_version); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Dri3::QueryVersion", false); +} + +Future Dri3::QueryVersion( + const uint32_t& major_version, + const uint32_t& minor_version) { + return Dri3::QueryVersion( + Dri3::QueryVersionRequest{major_version, minor_version}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Dri3::QueryVersionReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& major_version = (*reply).major_version; + auto& minor_version = (*reply).minor_version; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // major_version + Read(&major_version, &buf); + + // minor_version + Read(&minor_version, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Dri3::Open(const Dri3::OpenRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& drawable = request.drawable; + auto& provider = request.provider; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 1; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // drawable + buf.Write(&drawable); + + // provider + buf.Write(&provider); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Dri3::Open", true); +} + +Future Dri3::Open(const Drawable& drawable, + const uint32_t& provider) { + return Dri3::Open(Dri3::OpenRequest{drawable, provider}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& nfd = (*reply).nfd; + auto& sequence = (*reply).sequence; + auto& device_fd = (*reply).device_fd; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // nfd + Read(&nfd, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // device_fd + device_fd = RefCountedFD(buf.TakeFd()); + + // pad0 + Pad(&buf, 24); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Dri3::PixmapFromBuffer( + const Dri3::PixmapFromBufferRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& pixmap = request.pixmap; + auto& drawable = request.drawable; + auto& size = request.size; + auto& width = request.width; + auto& height = request.height; + auto& stride = request.stride; + auto& depth = request.depth; + auto& bpp = request.bpp; + auto& pixmap_fd = request.pixmap_fd; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 2; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // pixmap + buf.Write(&pixmap); + + // drawable + buf.Write(&drawable); + + // size + buf.Write(&size); + + // width + buf.Write(&width); + + // height + buf.Write(&height); + + // stride + buf.Write(&stride); + + // depth + buf.Write(&depth); + + // bpp + buf.Write(&bpp); + + // pixmap_fd + buf.fds().push_back(HANDLE_EINTR(dup(pixmap_fd.get()))); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Dri3::PixmapFromBuffer", false); +} + +Future Dri3::PixmapFromBuffer(const Pixmap& pixmap, + const Drawable& drawable, + const uint32_t& size, + const uint16_t& width, + const uint16_t& height, + const uint16_t& stride, + const uint8_t& depth, + const uint8_t& bpp, + const RefCountedFD& pixmap_fd) { + return Dri3::PixmapFromBuffer(Dri3::PixmapFromBufferRequest{ + pixmap, drawable, size, width, height, stride, depth, bpp, pixmap_fd}); +} + +Future Dri3::BufferFromPixmap( + const Dri3::BufferFromPixmapRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& pixmap = request.pixmap; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 3; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // pixmap + buf.Write(&pixmap); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Dri3::BufferFromPixmap", true); +} + +Future Dri3::BufferFromPixmap( + const Pixmap& pixmap) { + return Dri3::BufferFromPixmap(Dri3::BufferFromPixmapRequest{pixmap}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Dri3::BufferFromPixmapReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& nfd = (*reply).nfd; + auto& sequence = (*reply).sequence; + auto& size = (*reply).size; + auto& width = (*reply).width; + auto& height = (*reply).height; + auto& stride = (*reply).stride; + auto& depth = (*reply).depth; + auto& bpp = (*reply).bpp; + auto& pixmap_fd = (*reply).pixmap_fd; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // nfd + Read(&nfd, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // size + Read(&size, &buf); + + // width + Read(&width, &buf); + + // height + Read(&height, &buf); + + // stride + Read(&stride, &buf); + + // depth + Read(&depth, &buf); + + // bpp + Read(&bpp, &buf); + + // pixmap_fd + pixmap_fd = RefCountedFD(buf.TakeFd()); + + // pad0 + Pad(&buf, 12); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Dri3::FenceFromFD(const Dri3::FenceFromFDRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& drawable = request.drawable; + auto& fence = request.fence; + auto& initially_triggered = request.initially_triggered; + auto& fence_fd = request.fence_fd; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 4; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // drawable + buf.Write(&drawable); + + // fence + buf.Write(&fence); + + // initially_triggered + buf.Write(&initially_triggered); + + // pad0 + Pad(&buf, 3); + + // fence_fd + buf.fds().push_back(HANDLE_EINTR(dup(fence_fd.get()))); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Dri3::FenceFromFD", false); +} + +Future Dri3::FenceFromFD(const Drawable& drawable, + const uint32_t& fence, + const uint8_t& initially_triggered, + const RefCountedFD& fence_fd) { + return Dri3::FenceFromFD( + Dri3::FenceFromFDRequest{drawable, fence, initially_triggered, fence_fd}); +} + +Future Dri3::FDFromFence( + const Dri3::FDFromFenceRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& drawable = request.drawable; + auto& fence = request.fence; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 5; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // drawable + buf.Write(&drawable); + + // fence + buf.Write(&fence); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Dri3::FDFromFence", true); +} + +Future Dri3::FDFromFence(const Drawable& drawable, + const uint32_t& fence) { + return Dri3::FDFromFence(Dri3::FDFromFenceRequest{drawable, fence}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Dri3::FDFromFenceReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& nfd = (*reply).nfd; + auto& sequence = (*reply).sequence; + auto& fence_fd = (*reply).fence_fd; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // nfd + Read(&nfd, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // fence_fd + fence_fd = RefCountedFD(buf.TakeFd()); + + // pad0 + Pad(&buf, 24); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Dri3::GetSupportedModifiers( + const Dri3::GetSupportedModifiersRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + auto& depth = request.depth; + auto& bpp = request.bpp; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 6; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + // depth + buf.Write(&depth); + + // bpp + buf.Write(&bpp); + + // pad0 + Pad(&buf, 2); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Dri3::GetSupportedModifiers", false); +} + +Future Dri3::GetSupportedModifiers( + const uint32_t& window, + const uint8_t& depth, + const uint8_t& bpp) { + return Dri3::GetSupportedModifiers( + Dri3::GetSupportedModifiersRequest{window, depth, bpp}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Dri3::GetSupportedModifiersReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t num_window_modifiers{}; + uint32_t num_screen_modifiers{}; + auto& window_modifiers = (*reply).window_modifiers; + size_t window_modifiers_len = window_modifiers.size(); + auto& screen_modifiers = (*reply).screen_modifiers; + size_t screen_modifiers_len = screen_modifiers.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // num_window_modifiers + Read(&num_window_modifiers, &buf); + + // num_screen_modifiers + Read(&num_screen_modifiers, &buf); + + // pad1 + Pad(&buf, 16); + + // window_modifiers + window_modifiers.resize(num_window_modifiers); + for (auto& window_modifiers_elem : window_modifiers) { + // window_modifiers_elem + Read(&window_modifiers_elem, &buf); + } + + // screen_modifiers + screen_modifiers.resize(num_screen_modifiers); + for (auto& screen_modifiers_elem : screen_modifiers) { + // screen_modifiers_elem + Read(&screen_modifiers_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Dri3::PixmapFromBuffers( + const Dri3::PixmapFromBuffersRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& pixmap = request.pixmap; + auto& window = request.window; + uint8_t num_buffers{}; + auto& width = request.width; + auto& height = request.height; + auto& stride0 = request.stride0; + auto& offset0 = request.offset0; + auto& stride1 = request.stride1; + auto& offset1 = request.offset1; + auto& stride2 = request.stride2; + auto& offset2 = request.offset2; + auto& stride3 = request.stride3; + auto& offset3 = request.offset3; + auto& depth = request.depth; + auto& bpp = request.bpp; + auto& modifier = request.modifier; + auto& buffers = request.buffers; + size_t buffers_len = buffers.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 7; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // pixmap + buf.Write(&pixmap); + + // window + buf.Write(&window); + + // num_buffers + num_buffers = buffers.size(); + buf.Write(&num_buffers); + + // pad0 + Pad(&buf, 3); + + // width + buf.Write(&width); + + // height + buf.Write(&height); + + // stride0 + buf.Write(&stride0); + + // offset0 + buf.Write(&offset0); + + // stride1 + buf.Write(&stride1); + + // offset1 + buf.Write(&offset1); + + // stride2 + buf.Write(&stride2); + + // offset2 + buf.Write(&offset2); + + // stride3 + buf.Write(&stride3); + + // offset3 + buf.Write(&offset3); + + // depth + buf.Write(&depth); + + // bpp + buf.Write(&bpp); + + // pad1 + Pad(&buf, 2); + + // modifier + buf.Write(&modifier); + + // buffers + DCHECK_EQ(static_cast(num_buffers), buffers.size()); + for (auto& buffers_elem : buffers) { + // buffers_elem + buf.fds().push_back(HANDLE_EINTR(dup(buffers_elem.get()))); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Dri3::PixmapFromBuffers", false); +} + +Future Dri3::PixmapFromBuffers(const Pixmap& pixmap, + const Window& window, + const uint16_t& width, + const uint16_t& height, + const uint32_t& stride0, + const uint32_t& offset0, + const uint32_t& stride1, + const uint32_t& offset1, + const uint32_t& stride2, + const uint32_t& offset2, + const uint32_t& stride3, + const uint32_t& offset3, + const uint8_t& depth, + const uint8_t& bpp, + const uint64_t& modifier, + const std::vector& buffers) { + return Dri3::PixmapFromBuffers(Dri3::PixmapFromBuffersRequest{ + pixmap, window, width, height, stride0, offset0, stride1, offset1, + stride2, offset2, stride3, offset3, depth, bpp, modifier, buffers}); +} + +Future Dri3::BuffersFromPixmap( + const Dri3::BuffersFromPixmapRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& pixmap = request.pixmap; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 8; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // pixmap + buf.Write(&pixmap); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Dri3::BuffersFromPixmap", true); +} + +Future Dri3::BuffersFromPixmap( + const Pixmap& pixmap) { + return Dri3::BuffersFromPixmap(Dri3::BuffersFromPixmapRequest{pixmap}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Dri3::BuffersFromPixmapReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + uint8_t nfd{}; + auto& sequence = (*reply).sequence; + auto& width = (*reply).width; + auto& height = (*reply).height; + auto& modifier = (*reply).modifier; + auto& depth = (*reply).depth; + auto& bpp = (*reply).bpp; + auto& strides = (*reply).strides; + size_t strides_len = strides.size(); + auto& offsets = (*reply).offsets; + size_t offsets_len = offsets.size(); + auto& buffers = (*reply).buffers; + size_t buffers_len = buffers.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // nfd + Read(&nfd, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // width + Read(&width, &buf); + + // height + Read(&height, &buf); + + // pad0 + Pad(&buf, 4); + + // modifier + Read(&modifier, &buf); + + // depth + Read(&depth, &buf); + + // bpp + Read(&bpp, &buf); + + // pad1 + Pad(&buf, 6); + + // strides + strides.resize(nfd); + for (auto& strides_elem : strides) { + // strides_elem + Read(&strides_elem, &buf); + } + + // offsets + offsets.resize(nfd); + for (auto& offsets_elem : offsets) { + // offsets_elem + Read(&offsets_elem, &buf); + } + + // buffers + buffers.resize(nfd); + for (auto& buffers_elem : buffers) { + // buffers_elem + buffers_elem = RefCountedFD(buf.TakeFd()); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +} // namespace x11 diff --git a/x/generated_protos/dri3.h b/x/generated_protos/dri3.h new file mode 100644 index 000000000000..9fda8e3e458f --- /dev/null +++ b/x/generated_protos/dri3.h @@ -0,0 +1,294 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#ifndef UI_GFX_X_GENERATED_PROTOS_DRI3_H_ +#define UI_GFX_X_GENERATED_PROTOS_DRI3_H_ + +#include +#include +#include +#include +#include + +#include "base/component_export.h" +#include "base/files/scoped_file.h" +#include "base/memory/ref_counted_memory.h" +#include "base/memory/scoped_refptr.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/gfx/x/error.h" +#include "ui/gfx/x/ref_counted_fd.h" +#include "xproto.h" + +namespace x11 { + +class Connection; + +template +struct Response; + +template +class Future; + +class COMPONENT_EXPORT(X11) Dri3 { + public: + static constexpr unsigned major_version = 1; + static constexpr unsigned minor_version = 2; + + Dri3(Connection* connection, const x11::QueryExtensionReply& info); + + uint8_t present() const { return info_.present; } + uint8_t major_opcode() const { return info_.major_opcode; } + uint8_t first_event() const { return info_.first_event; } + uint8_t first_error() const { return info_.first_error; } + + Connection* connection() const { return connection_; } + + struct QueryVersionRequest { + uint32_t major_version{}; + uint32_t minor_version{}; + }; + + struct QueryVersionReply { + uint16_t sequence{}; + uint32_t major_version{}; + uint32_t minor_version{}; + }; + + using QueryVersionResponse = Response; + + Future QueryVersion(const QueryVersionRequest& request); + + Future QueryVersion(const uint32_t& major_version = {}, + const uint32_t& minor_version = {}); + + struct OpenRequest { + Drawable drawable{}; + uint32_t provider{}; + }; + + struct OpenReply { + uint8_t nfd{}; + uint16_t sequence{}; + RefCountedFD device_fd{}; + }; + + using OpenResponse = Response; + + Future Open(const OpenRequest& request); + + Future Open(const Drawable& drawable = {}, + const uint32_t& provider = {}); + + struct PixmapFromBufferRequest { + Pixmap pixmap{}; + Drawable drawable{}; + uint32_t size{}; + uint16_t width{}; + uint16_t height{}; + uint16_t stride{}; + uint8_t depth{}; + uint8_t bpp{}; + RefCountedFD pixmap_fd{}; + }; + + using PixmapFromBufferResponse = Response; + + Future PixmapFromBuffer(const PixmapFromBufferRequest& request); + + Future PixmapFromBuffer(const Pixmap& pixmap = {}, + const Drawable& drawable = {}, + const uint32_t& size = {}, + const uint16_t& width = {}, + const uint16_t& height = {}, + const uint16_t& stride = {}, + const uint8_t& depth = {}, + const uint8_t& bpp = {}, + const RefCountedFD& pixmap_fd = {}); + + struct BufferFromPixmapRequest { + Pixmap pixmap{}; + }; + + struct BufferFromPixmapReply { + uint8_t nfd{}; + uint16_t sequence{}; + uint32_t size{}; + uint16_t width{}; + uint16_t height{}; + uint16_t stride{}; + uint8_t depth{}; + uint8_t bpp{}; + RefCountedFD pixmap_fd{}; + }; + + using BufferFromPixmapResponse = Response; + + Future BufferFromPixmap( + const BufferFromPixmapRequest& request); + + Future BufferFromPixmap(const Pixmap& pixmap = {}); + + struct FenceFromFDRequest { + Drawable drawable{}; + uint32_t fence{}; + uint8_t initially_triggered{}; + RefCountedFD fence_fd{}; + }; + + using FenceFromFDResponse = Response; + + Future FenceFromFD(const FenceFromFDRequest& request); + + Future FenceFromFD(const Drawable& drawable = {}, + const uint32_t& fence = {}, + const uint8_t& initially_triggered = {}, + const RefCountedFD& fence_fd = {}); + + struct FDFromFenceRequest { + Drawable drawable{}; + uint32_t fence{}; + }; + + struct FDFromFenceReply { + uint8_t nfd{}; + uint16_t sequence{}; + RefCountedFD fence_fd{}; + }; + + using FDFromFenceResponse = Response; + + Future FDFromFence(const FDFromFenceRequest& request); + + Future FDFromFence(const Drawable& drawable = {}, + const uint32_t& fence = {}); + + struct GetSupportedModifiersRequest { + uint32_t window{}; + uint8_t depth{}; + uint8_t bpp{}; + }; + + struct GetSupportedModifiersReply { + uint16_t sequence{}; + std::vector window_modifiers{}; + std::vector screen_modifiers{}; + }; + + using GetSupportedModifiersResponse = Response; + + Future GetSupportedModifiers( + const GetSupportedModifiersRequest& request); + + Future GetSupportedModifiers( + const uint32_t& window = {}, + const uint8_t& depth = {}, + const uint8_t& bpp = {}); + + struct PixmapFromBuffersRequest { + Pixmap pixmap{}; + Window window{}; + uint16_t width{}; + uint16_t height{}; + uint32_t stride0{}; + uint32_t offset0{}; + uint32_t stride1{}; + uint32_t offset1{}; + uint32_t stride2{}; + uint32_t offset2{}; + uint32_t stride3{}; + uint32_t offset3{}; + uint8_t depth{}; + uint8_t bpp{}; + uint64_t modifier{}; + std::vector buffers{}; + }; + + using PixmapFromBuffersResponse = Response; + + Future PixmapFromBuffers(const PixmapFromBuffersRequest& request); + + Future PixmapFromBuffers(const Pixmap& pixmap = {}, + const Window& window = {}, + const uint16_t& width = {}, + const uint16_t& height = {}, + const uint32_t& stride0 = {}, + const uint32_t& offset0 = {}, + const uint32_t& stride1 = {}, + const uint32_t& offset1 = {}, + const uint32_t& stride2 = {}, + const uint32_t& offset2 = {}, + const uint32_t& stride3 = {}, + const uint32_t& offset3 = {}, + const uint8_t& depth = {}, + const uint8_t& bpp = {}, + const uint64_t& modifier = {}, + const std::vector& buffers = {}); + + struct BuffersFromPixmapRequest { + Pixmap pixmap{}; + }; + + struct BuffersFromPixmapReply { + uint16_t sequence{}; + uint16_t width{}; + uint16_t height{}; + uint64_t modifier{}; + uint8_t depth{}; + uint8_t bpp{}; + std::vector strides{}; + std::vector offsets{}; + std::vector buffers{}; + }; + + using BuffersFromPixmapResponse = Response; + + Future BuffersFromPixmap( + const BuffersFromPixmapRequest& request); + + Future BuffersFromPixmap(const Pixmap& pixmap = {}); + + private: + Connection* const connection_; + x11::QueryExtensionReply info_{}; +}; + +} // namespace x11 + +#endif // UI_GFX_X_GENERATED_PROTOS_DRI3_H_ diff --git a/x/generated_protos/extension_manager.cc b/x/generated_protos/extension_manager.cc new file mode 100644 index 000000000000..5c680e815e15 --- /dev/null +++ b/x/generated_protos/extension_manager.cc @@ -0,0 +1,149 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#include "ui/gfx/x/extension_manager.h" + +#include "ui/gfx/x/bigreq.h" +#include "ui/gfx/x/composite.h" +#include "ui/gfx/x/connection.h" +#include "ui/gfx/x/damage.h" +#include "ui/gfx/x/dpms.h" +#include "ui/gfx/x/dri2.h" +#include "ui/gfx/x/dri3.h" +#include "ui/gfx/x/ge.h" +#include "ui/gfx/x/glx.h" +#include "ui/gfx/x/present.h" +#include "ui/gfx/x/randr.h" +#include "ui/gfx/x/record.h" +#include "ui/gfx/x/render.h" +#include "ui/gfx/x/res.h" +#include "ui/gfx/x/screensaver.h" +#include "ui/gfx/x/shape.h" +#include "ui/gfx/x/shm.h" +#include "ui/gfx/x/sync.h" +#include "ui/gfx/x/xc_misc.h" +#include "ui/gfx/x/xevie.h" +#include "ui/gfx/x/xf86dri.h" +#include "ui/gfx/x/xf86vidmode.h" +#include "ui/gfx/x/xfixes.h" +#include "ui/gfx/x/xinerama.h" +#include "ui/gfx/x/xinput.h" +#include "ui/gfx/x/xkb.h" +#include "ui/gfx/x/xprint.h" +#include "ui/gfx/x/xproto.h" +#include "ui/gfx/x/xproto_internal.h" +#include "ui/gfx/x/xselinux.h" +#include "ui/gfx/x/xtest.h" +#include "ui/gfx/x/xv.h" +#include "ui/gfx/x/xvmc.h" + +namespace x11 { + +void ExtensionManager::Init(Connection* conn) { + auto bigreq_future = conn->QueryExtension("BIG-REQUESTS"); + auto composite_future = conn->QueryExtension("Composite"); + auto damage_future = conn->QueryExtension("DAMAGE"); + auto dpms_future = conn->QueryExtension("DPMS"); + auto dri2_future = conn->QueryExtension("DRI2"); + auto dri3_future = conn->QueryExtension("DRI3"); + auto ge_future = conn->QueryExtension("Generic Event Extension"); + auto glx_future = conn->QueryExtension("GLX"); + auto present_future = conn->QueryExtension("Present"); + auto randr_future = conn->QueryExtension("RANDR"); + auto record_future = conn->QueryExtension("RECORD"); + auto render_future = conn->QueryExtension("RENDER"); + auto res_future = conn->QueryExtension("X-Resource"); + auto screensaver_future = conn->QueryExtension("MIT-SCREEN-SAVER"); + auto shape_future = conn->QueryExtension("SHAPE"); + auto shm_future = conn->QueryExtension("MIT-SHM"); + auto sync_future = conn->QueryExtension("SYNC"); + auto xc_misc_future = conn->QueryExtension("XC-MISC"); + auto xevie_future = conn->QueryExtension("XEVIE"); + auto xf86dri_future = conn->QueryExtension("XFree86-DRI"); + auto xf86vidmode_future = conn->QueryExtension("XFree86-VidModeExtension"); + auto xfixes_future = conn->QueryExtension("XFIXES"); + auto xinerama_future = conn->QueryExtension("XINERAMA"); + auto xinput_future = conn->QueryExtension("XInputExtension"); + auto xkb_future = conn->QueryExtension("XKEYBOARD"); + auto xprint_future = conn->QueryExtension("XpExtension"); + auto xselinux_future = conn->QueryExtension("SELinux"); + auto xtest_future = conn->QueryExtension("XTEST"); + auto xv_future = conn->QueryExtension("XVideo"); + auto xvmc_future = conn->QueryExtension("XVideo-MotionCompensation"); + conn->Flush(); + + bigreq_ = MakeExtension(conn, std::move(bigreq_future)); + composite_ = MakeExtension(conn, std::move(composite_future)); + damage_ = MakeExtension(conn, std::move(damage_future)); + dpms_ = MakeExtension(conn, std::move(dpms_future)); + dri2_ = MakeExtension(conn, std::move(dri2_future)); + dri3_ = MakeExtension(conn, std::move(dri3_future)); + ge_ = MakeExtension(conn, std::move(ge_future)); + glx_ = MakeExtension(conn, std::move(glx_future)); + present_ = MakeExtension(conn, std::move(present_future)); + randr_ = MakeExtension(conn, std::move(randr_future)); + record_ = MakeExtension(conn, std::move(record_future)); + render_ = MakeExtension(conn, std::move(render_future)); + res_ = MakeExtension(conn, std::move(res_future)); + screensaver_ = + MakeExtension(conn, std::move(screensaver_future)); + shape_ = MakeExtension(conn, std::move(shape_future)); + shm_ = MakeExtension(conn, std::move(shm_future)); + sync_ = MakeExtension(conn, std::move(sync_future)); + xc_misc_ = MakeExtension(conn, std::move(xc_misc_future)); + xevie_ = MakeExtension(conn, std::move(xevie_future)); + xf86dri_ = MakeExtension(conn, std::move(xf86dri_future)); + xf86vidmode_ = + MakeExtension(conn, std::move(xf86vidmode_future)); + xfixes_ = MakeExtension(conn, std::move(xfixes_future)); + xinerama_ = MakeExtension(conn, std::move(xinerama_future)); + xinput_ = MakeExtension(conn, std::move(xinput_future)); + xkb_ = MakeExtension(conn, std::move(xkb_future)); + xprint_ = MakeExtension(conn, std::move(xprint_future)); + xselinux_ = MakeExtension(conn, std::move(xselinux_future)); + xtest_ = MakeExtension(conn, std::move(xtest_future)); + xv_ = MakeExtension(conn, std::move(xv_future)); + xvmc_ = MakeExtension(conn, std::move(xvmc_future)); +} + +ExtensionManager::ExtensionManager() = default; +ExtensionManager::~ExtensionManager() = default; + +} // namespace x11 diff --git a/x/generated_protos/extension_manager.h b/x/generated_protos/extension_manager.h new file mode 100644 index 000000000000..625301a8bd23 --- /dev/null +++ b/x/generated_protos/extension_manager.h @@ -0,0 +1,158 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#ifndef UI_GFX_X_GENERATED_PROTOS_EXTENSION_MANAGER_H_ +#define UI_GFX_X_GENERATED_PROTOS_EXTENSION_MANAGER_H_ + +#include + +#include "base/component_export.h" + +namespace x11 { + +class Connection; + +class BigRequests; +class Composite; +class Damage; +class Dpms; +class Dri2; +class Dri3; +class GenericEvent; +class Glx; +class Present; +class RandR; +class Record; +class Render; +class Res; +class ScreenSaver; +class Shape; +class Shm; +class Sync; +class XCMisc; +class Xevie; +class XF86Dri; +class XF86VidMode; +class XFixes; +class Xinerama; +class Input; +class Xkb; +class XPrint; +class XProto; +class SELinux; +class Test; +class Xv; +class XvMC; + +class COMPONENT_EXPORT(X11) ExtensionManager { + public: + ExtensionManager(); + ~ExtensionManager(); + + BigRequests& bigreq() { return *bigreq_; } + Composite& composite() { return *composite_; } + Damage& damage() { return *damage_; } + Dpms& dpms() { return *dpms_; } + Dri2& dri2() { return *dri2_; } + Dri3& dri3() { return *dri3_; } + GenericEvent& ge() { return *ge_; } + Glx& glx() { return *glx_; } + Present& present() { return *present_; } + RandR& randr() { return *randr_; } + Record& record() { return *record_; } + Render& render() { return *render_; } + Res& res() { return *res_; } + ScreenSaver& screensaver() { return *screensaver_; } + Shape& shape() { return *shape_; } + Shm& shm() { return *shm_; } + Sync& sync() { return *sync_; } + XCMisc& xc_misc() { return *xc_misc_; } + Xevie& xevie() { return *xevie_; } + XF86Dri& xf86dri() { return *xf86dri_; } + XF86VidMode& xf86vidmode() { return *xf86vidmode_; } + XFixes& xfixes() { return *xfixes_; } + Xinerama& xinerama() { return *xinerama_; } + Input& xinput() { return *xinput_; } + Xkb& xkb() { return *xkb_; } + XPrint& xprint() { return *xprint_; } + SELinux& xselinux() { return *xselinux_; } + Test& xtest() { return *xtest_; } + Xv& xv() { return *xv_; } + XvMC& xvmc() { return *xvmc_; } + + protected: + void Init(Connection* conn); + + private: + std::unique_ptr bigreq_; + std::unique_ptr composite_; + std::unique_ptr damage_; + std::unique_ptr dpms_; + std::unique_ptr dri2_; + std::unique_ptr dri3_; + std::unique_ptr ge_; + std::unique_ptr glx_; + std::unique_ptr present_; + std::unique_ptr randr_; + std::unique_ptr record_; + std::unique_ptr render_; + std::unique_ptr res_; + std::unique_ptr screensaver_; + std::unique_ptr shape_; + std::unique_ptr shm_; + std::unique_ptr sync_; + std::unique_ptr xc_misc_; + std::unique_ptr xevie_; + std::unique_ptr xf86dri_; + std::unique_ptr xf86vidmode_; + std::unique_ptr xfixes_; + std::unique_ptr xinerama_; + std::unique_ptr xinput_; + std::unique_ptr xkb_; + std::unique_ptr xprint_; + std::unique_ptr xselinux_; + std::unique_ptr xtest_; + std::unique_ptr xv_; + std::unique_ptr xvmc_; +}; + +} // namespace x11 + +#endif // UI_GFX_X_GENERATED_PROTOS_EXTENSION_MANAGER_H_ diff --git a/x/generated_protos/ge.cc b/x/generated_protos/ge.cc new file mode 100644 index 000000000000..8afc628b363b --- /dev/null +++ b/x/generated_protos/ge.cc @@ -0,0 +1,137 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#include "ge.h" + +#include +#include + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "ui/gfx/x/xproto_internal.h" + +namespace x11 { + +GenericEvent::GenericEvent(Connection* connection, + const x11::QueryExtensionReply& info) + : connection_(connection), info_(info) {} + +Future GenericEvent::QueryVersion( + const GenericEvent::QueryVersionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& client_major_version = request.client_major_version; + auto& client_minor_version = request.client_minor_version; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 0; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // client_major_version + buf.Write(&client_major_version); + + // client_minor_version + buf.Write(&client_minor_version); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "GenericEvent::QueryVersion", false); +} + +Future GenericEvent::QueryVersion( + const uint16_t& client_major_version, + const uint16_t& client_minor_version) { + return GenericEvent::QueryVersion(GenericEvent::QueryVersionRequest{ + client_major_version, client_minor_version}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + GenericEvent::QueryVersionReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& major_version = (*reply).major_version; + auto& minor_version = (*reply).minor_version; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // major_version + Read(&major_version, &buf); + + // minor_version + Read(&minor_version, &buf); + + // pad1 + Pad(&buf, 20); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +} // namespace x11 diff --git a/x/generated_protos/ge.h b/x/generated_protos/ge.h new file mode 100644 index 000000000000..5e36176f067a --- /dev/null +++ b/x/generated_protos/ge.h @@ -0,0 +1,107 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#ifndef UI_GFX_X_GENERATED_PROTOS_GE_H_ +#define UI_GFX_X_GENERATED_PROTOS_GE_H_ + +#include +#include +#include + +#include "base/component_export.h" +#include "base/files/scoped_file.h" +#include "base/memory/ref_counted_memory.h" +#include "base/memory/scoped_refptr.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/gfx/x/error.h" +#include "ui/gfx/x/ref_counted_fd.h" +#include "xproto.h" + +namespace x11 { + +class Connection; + +template +struct Response; + +template +class Future; + +class COMPONENT_EXPORT(X11) GenericEvent { + public: + static constexpr unsigned major_version = 1; + static constexpr unsigned minor_version = 0; + + GenericEvent(Connection* connection, const x11::QueryExtensionReply& info); + + uint8_t present() const { return info_.present; } + uint8_t major_opcode() const { return info_.major_opcode; } + uint8_t first_event() const { return info_.first_event; } + uint8_t first_error() const { return info_.first_error; } + + Connection* connection() const { return connection_; } + + struct QueryVersionRequest { + uint16_t client_major_version{}; + uint16_t client_minor_version{}; + }; + + struct QueryVersionReply { + uint16_t sequence{}; + uint16_t major_version{}; + uint16_t minor_version{}; + }; + + using QueryVersionResponse = Response; + + Future QueryVersion(const QueryVersionRequest& request); + + Future QueryVersion( + const uint16_t& client_major_version = {}, + const uint16_t& client_minor_version = {}); + + private: + Connection* const connection_; + x11::QueryExtensionReply info_{}; +}; + +} // namespace x11 + +#endif // UI_GFX_X_GENERATED_PROTOS_GE_H_ diff --git a/x/generated_protos/glx.cc b/x/generated_protos/glx.cc new file mode 100644 index 000000000000..3ae9c18c0bfb --- /dev/null +++ b/x/generated_protos/glx.cc @@ -0,0 +1,8598 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#include "glx.h" + +#include +#include + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "ui/gfx/x/xproto_internal.h" + +namespace x11 { + +Glx::Glx(Connection* connection, const x11::QueryExtensionReply& info) + : connection_(connection), info_(info) {} + +std::string Glx::GenericError::ToString() const { + std::stringstream ss_; + ss_ << "Glx::GenericError{"; + ss_ << ".sequence = " << static_cast(sequence) << ", "; + ss_ << ".bad_value = " << static_cast(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast(major_opcode); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError(Glx::GenericError* error_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + + // pad0 + Pad(&buf, 21); + + DCHECK_LE(buf.offset, 32ul); +} +std::string Glx::BadContextError::ToString() const { + std::stringstream ss_; + ss_ << "Glx::BadContextError{"; + ss_ << ".sequence = " << static_cast(sequence) << ", "; + ss_ << ".bad_value = " << static_cast(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast(major_opcode); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError(Glx::BadContextError* error_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + + // pad0 + Pad(&buf, 21); + + DCHECK_LE(buf.offset, 32ul); +} +std::string Glx::BadContextStateError::ToString() const { + std::stringstream ss_; + ss_ << "Glx::BadContextStateError{"; + ss_ << ".sequence = " << static_cast(sequence) << ", "; + ss_ << ".bad_value = " << static_cast(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast(major_opcode); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError(Glx::BadContextStateError* error_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + + // pad0 + Pad(&buf, 21); + + DCHECK_LE(buf.offset, 32ul); +} +std::string Glx::BadDrawableError::ToString() const { + std::stringstream ss_; + ss_ << "Glx::BadDrawableError{"; + ss_ << ".sequence = " << static_cast(sequence) << ", "; + ss_ << ".bad_value = " << static_cast(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast(major_opcode); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError(Glx::BadDrawableError* error_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + + // pad0 + Pad(&buf, 21); + + DCHECK_LE(buf.offset, 32ul); +} +std::string Glx::BadPixmapError::ToString() const { + std::stringstream ss_; + ss_ << "Glx::BadPixmapError{"; + ss_ << ".sequence = " << static_cast(sequence) << ", "; + ss_ << ".bad_value = " << static_cast(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast(major_opcode); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError(Glx::BadPixmapError* error_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + + // pad0 + Pad(&buf, 21); + + DCHECK_LE(buf.offset, 32ul); +} +std::string Glx::BadContextTagError::ToString() const { + std::stringstream ss_; + ss_ << "Glx::BadContextTagError{"; + ss_ << ".sequence = " << static_cast(sequence) << ", "; + ss_ << ".bad_value = " << static_cast(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast(major_opcode); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError(Glx::BadContextTagError* error_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + + // pad0 + Pad(&buf, 21); + + DCHECK_LE(buf.offset, 32ul); +} +std::string Glx::BadCurrentWindowError::ToString() const { + std::stringstream ss_; + ss_ << "Glx::BadCurrentWindowError{"; + ss_ << ".sequence = " << static_cast(sequence) << ", "; + ss_ << ".bad_value = " << static_cast(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast(major_opcode); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError(Glx::BadCurrentWindowError* error_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + + // pad0 + Pad(&buf, 21); + + DCHECK_LE(buf.offset, 32ul); +} +std::string Glx::BadRenderRequestError::ToString() const { + std::stringstream ss_; + ss_ << "Glx::BadRenderRequestError{"; + ss_ << ".sequence = " << static_cast(sequence) << ", "; + ss_ << ".bad_value = " << static_cast(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast(major_opcode); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError(Glx::BadRenderRequestError* error_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + + // pad0 + Pad(&buf, 21); + + DCHECK_LE(buf.offset, 32ul); +} +std::string Glx::BadLargeRequestError::ToString() const { + std::stringstream ss_; + ss_ << "Glx::BadLargeRequestError{"; + ss_ << ".sequence = " << static_cast(sequence) << ", "; + ss_ << ".bad_value = " << static_cast(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast(major_opcode); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError(Glx::BadLargeRequestError* error_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + + // pad0 + Pad(&buf, 21); + + DCHECK_LE(buf.offset, 32ul); +} +std::string Glx::UnsupportedPrivateRequestError::ToString() const { + std::stringstream ss_; + ss_ << "Glx::UnsupportedPrivateRequestError{"; + ss_ << ".sequence = " << static_cast(sequence) << ", "; + ss_ << ".bad_value = " << static_cast(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast(major_opcode); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError( + Glx::UnsupportedPrivateRequestError* error_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + + // pad0 + Pad(&buf, 21); + + DCHECK_LE(buf.offset, 32ul); +} +std::string Glx::BadFBConfigError::ToString() const { + std::stringstream ss_; + ss_ << "Glx::BadFBConfigError{"; + ss_ << ".sequence = " << static_cast(sequence) << ", "; + ss_ << ".bad_value = " << static_cast(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast(major_opcode); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError(Glx::BadFBConfigError* error_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + + // pad0 + Pad(&buf, 21); + + DCHECK_LE(buf.offset, 32ul); +} +std::string Glx::BadPbufferError::ToString() const { + std::stringstream ss_; + ss_ << "Glx::BadPbufferError{"; + ss_ << ".sequence = " << static_cast(sequence) << ", "; + ss_ << ".bad_value = " << static_cast(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast(major_opcode); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError(Glx::BadPbufferError* error_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + + // pad0 + Pad(&buf, 21); + + DCHECK_LE(buf.offset, 32ul); +} +std::string Glx::BadCurrentDrawableError::ToString() const { + std::stringstream ss_; + ss_ << "Glx::BadCurrentDrawableError{"; + ss_ << ".sequence = " << static_cast(sequence) << ", "; + ss_ << ".bad_value = " << static_cast(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast(major_opcode); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError( + Glx::BadCurrentDrawableError* error_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + + // pad0 + Pad(&buf, 21); + + DCHECK_LE(buf.offset, 32ul); +} +std::string Glx::BadWindowError::ToString() const { + std::stringstream ss_; + ss_ << "Glx::BadWindowError{"; + ss_ << ".sequence = " << static_cast(sequence) << ", "; + ss_ << ".bad_value = " << static_cast(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast(major_opcode); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError(Glx::BadWindowError* error_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + + // pad0 + Pad(&buf, 21); + + DCHECK_LE(buf.offset, 32ul); +} +std::string Glx::GLXBadProfileARBError::ToString() const { + std::stringstream ss_; + ss_ << "Glx::GLXBadProfileARBError{"; + ss_ << ".sequence = " << static_cast(sequence) << ", "; + ss_ << ".bad_value = " << static_cast(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast(major_opcode); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError(Glx::GLXBadProfileARBError* error_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + + // pad0 + Pad(&buf, 21); + + DCHECK_LE(buf.offset, 32ul); +} +template <> +COMPONENT_EXPORT(X11) +void ReadEvent(Glx::PbufferClobberEvent* event_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*event_).sequence; + auto& event_type = (*event_).event_type; + auto& draw_type = (*event_).draw_type; + auto& drawable = (*event_).drawable; + auto& b_mask = (*event_).b_mask; + auto& aux_buffer = (*event_).aux_buffer; + auto& x = (*event_).x; + auto& y = (*event_).y; + auto& width = (*event_).width; + auto& height = (*event_).height; + auto& count = (*event_).count; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // event_type + Read(&event_type, &buf); + + // draw_type + Read(&draw_type, &buf); + + // drawable + Read(&drawable, &buf); + + // b_mask + Read(&b_mask, &buf); + + // aux_buffer + Read(&aux_buffer, &buf); + + // x + Read(&x, &buf); + + // y + Read(&y, &buf); + + // width + Read(&width, &buf); + + // height + Read(&height, &buf); + + // count + Read(&count, &buf); + + // pad1 + Pad(&buf, 4); + + DCHECK_LE(buf.offset, 32ul); +} + +template <> +COMPONENT_EXPORT(X11) +void ReadEvent( + Glx::BufferSwapCompleteEvent* event_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*event_).sequence; + auto& event_type = (*event_).event_type; + auto& drawable = (*event_).drawable; + auto& ust_hi = (*event_).ust_hi; + auto& ust_lo = (*event_).ust_lo; + auto& msc_hi = (*event_).msc_hi; + auto& msc_lo = (*event_).msc_lo; + auto& sbc = (*event_).sbc; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // event_type + Read(&event_type, &buf); + + // pad1 + Pad(&buf, 2); + + // drawable + Read(&drawable, &buf); + + // ust_hi + Read(&ust_hi, &buf); + + // ust_lo + Read(&ust_lo, &buf); + + // msc_hi + Read(&msc_hi, &buf); + + // msc_lo + Read(&msc_lo, &buf); + + // sbc + Read(&sbc, &buf); + + DCHECK_LE(buf.offset, 32ul); +} + +Future Glx::Render(const Glx::RenderRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& data = request.data; + size_t data_len = data.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 1; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // data + DCHECK_EQ(static_cast(data_len), data.size()); + for (auto& data_elem : data) { + // data_elem + buf.Write(&data_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::Render", false); +} + +Future Glx::Render(const ContextTag& context_tag, + const std::vector& data) { + return Glx::Render(Glx::RenderRequest{context_tag, data}); +} + +Future Glx::RenderLarge(const Glx::RenderLargeRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& request_num = request.request_num; + auto& request_total = request.request_total; + uint32_t data_len{}; + auto& data = request.data; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 2; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // request_num + buf.Write(&request_num); + + // request_total + buf.Write(&request_total); + + // data_len + data_len = data.size(); + buf.Write(&data_len); + + // data + DCHECK_EQ(static_cast(data_len), data.size()); + for (auto& data_elem : data) { + // data_elem + buf.Write(&data_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::RenderLarge", false); +} + +Future Glx::RenderLarge(const ContextTag& context_tag, + const uint16_t& request_num, + const uint16_t& request_total, + const std::vector& data) { + return Glx::RenderLarge( + Glx::RenderLargeRequest{context_tag, request_num, request_total, data}); +} + +Future Glx::CreateContext(const Glx::CreateContextRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context = request.context; + auto& visual = request.visual; + auto& screen = request.screen; + auto& share_list = request.share_list; + auto& is_direct = request.is_direct; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 3; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context + buf.Write(&context); + + // visual + buf.Write(&visual); + + // screen + buf.Write(&screen); + + // share_list + buf.Write(&share_list); + + // is_direct + buf.Write(&is_direct); + + // pad0 + Pad(&buf, 3); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::CreateContext", false); +} + +Future Glx::CreateContext(const Context& context, + const VisualId& visual, + const uint32_t& screen, + const Context& share_list, + const uint8_t& is_direct) { + return Glx::CreateContext(Glx::CreateContextRequest{context, visual, screen, + share_list, is_direct}); +} + +Future Glx::DestroyContext(const Glx::DestroyContextRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context = request.context; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 4; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context + buf.Write(&context); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::DestroyContext", false); +} + +Future Glx::DestroyContext(const Context& context) { + return Glx::DestroyContext(Glx::DestroyContextRequest{context}); +} + +Future Glx::MakeCurrent( + const Glx::MakeCurrentRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& drawable = request.drawable; + auto& context = request.context; + auto& old_context_tag = request.old_context_tag; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 5; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // drawable + buf.Write(&drawable); + + // context + buf.Write(&context); + + // old_context_tag + buf.Write(&old_context_tag); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::MakeCurrent", false); +} + +Future Glx::MakeCurrent( + const Drawable& drawable, + const Context& context, + const ContextTag& old_context_tag) { + return Glx::MakeCurrent( + Glx::MakeCurrentRequest{drawable, context, old_context_tag}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& context_tag = (*reply).context_tag; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // context_tag + Read(&context_tag, &buf); + + // pad1 + Pad(&buf, 20); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::IsDirect(const Glx::IsDirectRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context = request.context; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 6; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context + buf.Write(&context); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::IsDirect", + false); +} + +Future Glx::IsDirect(const Context& context) { + return Glx::IsDirect(Glx::IsDirectRequest{context}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& is_direct = (*reply).is_direct; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // is_direct + Read(&is_direct, &buf); + + // pad1 + Pad(&buf, 23); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::QueryVersion( + const Glx::QueryVersionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& major_version = request.major_version; + auto& minor_version = request.minor_version; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 7; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // major_version + buf.Write(&major_version); + + // minor_version + buf.Write(&minor_version); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::QueryVersion", false); +} + +Future Glx::QueryVersion( + const uint32_t& major_version, + const uint32_t& minor_version) { + return Glx::QueryVersion( + Glx::QueryVersionRequest{major_version, minor_version}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Glx::QueryVersionReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& major_version = (*reply).major_version; + auto& minor_version = (*reply).minor_version; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // major_version + Read(&major_version, &buf); + + // minor_version + Read(&minor_version, &buf); + + // pad1 + Pad(&buf, 16); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::WaitGL(const Glx::WaitGLRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 8; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::WaitGL", false); +} + +Future Glx::WaitGL(const ContextTag& context_tag) { + return Glx::WaitGL(Glx::WaitGLRequest{context_tag}); +} + +Future Glx::WaitX(const Glx::WaitXRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 9; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::WaitX", false); +} + +Future Glx::WaitX(const ContextTag& context_tag) { + return Glx::WaitX(Glx::WaitXRequest{context_tag}); +} + +Future Glx::CopyContext(const Glx::CopyContextRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& src = request.src; + auto& dest = request.dest; + auto& mask = request.mask; + auto& src_context_tag = request.src_context_tag; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 10; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // src + buf.Write(&src); + + // dest + buf.Write(&dest); + + // mask + buf.Write(&mask); + + // src_context_tag + buf.Write(&src_context_tag); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::CopyContext", false); +} + +Future Glx::CopyContext(const Context& src, + const Context& dest, + const uint32_t& mask, + const ContextTag& src_context_tag) { + return Glx::CopyContext( + Glx::CopyContextRequest{src, dest, mask, src_context_tag}); +} + +Future Glx::SwapBuffers(const Glx::SwapBuffersRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& drawable = request.drawable; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 11; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // drawable + buf.Write(&drawable); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::SwapBuffers", false); +} + +Future Glx::SwapBuffers(const ContextTag& context_tag, + const Drawable& drawable) { + return Glx::SwapBuffers(Glx::SwapBuffersRequest{context_tag, drawable}); +} + +Future Glx::UseXFont(const Glx::UseXFontRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& font = request.font; + auto& first = request.first; + auto& count = request.count; + auto& list_base = request.list_base; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 12; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // font + buf.Write(&font); + + // first + buf.Write(&first); + + // count + buf.Write(&count); + + // list_base + buf.Write(&list_base); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::UseXFont", false); +} + +Future Glx::UseXFont(const ContextTag& context_tag, + const Font& font, + const uint32_t& first, + const uint32_t& count, + const uint32_t& list_base) { + return Glx::UseXFont( + Glx::UseXFontRequest{context_tag, font, first, count, list_base}); +} + +Future Glx::CreateGLXPixmap(const Glx::CreateGLXPixmapRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& screen = request.screen; + auto& visual = request.visual; + auto& pixmap = request.pixmap; + auto& glx_pixmap = request.glx_pixmap; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 13; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // screen + buf.Write(&screen); + + // visual + buf.Write(&visual); + + // pixmap + buf.Write(&pixmap); + + // glx_pixmap + buf.Write(&glx_pixmap); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::CreateGLXPixmap", false); +} + +Future Glx::CreateGLXPixmap(const uint32_t& screen, + const VisualId& visual, + const x11::Pixmap& pixmap, + const Pixmap& glx_pixmap) { + return Glx::CreateGLXPixmap( + Glx::CreateGLXPixmapRequest{screen, visual, pixmap, glx_pixmap}); +} + +Future Glx::GetVisualConfigs( + const Glx::GetVisualConfigsRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& screen = request.screen; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 14; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // screen + buf.Write(&screen); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::GetVisualConfigs", false); +} + +Future Glx::GetVisualConfigs( + const uint32_t& screen) { + return Glx::GetVisualConfigs(Glx::GetVisualConfigsRequest{screen}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Glx::GetVisualConfigsReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& num_visuals = (*reply).num_visuals; + auto& num_properties = (*reply).num_properties; + auto& property_list = (*reply).property_list; + size_t property_list_len = property_list.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // num_visuals + Read(&num_visuals, &buf); + + // num_properties + Read(&num_properties, &buf); + + // pad1 + Pad(&buf, 16); + + // property_list + property_list.resize(length); + for (auto& property_list_elem : property_list) { + // property_list_elem + Read(&property_list_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::DestroyGLXPixmap( + const Glx::DestroyGLXPixmapRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& glx_pixmap = request.glx_pixmap; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 15; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // glx_pixmap + buf.Write(&glx_pixmap); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::DestroyGLXPixmap", false); +} + +Future Glx::DestroyGLXPixmap(const Pixmap& glx_pixmap) { + return Glx::DestroyGLXPixmap(Glx::DestroyGLXPixmapRequest{glx_pixmap}); +} + +Future Glx::VendorPrivate(const Glx::VendorPrivateRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& vendor_code = request.vendor_code; + auto& context_tag = request.context_tag; + auto& data = request.data; + size_t data_len = data.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 16; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // vendor_code + buf.Write(&vendor_code); + + // context_tag + buf.Write(&context_tag); + + // data + DCHECK_EQ(static_cast(data_len), data.size()); + for (auto& data_elem : data) { + // data_elem + buf.Write(&data_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::VendorPrivate", false); +} + +Future Glx::VendorPrivate(const uint32_t& vendor_code, + const ContextTag& context_tag, + const std::vector& data) { + return Glx::VendorPrivate( + Glx::VendorPrivateRequest{vendor_code, context_tag, data}); +} + +Future Glx::VendorPrivateWithReply( + const Glx::VendorPrivateWithReplyRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& vendor_code = request.vendor_code; + auto& context_tag = request.context_tag; + auto& data = request.data; + size_t data_len = data.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 17; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // vendor_code + buf.Write(&vendor_code); + + // context_tag + buf.Write(&context_tag); + + // data + DCHECK_EQ(static_cast(data_len), data.size()); + for (auto& data_elem : data) { + // data_elem + buf.Write(&data_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::VendorPrivateWithReply", false); +} + +Future Glx::VendorPrivateWithReply( + const uint32_t& vendor_code, + const ContextTag& context_tag, + const std::vector& data) { + return Glx::VendorPrivateWithReply( + Glx::VendorPrivateWithReplyRequest{vendor_code, context_tag, data}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Glx::VendorPrivateWithReplyReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& retval = (*reply).retval; + auto& data1 = (*reply).data1; + size_t data1_len = data1.size(); + auto& data2 = (*reply).data2; + size_t data2_len = data2.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // retval + Read(&retval, &buf); + + // data1 + for (auto& data1_elem : data1) { + // data1_elem + Read(&data1_elem, &buf); + } + + // data2 + data2.resize((length) * (4)); + for (auto& data2_elem : data2) { + // data2_elem + Read(&data2_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::QueryExtensionsString( + const Glx::QueryExtensionsStringRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& screen = request.screen; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 18; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // screen + buf.Write(&screen); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::QueryExtensionsString", false); +} + +Future Glx::QueryExtensionsString( + const uint32_t& screen) { + return Glx::QueryExtensionsString(Glx::QueryExtensionsStringRequest{screen}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Glx::QueryExtensionsStringReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& n = (*reply).n; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 4); + + // n + Read(&n, &buf); + + // pad2 + Pad(&buf, 16); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::QueryServerString( + const Glx::QueryServerStringRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& screen = request.screen; + auto& name = request.name; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 19; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // screen + buf.Write(&screen); + + // name + buf.Write(&name); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::QueryServerString", false); +} + +Future Glx::QueryServerString( + const uint32_t& screen, + const uint32_t& name) { + return Glx::QueryServerString(Glx::QueryServerStringRequest{screen, name}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Glx::QueryServerStringReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t str_len{}; + auto& string = (*reply).string; + size_t string_len = string.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 4); + + // str_len + Read(&str_len, &buf); + + // pad2 + Pad(&buf, 16); + + // string + string.resize(str_len); + for (auto& string_elem : string) { + // string_elem + Read(&string_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::ClientInfo(const Glx::ClientInfoRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& major_version = request.major_version; + auto& minor_version = request.minor_version; + uint32_t str_len{}; + auto& string = request.string; + size_t string_len = string.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 20; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // major_version + buf.Write(&major_version); + + // minor_version + buf.Write(&minor_version); + + // str_len + str_len = string.size(); + buf.Write(&str_len); + + // string + DCHECK_EQ(static_cast(str_len), string.size()); + for (auto& string_elem : string) { + // string_elem + buf.Write(&string_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::ClientInfo", false); +} + +Future Glx::ClientInfo(const uint32_t& major_version, + const uint32_t& minor_version, + const std::string& string) { + return Glx::ClientInfo( + Glx::ClientInfoRequest{major_version, minor_version, string}); +} + +Future Glx::GetFBConfigs( + const Glx::GetFBConfigsRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& screen = request.screen; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 21; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // screen + buf.Write(&screen); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::GetFBConfigs", false); +} + +Future Glx::GetFBConfigs(const uint32_t& screen) { + return Glx::GetFBConfigs(Glx::GetFBConfigsRequest{screen}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Glx::GetFBConfigsReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& num_FB_configs = (*reply).num_FB_configs; + auto& num_properties = (*reply).num_properties; + auto& property_list = (*reply).property_list; + size_t property_list_len = property_list.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // num_FB_configs + Read(&num_FB_configs, &buf); + + // num_properties + Read(&num_properties, &buf); + + // pad1 + Pad(&buf, 16); + + // property_list + property_list.resize(length); + for (auto& property_list_elem : property_list) { + // property_list_elem + Read(&property_list_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::CreatePixmap(const Glx::CreatePixmapRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& screen = request.screen; + auto& fbconfig = request.fbconfig; + auto& pixmap = request.pixmap; + auto& glx_pixmap = request.glx_pixmap; + auto& num_attribs = request.num_attribs; + auto& attribs = request.attribs; + size_t attribs_len = attribs.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 22; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // screen + buf.Write(&screen); + + // fbconfig + buf.Write(&fbconfig); + + // pixmap + buf.Write(&pixmap); + + // glx_pixmap + buf.Write(&glx_pixmap); + + // num_attribs + buf.Write(&num_attribs); + + // attribs + DCHECK_EQ(static_cast((num_attribs) * (2)), attribs.size()); + for (auto& attribs_elem : attribs) { + // attribs_elem + buf.Write(&attribs_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::CreatePixmap", false); +} + +Future Glx::CreatePixmap(const uint32_t& screen, + const FbConfig& fbconfig, + const x11::Pixmap& pixmap, + const Pixmap& glx_pixmap, + const uint32_t& num_attribs, + const std::vector& attribs) { + return Glx::CreatePixmap(Glx::CreatePixmapRequest{ + screen, fbconfig, pixmap, glx_pixmap, num_attribs, attribs}); +} + +Future Glx::DestroyPixmap(const Glx::DestroyPixmapRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& glx_pixmap = request.glx_pixmap; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 23; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // glx_pixmap + buf.Write(&glx_pixmap); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::DestroyPixmap", false); +} + +Future Glx::DestroyPixmap(const Pixmap& glx_pixmap) { + return Glx::DestroyPixmap(Glx::DestroyPixmapRequest{glx_pixmap}); +} + +Future Glx::CreateNewContext( + const Glx::CreateNewContextRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context = request.context; + auto& fbconfig = request.fbconfig; + auto& screen = request.screen; + auto& render_type = request.render_type; + auto& share_list = request.share_list; + auto& is_direct = request.is_direct; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 24; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context + buf.Write(&context); + + // fbconfig + buf.Write(&fbconfig); + + // screen + buf.Write(&screen); + + // render_type + buf.Write(&render_type); + + // share_list + buf.Write(&share_list); + + // is_direct + buf.Write(&is_direct); + + // pad0 + Pad(&buf, 3); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::CreateNewContext", false); +} + +Future Glx::CreateNewContext(const Context& context, + const FbConfig& fbconfig, + const uint32_t& screen, + const uint32_t& render_type, + const Context& share_list, + const uint8_t& is_direct) { + return Glx::CreateNewContext(Glx::CreateNewContextRequest{ + context, fbconfig, screen, render_type, share_list, is_direct}); +} + +Future Glx::QueryContext( + const Glx::QueryContextRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context = request.context; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 25; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context + buf.Write(&context); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::QueryContext", false); +} + +Future Glx::QueryContext(const Context& context) { + return Glx::QueryContext(Glx::QueryContextRequest{context}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Glx::QueryContextReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& num_attribs = (*reply).num_attribs; + auto& attribs = (*reply).attribs; + size_t attribs_len = attribs.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // num_attribs + Read(&num_attribs, &buf); + + // pad1 + Pad(&buf, 20); + + // attribs + attribs.resize((num_attribs) * (2)); + for (auto& attribs_elem : attribs) { + // attribs_elem + Read(&attribs_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::MakeContextCurrent( + const Glx::MakeContextCurrentRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& old_context_tag = request.old_context_tag; + auto& drawable = request.drawable; + auto& read_drawable = request.read_drawable; + auto& context = request.context; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 26; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // old_context_tag + buf.Write(&old_context_tag); + + // drawable + buf.Write(&drawable); + + // read_drawable + buf.Write(&read_drawable); + + // context + buf.Write(&context); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::MakeContextCurrent", false); +} + +Future Glx::MakeContextCurrent( + const ContextTag& old_context_tag, + const Drawable& drawable, + const Drawable& read_drawable, + const Context& context) { + return Glx::MakeContextCurrent(Glx::MakeContextCurrentRequest{ + old_context_tag, drawable, read_drawable, context}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Glx::MakeContextCurrentReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& context_tag = (*reply).context_tag; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // context_tag + Read(&context_tag, &buf); + + // pad1 + Pad(&buf, 20); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::CreatePbuffer(const Glx::CreatePbufferRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& screen = request.screen; + auto& fbconfig = request.fbconfig; + auto& pbuffer = request.pbuffer; + auto& num_attribs = request.num_attribs; + auto& attribs = request.attribs; + size_t attribs_len = attribs.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 27; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // screen + buf.Write(&screen); + + // fbconfig + buf.Write(&fbconfig); + + // pbuffer + buf.Write(&pbuffer); + + // num_attribs + buf.Write(&num_attribs); + + // attribs + DCHECK_EQ(static_cast((num_attribs) * (2)), attribs.size()); + for (auto& attribs_elem : attribs) { + // attribs_elem + buf.Write(&attribs_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::CreatePbuffer", false); +} + +Future Glx::CreatePbuffer(const uint32_t& screen, + const FbConfig& fbconfig, + const PBuffer& pbuffer, + const uint32_t& num_attribs, + const std::vector& attribs) { + return Glx::CreatePbuffer(Glx::CreatePbufferRequest{screen, fbconfig, pbuffer, + num_attribs, attribs}); +} + +Future Glx::DestroyPbuffer(const Glx::DestroyPbufferRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& pbuffer = request.pbuffer; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 28; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // pbuffer + buf.Write(&pbuffer); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::DestroyPbuffer", false); +} + +Future Glx::DestroyPbuffer(const PBuffer& pbuffer) { + return Glx::DestroyPbuffer(Glx::DestroyPbufferRequest{pbuffer}); +} + +Future Glx::GetDrawableAttributes( + const Glx::GetDrawableAttributesRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& drawable = request.drawable; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 29; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // drawable + buf.Write(&drawable); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::GetDrawableAttributes", false); +} + +Future Glx::GetDrawableAttributes( + const Drawable& drawable) { + return Glx::GetDrawableAttributes( + Glx::GetDrawableAttributesRequest{drawable}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Glx::GetDrawableAttributesReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& num_attribs = (*reply).num_attribs; + auto& attribs = (*reply).attribs; + size_t attribs_len = attribs.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // num_attribs + Read(&num_attribs, &buf); + + // pad1 + Pad(&buf, 20); + + // attribs + attribs.resize((num_attribs) * (2)); + for (auto& attribs_elem : attribs) { + // attribs_elem + Read(&attribs_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::ChangeDrawableAttributes( + const Glx::ChangeDrawableAttributesRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& drawable = request.drawable; + auto& num_attribs = request.num_attribs; + auto& attribs = request.attribs; + size_t attribs_len = attribs.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 30; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // drawable + buf.Write(&drawable); + + // num_attribs + buf.Write(&num_attribs); + + // attribs + DCHECK_EQ(static_cast((num_attribs) * (2)), attribs.size()); + for (auto& attribs_elem : attribs) { + // attribs_elem + buf.Write(&attribs_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::ChangeDrawableAttributes", + false); +} + +Future Glx::ChangeDrawableAttributes( + const Drawable& drawable, + const uint32_t& num_attribs, + const std::vector& attribs) { + return Glx::ChangeDrawableAttributes( + Glx::ChangeDrawableAttributesRequest{drawable, num_attribs, attribs}); +} + +Future Glx::CreateWindow(const Glx::CreateWindowRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& screen = request.screen; + auto& fbconfig = request.fbconfig; + auto& window = request.window; + auto& glx_window = request.glx_window; + auto& num_attribs = request.num_attribs; + auto& attribs = request.attribs; + size_t attribs_len = attribs.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 31; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // screen + buf.Write(&screen); + + // fbconfig + buf.Write(&fbconfig); + + // window + buf.Write(&window); + + // glx_window + buf.Write(&glx_window); + + // num_attribs + buf.Write(&num_attribs); + + // attribs + DCHECK_EQ(static_cast((num_attribs) * (2)), attribs.size()); + for (auto& attribs_elem : attribs) { + // attribs_elem + buf.Write(&attribs_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::CreateWindow", false); +} + +Future Glx::CreateWindow(const uint32_t& screen, + const FbConfig& fbconfig, + const x11::Window& window, + const Window& glx_window, + const uint32_t& num_attribs, + const std::vector& attribs) { + return Glx::CreateWindow(Glx::CreateWindowRequest{ + screen, fbconfig, window, glx_window, num_attribs, attribs}); +} + +Future Glx::DeleteWindow(const Glx::DeleteWindowRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& glxwindow = request.glxwindow; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 32; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // glxwindow + buf.Write(&glxwindow); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::DeleteWindow", false); +} + +Future Glx::DeleteWindow(const Window& glxwindow) { + return Glx::DeleteWindow(Glx::DeleteWindowRequest{glxwindow}); +} + +Future Glx::SetClientInfoARB( + const Glx::SetClientInfoARBRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& major_version = request.major_version; + auto& minor_version = request.minor_version; + auto& num_versions = request.num_versions; + uint32_t gl_str_len{}; + uint32_t glx_str_len{}; + auto& gl_versions = request.gl_versions; + size_t gl_versions_len = gl_versions.size(); + auto& gl_extension_string = request.gl_extension_string; + size_t gl_extension_string_len = gl_extension_string.size(); + auto& glx_extension_string = request.glx_extension_string; + size_t glx_extension_string_len = glx_extension_string.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 33; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // major_version + buf.Write(&major_version); + + // minor_version + buf.Write(&minor_version); + + // num_versions + buf.Write(&num_versions); + + // gl_str_len + gl_str_len = gl_extension_string.size(); + buf.Write(&gl_str_len); + + // glx_str_len + glx_str_len = glx_extension_string.size(); + buf.Write(&glx_str_len); + + // gl_versions + DCHECK_EQ(static_cast((num_versions) * (2)), gl_versions.size()); + for (auto& gl_versions_elem : gl_versions) { + // gl_versions_elem + buf.Write(&gl_versions_elem); + } + + // gl_extension_string + DCHECK_EQ(static_cast(gl_str_len), gl_extension_string.size()); + for (auto& gl_extension_string_elem : gl_extension_string) { + // gl_extension_string_elem + buf.Write(&gl_extension_string_elem); + } + + // glx_extension_string + DCHECK_EQ(static_cast(glx_str_len), glx_extension_string.size()); + for (auto& glx_extension_string_elem : glx_extension_string) { + // glx_extension_string_elem + buf.Write(&glx_extension_string_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::SetClientInfoARB", false); +} + +Future Glx::SetClientInfoARB(const uint32_t& major_version, + const uint32_t& minor_version, + const uint32_t& num_versions, + const std::vector& gl_versions, + const std::string& gl_extension_string, + const std::string& glx_extension_string) { + return Glx::SetClientInfoARB(Glx::SetClientInfoARBRequest{ + major_version, minor_version, num_versions, gl_versions, + gl_extension_string, glx_extension_string}); +} + +Future Glx::CreateContextAttribsARB( + const Glx::CreateContextAttribsARBRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context = request.context; + auto& fbconfig = request.fbconfig; + auto& screen = request.screen; + auto& share_list = request.share_list; + auto& is_direct = request.is_direct; + auto& num_attribs = request.num_attribs; + auto& attribs = request.attribs; + size_t attribs_len = attribs.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 34; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context + buf.Write(&context); + + // fbconfig + buf.Write(&fbconfig); + + // screen + buf.Write(&screen); + + // share_list + buf.Write(&share_list); + + // is_direct + buf.Write(&is_direct); + + // pad0 + Pad(&buf, 3); + + // num_attribs + buf.Write(&num_attribs); + + // attribs + DCHECK_EQ(static_cast((num_attribs) * (2)), attribs.size()); + for (auto& attribs_elem : attribs) { + // attribs_elem + buf.Write(&attribs_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::CreateContextAttribsARB", + false); +} + +Future Glx::CreateContextAttribsARB( + const Context& context, + const FbConfig& fbconfig, + const uint32_t& screen, + const Context& share_list, + const uint8_t& is_direct, + const uint32_t& num_attribs, + const std::vector& attribs) { + return Glx::CreateContextAttribsARB(Glx::CreateContextAttribsARBRequest{ + context, fbconfig, screen, share_list, is_direct, num_attribs, attribs}); +} + +Future Glx::SetClientInfo2ARB( + const Glx::SetClientInfo2ARBRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& major_version = request.major_version; + auto& minor_version = request.minor_version; + auto& num_versions = request.num_versions; + uint32_t gl_str_len{}; + uint32_t glx_str_len{}; + auto& gl_versions = request.gl_versions; + size_t gl_versions_len = gl_versions.size(); + auto& gl_extension_string = request.gl_extension_string; + size_t gl_extension_string_len = gl_extension_string.size(); + auto& glx_extension_string = request.glx_extension_string; + size_t glx_extension_string_len = glx_extension_string.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 35; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // major_version + buf.Write(&major_version); + + // minor_version + buf.Write(&minor_version); + + // num_versions + buf.Write(&num_versions); + + // gl_str_len + gl_str_len = gl_extension_string.size(); + buf.Write(&gl_str_len); + + // glx_str_len + glx_str_len = glx_extension_string.size(); + buf.Write(&glx_str_len); + + // gl_versions + DCHECK_EQ(static_cast((num_versions) * (3)), gl_versions.size()); + for (auto& gl_versions_elem : gl_versions) { + // gl_versions_elem + buf.Write(&gl_versions_elem); + } + + // gl_extension_string + DCHECK_EQ(static_cast(gl_str_len), gl_extension_string.size()); + for (auto& gl_extension_string_elem : gl_extension_string) { + // gl_extension_string_elem + buf.Write(&gl_extension_string_elem); + } + + // glx_extension_string + DCHECK_EQ(static_cast(glx_str_len), glx_extension_string.size()); + for (auto& glx_extension_string_elem : glx_extension_string) { + // glx_extension_string_elem + buf.Write(&glx_extension_string_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::SetClientInfo2ARB", false); +} + +Future Glx::SetClientInfo2ARB(const uint32_t& major_version, + const uint32_t& minor_version, + const uint32_t& num_versions, + const std::vector& gl_versions, + const std::string& gl_extension_string, + const std::string& glx_extension_string) { + return Glx::SetClientInfo2ARB(Glx::SetClientInfo2ARBRequest{ + major_version, minor_version, num_versions, gl_versions, + gl_extension_string, glx_extension_string}); +} + +Future Glx::NewList(const Glx::NewListRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& list = request.list; + auto& mode = request.mode; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 101; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // list + buf.Write(&list); + + // mode + buf.Write(&mode); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::NewList", false); +} + +Future Glx::NewList(const ContextTag& context_tag, + const uint32_t& list, + const uint32_t& mode) { + return Glx::NewList(Glx::NewListRequest{context_tag, list, mode}); +} + +Future Glx::EndList(const Glx::EndListRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 102; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::EndList", false); +} + +Future Glx::EndList(const ContextTag& context_tag) { + return Glx::EndList(Glx::EndListRequest{context_tag}); +} + +Future Glx::DeleteLists(const Glx::DeleteListsRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& list = request.list; + auto& range = request.range; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 103; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // list + buf.Write(&list); + + // range + buf.Write(&range); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::DeleteLists", false); +} + +Future Glx::DeleteLists(const ContextTag& context_tag, + const uint32_t& list, + const int32_t& range) { + return Glx::DeleteLists(Glx::DeleteListsRequest{context_tag, list, range}); +} + +Future Glx::GenLists(const Glx::GenListsRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& range = request.range; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 104; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // range + buf.Write(&range); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::GenLists", + false); +} + +Future Glx::GenLists(const ContextTag& context_tag, + const int32_t& range) { + return Glx::GenLists(Glx::GenListsRequest{context_tag, range}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& ret_val = (*reply).ret_val; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // ret_val + Read(&ret_val, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::FeedbackBuffer(const Glx::FeedbackBufferRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& size = request.size; + auto& type = request.type; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 105; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // size + buf.Write(&size); + + // type + buf.Write(&type); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::FeedbackBuffer", false); +} + +Future Glx::FeedbackBuffer(const ContextTag& context_tag, + const int32_t& size, + const int32_t& type) { + return Glx::FeedbackBuffer( + Glx::FeedbackBufferRequest{context_tag, size, type}); +} + +Future Glx::SelectBuffer(const Glx::SelectBufferRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& size = request.size; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 106; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // size + buf.Write(&size); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::SelectBuffer", false); +} + +Future Glx::SelectBuffer(const ContextTag& context_tag, + const int32_t& size) { + return Glx::SelectBuffer(Glx::SelectBufferRequest{context_tag, size}); +} + +Future Glx::RenderMode( + const Glx::RenderModeRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& mode = request.mode; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 107; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // mode + buf.Write(&mode); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::RenderMode", + false); +} + +Future Glx::RenderMode(const ContextTag& context_tag, + const uint32_t& mode) { + return Glx::RenderMode(Glx::RenderModeRequest{context_tag, mode}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& ret_val = (*reply).ret_val; + uint32_t n{}; + auto& new_mode = (*reply).new_mode; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // ret_val + Read(&ret_val, &buf); + + // n + Read(&n, &buf); + + // new_mode + Read(&new_mode, &buf); + + // pad1 + Pad(&buf, 12); + + // data + data.resize(n); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::Finish(const Glx::FinishRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 108; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::Finish", false); +} + +Future Glx::Finish(const ContextTag& context_tag) { + return Glx::Finish(Glx::FinishRequest{context_tag}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::PixelStoref(const Glx::PixelStorefRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& pname = request.pname; + auto& datum = request.datum; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 109; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // pname + buf.Write(&pname); + + // datum + buf.Write(&datum); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::PixelStoref", false); +} + +Future Glx::PixelStoref(const ContextTag& context_tag, + const uint32_t& pname, + const float& datum) { + return Glx::PixelStoref(Glx::PixelStorefRequest{context_tag, pname, datum}); +} + +Future Glx::PixelStorei(const Glx::PixelStoreiRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& pname = request.pname; + auto& datum = request.datum; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 110; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // pname + buf.Write(&pname); + + // datum + buf.Write(&datum); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::PixelStorei", false); +} + +Future Glx::PixelStorei(const ContextTag& context_tag, + const uint32_t& pname, + const int32_t& datum) { + return Glx::PixelStorei(Glx::PixelStoreiRequest{context_tag, pname, datum}); +} + +Future Glx::ReadPixels( + const Glx::ReadPixelsRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& x = request.x; + auto& y = request.y; + auto& width = request.width; + auto& height = request.height; + auto& format = request.format; + auto& type = request.type; + auto& swap_bytes = request.swap_bytes; + auto& lsb_first = request.lsb_first; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 111; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // x + buf.Write(&x); + + // y + buf.Write(&y); + + // width + buf.Write(&width); + + // height + buf.Write(&height); + + // format + buf.Write(&format); + + // type + buf.Write(&type); + + // swap_bytes + buf.Write(&swap_bytes); + + // lsb_first + buf.Write(&lsb_first); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::ReadPixels", + false); +} + +Future Glx::ReadPixels(const ContextTag& context_tag, + const int32_t& x, + const int32_t& y, + const int32_t& width, + const int32_t& height, + const uint32_t& format, + const uint32_t& type, + const uint8_t& swap_bytes, + const uint8_t& lsb_first) { + return Glx::ReadPixels(Glx::ReadPixelsRequest{ + context_tag, x, y, width, height, format, type, swap_bytes, lsb_first}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 24); + + // data + data.resize((length) * (4)); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetBooleanv( + const Glx::GetBooleanvRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& pname = request.pname; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 112; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // pname + buf.Write(&pname); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::GetBooleanv", false); +} + +Future Glx::GetBooleanv(const ContextTag& context_tag, + const int32_t& pname) { + return Glx::GetBooleanv(Glx::GetBooleanvRequest{context_tag, pname}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t n{}; + auto& datum = (*reply).datum; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 4); + + // n + Read(&n, &buf); + + // datum + Read(&datum, &buf); + + // pad2 + Pad(&buf, 15); + + // data + data.resize(n); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetClipPlane( + const Glx::GetClipPlaneRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& plane = request.plane; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 113; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // plane + buf.Write(&plane); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::GetClipPlane", false); +} + +Future Glx::GetClipPlane(const ContextTag& context_tag, + const int32_t& plane) { + return Glx::GetClipPlane(Glx::GetClipPlaneRequest{context_tag, plane}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Glx::GetClipPlaneReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 24); + + // data + data.resize((length) / (2)); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetDoublev( + const Glx::GetDoublevRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& pname = request.pname; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 114; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // pname + buf.Write(&pname); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::GetDoublev", + false); +} + +Future Glx::GetDoublev(const ContextTag& context_tag, + const uint32_t& pname) { + return Glx::GetDoublev(Glx::GetDoublevRequest{context_tag, pname}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t n{}; + auto& datum = (*reply).datum; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 4); + + // n + Read(&n, &buf); + + // datum + Read(&datum, &buf); + + // pad2 + Pad(&buf, 8); + + // data + data.resize(n); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetError(const Glx::GetErrorRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 115; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::GetError", + false); +} + +Future Glx::GetError(const ContextTag& context_tag) { + return Glx::GetError(Glx::GetErrorRequest{context_tag}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& error = (*reply).error; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // error + Read(&error, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetFloatv( + const Glx::GetFloatvRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& pname = request.pname; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 116; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // pname + buf.Write(&pname); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::GetFloatv", + false); +} + +Future Glx::GetFloatv(const ContextTag& context_tag, + const uint32_t& pname) { + return Glx::GetFloatv(Glx::GetFloatvRequest{context_tag, pname}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t n{}; + auto& datum = (*reply).datum; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 4); + + // n + Read(&n, &buf); + + // datum + Read(&datum, &buf); + + // pad2 + Pad(&buf, 12); + + // data + data.resize(n); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetIntegerv( + const Glx::GetIntegervRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& pname = request.pname; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 117; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // pname + buf.Write(&pname); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::GetIntegerv", false); +} + +Future Glx::GetIntegerv(const ContextTag& context_tag, + const uint32_t& pname) { + return Glx::GetIntegerv(Glx::GetIntegervRequest{context_tag, pname}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t n{}; + auto& datum = (*reply).datum; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 4); + + // n + Read(&n, &buf); + + // datum + Read(&datum, &buf); + + // pad2 + Pad(&buf, 12); + + // data + data.resize(n); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetLightfv( + const Glx::GetLightfvRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& light = request.light; + auto& pname = request.pname; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 118; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // light + buf.Write(&light); + + // pname + buf.Write(&pname); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::GetLightfv", + false); +} + +Future Glx::GetLightfv(const ContextTag& context_tag, + const uint32_t& light, + const uint32_t& pname) { + return Glx::GetLightfv(Glx::GetLightfvRequest{context_tag, light, pname}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t n{}; + auto& datum = (*reply).datum; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 4); + + // n + Read(&n, &buf); + + // datum + Read(&datum, &buf); + + // pad2 + Pad(&buf, 12); + + // data + data.resize(n); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetLightiv( + const Glx::GetLightivRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& light = request.light; + auto& pname = request.pname; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 119; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // light + buf.Write(&light); + + // pname + buf.Write(&pname); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::GetLightiv", + false); +} + +Future Glx::GetLightiv(const ContextTag& context_tag, + const uint32_t& light, + const uint32_t& pname) { + return Glx::GetLightiv(Glx::GetLightivRequest{context_tag, light, pname}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t n{}; + auto& datum = (*reply).datum; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 4); + + // n + Read(&n, &buf); + + // datum + Read(&datum, &buf); + + // pad2 + Pad(&buf, 12); + + // data + data.resize(n); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetMapdv(const Glx::GetMapdvRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& target = request.target; + auto& query = request.query; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 120; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // target + buf.Write(&target); + + // query + buf.Write(&query); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::GetMapdv", + false); +} + +Future Glx::GetMapdv(const ContextTag& context_tag, + const uint32_t& target, + const uint32_t& query) { + return Glx::GetMapdv(Glx::GetMapdvRequest{context_tag, target, query}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t n{}; + auto& datum = (*reply).datum; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 4); + + // n + Read(&n, &buf); + + // datum + Read(&datum, &buf); + + // pad2 + Pad(&buf, 8); + + // data + data.resize(n); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetMapfv(const Glx::GetMapfvRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& target = request.target; + auto& query = request.query; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 121; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // target + buf.Write(&target); + + // query + buf.Write(&query); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::GetMapfv", + false); +} + +Future Glx::GetMapfv(const ContextTag& context_tag, + const uint32_t& target, + const uint32_t& query) { + return Glx::GetMapfv(Glx::GetMapfvRequest{context_tag, target, query}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t n{}; + auto& datum = (*reply).datum; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 4); + + // n + Read(&n, &buf); + + // datum + Read(&datum, &buf); + + // pad2 + Pad(&buf, 12); + + // data + data.resize(n); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetMapiv(const Glx::GetMapivRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& target = request.target; + auto& query = request.query; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 122; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // target + buf.Write(&target); + + // query + buf.Write(&query); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::GetMapiv", + false); +} + +Future Glx::GetMapiv(const ContextTag& context_tag, + const uint32_t& target, + const uint32_t& query) { + return Glx::GetMapiv(Glx::GetMapivRequest{context_tag, target, query}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t n{}; + auto& datum = (*reply).datum; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 4); + + // n + Read(&n, &buf); + + // datum + Read(&datum, &buf); + + // pad2 + Pad(&buf, 12); + + // data + data.resize(n); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetMaterialfv( + const Glx::GetMaterialfvRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& face = request.face; + auto& pname = request.pname; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 123; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // face + buf.Write(&face); + + // pname + buf.Write(&pname); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::GetMaterialfv", false); +} + +Future Glx::GetMaterialfv( + const ContextTag& context_tag, + const uint32_t& face, + const uint32_t& pname) { + return Glx::GetMaterialfv( + Glx::GetMaterialfvRequest{context_tag, face, pname}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Glx::GetMaterialfvReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t n{}; + auto& datum = (*reply).datum; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 4); + + // n + Read(&n, &buf); + + // datum + Read(&datum, &buf); + + // pad2 + Pad(&buf, 12); + + // data + data.resize(n); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetMaterialiv( + const Glx::GetMaterialivRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& face = request.face; + auto& pname = request.pname; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 124; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // face + buf.Write(&face); + + // pname + buf.Write(&pname); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::GetMaterialiv", false); +} + +Future Glx::GetMaterialiv( + const ContextTag& context_tag, + const uint32_t& face, + const uint32_t& pname) { + return Glx::GetMaterialiv( + Glx::GetMaterialivRequest{context_tag, face, pname}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Glx::GetMaterialivReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t n{}; + auto& datum = (*reply).datum; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 4); + + // n + Read(&n, &buf); + + // datum + Read(&datum, &buf); + + // pad2 + Pad(&buf, 12); + + // data + data.resize(n); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetPixelMapfv( + const Glx::GetPixelMapfvRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& map = request.map; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 125; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // map + buf.Write(&map); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::GetPixelMapfv", false); +} + +Future Glx::GetPixelMapfv( + const ContextTag& context_tag, + const uint32_t& map) { + return Glx::GetPixelMapfv(Glx::GetPixelMapfvRequest{context_tag, map}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Glx::GetPixelMapfvReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t n{}; + auto& datum = (*reply).datum; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 4); + + // n + Read(&n, &buf); + + // datum + Read(&datum, &buf); + + // pad2 + Pad(&buf, 12); + + // data + data.resize(n); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetPixelMapuiv( + const Glx::GetPixelMapuivRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& map = request.map; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 126; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // map + buf.Write(&map); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::GetPixelMapuiv", false); +} + +Future Glx::GetPixelMapuiv( + const ContextTag& context_tag, + const uint32_t& map) { + return Glx::GetPixelMapuiv(Glx::GetPixelMapuivRequest{context_tag, map}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Glx::GetPixelMapuivReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t n{}; + auto& datum = (*reply).datum; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 4); + + // n + Read(&n, &buf); + + // datum + Read(&datum, &buf); + + // pad2 + Pad(&buf, 12); + + // data + data.resize(n); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetPixelMapusv( + const Glx::GetPixelMapusvRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& map = request.map; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 127; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // map + buf.Write(&map); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::GetPixelMapusv", false); +} + +Future Glx::GetPixelMapusv( + const ContextTag& context_tag, + const uint32_t& map) { + return Glx::GetPixelMapusv(Glx::GetPixelMapusvRequest{context_tag, map}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Glx::GetPixelMapusvReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t n{}; + auto& datum = (*reply).datum; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 4); + + // n + Read(&n, &buf); + + // datum + Read(&datum, &buf); + + // pad2 + Pad(&buf, 16); + + // data + data.resize(n); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetPolygonStipple( + const Glx::GetPolygonStippleRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& lsb_first = request.lsb_first; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 128; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // lsb_first + buf.Write(&lsb_first); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::GetPolygonStipple", false); +} + +Future Glx::GetPolygonStipple( + const ContextTag& context_tag, + const uint8_t& lsb_first) { + return Glx::GetPolygonStipple( + Glx::GetPolygonStippleRequest{context_tag, lsb_first}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Glx::GetPolygonStippleReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 24); + + // data + data.resize((length) * (4)); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetString( + const Glx::GetStringRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& name = request.name; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 129; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // name + buf.Write(&name); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::GetString", + false); +} + +Future Glx::GetString(const ContextTag& context_tag, + const uint32_t& name) { + return Glx::GetString(Glx::GetStringRequest{context_tag, name}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t n{}; + auto& string = (*reply).string; + size_t string_len = string.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 4); + + // n + Read(&n, &buf); + + // pad2 + Pad(&buf, 16); + + // string + string.resize(n); + for (auto& string_elem : string) { + // string_elem + Read(&string_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetTexEnvfv( + const Glx::GetTexEnvfvRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& target = request.target; + auto& pname = request.pname; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 130; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // target + buf.Write(&target); + + // pname + buf.Write(&pname); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::GetTexEnvfv", false); +} + +Future Glx::GetTexEnvfv(const ContextTag& context_tag, + const uint32_t& target, + const uint32_t& pname) { + return Glx::GetTexEnvfv(Glx::GetTexEnvfvRequest{context_tag, target, pname}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t n{}; + auto& datum = (*reply).datum; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 4); + + // n + Read(&n, &buf); + + // datum + Read(&datum, &buf); + + // pad2 + Pad(&buf, 12); + + // data + data.resize(n); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetTexEnviv( + const Glx::GetTexEnvivRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& target = request.target; + auto& pname = request.pname; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 131; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // target + buf.Write(&target); + + // pname + buf.Write(&pname); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::GetTexEnviv", false); +} + +Future Glx::GetTexEnviv(const ContextTag& context_tag, + const uint32_t& target, + const uint32_t& pname) { + return Glx::GetTexEnviv(Glx::GetTexEnvivRequest{context_tag, target, pname}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t n{}; + auto& datum = (*reply).datum; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 4); + + // n + Read(&n, &buf); + + // datum + Read(&datum, &buf); + + // pad2 + Pad(&buf, 12); + + // data + data.resize(n); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetTexGendv( + const Glx::GetTexGendvRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& coord = request.coord; + auto& pname = request.pname; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 132; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // coord + buf.Write(&coord); + + // pname + buf.Write(&pname); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::GetTexGendv", false); +} + +Future Glx::GetTexGendv(const ContextTag& context_tag, + const uint32_t& coord, + const uint32_t& pname) { + return Glx::GetTexGendv(Glx::GetTexGendvRequest{context_tag, coord, pname}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t n{}; + auto& datum = (*reply).datum; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 4); + + // n + Read(&n, &buf); + + // datum + Read(&datum, &buf); + + // pad2 + Pad(&buf, 8); + + // data + data.resize(n); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetTexGenfv( + const Glx::GetTexGenfvRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& coord = request.coord; + auto& pname = request.pname; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 133; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // coord + buf.Write(&coord); + + // pname + buf.Write(&pname); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::GetTexGenfv", false); +} + +Future Glx::GetTexGenfv(const ContextTag& context_tag, + const uint32_t& coord, + const uint32_t& pname) { + return Glx::GetTexGenfv(Glx::GetTexGenfvRequest{context_tag, coord, pname}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t n{}; + auto& datum = (*reply).datum; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 4); + + // n + Read(&n, &buf); + + // datum + Read(&datum, &buf); + + // pad2 + Pad(&buf, 12); + + // data + data.resize(n); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetTexGeniv( + const Glx::GetTexGenivRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& coord = request.coord; + auto& pname = request.pname; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 134; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // coord + buf.Write(&coord); + + // pname + buf.Write(&pname); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::GetTexGeniv", false); +} + +Future Glx::GetTexGeniv(const ContextTag& context_tag, + const uint32_t& coord, + const uint32_t& pname) { + return Glx::GetTexGeniv(Glx::GetTexGenivRequest{context_tag, coord, pname}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t n{}; + auto& datum = (*reply).datum; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 4); + + // n + Read(&n, &buf); + + // datum + Read(&datum, &buf); + + // pad2 + Pad(&buf, 12); + + // data + data.resize(n); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetTexImage( + const Glx::GetTexImageRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& target = request.target; + auto& level = request.level; + auto& format = request.format; + auto& type = request.type; + auto& swap_bytes = request.swap_bytes; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 135; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // target + buf.Write(&target); + + // level + buf.Write(&level); + + // format + buf.Write(&format); + + // type + buf.Write(&type); + + // swap_bytes + buf.Write(&swap_bytes); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::GetTexImage", false); +} + +Future Glx::GetTexImage(const ContextTag& context_tag, + const uint32_t& target, + const int32_t& level, + const uint32_t& format, + const uint32_t& type, + const uint8_t& swap_bytes) { + return Glx::GetTexImage(Glx::GetTexImageRequest{context_tag, target, level, + format, type, swap_bytes}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& width = (*reply).width; + auto& height = (*reply).height; + auto& depth = (*reply).depth; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 8); + + // width + Read(&width, &buf); + + // height + Read(&height, &buf); + + // depth + Read(&depth, &buf); + + // pad2 + Pad(&buf, 4); + + // data + data.resize((length) * (4)); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetTexParameterfv( + const Glx::GetTexParameterfvRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& target = request.target; + auto& pname = request.pname; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 136; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // target + buf.Write(&target); + + // pname + buf.Write(&pname); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::GetTexParameterfv", false); +} + +Future Glx::GetTexParameterfv( + const ContextTag& context_tag, + const uint32_t& target, + const uint32_t& pname) { + return Glx::GetTexParameterfv( + Glx::GetTexParameterfvRequest{context_tag, target, pname}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Glx::GetTexParameterfvReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t n{}; + auto& datum = (*reply).datum; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 4); + + // n + Read(&n, &buf); + + // datum + Read(&datum, &buf); + + // pad2 + Pad(&buf, 12); + + // data + data.resize(n); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetTexParameteriv( + const Glx::GetTexParameterivRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& target = request.target; + auto& pname = request.pname; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 137; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // target + buf.Write(&target); + + // pname + buf.Write(&pname); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::GetTexParameteriv", false); +} + +Future Glx::GetTexParameteriv( + const ContextTag& context_tag, + const uint32_t& target, + const uint32_t& pname) { + return Glx::GetTexParameteriv( + Glx::GetTexParameterivRequest{context_tag, target, pname}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Glx::GetTexParameterivReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t n{}; + auto& datum = (*reply).datum; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 4); + + // n + Read(&n, &buf); + + // datum + Read(&datum, &buf); + + // pad2 + Pad(&buf, 12); + + // data + data.resize(n); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetTexLevelParameterfv( + const Glx::GetTexLevelParameterfvRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& target = request.target; + auto& level = request.level; + auto& pname = request.pname; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 138; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // target + buf.Write(&target); + + // level + buf.Write(&level); + + // pname + buf.Write(&pname); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::GetTexLevelParameterfv", false); +} + +Future Glx::GetTexLevelParameterfv( + const ContextTag& context_tag, + const uint32_t& target, + const int32_t& level, + const uint32_t& pname) { + return Glx::GetTexLevelParameterfv( + Glx::GetTexLevelParameterfvRequest{context_tag, target, level, pname}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Glx::GetTexLevelParameterfvReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t n{}; + auto& datum = (*reply).datum; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 4); + + // n + Read(&n, &buf); + + // datum + Read(&datum, &buf); + + // pad2 + Pad(&buf, 12); + + // data + data.resize(n); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetTexLevelParameteriv( + const Glx::GetTexLevelParameterivRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& target = request.target; + auto& level = request.level; + auto& pname = request.pname; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 139; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // target + buf.Write(&target); + + // level + buf.Write(&level); + + // pname + buf.Write(&pname); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::GetTexLevelParameteriv", false); +} + +Future Glx::GetTexLevelParameteriv( + const ContextTag& context_tag, + const uint32_t& target, + const int32_t& level, + const uint32_t& pname) { + return Glx::GetTexLevelParameteriv( + Glx::GetTexLevelParameterivRequest{context_tag, target, level, pname}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Glx::GetTexLevelParameterivReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t n{}; + auto& datum = (*reply).datum; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 4); + + // n + Read(&n, &buf); + + // datum + Read(&datum, &buf); + + // pad2 + Pad(&buf, 12); + + // data + data.resize(n); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::IsEnabled( + const Glx::IsEnabledRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& capability = request.capability; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 140; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // capability + buf.Write(&capability); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::IsEnabled", + false); +} + +Future Glx::IsEnabled(const ContextTag& context_tag, + const uint32_t& capability) { + return Glx::IsEnabled(Glx::IsEnabledRequest{context_tag, capability}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& ret_val = (*reply).ret_val; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // ret_val + Read(&ret_val, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::IsList(const Glx::IsListRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& list = request.list; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 141; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // list + buf.Write(&list); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::IsList", false); +} + +Future Glx::IsList(const ContextTag& context_tag, + const uint32_t& list) { + return Glx::IsList(Glx::IsListRequest{context_tag, list}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& ret_val = (*reply).ret_val; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // ret_val + Read(&ret_val, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::Flush(const Glx::FlushRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 142; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::Flush", false); +} + +Future Glx::Flush(const ContextTag& context_tag) { + return Glx::Flush(Glx::FlushRequest{context_tag}); +} + +Future Glx::AreTexturesResident( + const Glx::AreTexturesResidentRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + int32_t n{}; + auto& textures = request.textures; + size_t textures_len = textures.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 143; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // n + n = textures.size(); + buf.Write(&n); + + // textures + DCHECK_EQ(static_cast(n), textures.size()); + for (auto& textures_elem : textures) { + // textures_elem + buf.Write(&textures_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::AreTexturesResident", false); +} + +Future Glx::AreTexturesResident( + const ContextTag& context_tag, + const std::vector& textures) { + return Glx::AreTexturesResident( + Glx::AreTexturesResidentRequest{context_tag, textures}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Glx::AreTexturesResidentReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& ret_val = (*reply).ret_val; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // ret_val + Read(&ret_val, &buf); + + // pad1 + Pad(&buf, 20); + + // data + data.resize((length) * (4)); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::DeleteTextures(const Glx::DeleteTexturesRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + int32_t n{}; + auto& textures = request.textures; + size_t textures_len = textures.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 144; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // n + n = textures.size(); + buf.Write(&n); + + // textures + DCHECK_EQ(static_cast(n), textures.size()); + for (auto& textures_elem : textures) { + // textures_elem + buf.Write(&textures_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::DeleteTextures", false); +} + +Future Glx::DeleteTextures(const ContextTag& context_tag, + const std::vector& textures) { + return Glx::DeleteTextures(Glx::DeleteTexturesRequest{context_tag, textures}); +} + +Future Glx::GenTextures( + const Glx::GenTexturesRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& n = request.n; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 145; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // n + buf.Write(&n); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::GenTextures", false); +} + +Future Glx::GenTextures(const ContextTag& context_tag, + const int32_t& n) { + return Glx::GenTextures(Glx::GenTexturesRequest{context_tag, n}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 24); + + // data + data.resize(length); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::IsTexture( + const Glx::IsTextureRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& texture = request.texture; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 146; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // texture + buf.Write(&texture); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::IsTexture", + false); +} + +Future Glx::IsTexture(const ContextTag& context_tag, + const uint32_t& texture) { + return Glx::IsTexture(Glx::IsTextureRequest{context_tag, texture}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& ret_val = (*reply).ret_val; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // ret_val + Read(&ret_val, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetColorTable( + const Glx::GetColorTableRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& target = request.target; + auto& format = request.format; + auto& type = request.type; + auto& swap_bytes = request.swap_bytes; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 147; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // target + buf.Write(&target); + + // format + buf.Write(&format); + + // type + buf.Write(&type); + + // swap_bytes + buf.Write(&swap_bytes); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::GetColorTable", false); +} + +Future Glx::GetColorTable( + const ContextTag& context_tag, + const uint32_t& target, + const uint32_t& format, + const uint32_t& type, + const uint8_t& swap_bytes) { + return Glx::GetColorTable( + Glx::GetColorTableRequest{context_tag, target, format, type, swap_bytes}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Glx::GetColorTableReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& width = (*reply).width; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 8); + + // width + Read(&width, &buf); + + // pad2 + Pad(&buf, 12); + + // data + data.resize((length) * (4)); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetColorTableParameterfv( + const Glx::GetColorTableParameterfvRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& target = request.target; + auto& pname = request.pname; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 148; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // target + buf.Write(&target); + + // pname + buf.Write(&pname); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::GetColorTableParameterfv", false); +} + +Future Glx::GetColorTableParameterfv( + const ContextTag& context_tag, + const uint32_t& target, + const uint32_t& pname) { + return Glx::GetColorTableParameterfv( + Glx::GetColorTableParameterfvRequest{context_tag, target, pname}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Glx::GetColorTableParameterfvReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t n{}; + auto& datum = (*reply).datum; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 4); + + // n + Read(&n, &buf); + + // datum + Read(&datum, &buf); + + // pad2 + Pad(&buf, 12); + + // data + data.resize(n); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetColorTableParameteriv( + const Glx::GetColorTableParameterivRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& target = request.target; + auto& pname = request.pname; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 149; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // target + buf.Write(&target); + + // pname + buf.Write(&pname); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::GetColorTableParameteriv", false); +} + +Future Glx::GetColorTableParameteriv( + const ContextTag& context_tag, + const uint32_t& target, + const uint32_t& pname) { + return Glx::GetColorTableParameteriv( + Glx::GetColorTableParameterivRequest{context_tag, target, pname}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Glx::GetColorTableParameterivReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t n{}; + auto& datum = (*reply).datum; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 4); + + // n + Read(&n, &buf); + + // datum + Read(&datum, &buf); + + // pad2 + Pad(&buf, 12); + + // data + data.resize(n); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetConvolutionFilter( + const Glx::GetConvolutionFilterRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& target = request.target; + auto& format = request.format; + auto& type = request.type; + auto& swap_bytes = request.swap_bytes; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 150; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // target + buf.Write(&target); + + // format + buf.Write(&format); + + // type + buf.Write(&type); + + // swap_bytes + buf.Write(&swap_bytes); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::GetConvolutionFilter", false); +} + +Future Glx::GetConvolutionFilter( + const ContextTag& context_tag, + const uint32_t& target, + const uint32_t& format, + const uint32_t& type, + const uint8_t& swap_bytes) { + return Glx::GetConvolutionFilter(Glx::GetConvolutionFilterRequest{ + context_tag, target, format, type, swap_bytes}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Glx::GetConvolutionFilterReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& width = (*reply).width; + auto& height = (*reply).height; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 8); + + // width + Read(&width, &buf); + + // height + Read(&height, &buf); + + // pad2 + Pad(&buf, 8); + + // data + data.resize((length) * (4)); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetConvolutionParameterfv( + const Glx::GetConvolutionParameterfvRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& target = request.target; + auto& pname = request.pname; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 151; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // target + buf.Write(&target); + + // pname + buf.Write(&pname); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::GetConvolutionParameterfv", false); +} + +Future Glx::GetConvolutionParameterfv( + const ContextTag& context_tag, + const uint32_t& target, + const uint32_t& pname) { + return Glx::GetConvolutionParameterfv( + Glx::GetConvolutionParameterfvRequest{context_tag, target, pname}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Glx::GetConvolutionParameterfvReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t n{}; + auto& datum = (*reply).datum; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 4); + + // n + Read(&n, &buf); + + // datum + Read(&datum, &buf); + + // pad2 + Pad(&buf, 12); + + // data + data.resize(n); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetConvolutionParameteriv( + const Glx::GetConvolutionParameterivRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& target = request.target; + auto& pname = request.pname; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 152; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // target + buf.Write(&target); + + // pname + buf.Write(&pname); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::GetConvolutionParameteriv", false); +} + +Future Glx::GetConvolutionParameteriv( + const ContextTag& context_tag, + const uint32_t& target, + const uint32_t& pname) { + return Glx::GetConvolutionParameteriv( + Glx::GetConvolutionParameterivRequest{context_tag, target, pname}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Glx::GetConvolutionParameterivReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t n{}; + auto& datum = (*reply).datum; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 4); + + // n + Read(&n, &buf); + + // datum + Read(&datum, &buf); + + // pad2 + Pad(&buf, 12); + + // data + data.resize(n); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetSeparableFilter( + const Glx::GetSeparableFilterRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& target = request.target; + auto& format = request.format; + auto& type = request.type; + auto& swap_bytes = request.swap_bytes; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 153; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // target + buf.Write(&target); + + // format + buf.Write(&format); + + // type + buf.Write(&type); + + // swap_bytes + buf.Write(&swap_bytes); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::GetSeparableFilter", false); +} + +Future Glx::GetSeparableFilter( + const ContextTag& context_tag, + const uint32_t& target, + const uint32_t& format, + const uint32_t& type, + const uint8_t& swap_bytes) { + return Glx::GetSeparableFilter(Glx::GetSeparableFilterRequest{ + context_tag, target, format, type, swap_bytes}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Glx::GetSeparableFilterReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& row_w = (*reply).row_w; + auto& col_h = (*reply).col_h; + auto& rows_and_cols = (*reply).rows_and_cols; + size_t rows_and_cols_len = rows_and_cols.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 8); + + // row_w + Read(&row_w, &buf); + + // col_h + Read(&col_h, &buf); + + // pad2 + Pad(&buf, 8); + + // rows_and_cols + rows_and_cols.resize((length) * (4)); + for (auto& rows_and_cols_elem : rows_and_cols) { + // rows_and_cols_elem + Read(&rows_and_cols_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetHistogram( + const Glx::GetHistogramRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& target = request.target; + auto& format = request.format; + auto& type = request.type; + auto& swap_bytes = request.swap_bytes; + auto& reset = request.reset; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 154; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // target + buf.Write(&target); + + // format + buf.Write(&format); + + // type + buf.Write(&type); + + // swap_bytes + buf.Write(&swap_bytes); + + // reset + buf.Write(&reset); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::GetHistogram", false); +} + +Future Glx::GetHistogram(const ContextTag& context_tag, + const uint32_t& target, + const uint32_t& format, + const uint32_t& type, + const uint8_t& swap_bytes, + const uint8_t& reset) { + return Glx::GetHistogram(Glx::GetHistogramRequest{context_tag, target, format, + type, swap_bytes, reset}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Glx::GetHistogramReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& width = (*reply).width; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 8); + + // width + Read(&width, &buf); + + // pad2 + Pad(&buf, 12); + + // data + data.resize((length) * (4)); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetHistogramParameterfv( + const Glx::GetHistogramParameterfvRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& target = request.target; + auto& pname = request.pname; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 155; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // target + buf.Write(&target); + + // pname + buf.Write(&pname); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::GetHistogramParameterfv", false); +} + +Future Glx::GetHistogramParameterfv( + const ContextTag& context_tag, + const uint32_t& target, + const uint32_t& pname) { + return Glx::GetHistogramParameterfv( + Glx::GetHistogramParameterfvRequest{context_tag, target, pname}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Glx::GetHistogramParameterfvReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t n{}; + auto& datum = (*reply).datum; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 4); + + // n + Read(&n, &buf); + + // datum + Read(&datum, &buf); + + // pad2 + Pad(&buf, 12); + + // data + data.resize(n); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetHistogramParameteriv( + const Glx::GetHistogramParameterivRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& target = request.target; + auto& pname = request.pname; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 156; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // target + buf.Write(&target); + + // pname + buf.Write(&pname); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::GetHistogramParameteriv", false); +} + +Future Glx::GetHistogramParameteriv( + const ContextTag& context_tag, + const uint32_t& target, + const uint32_t& pname) { + return Glx::GetHistogramParameteriv( + Glx::GetHistogramParameterivRequest{context_tag, target, pname}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Glx::GetHistogramParameterivReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t n{}; + auto& datum = (*reply).datum; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 4); + + // n + Read(&n, &buf); + + // datum + Read(&datum, &buf); + + // pad2 + Pad(&buf, 12); + + // data + data.resize(n); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetMinmax( + const Glx::GetMinmaxRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& target = request.target; + auto& format = request.format; + auto& type = request.type; + auto& swap_bytes = request.swap_bytes; + auto& reset = request.reset; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 157; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // target + buf.Write(&target); + + // format + buf.Write(&format); + + // type + buf.Write(&type); + + // swap_bytes + buf.Write(&swap_bytes); + + // reset + buf.Write(&reset); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::GetMinmax", + false); +} + +Future Glx::GetMinmax(const ContextTag& context_tag, + const uint32_t& target, + const uint32_t& format, + const uint32_t& type, + const uint8_t& swap_bytes, + const uint8_t& reset) { + return Glx::GetMinmax(Glx::GetMinmaxRequest{context_tag, target, format, type, + swap_bytes, reset}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 24); + + // data + data.resize((length) * (4)); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetMinmaxParameterfv( + const Glx::GetMinmaxParameterfvRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& target = request.target; + auto& pname = request.pname; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 158; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // target + buf.Write(&target); + + // pname + buf.Write(&pname); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::GetMinmaxParameterfv", false); +} + +Future Glx::GetMinmaxParameterfv( + const ContextTag& context_tag, + const uint32_t& target, + const uint32_t& pname) { + return Glx::GetMinmaxParameterfv( + Glx::GetMinmaxParameterfvRequest{context_tag, target, pname}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Glx::GetMinmaxParameterfvReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t n{}; + auto& datum = (*reply).datum; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 4); + + // n + Read(&n, &buf); + + // datum + Read(&datum, &buf); + + // pad2 + Pad(&buf, 12); + + // data + data.resize(n); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetMinmaxParameteriv( + const Glx::GetMinmaxParameterivRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& target = request.target; + auto& pname = request.pname; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 159; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // target + buf.Write(&target); + + // pname + buf.Write(&pname); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::GetMinmaxParameteriv", false); +} + +Future Glx::GetMinmaxParameteriv( + const ContextTag& context_tag, + const uint32_t& target, + const uint32_t& pname) { + return Glx::GetMinmaxParameteriv( + Glx::GetMinmaxParameterivRequest{context_tag, target, pname}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Glx::GetMinmaxParameterivReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t n{}; + auto& datum = (*reply).datum; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 4); + + // n + Read(&n, &buf); + + // datum + Read(&datum, &buf); + + // pad2 + Pad(&buf, 12); + + // data + data.resize(n); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetCompressedTexImageARB( + const Glx::GetCompressedTexImageARBRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& target = request.target; + auto& level = request.level; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 160; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // target + buf.Write(&target); + + // level + buf.Write(&level); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::GetCompressedTexImageARB", false); +} + +Future Glx::GetCompressedTexImageARB( + const ContextTag& context_tag, + const uint32_t& target, + const int32_t& level) { + return Glx::GetCompressedTexImageARB( + Glx::GetCompressedTexImageARBRequest{context_tag, target, level}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Glx::GetCompressedTexImageARBReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& size = (*reply).size; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 8); + + // size + Read(&size, &buf); + + // pad2 + Pad(&buf, 12); + + // data + data.resize((length) * (4)); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::DeleteQueriesARB( + const Glx::DeleteQueriesARBRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + int32_t n{}; + auto& ids = request.ids; + size_t ids_len = ids.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 161; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // n + n = ids.size(); + buf.Write(&n); + + // ids + DCHECK_EQ(static_cast(n), ids.size()); + for (auto& ids_elem : ids) { + // ids_elem + buf.Write(&ids_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::DeleteQueriesARB", false); +} + +Future Glx::DeleteQueriesARB(const ContextTag& context_tag, + const std::vector& ids) { + return Glx::DeleteQueriesARB(Glx::DeleteQueriesARBRequest{context_tag, ids}); +} + +Future Glx::GenQueriesARB( + const Glx::GenQueriesARBRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& n = request.n; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 162; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // n + buf.Write(&n); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::GenQueriesARB", false); +} + +Future Glx::GenQueriesARB( + const ContextTag& context_tag, + const int32_t& n) { + return Glx::GenQueriesARB(Glx::GenQueriesARBRequest{context_tag, n}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Glx::GenQueriesARBReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 24); + + // data + data.resize(length); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::IsQueryARB( + const Glx::IsQueryARBRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& id = request.id; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 163; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // id + buf.Write(&id); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Glx::IsQueryARB", + false); +} + +Future Glx::IsQueryARB(const ContextTag& context_tag, + const uint32_t& id) { + return Glx::IsQueryARB(Glx::IsQueryARBRequest{context_tag, id}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& ret_val = (*reply).ret_val; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // ret_val + Read(&ret_val, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetQueryivARB( + const Glx::GetQueryivARBRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& target = request.target; + auto& pname = request.pname; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 164; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // target + buf.Write(&target); + + // pname + buf.Write(&pname); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::GetQueryivARB", false); +} + +Future Glx::GetQueryivARB( + const ContextTag& context_tag, + const uint32_t& target, + const uint32_t& pname) { + return Glx::GetQueryivARB( + Glx::GetQueryivARBRequest{context_tag, target, pname}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Glx::GetQueryivARBReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t n{}; + auto& datum = (*reply).datum; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 4); + + // n + Read(&n, &buf); + + // datum + Read(&datum, &buf); + + // pad2 + Pad(&buf, 12); + + // data + data.resize(n); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetQueryObjectivARB( + const Glx::GetQueryObjectivARBRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& id = request.id; + auto& pname = request.pname; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 165; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // id + buf.Write(&id); + + // pname + buf.Write(&pname); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::GetQueryObjectivARB", false); +} + +Future Glx::GetQueryObjectivARB( + const ContextTag& context_tag, + const uint32_t& id, + const uint32_t& pname) { + return Glx::GetQueryObjectivARB( + Glx::GetQueryObjectivARBRequest{context_tag, id, pname}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Glx::GetQueryObjectivARBReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t n{}; + auto& datum = (*reply).datum; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 4); + + // n + Read(&n, &buf); + + // datum + Read(&datum, &buf); + + // pad2 + Pad(&buf, 12); + + // data + data.resize(n); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Glx::GetQueryObjectuivARB( + const Glx::GetQueryObjectuivARBRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context_tag = request.context_tag; + auto& id = request.id; + auto& pname = request.pname; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 166; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context_tag + buf.Write(&context_tag); + + // id + buf.Write(&id); + + // pname + buf.Write(&pname); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Glx::GetQueryObjectuivARB", false); +} + +Future Glx::GetQueryObjectuivARB( + const ContextTag& context_tag, + const uint32_t& id, + const uint32_t& pname) { + return Glx::GetQueryObjectuivARB( + Glx::GetQueryObjectuivARBRequest{context_tag, id, pname}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Glx::GetQueryObjectuivARBReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t n{}; + auto& datum = (*reply).datum; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 4); + + // n + Read(&n, &buf); + + // datum + Read(&datum, &buf); + + // pad2 + Pad(&buf, 12); + + // data + data.resize(n); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +} // namespace x11 diff --git a/x/generated_protos/glx.h b/x/generated_protos/glx.h new file mode 100644 index 000000000000..cc805bbd2327 --- /dev/null +++ b/x/generated_protos/glx.h @@ -0,0 +1,2235 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#ifndef UI_GFX_X_GENERATED_PROTOS_GLX_H_ +#define UI_GFX_X_GENERATED_PROTOS_GLX_H_ + +#include +#include +#include +#include +#include + +#include "base/component_export.h" +#include "base/files/scoped_file.h" +#include "base/memory/ref_counted_memory.h" +#include "base/memory/scoped_refptr.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/gfx/x/error.h" +#include "ui/gfx/x/ref_counted_fd.h" +#include "xproto.h" + +namespace x11 { + +class Connection; + +template +struct Response; + +template +class Future; + +class COMPONENT_EXPORT(X11) Glx { + public: + static constexpr unsigned major_version = 1; + static constexpr unsigned minor_version = 4; + + Glx(Connection* connection, const x11::QueryExtensionReply& info); + + uint8_t present() const { return info_.present; } + uint8_t major_opcode() const { return info_.major_opcode; } + uint8_t first_event() const { return info_.first_event; } + uint8_t first_error() const { return info_.first_error; } + + Connection* connection() const { return connection_; } + + enum class Pixmap : uint32_t {}; + + enum class Context : uint32_t {}; + + enum class PBuffer : uint32_t {}; + + enum class Window : uint32_t {}; + + enum class FbConfig : uint32_t {}; + + enum class Bool32 : uint32_t {}; + + enum class ContextTag : uint32_t {}; + + enum class Pbcet : int { + Damaged = 32791, + Saved = 32792, + }; + + enum class Pbcdt : int { + Window = 32793, + Pbuffer = 32794, + }; + + enum class GraphicsContextAttribute : int { + XPROTO_GL_ALL_ATTRIB_BITS = 16777215, + XPROTO_GL_CURRENT_BIT = 1 << 0, + XPROTO_GL_POINT_BIT = 1 << 1, + XPROTO_GL_LINE_BIT = 1 << 2, + XPROTO_GL_POLYGON_BIT = 1 << 3, + XPROTO_GL_POLYGON_STIPPLE_BIT = 1 << 4, + XPROTO_GL_PIXEL_MODE_BIT = 1 << 5, + XPROTO_GL_LIGHTING_BIT = 1 << 6, + XPROTO_GL_FOG_BIT = 1 << 7, + XPROTO_GL_DEPTH_BUFFER_BIT = 1 << 8, + XPROTO_GL_ACCUM_BUFFER_BIT = 1 << 9, + XPROTO_GL_STENCIL_BUFFER_BIT = 1 << 10, + XPROTO_GL_VIEWPORT_BIT = 1 << 11, + XPROTO_GL_TRANSFORM_BIT = 1 << 12, + XPROTO_GL_ENABLE_BIT = 1 << 13, + XPROTO_GL_COLOR_BUFFER_BIT = 1 << 14, + XPROTO_GL_HINT_BIT = 1 << 15, + XPROTO_GL_EVAL_BIT = 1 << 16, + XPROTO_GL_LIST_BIT = 1 << 17, + XPROTO_GL_TEXTURE_BIT = 1 << 18, + XPROTO_GL_SCISSOR_BIT = 1 << 19, + }; + + enum class Rm : int { + XPROTO_GL_RENDER = 7168, + XPROTO_GL_FEEDBACK = 7169, + XPROTO_GL_SELECT = 7170, + }; + + struct Drawable { + Drawable() : value{} {} + + Drawable(x11::Window value) : value{static_cast(value)} {} + operator x11::Window() const { return static_cast(value); } + + Drawable(PBuffer value) : value{static_cast(value)} {} + operator PBuffer() const { return static_cast(value); } + + Drawable(Pixmap value) : value{static_cast(value)} {} + operator Pixmap() const { return static_cast(value); } + + Drawable(Window value) : value{static_cast(value)} {} + operator Window() const { return static_cast(value); } + + uint32_t value{}; + }; + + struct GenericError : public x11::Error { + uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; + + std::string ToString() const override; + }; + + struct BadContextError : public x11::Error { + uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; + + std::string ToString() const override; + }; + + struct BadContextStateError : public x11::Error { + uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; + + std::string ToString() const override; + }; + + struct BadDrawableError : public x11::Error { + uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; + + std::string ToString() const override; + }; + + struct BadPixmapError : public x11::Error { + uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; + + std::string ToString() const override; + }; + + struct BadContextTagError : public x11::Error { + uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; + + std::string ToString() const override; + }; + + struct BadCurrentWindowError : public x11::Error { + uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; + + std::string ToString() const override; + }; + + struct BadRenderRequestError : public x11::Error { + uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; + + std::string ToString() const override; + }; + + struct BadLargeRequestError : public x11::Error { + uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; + + std::string ToString() const override; + }; + + struct UnsupportedPrivateRequestError : public x11::Error { + uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; + + std::string ToString() const override; + }; + + struct BadFBConfigError : public x11::Error { + uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; + + std::string ToString() const override; + }; + + struct BadPbufferError : public x11::Error { + uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; + + std::string ToString() const override; + }; + + struct BadCurrentDrawableError : public x11::Error { + uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; + + std::string ToString() const override; + }; + + struct BadWindowError : public x11::Error { + uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; + + std::string ToString() const override; + }; + + struct GLXBadProfileARBError : public x11::Error { + uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; + + std::string ToString() const override; + }; + + struct PbufferClobberEvent { + static constexpr int type_id = 4; + static constexpr uint8_t opcode = 0; + bool send_event{}; + uint16_t sequence{}; + uint16_t event_type{}; + uint16_t draw_type{}; + Drawable drawable{}; + uint32_t b_mask{}; + uint16_t aux_buffer{}; + uint16_t x{}; + uint16_t y{}; + uint16_t width{}; + uint16_t height{}; + uint16_t count{}; + + x11::Window* GetWindow() { + return reinterpret_cast(&drawable); + } + }; + + struct BufferSwapCompleteEvent { + static constexpr int type_id = 5; + static constexpr uint8_t opcode = 1; + bool send_event{}; + uint16_t sequence{}; + uint16_t event_type{}; + Drawable drawable{}; + uint32_t ust_hi{}; + uint32_t ust_lo{}; + uint32_t msc_hi{}; + uint32_t msc_lo{}; + uint32_t sbc{}; + + x11::Window* GetWindow() { + return reinterpret_cast(&drawable); + } + }; + + struct RenderRequest { + ContextTag context_tag{}; + std::vector data{}; + }; + + using RenderResponse = Response; + + Future Render(const RenderRequest& request); + + Future Render(const ContextTag& context_tag = {}, + const std::vector& data = {}); + + struct RenderLargeRequest { + ContextTag context_tag{}; + uint16_t request_num{}; + uint16_t request_total{}; + std::vector data{}; + }; + + using RenderLargeResponse = Response; + + Future RenderLarge(const RenderLargeRequest& request); + + Future RenderLarge(const ContextTag& context_tag = {}, + const uint16_t& request_num = {}, + const uint16_t& request_total = {}, + const std::vector& data = {}); + + struct CreateContextRequest { + Context context{}; + VisualId visual{}; + uint32_t screen{}; + Context share_list{}; + uint8_t is_direct{}; + }; + + using CreateContextResponse = Response; + + Future CreateContext(const CreateContextRequest& request); + + Future CreateContext(const Context& context = {}, + const VisualId& visual = {}, + const uint32_t& screen = {}, + const Context& share_list = {}, + const uint8_t& is_direct = {}); + + struct DestroyContextRequest { + Context context{}; + }; + + using DestroyContextResponse = Response; + + Future DestroyContext(const DestroyContextRequest& request); + + Future DestroyContext(const Context& context = {}); + + struct MakeCurrentRequest { + Drawable drawable{}; + Context context{}; + ContextTag old_context_tag{}; + }; + + struct MakeCurrentReply { + uint16_t sequence{}; + ContextTag context_tag{}; + }; + + using MakeCurrentResponse = Response; + + Future MakeCurrent(const MakeCurrentRequest& request); + + Future MakeCurrent(const Drawable& drawable = {}, + const Context& context = {}, + const ContextTag& old_context_tag = {}); + + struct IsDirectRequest { + Context context{}; + }; + + struct IsDirectReply { + uint16_t sequence{}; + uint8_t is_direct{}; + }; + + using IsDirectResponse = Response; + + Future IsDirect(const IsDirectRequest& request); + + Future IsDirect(const Context& context = {}); + + struct QueryVersionRequest { + uint32_t major_version{}; + uint32_t minor_version{}; + }; + + struct QueryVersionReply { + uint16_t sequence{}; + uint32_t major_version{}; + uint32_t minor_version{}; + }; + + using QueryVersionResponse = Response; + + Future QueryVersion(const QueryVersionRequest& request); + + Future QueryVersion(const uint32_t& major_version = {}, + const uint32_t& minor_version = {}); + + struct WaitGLRequest { + ContextTag context_tag{}; + }; + + using WaitGLResponse = Response; + + Future WaitGL(const WaitGLRequest& request); + + Future WaitGL(const ContextTag& context_tag = {}); + + struct WaitXRequest { + ContextTag context_tag{}; + }; + + using WaitXResponse = Response; + + Future WaitX(const WaitXRequest& request); + + Future WaitX(const ContextTag& context_tag = {}); + + struct CopyContextRequest { + Context src{}; + Context dest{}; + uint32_t mask{}; + ContextTag src_context_tag{}; + }; + + using CopyContextResponse = Response; + + Future CopyContext(const CopyContextRequest& request); + + Future CopyContext(const Context& src = {}, + const Context& dest = {}, + const uint32_t& mask = {}, + const ContextTag& src_context_tag = {}); + + struct SwapBuffersRequest { + ContextTag context_tag{}; + Drawable drawable{}; + }; + + using SwapBuffersResponse = Response; + + Future SwapBuffers(const SwapBuffersRequest& request); + + Future SwapBuffers(const ContextTag& context_tag = {}, + const Drawable& drawable = {}); + + struct UseXFontRequest { + ContextTag context_tag{}; + Font font{}; + uint32_t first{}; + uint32_t count{}; + uint32_t list_base{}; + }; + + using UseXFontResponse = Response; + + Future UseXFont(const UseXFontRequest& request); + + Future UseXFont(const ContextTag& context_tag = {}, + const Font& font = {}, + const uint32_t& first = {}, + const uint32_t& count = {}, + const uint32_t& list_base = {}); + + struct CreateGLXPixmapRequest { + uint32_t screen{}; + VisualId visual{}; + x11::Pixmap pixmap{}; + Pixmap glx_pixmap{}; + }; + + using CreateGLXPixmapResponse = Response; + + Future CreateGLXPixmap(const CreateGLXPixmapRequest& request); + + Future CreateGLXPixmap(const uint32_t& screen = {}, + const VisualId& visual = {}, + const x11::Pixmap& pixmap = {}, + const Pixmap& glx_pixmap = {}); + + struct GetVisualConfigsRequest { + uint32_t screen{}; + }; + + struct GetVisualConfigsReply { + uint16_t sequence{}; + uint32_t num_visuals{}; + uint32_t num_properties{}; + std::vector property_list{}; + }; + + using GetVisualConfigsResponse = Response; + + Future GetVisualConfigs( + const GetVisualConfigsRequest& request); + + Future GetVisualConfigs(const uint32_t& screen = {}); + + struct DestroyGLXPixmapRequest { + Pixmap glx_pixmap{}; + }; + + using DestroyGLXPixmapResponse = Response; + + Future DestroyGLXPixmap(const DestroyGLXPixmapRequest& request); + + Future DestroyGLXPixmap(const Pixmap& glx_pixmap = {}); + + struct VendorPrivateRequest { + uint32_t vendor_code{}; + ContextTag context_tag{}; + std::vector data{}; + }; + + using VendorPrivateResponse = Response; + + Future VendorPrivate(const VendorPrivateRequest& request); + + Future VendorPrivate(const uint32_t& vendor_code = {}, + const ContextTag& context_tag = {}, + const std::vector& data = {}); + + struct VendorPrivateWithReplyRequest { + uint32_t vendor_code{}; + ContextTag context_tag{}; + std::vector data{}; + }; + + struct VendorPrivateWithReplyReply { + uint16_t sequence{}; + uint32_t retval{}; + std::array data1{}; + std::vector data2{}; + }; + + using VendorPrivateWithReplyResponse = Response; + + Future VendorPrivateWithReply( + const VendorPrivateWithReplyRequest& request); + + Future VendorPrivateWithReply( + const uint32_t& vendor_code = {}, + const ContextTag& context_tag = {}, + const std::vector& data = {}); + + struct QueryExtensionsStringRequest { + uint32_t screen{}; + }; + + struct QueryExtensionsStringReply { + uint16_t sequence{}; + uint32_t n{}; + }; + + using QueryExtensionsStringResponse = Response; + + Future QueryExtensionsString( + const QueryExtensionsStringRequest& request); + + Future QueryExtensionsString( + const uint32_t& screen = {}); + + struct QueryServerStringRequest { + uint32_t screen{}; + uint32_t name{}; + }; + + struct QueryServerStringReply { + uint16_t sequence{}; + std::string string{}; + }; + + using QueryServerStringResponse = Response; + + Future QueryServerString( + const QueryServerStringRequest& request); + + Future QueryServerString(const uint32_t& screen = {}, + const uint32_t& name = {}); + + struct ClientInfoRequest { + uint32_t major_version{}; + uint32_t minor_version{}; + std::string string{}; + }; + + using ClientInfoResponse = Response; + + Future ClientInfo(const ClientInfoRequest& request); + + Future ClientInfo(const uint32_t& major_version = {}, + const uint32_t& minor_version = {}, + const std::string& string = {}); + + struct GetFBConfigsRequest { + uint32_t screen{}; + }; + + struct GetFBConfigsReply { + uint16_t sequence{}; + uint32_t num_FB_configs{}; + uint32_t num_properties{}; + std::vector property_list{}; + }; + + using GetFBConfigsResponse = Response; + + Future GetFBConfigs(const GetFBConfigsRequest& request); + + Future GetFBConfigs(const uint32_t& screen = {}); + + struct CreatePixmapRequest { + uint32_t screen{}; + FbConfig fbconfig{}; + x11::Pixmap pixmap{}; + Pixmap glx_pixmap{}; + uint32_t num_attribs{}; + std::vector attribs{}; + }; + + using CreatePixmapResponse = Response; + + Future CreatePixmap(const CreatePixmapRequest& request); + + Future CreatePixmap(const uint32_t& screen = {}, + const FbConfig& fbconfig = {}, + const x11::Pixmap& pixmap = {}, + const Pixmap& glx_pixmap = {}, + const uint32_t& num_attribs = {}, + const std::vector& attribs = {}); + + struct DestroyPixmapRequest { + Pixmap glx_pixmap{}; + }; + + using DestroyPixmapResponse = Response; + + Future DestroyPixmap(const DestroyPixmapRequest& request); + + Future DestroyPixmap(const Pixmap& glx_pixmap = {}); + + struct CreateNewContextRequest { + Context context{}; + FbConfig fbconfig{}; + uint32_t screen{}; + uint32_t render_type{}; + Context share_list{}; + uint8_t is_direct{}; + }; + + using CreateNewContextResponse = Response; + + Future CreateNewContext(const CreateNewContextRequest& request); + + Future CreateNewContext(const Context& context = {}, + const FbConfig& fbconfig = {}, + const uint32_t& screen = {}, + const uint32_t& render_type = {}, + const Context& share_list = {}, + const uint8_t& is_direct = {}); + + struct QueryContextRequest { + Context context{}; + }; + + struct QueryContextReply { + uint16_t sequence{}; + uint32_t num_attribs{}; + std::vector attribs{}; + }; + + using QueryContextResponse = Response; + + Future QueryContext(const QueryContextRequest& request); + + Future QueryContext(const Context& context = {}); + + struct MakeContextCurrentRequest { + ContextTag old_context_tag{}; + Drawable drawable{}; + Drawable read_drawable{}; + Context context{}; + }; + + struct MakeContextCurrentReply { + uint16_t sequence{}; + ContextTag context_tag{}; + }; + + using MakeContextCurrentResponse = Response; + + Future MakeContextCurrent( + const MakeContextCurrentRequest& request); + + Future MakeContextCurrent( + const ContextTag& old_context_tag = {}, + const Drawable& drawable = {}, + const Drawable& read_drawable = {}, + const Context& context = {}); + + struct CreatePbufferRequest { + uint32_t screen{}; + FbConfig fbconfig{}; + PBuffer pbuffer{}; + uint32_t num_attribs{}; + std::vector attribs{}; + }; + + using CreatePbufferResponse = Response; + + Future CreatePbuffer(const CreatePbufferRequest& request); + + Future CreatePbuffer(const uint32_t& screen = {}, + const FbConfig& fbconfig = {}, + const PBuffer& pbuffer = {}, + const uint32_t& num_attribs = {}, + const std::vector& attribs = {}); + + struct DestroyPbufferRequest { + PBuffer pbuffer{}; + }; + + using DestroyPbufferResponse = Response; + + Future DestroyPbuffer(const DestroyPbufferRequest& request); + + Future DestroyPbuffer(const PBuffer& pbuffer = {}); + + struct GetDrawableAttributesRequest { + Drawable drawable{}; + }; + + struct GetDrawableAttributesReply { + uint16_t sequence{}; + uint32_t num_attribs{}; + std::vector attribs{}; + }; + + using GetDrawableAttributesResponse = Response; + + Future GetDrawableAttributes( + const GetDrawableAttributesRequest& request); + + Future GetDrawableAttributes( + const Drawable& drawable = {}); + + struct ChangeDrawableAttributesRequest { + Drawable drawable{}; + uint32_t num_attribs{}; + std::vector attribs{}; + }; + + using ChangeDrawableAttributesResponse = Response; + + Future ChangeDrawableAttributes( + const ChangeDrawableAttributesRequest& request); + + Future ChangeDrawableAttributes( + const Drawable& drawable = {}, + const uint32_t& num_attribs = {}, + const std::vector& attribs = {}); + + struct CreateWindowRequest { + uint32_t screen{}; + FbConfig fbconfig{}; + x11::Window window{}; + Window glx_window{}; + uint32_t num_attribs{}; + std::vector attribs{}; + }; + + using CreateWindowResponse = Response; + + Future CreateWindow(const CreateWindowRequest& request); + + Future CreateWindow(const uint32_t& screen = {}, + const FbConfig& fbconfig = {}, + const x11::Window& window = {}, + const Window& glx_window = {}, + const uint32_t& num_attribs = {}, + const std::vector& attribs = {}); + + struct DeleteWindowRequest { + Window glxwindow{}; + }; + + using DeleteWindowResponse = Response; + + Future DeleteWindow(const DeleteWindowRequest& request); + + Future DeleteWindow(const Window& glxwindow = {}); + + struct SetClientInfoARBRequest { + uint32_t major_version{}; + uint32_t minor_version{}; + uint32_t num_versions{}; + std::vector gl_versions{}; + std::string gl_extension_string{}; + std::string glx_extension_string{}; + }; + + using SetClientInfoARBResponse = Response; + + Future SetClientInfoARB(const SetClientInfoARBRequest& request); + + Future SetClientInfoARB(const uint32_t& major_version = {}, + const uint32_t& minor_version = {}, + const uint32_t& num_versions = {}, + const std::vector& gl_versions = {}, + const std::string& gl_extension_string = {}, + const std::string& glx_extension_string = {}); + + struct CreateContextAttribsARBRequest { + Context context{}; + FbConfig fbconfig{}; + uint32_t screen{}; + Context share_list{}; + uint8_t is_direct{}; + uint32_t num_attribs{}; + std::vector attribs{}; + }; + + using CreateContextAttribsARBResponse = Response; + + Future CreateContextAttribsARB( + const CreateContextAttribsARBRequest& request); + + Future CreateContextAttribsARB( + const Context& context = {}, + const FbConfig& fbconfig = {}, + const uint32_t& screen = {}, + const Context& share_list = {}, + const uint8_t& is_direct = {}, + const uint32_t& num_attribs = {}, + const std::vector& attribs = {}); + + struct SetClientInfo2ARBRequest { + uint32_t major_version{}; + uint32_t minor_version{}; + uint32_t num_versions{}; + std::vector gl_versions{}; + std::string gl_extension_string{}; + std::string glx_extension_string{}; + }; + + using SetClientInfo2ARBResponse = Response; + + Future SetClientInfo2ARB(const SetClientInfo2ARBRequest& request); + + Future SetClientInfo2ARB(const uint32_t& major_version = {}, + const uint32_t& minor_version = {}, + const uint32_t& num_versions = {}, + const std::vector& gl_versions = {}, + const std::string& gl_extension_string = {}, + const std::string& glx_extension_string = {}); + + struct NewListRequest { + ContextTag context_tag{}; + uint32_t list{}; + uint32_t mode{}; + }; + + using NewListResponse = Response; + + Future NewList(const NewListRequest& request); + + Future NewList(const ContextTag& context_tag = {}, + const uint32_t& list = {}, + const uint32_t& mode = {}); + + struct EndListRequest { + ContextTag context_tag{}; + }; + + using EndListResponse = Response; + + Future EndList(const EndListRequest& request); + + Future EndList(const ContextTag& context_tag = {}); + + struct DeleteListsRequest { + ContextTag context_tag{}; + uint32_t list{}; + int32_t range{}; + }; + + using DeleteListsResponse = Response; + + Future DeleteLists(const DeleteListsRequest& request); + + Future DeleteLists(const ContextTag& context_tag = {}, + const uint32_t& list = {}, + const int32_t& range = {}); + + struct GenListsRequest { + ContextTag context_tag{}; + int32_t range{}; + }; + + struct GenListsReply { + uint16_t sequence{}; + uint32_t ret_val{}; + }; + + using GenListsResponse = Response; + + Future GenLists(const GenListsRequest& request); + + Future GenLists(const ContextTag& context_tag = {}, + const int32_t& range = {}); + + struct FeedbackBufferRequest { + ContextTag context_tag{}; + int32_t size{}; + int32_t type{}; + }; + + using FeedbackBufferResponse = Response; + + Future FeedbackBuffer(const FeedbackBufferRequest& request); + + Future FeedbackBuffer(const ContextTag& context_tag = {}, + const int32_t& size = {}, + const int32_t& type = {}); + + struct SelectBufferRequest { + ContextTag context_tag{}; + int32_t size{}; + }; + + using SelectBufferResponse = Response; + + Future SelectBuffer(const SelectBufferRequest& request); + + Future SelectBuffer(const ContextTag& context_tag = {}, + const int32_t& size = {}); + + struct RenderModeRequest { + ContextTag context_tag{}; + uint32_t mode{}; + }; + + struct RenderModeReply { + uint16_t sequence{}; + uint32_t ret_val{}; + uint32_t new_mode{}; + std::vector data{}; + }; + + using RenderModeResponse = Response; + + Future RenderMode(const RenderModeRequest& request); + + Future RenderMode(const ContextTag& context_tag = {}, + const uint32_t& mode = {}); + + struct FinishRequest { + ContextTag context_tag{}; + }; + + struct FinishReply { + uint16_t sequence{}; + }; + + using FinishResponse = Response; + + Future Finish(const FinishRequest& request); + + Future Finish(const ContextTag& context_tag = {}); + + struct PixelStorefRequest { + ContextTag context_tag{}; + uint32_t pname{}; + float datum{}; + }; + + using PixelStorefResponse = Response; + + Future PixelStoref(const PixelStorefRequest& request); + + Future PixelStoref(const ContextTag& context_tag = {}, + const uint32_t& pname = {}, + const float& datum = {}); + + struct PixelStoreiRequest { + ContextTag context_tag{}; + uint32_t pname{}; + int32_t datum{}; + }; + + using PixelStoreiResponse = Response; + + Future PixelStorei(const PixelStoreiRequest& request); + + Future PixelStorei(const ContextTag& context_tag = {}, + const uint32_t& pname = {}, + const int32_t& datum = {}); + + struct ReadPixelsRequest { + ContextTag context_tag{}; + int32_t x{}; + int32_t y{}; + int32_t width{}; + int32_t height{}; + uint32_t format{}; + uint32_t type{}; + uint8_t swap_bytes{}; + uint8_t lsb_first{}; + }; + + struct ReadPixelsReply { + uint16_t sequence{}; + std::vector data{}; + }; + + using ReadPixelsResponse = Response; + + Future ReadPixels(const ReadPixelsRequest& request); + + Future ReadPixels(const ContextTag& context_tag = {}, + const int32_t& x = {}, + const int32_t& y = {}, + const int32_t& width = {}, + const int32_t& height = {}, + const uint32_t& format = {}, + const uint32_t& type = {}, + const uint8_t& swap_bytes = {}, + const uint8_t& lsb_first = {}); + + struct GetBooleanvRequest { + ContextTag context_tag{}; + int32_t pname{}; + }; + + struct GetBooleanvReply { + uint16_t sequence{}; + uint8_t datum{}; + std::vector data{}; + }; + + using GetBooleanvResponse = Response; + + Future GetBooleanv(const GetBooleanvRequest& request); + + Future GetBooleanv(const ContextTag& context_tag = {}, + const int32_t& pname = {}); + + struct GetClipPlaneRequest { + ContextTag context_tag{}; + int32_t plane{}; + }; + + struct GetClipPlaneReply { + uint16_t sequence{}; + std::vector data{}; + }; + + using GetClipPlaneResponse = Response; + + Future GetClipPlane(const GetClipPlaneRequest& request); + + Future GetClipPlane(const ContextTag& context_tag = {}, + const int32_t& plane = {}); + + struct GetDoublevRequest { + ContextTag context_tag{}; + uint32_t pname{}; + }; + + struct GetDoublevReply { + uint16_t sequence{}; + double datum{}; + std::vector data{}; + }; + + using GetDoublevResponse = Response; + + Future GetDoublev(const GetDoublevRequest& request); + + Future GetDoublev(const ContextTag& context_tag = {}, + const uint32_t& pname = {}); + + struct GetErrorRequest { + ContextTag context_tag{}; + }; + + struct GetErrorReply { + uint16_t sequence{}; + int32_t error{}; + }; + + using GetErrorResponse = Response; + + Future GetError(const GetErrorRequest& request); + + Future GetError(const ContextTag& context_tag = {}); + + struct GetFloatvRequest { + ContextTag context_tag{}; + uint32_t pname{}; + }; + + struct GetFloatvReply { + uint16_t sequence{}; + float datum{}; + std::vector data{}; + }; + + using GetFloatvResponse = Response; + + Future GetFloatv(const GetFloatvRequest& request); + + Future GetFloatv(const ContextTag& context_tag = {}, + const uint32_t& pname = {}); + + struct GetIntegervRequest { + ContextTag context_tag{}; + uint32_t pname{}; + }; + + struct GetIntegervReply { + uint16_t sequence{}; + int32_t datum{}; + std::vector data{}; + }; + + using GetIntegervResponse = Response; + + Future GetIntegerv(const GetIntegervRequest& request); + + Future GetIntegerv(const ContextTag& context_tag = {}, + const uint32_t& pname = {}); + + struct GetLightfvRequest { + ContextTag context_tag{}; + uint32_t light{}; + uint32_t pname{}; + }; + + struct GetLightfvReply { + uint16_t sequence{}; + float datum{}; + std::vector data{}; + }; + + using GetLightfvResponse = Response; + + Future GetLightfv(const GetLightfvRequest& request); + + Future GetLightfv(const ContextTag& context_tag = {}, + const uint32_t& light = {}, + const uint32_t& pname = {}); + + struct GetLightivRequest { + ContextTag context_tag{}; + uint32_t light{}; + uint32_t pname{}; + }; + + struct GetLightivReply { + uint16_t sequence{}; + int32_t datum{}; + std::vector data{}; + }; + + using GetLightivResponse = Response; + + Future GetLightiv(const GetLightivRequest& request); + + Future GetLightiv(const ContextTag& context_tag = {}, + const uint32_t& light = {}, + const uint32_t& pname = {}); + + struct GetMapdvRequest { + ContextTag context_tag{}; + uint32_t target{}; + uint32_t query{}; + }; + + struct GetMapdvReply { + uint16_t sequence{}; + double datum{}; + std::vector data{}; + }; + + using GetMapdvResponse = Response; + + Future GetMapdv(const GetMapdvRequest& request); + + Future GetMapdv(const ContextTag& context_tag = {}, + const uint32_t& target = {}, + const uint32_t& query = {}); + + struct GetMapfvRequest { + ContextTag context_tag{}; + uint32_t target{}; + uint32_t query{}; + }; + + struct GetMapfvReply { + uint16_t sequence{}; + float datum{}; + std::vector data{}; + }; + + using GetMapfvResponse = Response; + + Future GetMapfv(const GetMapfvRequest& request); + + Future GetMapfv(const ContextTag& context_tag = {}, + const uint32_t& target = {}, + const uint32_t& query = {}); + + struct GetMapivRequest { + ContextTag context_tag{}; + uint32_t target{}; + uint32_t query{}; + }; + + struct GetMapivReply { + uint16_t sequence{}; + int32_t datum{}; + std::vector data{}; + }; + + using GetMapivResponse = Response; + + Future GetMapiv(const GetMapivRequest& request); + + Future GetMapiv(const ContextTag& context_tag = {}, + const uint32_t& target = {}, + const uint32_t& query = {}); + + struct GetMaterialfvRequest { + ContextTag context_tag{}; + uint32_t face{}; + uint32_t pname{}; + }; + + struct GetMaterialfvReply { + uint16_t sequence{}; + float datum{}; + std::vector data{}; + }; + + using GetMaterialfvResponse = Response; + + Future GetMaterialfv(const GetMaterialfvRequest& request); + + Future GetMaterialfv(const ContextTag& context_tag = {}, + const uint32_t& face = {}, + const uint32_t& pname = {}); + + struct GetMaterialivRequest { + ContextTag context_tag{}; + uint32_t face{}; + uint32_t pname{}; + }; + + struct GetMaterialivReply { + uint16_t sequence{}; + int32_t datum{}; + std::vector data{}; + }; + + using GetMaterialivResponse = Response; + + Future GetMaterialiv(const GetMaterialivRequest& request); + + Future GetMaterialiv(const ContextTag& context_tag = {}, + const uint32_t& face = {}, + const uint32_t& pname = {}); + + struct GetPixelMapfvRequest { + ContextTag context_tag{}; + uint32_t map{}; + }; + + struct GetPixelMapfvReply { + uint16_t sequence{}; + float datum{}; + std::vector data{}; + }; + + using GetPixelMapfvResponse = Response; + + Future GetPixelMapfv(const GetPixelMapfvRequest& request); + + Future GetPixelMapfv(const ContextTag& context_tag = {}, + const uint32_t& map = {}); + + struct GetPixelMapuivRequest { + ContextTag context_tag{}; + uint32_t map{}; + }; + + struct GetPixelMapuivReply { + uint16_t sequence{}; + uint32_t datum{}; + std::vector data{}; + }; + + using GetPixelMapuivResponse = Response; + + Future GetPixelMapuiv( + const GetPixelMapuivRequest& request); + + Future GetPixelMapuiv(const ContextTag& context_tag = {}, + const uint32_t& map = {}); + + struct GetPixelMapusvRequest { + ContextTag context_tag{}; + uint32_t map{}; + }; + + struct GetPixelMapusvReply { + uint16_t sequence{}; + uint16_t datum{}; + std::vector data{}; + }; + + using GetPixelMapusvResponse = Response; + + Future GetPixelMapusv( + const GetPixelMapusvRequest& request); + + Future GetPixelMapusv(const ContextTag& context_tag = {}, + const uint32_t& map = {}); + + struct GetPolygonStippleRequest { + ContextTag context_tag{}; + uint8_t lsb_first{}; + }; + + struct GetPolygonStippleReply { + uint16_t sequence{}; + std::vector data{}; + }; + + using GetPolygonStippleResponse = Response; + + Future GetPolygonStipple( + const GetPolygonStippleRequest& request); + + Future GetPolygonStipple( + const ContextTag& context_tag = {}, + const uint8_t& lsb_first = {}); + + struct GetStringRequest { + ContextTag context_tag{}; + uint32_t name{}; + }; + + struct GetStringReply { + uint16_t sequence{}; + std::string string{}; + }; + + using GetStringResponse = Response; + + Future GetString(const GetStringRequest& request); + + Future GetString(const ContextTag& context_tag = {}, + const uint32_t& name = {}); + + struct GetTexEnvfvRequest { + ContextTag context_tag{}; + uint32_t target{}; + uint32_t pname{}; + }; + + struct GetTexEnvfvReply { + uint16_t sequence{}; + float datum{}; + std::vector data{}; + }; + + using GetTexEnvfvResponse = Response; + + Future GetTexEnvfv(const GetTexEnvfvRequest& request); + + Future GetTexEnvfv(const ContextTag& context_tag = {}, + const uint32_t& target = {}, + const uint32_t& pname = {}); + + struct GetTexEnvivRequest { + ContextTag context_tag{}; + uint32_t target{}; + uint32_t pname{}; + }; + + struct GetTexEnvivReply { + uint16_t sequence{}; + int32_t datum{}; + std::vector data{}; + }; + + using GetTexEnvivResponse = Response; + + Future GetTexEnviv(const GetTexEnvivRequest& request); + + Future GetTexEnviv(const ContextTag& context_tag = {}, + const uint32_t& target = {}, + const uint32_t& pname = {}); + + struct GetTexGendvRequest { + ContextTag context_tag{}; + uint32_t coord{}; + uint32_t pname{}; + }; + + struct GetTexGendvReply { + uint16_t sequence{}; + double datum{}; + std::vector data{}; + }; + + using GetTexGendvResponse = Response; + + Future GetTexGendv(const GetTexGendvRequest& request); + + Future GetTexGendv(const ContextTag& context_tag = {}, + const uint32_t& coord = {}, + const uint32_t& pname = {}); + + struct GetTexGenfvRequest { + ContextTag context_tag{}; + uint32_t coord{}; + uint32_t pname{}; + }; + + struct GetTexGenfvReply { + uint16_t sequence{}; + float datum{}; + std::vector data{}; + }; + + using GetTexGenfvResponse = Response; + + Future GetTexGenfv(const GetTexGenfvRequest& request); + + Future GetTexGenfv(const ContextTag& context_tag = {}, + const uint32_t& coord = {}, + const uint32_t& pname = {}); + + struct GetTexGenivRequest { + ContextTag context_tag{}; + uint32_t coord{}; + uint32_t pname{}; + }; + + struct GetTexGenivReply { + uint16_t sequence{}; + int32_t datum{}; + std::vector data{}; + }; + + using GetTexGenivResponse = Response; + + Future GetTexGeniv(const GetTexGenivRequest& request); + + Future GetTexGeniv(const ContextTag& context_tag = {}, + const uint32_t& coord = {}, + const uint32_t& pname = {}); + + struct GetTexImageRequest { + ContextTag context_tag{}; + uint32_t target{}; + int32_t level{}; + uint32_t format{}; + uint32_t type{}; + uint8_t swap_bytes{}; + }; + + struct GetTexImageReply { + uint16_t sequence{}; + int32_t width{}; + int32_t height{}; + int32_t depth{}; + std::vector data{}; + }; + + using GetTexImageResponse = Response; + + Future GetTexImage(const GetTexImageRequest& request); + + Future GetTexImage(const ContextTag& context_tag = {}, + const uint32_t& target = {}, + const int32_t& level = {}, + const uint32_t& format = {}, + const uint32_t& type = {}, + const uint8_t& swap_bytes = {}); + + struct GetTexParameterfvRequest { + ContextTag context_tag{}; + uint32_t target{}; + uint32_t pname{}; + }; + + struct GetTexParameterfvReply { + uint16_t sequence{}; + float datum{}; + std::vector data{}; + }; + + using GetTexParameterfvResponse = Response; + + Future GetTexParameterfv( + const GetTexParameterfvRequest& request); + + Future GetTexParameterfv( + const ContextTag& context_tag = {}, + const uint32_t& target = {}, + const uint32_t& pname = {}); + + struct GetTexParameterivRequest { + ContextTag context_tag{}; + uint32_t target{}; + uint32_t pname{}; + }; + + struct GetTexParameterivReply { + uint16_t sequence{}; + int32_t datum{}; + std::vector data{}; + }; + + using GetTexParameterivResponse = Response; + + Future GetTexParameteriv( + const GetTexParameterivRequest& request); + + Future GetTexParameteriv( + const ContextTag& context_tag = {}, + const uint32_t& target = {}, + const uint32_t& pname = {}); + + struct GetTexLevelParameterfvRequest { + ContextTag context_tag{}; + uint32_t target{}; + int32_t level{}; + uint32_t pname{}; + }; + + struct GetTexLevelParameterfvReply { + uint16_t sequence{}; + float datum{}; + std::vector data{}; + }; + + using GetTexLevelParameterfvResponse = Response; + + Future GetTexLevelParameterfv( + const GetTexLevelParameterfvRequest& request); + + Future GetTexLevelParameterfv( + const ContextTag& context_tag = {}, + const uint32_t& target = {}, + const int32_t& level = {}, + const uint32_t& pname = {}); + + struct GetTexLevelParameterivRequest { + ContextTag context_tag{}; + uint32_t target{}; + int32_t level{}; + uint32_t pname{}; + }; + + struct GetTexLevelParameterivReply { + uint16_t sequence{}; + int32_t datum{}; + std::vector data{}; + }; + + using GetTexLevelParameterivResponse = Response; + + Future GetTexLevelParameteriv( + const GetTexLevelParameterivRequest& request); + + Future GetTexLevelParameteriv( + const ContextTag& context_tag = {}, + const uint32_t& target = {}, + const int32_t& level = {}, + const uint32_t& pname = {}); + + struct IsEnabledRequest { + ContextTag context_tag{}; + uint32_t capability{}; + }; + + struct IsEnabledReply { + uint16_t sequence{}; + Bool32 ret_val{}; + }; + + using IsEnabledResponse = Response; + + Future IsEnabled(const IsEnabledRequest& request); + + Future IsEnabled(const ContextTag& context_tag = {}, + const uint32_t& capability = {}); + + struct IsListRequest { + ContextTag context_tag{}; + uint32_t list{}; + }; + + struct IsListReply { + uint16_t sequence{}; + Bool32 ret_val{}; + }; + + using IsListResponse = Response; + + Future IsList(const IsListRequest& request); + + Future IsList(const ContextTag& context_tag = {}, + const uint32_t& list = {}); + + struct FlushRequest { + ContextTag context_tag{}; + }; + + using FlushResponse = Response; + + Future Flush(const FlushRequest& request); + + Future Flush(const ContextTag& context_tag = {}); + + struct AreTexturesResidentRequest { + ContextTag context_tag{}; + std::vector textures{}; + }; + + struct AreTexturesResidentReply { + uint16_t sequence{}; + Bool32 ret_val{}; + std::vector data{}; + }; + + using AreTexturesResidentResponse = Response; + + Future AreTexturesResident( + const AreTexturesResidentRequest& request); + + Future AreTexturesResident( + const ContextTag& context_tag = {}, + const std::vector& textures = {}); + + struct DeleteTexturesRequest { + ContextTag context_tag{}; + std::vector textures{}; + }; + + using DeleteTexturesResponse = Response; + + Future DeleteTextures(const DeleteTexturesRequest& request); + + Future DeleteTextures(const ContextTag& context_tag = {}, + const std::vector& textures = {}); + + struct GenTexturesRequest { + ContextTag context_tag{}; + int32_t n{}; + }; + + struct GenTexturesReply { + uint16_t sequence{}; + std::vector data{}; + }; + + using GenTexturesResponse = Response; + + Future GenTextures(const GenTexturesRequest& request); + + Future GenTextures(const ContextTag& context_tag = {}, + const int32_t& n = {}); + + struct IsTextureRequest { + ContextTag context_tag{}; + uint32_t texture{}; + }; + + struct IsTextureReply { + uint16_t sequence{}; + Bool32 ret_val{}; + }; + + using IsTextureResponse = Response; + + Future IsTexture(const IsTextureRequest& request); + + Future IsTexture(const ContextTag& context_tag = {}, + const uint32_t& texture = {}); + + struct GetColorTableRequest { + ContextTag context_tag{}; + uint32_t target{}; + uint32_t format{}; + uint32_t type{}; + uint8_t swap_bytes{}; + }; + + struct GetColorTableReply { + uint16_t sequence{}; + int32_t width{}; + std::vector data{}; + }; + + using GetColorTableResponse = Response; + + Future GetColorTable(const GetColorTableRequest& request); + + Future GetColorTable(const ContextTag& context_tag = {}, + const uint32_t& target = {}, + const uint32_t& format = {}, + const uint32_t& type = {}, + const uint8_t& swap_bytes = {}); + + struct GetColorTableParameterfvRequest { + ContextTag context_tag{}; + uint32_t target{}; + uint32_t pname{}; + }; + + struct GetColorTableParameterfvReply { + uint16_t sequence{}; + float datum{}; + std::vector data{}; + }; + + using GetColorTableParameterfvResponse = + Response; + + Future GetColorTableParameterfv( + const GetColorTableParameterfvRequest& request); + + Future GetColorTableParameterfv( + const ContextTag& context_tag = {}, + const uint32_t& target = {}, + const uint32_t& pname = {}); + + struct GetColorTableParameterivRequest { + ContextTag context_tag{}; + uint32_t target{}; + uint32_t pname{}; + }; + + struct GetColorTableParameterivReply { + uint16_t sequence{}; + int32_t datum{}; + std::vector data{}; + }; + + using GetColorTableParameterivResponse = + Response; + + Future GetColorTableParameteriv( + const GetColorTableParameterivRequest& request); + + Future GetColorTableParameteriv( + const ContextTag& context_tag = {}, + const uint32_t& target = {}, + const uint32_t& pname = {}); + + struct GetConvolutionFilterRequest { + ContextTag context_tag{}; + uint32_t target{}; + uint32_t format{}; + uint32_t type{}; + uint8_t swap_bytes{}; + }; + + struct GetConvolutionFilterReply { + uint16_t sequence{}; + int32_t width{}; + int32_t height{}; + std::vector data{}; + }; + + using GetConvolutionFilterResponse = Response; + + Future GetConvolutionFilter( + const GetConvolutionFilterRequest& request); + + Future GetConvolutionFilter( + const ContextTag& context_tag = {}, + const uint32_t& target = {}, + const uint32_t& format = {}, + const uint32_t& type = {}, + const uint8_t& swap_bytes = {}); + + struct GetConvolutionParameterfvRequest { + ContextTag context_tag{}; + uint32_t target{}; + uint32_t pname{}; + }; + + struct GetConvolutionParameterfvReply { + uint16_t sequence{}; + float datum{}; + std::vector data{}; + }; + + using GetConvolutionParameterfvResponse = + Response; + + Future GetConvolutionParameterfv( + const GetConvolutionParameterfvRequest& request); + + Future GetConvolutionParameterfv( + const ContextTag& context_tag = {}, + const uint32_t& target = {}, + const uint32_t& pname = {}); + + struct GetConvolutionParameterivRequest { + ContextTag context_tag{}; + uint32_t target{}; + uint32_t pname{}; + }; + + struct GetConvolutionParameterivReply { + uint16_t sequence{}; + int32_t datum{}; + std::vector data{}; + }; + + using GetConvolutionParameterivResponse = + Response; + + Future GetConvolutionParameteriv( + const GetConvolutionParameterivRequest& request); + + Future GetConvolutionParameteriv( + const ContextTag& context_tag = {}, + const uint32_t& target = {}, + const uint32_t& pname = {}); + + struct GetSeparableFilterRequest { + ContextTag context_tag{}; + uint32_t target{}; + uint32_t format{}; + uint32_t type{}; + uint8_t swap_bytes{}; + }; + + struct GetSeparableFilterReply { + uint16_t sequence{}; + int32_t row_w{}; + int32_t col_h{}; + std::vector rows_and_cols{}; + }; + + using GetSeparableFilterResponse = Response; + + Future GetSeparableFilter( + const GetSeparableFilterRequest& request); + + Future GetSeparableFilter( + const ContextTag& context_tag = {}, + const uint32_t& target = {}, + const uint32_t& format = {}, + const uint32_t& type = {}, + const uint8_t& swap_bytes = {}); + + struct GetHistogramRequest { + ContextTag context_tag{}; + uint32_t target{}; + uint32_t format{}; + uint32_t type{}; + uint8_t swap_bytes{}; + uint8_t reset{}; + }; + + struct GetHistogramReply { + uint16_t sequence{}; + int32_t width{}; + std::vector data{}; + }; + + using GetHistogramResponse = Response; + + Future GetHistogram(const GetHistogramRequest& request); + + Future GetHistogram(const ContextTag& context_tag = {}, + const uint32_t& target = {}, + const uint32_t& format = {}, + const uint32_t& type = {}, + const uint8_t& swap_bytes = {}, + const uint8_t& reset = {}); + + struct GetHistogramParameterfvRequest { + ContextTag context_tag{}; + uint32_t target{}; + uint32_t pname{}; + }; + + struct GetHistogramParameterfvReply { + uint16_t sequence{}; + float datum{}; + std::vector data{}; + }; + + using GetHistogramParameterfvResponse = + Response; + + Future GetHistogramParameterfv( + const GetHistogramParameterfvRequest& request); + + Future GetHistogramParameterfv( + const ContextTag& context_tag = {}, + const uint32_t& target = {}, + const uint32_t& pname = {}); + + struct GetHistogramParameterivRequest { + ContextTag context_tag{}; + uint32_t target{}; + uint32_t pname{}; + }; + + struct GetHistogramParameterivReply { + uint16_t sequence{}; + int32_t datum{}; + std::vector data{}; + }; + + using GetHistogramParameterivResponse = + Response; + + Future GetHistogramParameteriv( + const GetHistogramParameterivRequest& request); + + Future GetHistogramParameteriv( + const ContextTag& context_tag = {}, + const uint32_t& target = {}, + const uint32_t& pname = {}); + + struct GetMinmaxRequest { + ContextTag context_tag{}; + uint32_t target{}; + uint32_t format{}; + uint32_t type{}; + uint8_t swap_bytes{}; + uint8_t reset{}; + }; + + struct GetMinmaxReply { + uint16_t sequence{}; + std::vector data{}; + }; + + using GetMinmaxResponse = Response; + + Future GetMinmax(const GetMinmaxRequest& request); + + Future GetMinmax(const ContextTag& context_tag = {}, + const uint32_t& target = {}, + const uint32_t& format = {}, + const uint32_t& type = {}, + const uint8_t& swap_bytes = {}, + const uint8_t& reset = {}); + + struct GetMinmaxParameterfvRequest { + ContextTag context_tag{}; + uint32_t target{}; + uint32_t pname{}; + }; + + struct GetMinmaxParameterfvReply { + uint16_t sequence{}; + float datum{}; + std::vector data{}; + }; + + using GetMinmaxParameterfvResponse = Response; + + Future GetMinmaxParameterfv( + const GetMinmaxParameterfvRequest& request); + + Future GetMinmaxParameterfv( + const ContextTag& context_tag = {}, + const uint32_t& target = {}, + const uint32_t& pname = {}); + + struct GetMinmaxParameterivRequest { + ContextTag context_tag{}; + uint32_t target{}; + uint32_t pname{}; + }; + + struct GetMinmaxParameterivReply { + uint16_t sequence{}; + int32_t datum{}; + std::vector data{}; + }; + + using GetMinmaxParameterivResponse = Response; + + Future GetMinmaxParameteriv( + const GetMinmaxParameterivRequest& request); + + Future GetMinmaxParameteriv( + const ContextTag& context_tag = {}, + const uint32_t& target = {}, + const uint32_t& pname = {}); + + struct GetCompressedTexImageARBRequest { + ContextTag context_tag{}; + uint32_t target{}; + int32_t level{}; + }; + + struct GetCompressedTexImageARBReply { + uint16_t sequence{}; + int32_t size{}; + std::vector data{}; + }; + + using GetCompressedTexImageARBResponse = + Response; + + Future GetCompressedTexImageARB( + const GetCompressedTexImageARBRequest& request); + + Future GetCompressedTexImageARB( + const ContextTag& context_tag = {}, + const uint32_t& target = {}, + const int32_t& level = {}); + + struct DeleteQueriesARBRequest { + ContextTag context_tag{}; + std::vector ids{}; + }; + + using DeleteQueriesARBResponse = Response; + + Future DeleteQueriesARB(const DeleteQueriesARBRequest& request); + + Future DeleteQueriesARB(const ContextTag& context_tag = {}, + const std::vector& ids = {}); + + struct GenQueriesARBRequest { + ContextTag context_tag{}; + int32_t n{}; + }; + + struct GenQueriesARBReply { + uint16_t sequence{}; + std::vector data{}; + }; + + using GenQueriesARBResponse = Response; + + Future GenQueriesARB(const GenQueriesARBRequest& request); + + Future GenQueriesARB(const ContextTag& context_tag = {}, + const int32_t& n = {}); + + struct IsQueryARBRequest { + ContextTag context_tag{}; + uint32_t id{}; + }; + + struct IsQueryARBReply { + uint16_t sequence{}; + Bool32 ret_val{}; + }; + + using IsQueryARBResponse = Response; + + Future IsQueryARB(const IsQueryARBRequest& request); + + Future IsQueryARB(const ContextTag& context_tag = {}, + const uint32_t& id = {}); + + struct GetQueryivARBRequest { + ContextTag context_tag{}; + uint32_t target{}; + uint32_t pname{}; + }; + + struct GetQueryivARBReply { + uint16_t sequence{}; + int32_t datum{}; + std::vector data{}; + }; + + using GetQueryivARBResponse = Response; + + Future GetQueryivARB(const GetQueryivARBRequest& request); + + Future GetQueryivARB(const ContextTag& context_tag = {}, + const uint32_t& target = {}, + const uint32_t& pname = {}); + + struct GetQueryObjectivARBRequest { + ContextTag context_tag{}; + uint32_t id{}; + uint32_t pname{}; + }; + + struct GetQueryObjectivARBReply { + uint16_t sequence{}; + int32_t datum{}; + std::vector data{}; + }; + + using GetQueryObjectivARBResponse = Response; + + Future GetQueryObjectivARB( + const GetQueryObjectivARBRequest& request); + + Future GetQueryObjectivARB( + const ContextTag& context_tag = {}, + const uint32_t& id = {}, + const uint32_t& pname = {}); + + struct GetQueryObjectuivARBRequest { + ContextTag context_tag{}; + uint32_t id{}; + uint32_t pname{}; + }; + + struct GetQueryObjectuivARBReply { + uint16_t sequence{}; + uint32_t datum{}; + std::vector data{}; + }; + + using GetQueryObjectuivARBResponse = Response; + + Future GetQueryObjectuivARB( + const GetQueryObjectuivARBRequest& request); + + Future GetQueryObjectuivARB( + const ContextTag& context_tag = {}, + const uint32_t& id = {}, + const uint32_t& pname = {}); + + private: + Connection* const connection_; + x11::QueryExtensionReply info_{}; +}; + +} // namespace x11 + +inline constexpr x11::Glx::Pbcet operator|(x11::Glx::Pbcet l, + x11::Glx::Pbcet r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | static_cast(r)); +} + +inline constexpr x11::Glx::Pbcet operator&(x11::Glx::Pbcet l, + x11::Glx::Pbcet r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & static_cast(r)); +} + +inline constexpr x11::Glx::Pbcdt operator|(x11::Glx::Pbcdt l, + x11::Glx::Pbcdt r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | static_cast(r)); +} + +inline constexpr x11::Glx::Pbcdt operator&(x11::Glx::Pbcdt l, + x11::Glx::Pbcdt r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & static_cast(r)); +} + +inline constexpr x11::Glx::GraphicsContextAttribute operator|( + x11::Glx::GraphicsContextAttribute l, + x11::Glx::GraphicsContextAttribute r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::Glx::GraphicsContextAttribute operator&( + x11::Glx::GraphicsContextAttribute l, + x11::Glx::GraphicsContextAttribute r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +inline constexpr x11::Glx::Rm operator|(x11::Glx::Rm l, x11::Glx::Rm r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | static_cast(r)); +} + +inline constexpr x11::Glx::Rm operator&(x11::Glx::Rm l, x11::Glx::Rm r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & static_cast(r)); +} + +#endif // UI_GFX_X_GENERATED_PROTOS_GLX_H_ diff --git a/x/generated_protos/present.cc b/x/generated_protos/present.cc new file mode 100644 index 000000000000..b4edda68ec51 --- /dev/null +++ b/x/generated_protos/present.cc @@ -0,0 +1,825 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#include "present.h" + +#include +#include + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "ui/gfx/x/xproto_internal.h" + +namespace x11 { + +Present::Present(Connection* connection, const x11::QueryExtensionReply& info) + : connection_(connection), info_(info) {} + +template <> +COMPONENT_EXPORT(X11) +void ReadEvent(Present::GenericEvent* event_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& extension = (*event_).extension; + auto& sequence = (*event_).sequence; + auto& length = (*event_).length; + auto& evtype = (*event_).evtype; + auto& event = (*event_).event; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // extension + Read(&extension, &buf); + + // sequence + Read(&sequence, &buf); + + // length + Read(&length, &buf); + + // evtype + Read(&evtype, &buf); + + // pad0 + Pad(&buf, 2); + + // event + Read(&event, &buf); + + DCHECK_LE(buf.offset, 32ul); +} + +template <> +COMPONENT_EXPORT(X11) +void ReadEvent( + Present::ConfigureNotifyEvent* event_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*event_).sequence; + auto& event = (*event_).event; + auto& window = (*event_).window; + auto& x = (*event_).x; + auto& y = (*event_).y; + auto& width = (*event_).width; + auto& height = (*event_).height; + auto& off_x = (*event_).off_x; + auto& off_y = (*event_).off_y; + auto& pixmap_width = (*event_).pixmap_width; + auto& pixmap_height = (*event_).pixmap_height; + auto& pixmap_flags = (*event_).pixmap_flags; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // extension + uint8_t extension; + Read(&extension, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // event_type + uint16_t event_type; + Read(&event_type, &buf); + + // pad0 + Pad(&buf, 2); + + // event + Read(&event, &buf); + + // window + Read(&window, &buf); + + // x + Read(&x, &buf); + + // y + Read(&y, &buf); + + // width + Read(&width, &buf); + + // height + Read(&height, &buf); + + // off_x + Read(&off_x, &buf); + + // off_y + Read(&off_y, &buf); + + // pixmap_width + Read(&pixmap_width, &buf); + + // pixmap_height + Read(&pixmap_height, &buf); + + // pixmap_flags + Read(&pixmap_flags, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset, 32 + 4 * length); +} + +template <> +COMPONENT_EXPORT(X11) +void ReadEvent( + Present::CompleteNotifyEvent* event_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*event_).sequence; + auto& kind = (*event_).kind; + auto& mode = (*event_).mode; + auto& event = (*event_).event; + auto& window = (*event_).window; + auto& serial = (*event_).serial; + auto& ust = (*event_).ust; + auto& msc = (*event_).msc; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // extension + uint8_t extension; + Read(&extension, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // event_type + uint16_t event_type; + Read(&event_type, &buf); + + // kind + uint8_t tmp0; + Read(&tmp0, &buf); + kind = static_cast(tmp0); + + // mode + uint8_t tmp1; + Read(&tmp1, &buf); + mode = static_cast(tmp1); + + // event + Read(&event, &buf); + + // window + Read(&window, &buf); + + // serial + Read(&serial, &buf); + + // ust + Read(&ust, &buf); + + // msc + Read(&msc, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset, 32 + 4 * length); +} + +template <> +COMPONENT_EXPORT(X11) +void ReadEvent(Present::IdleNotifyEvent* event_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*event_).sequence; + auto& event = (*event_).event; + auto& window = (*event_).window; + auto& serial = (*event_).serial; + auto& pixmap = (*event_).pixmap; + auto& idle_fence = (*event_).idle_fence; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // extension + uint8_t extension; + Read(&extension, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // event_type + uint16_t event_type; + Read(&event_type, &buf); + + // pad0 + Pad(&buf, 2); + + // event + Read(&event, &buf); + + // window + Read(&window, &buf); + + // serial + Read(&serial, &buf); + + // pixmap + Read(&pixmap, &buf); + + // idle_fence + Read(&idle_fence, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset, 32 + 4 * length); +} + +template <> +COMPONENT_EXPORT(X11) +void ReadEvent( + Present::RedirectNotifyEvent* event_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*event_).sequence; + auto& update_window = (*event_).update_window; + auto& event = (*event_).event; + auto& event_window = (*event_).event_window; + auto& window = (*event_).window; + auto& pixmap = (*event_).pixmap; + auto& serial = (*event_).serial; + auto& valid_region = (*event_).valid_region; + auto& update_region = (*event_).update_region; + auto& valid_rect = (*event_).valid_rect; + auto& update_rect = (*event_).update_rect; + auto& x_off = (*event_).x_off; + auto& y_off = (*event_).y_off; + auto& target_crtc = (*event_).target_crtc; + auto& wait_fence = (*event_).wait_fence; + auto& idle_fence = (*event_).idle_fence; + auto& options = (*event_).options; + auto& target_msc = (*event_).target_msc; + auto& divisor = (*event_).divisor; + auto& remainder = (*event_).remainder; + auto& notifies = (*event_).notifies; + size_t notifies_len = notifies.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // extension + uint8_t extension; + Read(&extension, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // event_type + uint16_t event_type; + Read(&event_type, &buf); + + // update_window + Read(&update_window, &buf); + + // pad0 + Pad(&buf, 1); + + // event + Read(&event, &buf); + + // event_window + Read(&event_window, &buf); + + // window + Read(&window, &buf); + + // pixmap + Read(&pixmap, &buf); + + // serial + Read(&serial, &buf); + + // valid_region + Read(&valid_region, &buf); + + // update_region + Read(&update_region, &buf); + + // valid_rect + { + auto& x = valid_rect.x; + auto& y = valid_rect.y; + auto& width = valid_rect.width; + auto& height = valid_rect.height; + + // x + Read(&x, &buf); + + // y + Read(&y, &buf); + + // width + Read(&width, &buf); + + // height + Read(&height, &buf); + } + + // update_rect + { + auto& x = update_rect.x; + auto& y = update_rect.y; + auto& width = update_rect.width; + auto& height = update_rect.height; + + // x + Read(&x, &buf); + + // y + Read(&y, &buf); + + // width + Read(&width, &buf); + + // height + Read(&height, &buf); + } + + // x_off + Read(&x_off, &buf); + + // y_off + Read(&y_off, &buf); + + // target_crtc + Read(&target_crtc, &buf); + + // wait_fence + Read(&wait_fence, &buf); + + // idle_fence + Read(&idle_fence, &buf); + + // options + Read(&options, &buf); + + // pad1 + Pad(&buf, 4); + + // target_msc + Read(&target_msc, &buf); + + // divisor + Read(&divisor, &buf); + + // remainder + Read(&remainder, &buf); + + // notifies + notifies.resize(notifies_len); + for (auto& notifies_elem : notifies) { + // notifies_elem + { + auto& window = notifies_elem.window; + auto& serial = notifies_elem.serial; + + // window + Read(&window, &buf); + + // serial + Read(&serial, &buf); + } + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset, 32 + 4 * length); +} + +Future Present::QueryVersion( + const Present::QueryVersionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& major_version = request.major_version; + auto& minor_version = request.minor_version; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 0; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // major_version + buf.Write(&major_version); + + // minor_version + buf.Write(&minor_version); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Present::QueryVersion", false); +} + +Future Present::QueryVersion( + const uint32_t& major_version, + const uint32_t& minor_version) { + return Present::QueryVersion( + Present::QueryVersionRequest{major_version, minor_version}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Present::QueryVersionReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& major_version = (*reply).major_version; + auto& minor_version = (*reply).minor_version; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // major_version + Read(&major_version, &buf); + + // minor_version + Read(&minor_version, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Present::PresentPixmap( + const Present::PresentPixmapRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + auto& pixmap = request.pixmap; + auto& serial = request.serial; + auto& valid = request.valid; + auto& update = request.update; + auto& x_off = request.x_off; + auto& y_off = request.y_off; + auto& target_crtc = request.target_crtc; + auto& wait_fence = request.wait_fence; + auto& idle_fence = request.idle_fence; + auto& options = request.options; + auto& target_msc = request.target_msc; + auto& divisor = request.divisor; + auto& remainder = request.remainder; + auto& notifies = request.notifies; + size_t notifies_len = notifies.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 1; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + // pixmap + buf.Write(&pixmap); + + // serial + buf.Write(&serial); + + // valid + buf.Write(&valid); + + // update + buf.Write(&update); + + // x_off + buf.Write(&x_off); + + // y_off + buf.Write(&y_off); + + // target_crtc + buf.Write(&target_crtc); + + // wait_fence + buf.Write(&wait_fence); + + // idle_fence + buf.Write(&idle_fence); + + // options + buf.Write(&options); + + // pad0 + Pad(&buf, 4); + + // target_msc + buf.Write(&target_msc); + + // divisor + buf.Write(&divisor); + + // remainder + buf.Write(&remainder); + + // notifies + DCHECK_EQ(static_cast(notifies_len), notifies.size()); + for (auto& notifies_elem : notifies) { + // notifies_elem + { + auto& window = notifies_elem.window; + auto& serial = notifies_elem.serial; + + // window + buf.Write(&window); + + // serial + buf.Write(&serial); + } + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Present::PresentPixmap", false); +} + +Future Present::PresentPixmap(const Window& window, + const Pixmap& pixmap, + const uint32_t& serial, + const XFixes::Region& valid, + const XFixes::Region& update, + const int16_t& x_off, + const int16_t& y_off, + const RandR::Crtc& target_crtc, + const Sync::Fence& wait_fence, + const Sync::Fence& idle_fence, + const uint32_t& options, + const uint64_t& target_msc, + const uint64_t& divisor, + const uint64_t& remainder, + const std::vector& notifies) { + return Present::PresentPixmap(Present::PresentPixmapRequest{ + window, pixmap, serial, valid, update, x_off, y_off, target_crtc, + wait_fence, idle_fence, options, target_msc, divisor, remainder, + notifies}); +} + +Future Present::NotifyMSC(const Present::NotifyMSCRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + auto& serial = request.serial; + auto& target_msc = request.target_msc; + auto& divisor = request.divisor; + auto& remainder = request.remainder; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 2; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + // serial + buf.Write(&serial); + + // pad0 + Pad(&buf, 4); + + // target_msc + buf.Write(&target_msc); + + // divisor + buf.Write(&divisor); + + // remainder + buf.Write(&remainder); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Present::NotifyMSC", false); +} + +Future Present::NotifyMSC(const Window& window, + const uint32_t& serial, + const uint64_t& target_msc, + const uint64_t& divisor, + const uint64_t& remainder) { + return Present::NotifyMSC(Present::NotifyMSCRequest{ + window, serial, target_msc, divisor, remainder}); +} + +Future Present::SelectInput(const Present::SelectInputRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& eid = request.eid; + auto& window = request.window; + auto& event_mask = request.event_mask; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 3; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // eid + buf.Write(&eid); + + // window + buf.Write(&window); + + // event_mask + uint32_t tmp2; + tmp2 = static_cast(event_mask); + buf.Write(&tmp2); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Present::SelectInput", false); +} + +Future Present::SelectInput(const Event& eid, + const Window& window, + const EventMask& event_mask) { + return Present::SelectInput( + Present::SelectInputRequest{eid, window, event_mask}); +} + +Future Present::QueryCapabilities( + const Present::QueryCapabilitiesRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& target = request.target; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 4; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // target + buf.Write(&target); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Present::QueryCapabilities", false); +} + +Future Present::QueryCapabilities( + const uint32_t& target) { + return Present::QueryCapabilities(Present::QueryCapabilitiesRequest{target}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Present::QueryCapabilitiesReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& capabilities = (*reply).capabilities; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // capabilities + Read(&capabilities, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +} // namespace x11 diff --git a/x/generated_protos/present.h b/x/generated_protos/present.h new file mode 100644 index 000000000000..dcfd695858c9 --- /dev/null +++ b/x/generated_protos/present.h @@ -0,0 +1,426 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#ifndef UI_GFX_X_GENERATED_PROTOS_PRESENT_H_ +#define UI_GFX_X_GENERATED_PROTOS_PRESENT_H_ + +#include +#include +#include +#include +#include + +#include "base/component_export.h" +#include "base/files/scoped_file.h" +#include "base/memory/ref_counted_memory.h" +#include "base/memory/scoped_refptr.h" +#include "randr.h" +#include "sync.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/gfx/x/error.h" +#include "ui/gfx/x/ref_counted_fd.h" +#include "xfixes.h" +#include "xproto.h" + +namespace x11 { + +class Connection; + +template +struct Response; + +template +class Future; + +class COMPONENT_EXPORT(X11) Present { + public: + static constexpr unsigned major_version = 1; + static constexpr unsigned minor_version = 2; + + Present(Connection* connection, const x11::QueryExtensionReply& info); + + uint8_t present() const { return info_.present; } + uint8_t major_opcode() const { return info_.major_opcode; } + uint8_t first_event() const { return info_.first_event; } + uint8_t first_error() const { return info_.first_error; } + + Connection* connection() const { return connection_; } + + enum class Event : uint32_t { + ConfigureNotify = 0, + CompleteNotify = 1, + IdleNotify = 2, + RedirectNotify = 3, + }; + + enum class EventMask : int { + NoEvent = 0, + ConfigureNotify = 1 << 0, + CompleteNotify = 1 << 1, + IdleNotify = 1 << 2, + RedirectNotify = 1 << 3, + }; + + enum class Option : int { + None = 0, + Async = 1 << 0, + Copy = 1 << 1, + UST = 1 << 2, + Suboptimal = 1 << 3, + }; + + enum class Capability : int { + None = 0, + Async = 1 << 0, + Fence = 1 << 1, + UST = 1 << 2, + }; + + enum class CompleteKind : int { + Pixmap = 0, + NotifyMSC = 1, + }; + + enum class CompleteMode : int { + Copy = 0, + Flip = 1, + Skip = 2, + SuboptimalCopy = 3, + }; + + struct Notify { + Window window{}; + uint32_t serial{}; + }; + + struct GenericEvent { + static constexpr int type_id = 6; + static constexpr uint8_t opcode = 0; + bool send_event{}; + uint8_t extension{}; + uint16_t sequence{}; + uint32_t length{}; + uint16_t evtype{}; + Event event{}; + + x11::Window* GetWindow() { return nullptr; } + }; + + struct ConfigureNotifyEvent { + static constexpr int type_id = 7; + static constexpr uint8_t opcode = 0; + bool send_event{}; + uint16_t sequence{}; + Event event{}; + Window window{}; + int16_t x{}; + int16_t y{}; + uint16_t width{}; + uint16_t height{}; + int16_t off_x{}; + int16_t off_y{}; + uint16_t pixmap_width{}; + uint16_t pixmap_height{}; + uint32_t pixmap_flags{}; + + x11::Window* GetWindow() { return reinterpret_cast(&window); } + }; + + struct CompleteNotifyEvent { + static constexpr int type_id = 8; + static constexpr uint8_t opcode = 1; + bool send_event{}; + uint16_t sequence{}; + CompleteKind kind{}; + CompleteMode mode{}; + Event event{}; + Window window{}; + uint32_t serial{}; + uint64_t ust{}; + uint64_t msc{}; + + x11::Window* GetWindow() { return reinterpret_cast(&window); } + }; + + struct IdleNotifyEvent { + static constexpr int type_id = 9; + static constexpr uint8_t opcode = 2; + bool send_event{}; + uint16_t sequence{}; + Event event{}; + Window window{}; + uint32_t serial{}; + Pixmap pixmap{}; + Sync::Fence idle_fence{}; + + x11::Window* GetWindow() { return reinterpret_cast(&window); } + }; + + struct RedirectNotifyEvent { + static constexpr int type_id = 10; + static constexpr uint8_t opcode = 3; + bool send_event{}; + uint16_t sequence{}; + uint8_t update_window{}; + Event event{}; + Window event_window{}; + Window window{}; + Pixmap pixmap{}; + uint32_t serial{}; + XFixes::Region valid_region{}; + XFixes::Region update_region{}; + Rectangle valid_rect{}; + Rectangle update_rect{}; + int16_t x_off{}; + int16_t y_off{}; + RandR::Crtc target_crtc{}; + Sync::Fence wait_fence{}; + Sync::Fence idle_fence{}; + uint32_t options{}; + uint64_t target_msc{}; + uint64_t divisor{}; + uint64_t remainder{}; + std::vector notifies{}; + + x11::Window* GetWindow() { return reinterpret_cast(&window); } + }; + + struct QueryVersionRequest { + uint32_t major_version{}; + uint32_t minor_version{}; + }; + + struct QueryVersionReply { + uint16_t sequence{}; + uint32_t major_version{}; + uint32_t minor_version{}; + }; + + using QueryVersionResponse = Response; + + Future QueryVersion(const QueryVersionRequest& request); + + Future QueryVersion(const uint32_t& major_version = {}, + const uint32_t& minor_version = {}); + + struct PresentPixmapRequest { + Window window{}; + Pixmap pixmap{}; + uint32_t serial{}; + XFixes::Region valid{}; + XFixes::Region update{}; + int16_t x_off{}; + int16_t y_off{}; + RandR::Crtc target_crtc{}; + Sync::Fence wait_fence{}; + Sync::Fence idle_fence{}; + uint32_t options{}; + uint64_t target_msc{}; + uint64_t divisor{}; + uint64_t remainder{}; + std::vector notifies{}; + }; + + using PresentPixmapResponse = Response; + + Future PresentPixmap(const PresentPixmapRequest& request); + + Future PresentPixmap(const Window& window = {}, + const Pixmap& pixmap = {}, + const uint32_t& serial = {}, + const XFixes::Region& valid = {}, + const XFixes::Region& update = {}, + const int16_t& x_off = {}, + const int16_t& y_off = {}, + const RandR::Crtc& target_crtc = {}, + const Sync::Fence& wait_fence = {}, + const Sync::Fence& idle_fence = {}, + const uint32_t& options = {}, + const uint64_t& target_msc = {}, + const uint64_t& divisor = {}, + const uint64_t& remainder = {}, + const std::vector& notifies = {}); + + struct NotifyMSCRequest { + Window window{}; + uint32_t serial{}; + uint64_t target_msc{}; + uint64_t divisor{}; + uint64_t remainder{}; + }; + + using NotifyMSCResponse = Response; + + Future NotifyMSC(const NotifyMSCRequest& request); + + Future NotifyMSC(const Window& window = {}, + const uint32_t& serial = {}, + const uint64_t& target_msc = {}, + const uint64_t& divisor = {}, + const uint64_t& remainder = {}); + + struct SelectInputRequest { + Event eid{}; + Window window{}; + EventMask event_mask{}; + }; + + using SelectInputResponse = Response; + + Future SelectInput(const SelectInputRequest& request); + + Future SelectInput(const Event& eid = {}, + const Window& window = {}, + const EventMask& event_mask = {}); + + struct QueryCapabilitiesRequest { + uint32_t target{}; + }; + + struct QueryCapabilitiesReply { + uint16_t sequence{}; + uint32_t capabilities{}; + }; + + using QueryCapabilitiesResponse = Response; + + Future QueryCapabilities( + const QueryCapabilitiesRequest& request); + + Future QueryCapabilities(const uint32_t& target = {}); + + private: + Connection* const connection_; + x11::QueryExtensionReply info_{}; +}; + +} // namespace x11 + +inline constexpr x11::Present::Event operator|(x11::Present::Event l, + x11::Present::Event r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::Present::Event operator&(x11::Present::Event l, + x11::Present::Event r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +inline constexpr x11::Present::EventMask operator|(x11::Present::EventMask l, + x11::Present::EventMask r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::Present::EventMask operator&(x11::Present::EventMask l, + x11::Present::EventMask r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +inline constexpr x11::Present::Option operator|(x11::Present::Option l, + x11::Present::Option r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::Present::Option operator&(x11::Present::Option l, + x11::Present::Option r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +inline constexpr x11::Present::Capability operator|( + x11::Present::Capability l, + x11::Present::Capability r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::Present::Capability operator&( + x11::Present::Capability l, + x11::Present::Capability r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +inline constexpr x11::Present::CompleteKind operator|( + x11::Present::CompleteKind l, + x11::Present::CompleteKind r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::Present::CompleteKind operator&( + x11::Present::CompleteKind l, + x11::Present::CompleteKind r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +inline constexpr x11::Present::CompleteMode operator|( + x11::Present::CompleteMode l, + x11::Present::CompleteMode r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::Present::CompleteMode operator&( + x11::Present::CompleteMode l, + x11::Present::CompleteMode r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +#endif // UI_GFX_X_GENERATED_PROTOS_PRESENT_H_ diff --git a/x/generated_protos/randr.cc b/x/generated_protos/randr.cc new file mode 100644 index 000000000000..7228c86c7632 --- /dev/null +++ b/x/generated_protos/randr.cc @@ -0,0 +1,4599 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#include "randr.h" + +#include +#include + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "ui/gfx/x/xproto_internal.h" + +namespace x11 { + +RandR::RandR(Connection* connection, const x11::QueryExtensionReply& info) + : connection_(connection), info_(info) {} + +std::string RandR::BadOutputError::ToString() const { + std::stringstream ss_; + ss_ << "RandR::BadOutputError{"; + ss_ << ".sequence = " << static_cast(sequence); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError(RandR::BadOutputError* error_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + DCHECK_LE(buf.offset, 32ul); +} +std::string RandR::BadCrtcError::ToString() const { + std::stringstream ss_; + ss_ << "RandR::BadCrtcError{"; + ss_ << ".sequence = " << static_cast(sequence); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError(RandR::BadCrtcError* error_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + DCHECK_LE(buf.offset, 32ul); +} +std::string RandR::BadModeError::ToString() const { + std::stringstream ss_; + ss_ << "RandR::BadModeError{"; + ss_ << ".sequence = " << static_cast(sequence); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError(RandR::BadModeError* error_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + DCHECK_LE(buf.offset, 32ul); +} +std::string RandR::BadProviderError::ToString() const { + std::stringstream ss_; + ss_ << "RandR::BadProviderError{"; + ss_ << ".sequence = " << static_cast(sequence); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError(RandR::BadProviderError* error_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + DCHECK_LE(buf.offset, 32ul); +} +template <> +COMPONENT_EXPORT(X11) +void ReadEvent( + RandR::ScreenChangeNotifyEvent* event_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& rotation = (*event_).rotation; + auto& sequence = (*event_).sequence; + auto& timestamp = (*event_).timestamp; + auto& config_timestamp = (*event_).config_timestamp; + auto& root = (*event_).root; + auto& request_window = (*event_).request_window; + auto& sizeID = (*event_).sizeID; + auto& subpixel_order = (*event_).subpixel_order; + auto& width = (*event_).width; + auto& height = (*event_).height; + auto& mwidth = (*event_).mwidth; + auto& mheight = (*event_).mheight; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // rotation + uint8_t tmp0; + Read(&tmp0, &buf); + rotation = static_cast(tmp0); + + // sequence + Read(&sequence, &buf); + + // timestamp + Read(×tamp, &buf); + + // config_timestamp + Read(&config_timestamp, &buf); + + // root + Read(&root, &buf); + + // request_window + Read(&request_window, &buf); + + // sizeID + Read(&sizeID, &buf); + + // subpixel_order + uint16_t tmp1; + Read(&tmp1, &buf); + subpixel_order = static_cast(tmp1); + + // width + Read(&width, &buf); + + // height + Read(&height, &buf); + + // mwidth + Read(&mwidth, &buf); + + // mheight + Read(&mheight, &buf); + + DCHECK_LE(buf.offset, 32ul); +} + +template <> +COMPONENT_EXPORT(X11) +void ReadEvent(RandR::NotifyEvent* event_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + RandR::Notify subCode{}; + auto& sequence = (*event_).sequence; + auto& data = (*event_); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // subCode + uint8_t tmp2; + Read(&tmp2, &buf); + subCode = static_cast(tmp2); + + // sequence + Read(&sequence, &buf); + + // data + auto data_expr = subCode; + if (CaseEq(data_expr, RandR::Notify::CrtcChange)) { + data.cc.emplace(); + auto& timestamp = (*data.cc).timestamp; + auto& window = (*data.cc).window; + auto& crtc = (*data.cc).crtc; + auto& mode = (*data.cc).mode; + auto& rotation = (*data.cc).rotation; + auto& x = (*data.cc).x; + auto& y = (*data.cc).y; + auto& width = (*data.cc).width; + auto& height = (*data.cc).height; + + // timestamp + Read(×tamp, &buf); + + // window + Read(&window, &buf); + + // crtc + Read(&crtc, &buf); + + // mode + Read(&mode, &buf); + + // rotation + uint16_t tmp3; + Read(&tmp3, &buf); + rotation = static_cast(tmp3); + + // pad0 + Pad(&buf, 2); + + // x + Read(&x, &buf); + + // y + Read(&y, &buf); + + // width + Read(&width, &buf); + + // height + Read(&height, &buf); + } + if (CaseEq(data_expr, RandR::Notify::OutputChange)) { + data.oc.emplace(); + auto& timestamp = (*data.oc).timestamp; + auto& config_timestamp = (*data.oc).config_timestamp; + auto& window = (*data.oc).window; + auto& output = (*data.oc).output; + auto& crtc = (*data.oc).crtc; + auto& mode = (*data.oc).mode; + auto& rotation = (*data.oc).rotation; + auto& connection = (*data.oc).connection; + auto& subpixel_order = (*data.oc).subpixel_order; + + // timestamp + Read(×tamp, &buf); + + // config_timestamp + Read(&config_timestamp, &buf); + + // window + Read(&window, &buf); + + // output + Read(&output, &buf); + + // crtc + Read(&crtc, &buf); + + // mode + Read(&mode, &buf); + + // rotation + uint16_t tmp4; + Read(&tmp4, &buf); + rotation = static_cast(tmp4); + + // connection + uint8_t tmp5; + Read(&tmp5, &buf); + connection = static_cast(tmp5); + + // subpixel_order + uint8_t tmp6; + Read(&tmp6, &buf); + subpixel_order = static_cast(tmp6); + } + if (CaseEq(data_expr, RandR::Notify::OutputProperty)) { + data.op.emplace(); + auto& window = (*data.op).window; + auto& output = (*data.op).output; + auto& atom = (*data.op).atom; + auto& timestamp = (*data.op).timestamp; + auto& status = (*data.op).status; + + // window + Read(&window, &buf); + + // output + Read(&output, &buf); + + // atom + Read(&atom, &buf); + + // timestamp + Read(×tamp, &buf); + + // status + uint8_t tmp7; + Read(&tmp7, &buf); + status = static_cast(tmp7); + + // pad1 + Pad(&buf, 11); + } + if (CaseEq(data_expr, RandR::Notify::ProviderChange)) { + data.pc.emplace(); + auto& timestamp = (*data.pc).timestamp; + auto& window = (*data.pc).window; + auto& provider = (*data.pc).provider; + + // timestamp + Read(×tamp, &buf); + + // window + Read(&window, &buf); + + // provider + Read(&provider, &buf); + + // pad2 + Pad(&buf, 16); + } + if (CaseEq(data_expr, RandR::Notify::ProviderProperty)) { + data.pp.emplace(); + auto& window = (*data.pp).window; + auto& provider = (*data.pp).provider; + auto& atom = (*data.pp).atom; + auto& timestamp = (*data.pp).timestamp; + auto& state = (*data.pp).state; + + // window + Read(&window, &buf); + + // provider + Read(&provider, &buf); + + // atom + Read(&atom, &buf); + + // timestamp + Read(×tamp, &buf); + + // state + Read(&state, &buf); + + // pad3 + Pad(&buf, 11); + } + if (CaseEq(data_expr, RandR::Notify::ResourceChange)) { + data.rc.emplace(); + auto& timestamp = (*data.rc).timestamp; + auto& window = (*data.rc).window; + + // timestamp + Read(×tamp, &buf); + + // window + Read(&window, &buf); + + // pad4 + Pad(&buf, 20); + } + if (CaseEq(data_expr, RandR::Notify::Lease)) { + data.lc.emplace(); + auto& timestamp = (*data.lc).timestamp; + auto& window = (*data.lc).window; + auto& lease = (*data.lc).lease; + auto& created = (*data.lc).created; + + // timestamp + Read(×tamp, &buf); + + // window + Read(&window, &buf); + + // lease + Read(&lease, &buf); + + // created + Read(&created, &buf); + + // pad5 + Pad(&buf, 15); + } + + DCHECK_LE(buf.offset, 32ul); +} + +Future RandR::QueryVersion( + const RandR::QueryVersionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& major_version = request.major_version; + auto& minor_version = request.minor_version; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 0; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // major_version + buf.Write(&major_version); + + // minor_version + buf.Write(&minor_version); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "RandR::QueryVersion", false); +} + +Future RandR::QueryVersion( + const uint32_t& major_version, + const uint32_t& minor_version) { + return RandR::QueryVersion( + RandR::QueryVersionRequest{major_version, minor_version}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + RandR::QueryVersionReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& major_version = (*reply).major_version; + auto& minor_version = (*reply).minor_version; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // major_version + Read(&major_version, &buf); + + // minor_version + Read(&minor_version, &buf); + + // pad1 + Pad(&buf, 16); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future RandR::SetScreenConfig( + const RandR::SetScreenConfigRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + auto& timestamp = request.timestamp; + auto& config_timestamp = request.config_timestamp; + auto& sizeID = request.sizeID; + auto& rotation = request.rotation; + auto& rate = request.rate; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 2; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + // timestamp + buf.Write(×tamp); + + // config_timestamp + buf.Write(&config_timestamp); + + // sizeID + buf.Write(&sizeID); + + // rotation + uint16_t tmp8; + tmp8 = static_cast(rotation); + buf.Write(&tmp8); + + // rate + buf.Write(&rate); + + // pad0 + Pad(&buf, 2); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "RandR::SetScreenConfig", false); +} + +Future RandR::SetScreenConfig( + const Window& window, + const Time& timestamp, + const Time& config_timestamp, + const uint16_t& sizeID, + const Rotation& rotation, + const uint16_t& rate) { + return RandR::SetScreenConfig(RandR::SetScreenConfigRequest{ + window, timestamp, config_timestamp, sizeID, rotation, rate}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + RandR::SetScreenConfigReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& status = (*reply).status; + auto& sequence = (*reply).sequence; + auto& new_timestamp = (*reply).new_timestamp; + auto& config_timestamp = (*reply).config_timestamp; + auto& root = (*reply).root; + auto& subpixel_order = (*reply).subpixel_order; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // status + uint8_t tmp9; + Read(&tmp9, &buf); + status = static_cast(tmp9); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // new_timestamp + Read(&new_timestamp, &buf); + + // config_timestamp + Read(&config_timestamp, &buf); + + // root + Read(&root, &buf); + + // subpixel_order + uint16_t tmp10; + Read(&tmp10, &buf); + subpixel_order = static_cast(tmp10); + + // pad0 + Pad(&buf, 10); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future RandR::SelectInput(const RandR::SelectInputRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + auto& enable = request.enable; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 4; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + // enable + uint16_t tmp11; + tmp11 = static_cast(enable); + buf.Write(&tmp11); + + // pad0 + Pad(&buf, 2); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "RandR::SelectInput", false); +} + +Future RandR::SelectInput(const Window& window, + const NotifyMask& enable) { + return RandR::SelectInput(RandR::SelectInputRequest{window, enable}); +} + +Future RandR::GetScreenInfo( + const RandR::GetScreenInfoRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 5; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "RandR::GetScreenInfo", false); +} + +Future RandR::GetScreenInfo(const Window& window) { + return RandR::GetScreenInfo(RandR::GetScreenInfoRequest{window}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + RandR::GetScreenInfoReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& rotations = (*reply).rotations; + auto& sequence = (*reply).sequence; + auto& root = (*reply).root; + auto& timestamp = (*reply).timestamp; + auto& config_timestamp = (*reply).config_timestamp; + uint16_t nSizes{}; + auto& sizeID = (*reply).sizeID; + auto& rotation = (*reply).rotation; + auto& rate = (*reply).rate; + auto& nInfo = (*reply).nInfo; + auto& sizes = (*reply).sizes; + size_t sizes_len = sizes.size(); + auto& rates = (*reply).rates; + size_t rates_len = rates.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // rotations + uint8_t tmp12; + Read(&tmp12, &buf); + rotations = static_cast(tmp12); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // root + Read(&root, &buf); + + // timestamp + Read(×tamp, &buf); + + // config_timestamp + Read(&config_timestamp, &buf); + + // nSizes + Read(&nSizes, &buf); + + // sizeID + Read(&sizeID, &buf); + + // rotation + uint16_t tmp13; + Read(&tmp13, &buf); + rotation = static_cast(tmp13); + + // rate + Read(&rate, &buf); + + // nInfo + Read(&nInfo, &buf); + + // pad0 + Pad(&buf, 2); + + // sizes + sizes.resize(nSizes); + for (auto& sizes_elem : sizes) { + // sizes_elem + { + auto& width = sizes_elem.width; + auto& height = sizes_elem.height; + auto& mwidth = sizes_elem.mwidth; + auto& mheight = sizes_elem.mheight; + + // width + Read(&width, &buf); + + // height + Read(&height, &buf); + + // mwidth + Read(&mwidth, &buf); + + // mheight + Read(&mheight, &buf); + } + } + + // rates + rates.resize((nInfo) - (nSizes)); + for (auto& rates_elem : rates) { + // rates_elem + { + uint16_t nRates{}; + auto& rates = rates_elem.rates; + size_t rates_len = rates.size(); + + // nRates + Read(&nRates, &buf); + + // rates + rates.resize(nRates); + for (auto& rates_elem : rates) { + // rates_elem + Read(&rates_elem, &buf); + } + } + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future RandR::GetScreenSizeRange( + const RandR::GetScreenSizeRangeRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 6; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "RandR::GetScreenSizeRange", false); +} + +Future RandR::GetScreenSizeRange( + const Window& window) { + return RandR::GetScreenSizeRange(RandR::GetScreenSizeRangeRequest{window}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + RandR::GetScreenSizeRangeReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& min_width = (*reply).min_width; + auto& min_height = (*reply).min_height; + auto& max_width = (*reply).max_width; + auto& max_height = (*reply).max_height; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // min_width + Read(&min_width, &buf); + + // min_height + Read(&min_height, &buf); + + // max_width + Read(&max_width, &buf); + + // max_height + Read(&max_height, &buf); + + // pad1 + Pad(&buf, 16); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future RandR::SetScreenSize(const RandR::SetScreenSizeRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + auto& width = request.width; + auto& height = request.height; + auto& mm_width = request.mm_width; + auto& mm_height = request.mm_height; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 7; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + // width + buf.Write(&width); + + // height + buf.Write(&height); + + // mm_width + buf.Write(&mm_width); + + // mm_height + buf.Write(&mm_height); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "RandR::SetScreenSize", false); +} + +Future RandR::SetScreenSize(const Window& window, + const uint16_t& width, + const uint16_t& height, + const uint32_t& mm_width, + const uint32_t& mm_height) { + return RandR::SetScreenSize( + RandR::SetScreenSizeRequest{window, width, height, mm_width, mm_height}); +} + +Future RandR::GetScreenResources( + const RandR::GetScreenResourcesRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 8; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "RandR::GetScreenResources", false); +} + +Future RandR::GetScreenResources( + const Window& window) { + return RandR::GetScreenResources(RandR::GetScreenResourcesRequest{window}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + RandR::GetScreenResourcesReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& timestamp = (*reply).timestamp; + auto& config_timestamp = (*reply).config_timestamp; + uint16_t num_crtcs{}; + uint16_t num_outputs{}; + uint16_t num_modes{}; + uint16_t names_len{}; + auto& crtcs = (*reply).crtcs; + size_t crtcs_len = crtcs.size(); + auto& outputs = (*reply).outputs; + size_t outputs_len = outputs.size(); + auto& modes = (*reply).modes; + size_t modes_len = modes.size(); + auto& names = (*reply).names; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // timestamp + Read(×tamp, &buf); + + // config_timestamp + Read(&config_timestamp, &buf); + + // num_crtcs + Read(&num_crtcs, &buf); + + // num_outputs + Read(&num_outputs, &buf); + + // num_modes + Read(&num_modes, &buf); + + // names_len + Read(&names_len, &buf); + + // pad1 + Pad(&buf, 8); + + // crtcs + crtcs.resize(num_crtcs); + for (auto& crtcs_elem : crtcs) { + // crtcs_elem + Read(&crtcs_elem, &buf); + } + + // outputs + outputs.resize(num_outputs); + for (auto& outputs_elem : outputs) { + // outputs_elem + Read(&outputs_elem, &buf); + } + + // modes + modes.resize(num_modes); + for (auto& modes_elem : modes) { + // modes_elem + { + auto& id = modes_elem.id; + auto& width = modes_elem.width; + auto& height = modes_elem.height; + auto& dot_clock = modes_elem.dot_clock; + auto& hsync_start = modes_elem.hsync_start; + auto& hsync_end = modes_elem.hsync_end; + auto& htotal = modes_elem.htotal; + auto& hskew = modes_elem.hskew; + auto& vsync_start = modes_elem.vsync_start; + auto& vsync_end = modes_elem.vsync_end; + auto& vtotal = modes_elem.vtotal; + auto& name_len = modes_elem.name_len; + auto& mode_flags = modes_elem.mode_flags; + + // id + Read(&id, &buf); + + // width + Read(&width, &buf); + + // height + Read(&height, &buf); + + // dot_clock + Read(&dot_clock, &buf); + + // hsync_start + Read(&hsync_start, &buf); + + // hsync_end + Read(&hsync_end, &buf); + + // htotal + Read(&htotal, &buf); + + // hskew + Read(&hskew, &buf); + + // vsync_start + Read(&vsync_start, &buf); + + // vsync_end + Read(&vsync_end, &buf); + + // vtotal + Read(&vtotal, &buf); + + // name_len + Read(&name_len, &buf); + + // mode_flags + uint32_t tmp14; + Read(&tmp14, &buf); + mode_flags = static_cast(tmp14); + } + } + + // names + names.resize(names_len); + for (auto& names_elem : names) { + // names_elem + Read(&names_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future RandR::GetOutputInfo( + const RandR::GetOutputInfoRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& output = request.output; + auto& config_timestamp = request.config_timestamp; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 9; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // output + buf.Write(&output); + + // config_timestamp + buf.Write(&config_timestamp); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "RandR::GetOutputInfo", false); +} + +Future RandR::GetOutputInfo( + const Output& output, + const Time& config_timestamp) { + return RandR::GetOutputInfo( + RandR::GetOutputInfoRequest{output, config_timestamp}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + RandR::GetOutputInfoReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& status = (*reply).status; + auto& sequence = (*reply).sequence; + auto& timestamp = (*reply).timestamp; + auto& crtc = (*reply).crtc; + auto& mm_width = (*reply).mm_width; + auto& mm_height = (*reply).mm_height; + auto& connection = (*reply).connection; + auto& subpixel_order = (*reply).subpixel_order; + uint16_t num_crtcs{}; + uint16_t num_modes{}; + auto& num_preferred = (*reply).num_preferred; + uint16_t num_clones{}; + uint16_t name_len{}; + auto& crtcs = (*reply).crtcs; + size_t crtcs_len = crtcs.size(); + auto& modes = (*reply).modes; + size_t modes_len = modes.size(); + auto& clones = (*reply).clones; + size_t clones_len = clones.size(); + auto& name = (*reply).name; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // status + uint8_t tmp15; + Read(&tmp15, &buf); + status = static_cast(tmp15); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // timestamp + Read(×tamp, &buf); + + // crtc + Read(&crtc, &buf); + + // mm_width + Read(&mm_width, &buf); + + // mm_height + Read(&mm_height, &buf); + + // connection + uint8_t tmp16; + Read(&tmp16, &buf); + connection = static_cast(tmp16); + + // subpixel_order + uint8_t tmp17; + Read(&tmp17, &buf); + subpixel_order = static_cast(tmp17); + + // num_crtcs + Read(&num_crtcs, &buf); + + // num_modes + Read(&num_modes, &buf); + + // num_preferred + Read(&num_preferred, &buf); + + // num_clones + Read(&num_clones, &buf); + + // name_len + Read(&name_len, &buf); + + // crtcs + crtcs.resize(num_crtcs); + for (auto& crtcs_elem : crtcs) { + // crtcs_elem + Read(&crtcs_elem, &buf); + } + + // modes + modes.resize(num_modes); + for (auto& modes_elem : modes) { + // modes_elem + Read(&modes_elem, &buf); + } + + // clones + clones.resize(num_clones); + for (auto& clones_elem : clones) { + // clones_elem + Read(&clones_elem, &buf); + } + + // name + name.resize(name_len); + for (auto& name_elem : name) { + // name_elem + Read(&name_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future RandR::ListOutputProperties( + const RandR::ListOutputPropertiesRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& output = request.output; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 10; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // output + buf.Write(&output); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "RandR::ListOutputProperties", false); +} + +Future RandR::ListOutputProperties( + const Output& output) { + return RandR::ListOutputProperties( + RandR::ListOutputPropertiesRequest{output}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + RandR::ListOutputPropertiesReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint16_t num_atoms{}; + auto& atoms = (*reply).atoms; + size_t atoms_len = atoms.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // num_atoms + Read(&num_atoms, &buf); + + // pad1 + Pad(&buf, 22); + + // atoms + atoms.resize(num_atoms); + for (auto& atoms_elem : atoms) { + // atoms_elem + Read(&atoms_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future RandR::QueryOutputProperty( + const RandR::QueryOutputPropertyRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& output = request.output; + auto& property = request.property; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 11; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // output + buf.Write(&output); + + // property + buf.Write(&property); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "RandR::QueryOutputProperty", false); +} + +Future RandR::QueryOutputProperty( + const Output& output, + const Atom& property) { + return RandR::QueryOutputProperty( + RandR::QueryOutputPropertyRequest{output, property}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + RandR::QueryOutputPropertyReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& pending = (*reply).pending; + auto& range = (*reply).range; + auto& immutable = (*reply).immutable; + auto& validValues = (*reply).validValues; + size_t validValues_len = validValues.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pending + Read(&pending, &buf); + + // range + Read(&range, &buf); + + // immutable + Read(&immutable, &buf); + + // pad1 + Pad(&buf, 21); + + // validValues + validValues.resize(length); + for (auto& validValues_elem : validValues) { + // validValues_elem + Read(&validValues_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future RandR::ConfigureOutputProperty( + const RandR::ConfigureOutputPropertyRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& output = request.output; + auto& property = request.property; + auto& pending = request.pending; + auto& range = request.range; + auto& values = request.values; + size_t values_len = values.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 12; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // output + buf.Write(&output); + + // property + buf.Write(&property); + + // pending + buf.Write(&pending); + + // range + buf.Write(&range); + + // pad0 + Pad(&buf, 2); + + // values + DCHECK_EQ(static_cast(values_len), values.size()); + for (auto& values_elem : values) { + // values_elem + buf.Write(&values_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "RandR::ConfigureOutputProperty", + false); +} + +Future RandR::ConfigureOutputProperty( + const Output& output, + const Atom& property, + const uint8_t& pending, + const uint8_t& range, + const std::vector& values) { + return RandR::ConfigureOutputProperty(RandR::ConfigureOutputPropertyRequest{ + output, property, pending, range, values}); +} + +Future RandR::ChangeOutputProperty( + const RandR::ChangeOutputPropertyRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& output = request.output; + auto& property = request.property; + auto& type = request.type; + auto& format = request.format; + auto& mode = request.mode; + auto& num_units = request.num_units; + auto& data = request.data; + size_t data_len = data ? data->size() : 0; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 13; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // output + buf.Write(&output); + + // property + buf.Write(&property); + + // type + buf.Write(&type); + + // format + buf.Write(&format); + + // mode + uint8_t tmp18; + tmp18 = static_cast(mode); + buf.Write(&tmp18); + + // pad0 + Pad(&buf, 2); + + // num_units + buf.Write(&num_units); + + // data + buf.AppendBuffer(data, ((num_units) * (format)) / (8)); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "RandR::ChangeOutputProperty", + false); +} + +Future RandR::ChangeOutputProperty( + const Output& output, + const Atom& property, + const Atom& type, + const uint8_t& format, + const PropMode& mode, + const uint32_t& num_units, + const scoped_refptr& data) { + return RandR::ChangeOutputProperty(RandR::ChangeOutputPropertyRequest{ + output, property, type, format, mode, num_units, data}); +} + +Future RandR::DeleteOutputProperty( + const RandR::DeleteOutputPropertyRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& output = request.output; + auto& property = request.property; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 14; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // output + buf.Write(&output); + + // property + buf.Write(&property); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "RandR::DeleteOutputProperty", + false); +} + +Future RandR::DeleteOutputProperty(const Output& output, + const Atom& property) { + return RandR::DeleteOutputProperty( + RandR::DeleteOutputPropertyRequest{output, property}); +} + +Future RandR::GetOutputProperty( + const RandR::GetOutputPropertyRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& output = request.output; + auto& property = request.property; + auto& type = request.type; + auto& long_offset = request.long_offset; + auto& long_length = request.long_length; + auto& c_delete = request.c_delete; + auto& pending = request.pending; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 15; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // output + buf.Write(&output); + + // property + buf.Write(&property); + + // type + buf.Write(&type); + + // long_offset + buf.Write(&long_offset); + + // long_length + buf.Write(&long_length); + + // c_delete + buf.Write(&c_delete); + + // pending + buf.Write(&pending); + + // pad0 + Pad(&buf, 2); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "RandR::GetOutputProperty", false); +} + +Future RandR::GetOutputProperty( + const Output& output, + const Atom& property, + const Atom& type, + const uint32_t& long_offset, + const uint32_t& long_length, + const uint8_t& c_delete, + const uint8_t& pending) { + return RandR::GetOutputProperty(RandR::GetOutputPropertyRequest{ + output, property, type, long_offset, long_length, c_delete, pending}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + RandR::GetOutputPropertyReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& format = (*reply).format; + auto& sequence = (*reply).sequence; + auto& type = (*reply).type; + auto& bytes_after = (*reply).bytes_after; + auto& num_items = (*reply).num_items; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // format + Read(&format, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // type + Read(&type, &buf); + + // bytes_after + Read(&bytes_after, &buf); + + // num_items + Read(&num_items, &buf); + + // pad0 + Pad(&buf, 12); + + // data + data.resize((num_items) * ((format) / (8))); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future RandR::CreateMode( + const RandR::CreateModeRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + auto& mode_info = request.mode_info; + auto& name = request.name; + size_t name_len = name.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 16; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + // mode_info + { + auto& id = mode_info.id; + auto& width = mode_info.width; + auto& height = mode_info.height; + auto& dot_clock = mode_info.dot_clock; + auto& hsync_start = mode_info.hsync_start; + auto& hsync_end = mode_info.hsync_end; + auto& htotal = mode_info.htotal; + auto& hskew = mode_info.hskew; + auto& vsync_start = mode_info.vsync_start; + auto& vsync_end = mode_info.vsync_end; + auto& vtotal = mode_info.vtotal; + auto& name_len = mode_info.name_len; + auto& mode_flags = mode_info.mode_flags; + + // id + buf.Write(&id); + + // width + buf.Write(&width); + + // height + buf.Write(&height); + + // dot_clock + buf.Write(&dot_clock); + + // hsync_start + buf.Write(&hsync_start); + + // hsync_end + buf.Write(&hsync_end); + + // htotal + buf.Write(&htotal); + + // hskew + buf.Write(&hskew); + + // vsync_start + buf.Write(&vsync_start); + + // vsync_end + buf.Write(&vsync_end); + + // vtotal + buf.Write(&vtotal); + + // name_len + buf.Write(&name_len); + + // mode_flags + uint32_t tmp19; + tmp19 = static_cast(mode_flags); + buf.Write(&tmp19); + } + + // name + DCHECK_EQ(static_cast(name_len), name.size()); + for (auto& name_elem : name) { + // name_elem + buf.Write(&name_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "RandR::CreateMode", false); +} + +Future RandR::CreateMode(const Window& window, + const ModeInfo& mode_info, + const std::string& name) { + return RandR::CreateMode(RandR::CreateModeRequest{window, mode_info, name}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + RandR::CreateModeReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& mode = (*reply).mode; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // mode + Read(&mode, &buf); + + // pad1 + Pad(&buf, 20); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future RandR::DestroyMode(const RandR::DestroyModeRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& mode = request.mode; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 17; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // mode + buf.Write(&mode); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "RandR::DestroyMode", false); +} + +Future RandR::DestroyMode(const Mode& mode) { + return RandR::DestroyMode(RandR::DestroyModeRequest{mode}); +} + +Future RandR::AddOutputMode(const RandR::AddOutputModeRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& output = request.output; + auto& mode = request.mode; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 18; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // output + buf.Write(&output); + + // mode + buf.Write(&mode); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "RandR::AddOutputMode", false); +} + +Future RandR::AddOutputMode(const Output& output, const Mode& mode) { + return RandR::AddOutputMode(RandR::AddOutputModeRequest{output, mode}); +} + +Future RandR::DeleteOutputMode( + const RandR::DeleteOutputModeRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& output = request.output; + auto& mode = request.mode; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 19; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // output + buf.Write(&output); + + // mode + buf.Write(&mode); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "RandR::DeleteOutputMode", false); +} + +Future RandR::DeleteOutputMode(const Output& output, const Mode& mode) { + return RandR::DeleteOutputMode(RandR::DeleteOutputModeRequest{output, mode}); +} + +Future RandR::GetCrtcInfo( + const RandR::GetCrtcInfoRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& crtc = request.crtc; + auto& config_timestamp = request.config_timestamp; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 20; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // crtc + buf.Write(&crtc); + + // config_timestamp + buf.Write(&config_timestamp); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "RandR::GetCrtcInfo", false); +} + +Future RandR::GetCrtcInfo( + const Crtc& crtc, + const Time& config_timestamp) { + return RandR::GetCrtcInfo(RandR::GetCrtcInfoRequest{crtc, config_timestamp}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + RandR::GetCrtcInfoReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& status = (*reply).status; + auto& sequence = (*reply).sequence; + auto& timestamp = (*reply).timestamp; + auto& x = (*reply).x; + auto& y = (*reply).y; + auto& width = (*reply).width; + auto& height = (*reply).height; + auto& mode = (*reply).mode; + auto& rotation = (*reply).rotation; + auto& rotations = (*reply).rotations; + uint16_t num_outputs{}; + uint16_t num_possible_outputs{}; + auto& outputs = (*reply).outputs; + size_t outputs_len = outputs.size(); + auto& possible = (*reply).possible; + size_t possible_len = possible.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // status + uint8_t tmp20; + Read(&tmp20, &buf); + status = static_cast(tmp20); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // timestamp + Read(×tamp, &buf); + + // x + Read(&x, &buf); + + // y + Read(&y, &buf); + + // width + Read(&width, &buf); + + // height + Read(&height, &buf); + + // mode + Read(&mode, &buf); + + // rotation + uint16_t tmp21; + Read(&tmp21, &buf); + rotation = static_cast(tmp21); + + // rotations + uint16_t tmp22; + Read(&tmp22, &buf); + rotations = static_cast(tmp22); + + // num_outputs + Read(&num_outputs, &buf); + + // num_possible_outputs + Read(&num_possible_outputs, &buf); + + // outputs + outputs.resize(num_outputs); + for (auto& outputs_elem : outputs) { + // outputs_elem + Read(&outputs_elem, &buf); + } + + // possible + possible.resize(num_possible_outputs); + for (auto& possible_elem : possible) { + // possible_elem + Read(&possible_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future RandR::SetCrtcConfig( + const RandR::SetCrtcConfigRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& crtc = request.crtc; + auto& timestamp = request.timestamp; + auto& config_timestamp = request.config_timestamp; + auto& x = request.x; + auto& y = request.y; + auto& mode = request.mode; + auto& rotation = request.rotation; + auto& outputs = request.outputs; + size_t outputs_len = outputs.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 21; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // crtc + buf.Write(&crtc); + + // timestamp + buf.Write(×tamp); + + // config_timestamp + buf.Write(&config_timestamp); + + // x + buf.Write(&x); + + // y + buf.Write(&y); + + // mode + buf.Write(&mode); + + // rotation + uint16_t tmp23; + tmp23 = static_cast(rotation); + buf.Write(&tmp23); + + // pad0 + Pad(&buf, 2); + + // outputs + DCHECK_EQ(static_cast(outputs_len), outputs.size()); + for (auto& outputs_elem : outputs) { + // outputs_elem + buf.Write(&outputs_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "RandR::SetCrtcConfig", false); +} + +Future RandR::SetCrtcConfig( + const Crtc& crtc, + const Time& timestamp, + const Time& config_timestamp, + const int16_t& x, + const int16_t& y, + const Mode& mode, + const Rotation& rotation, + const std::vector& outputs) { + return RandR::SetCrtcConfig(RandR::SetCrtcConfigRequest{ + crtc, timestamp, config_timestamp, x, y, mode, rotation, outputs}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + RandR::SetCrtcConfigReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& status = (*reply).status; + auto& sequence = (*reply).sequence; + auto& timestamp = (*reply).timestamp; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // status + uint8_t tmp24; + Read(&tmp24, &buf); + status = static_cast(tmp24); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // timestamp + Read(×tamp, &buf); + + // pad0 + Pad(&buf, 20); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future RandR::GetCrtcGammaSize( + const RandR::GetCrtcGammaSizeRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& crtc = request.crtc; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 22; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // crtc + buf.Write(&crtc); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "RandR::GetCrtcGammaSize", false); +} + +Future RandR::GetCrtcGammaSize(const Crtc& crtc) { + return RandR::GetCrtcGammaSize(RandR::GetCrtcGammaSizeRequest{crtc}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + RandR::GetCrtcGammaSizeReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& size = (*reply).size; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // size + Read(&size, &buf); + + // pad1 + Pad(&buf, 22); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future RandR::GetCrtcGamma( + const RandR::GetCrtcGammaRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& crtc = request.crtc; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 23; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // crtc + buf.Write(&crtc); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "RandR::GetCrtcGamma", false); +} + +Future RandR::GetCrtcGamma(const Crtc& crtc) { + return RandR::GetCrtcGamma(RandR::GetCrtcGammaRequest{crtc}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + RandR::GetCrtcGammaReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint16_t size{}; + auto& red = (*reply).red; + size_t red_len = red.size(); + auto& green = (*reply).green; + size_t green_len = green.size(); + auto& blue = (*reply).blue; + size_t blue_len = blue.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // size + Read(&size, &buf); + + // pad1 + Pad(&buf, 22); + + // red + red.resize(size); + for (auto& red_elem : red) { + // red_elem + Read(&red_elem, &buf); + } + + // green + green.resize(size); + for (auto& green_elem : green) { + // green_elem + Read(&green_elem, &buf); + } + + // blue + blue.resize(size); + for (auto& blue_elem : blue) { + // blue_elem + Read(&blue_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future RandR::SetCrtcGamma(const RandR::SetCrtcGammaRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& crtc = request.crtc; + uint16_t size{}; + auto& red = request.red; + size_t red_len = red.size(); + auto& green = request.green; + size_t green_len = green.size(); + auto& blue = request.blue; + size_t blue_len = blue.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 24; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // crtc + buf.Write(&crtc); + + // size + size = red.size(); + buf.Write(&size); + + // pad0 + Pad(&buf, 2); + + // red + DCHECK_EQ(static_cast(size), red.size()); + for (auto& red_elem : red) { + // red_elem + buf.Write(&red_elem); + } + + // green + DCHECK_EQ(static_cast(size), green.size()); + for (auto& green_elem : green) { + // green_elem + buf.Write(&green_elem); + } + + // blue + DCHECK_EQ(static_cast(size), blue.size()); + for (auto& blue_elem : blue) { + // blue_elem + buf.Write(&blue_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "RandR::SetCrtcGamma", false); +} + +Future RandR::SetCrtcGamma(const Crtc& crtc, + const std::vector& red, + const std::vector& green, + const std::vector& blue) { + return RandR::SetCrtcGamma( + RandR::SetCrtcGammaRequest{crtc, red, green, blue}); +} + +Future RandR::GetScreenResourcesCurrent( + const RandR::GetScreenResourcesCurrentRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 25; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "RandR::GetScreenResourcesCurrent", false); +} + +Future RandR::GetScreenResourcesCurrent( + const Window& window) { + return RandR::GetScreenResourcesCurrent( + RandR::GetScreenResourcesCurrentRequest{window}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + RandR::GetScreenResourcesCurrentReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& timestamp = (*reply).timestamp; + auto& config_timestamp = (*reply).config_timestamp; + uint16_t num_crtcs{}; + uint16_t num_outputs{}; + uint16_t num_modes{}; + uint16_t names_len{}; + auto& crtcs = (*reply).crtcs; + size_t crtcs_len = crtcs.size(); + auto& outputs = (*reply).outputs; + size_t outputs_len = outputs.size(); + auto& modes = (*reply).modes; + size_t modes_len = modes.size(); + auto& names = (*reply).names; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // timestamp + Read(×tamp, &buf); + + // config_timestamp + Read(&config_timestamp, &buf); + + // num_crtcs + Read(&num_crtcs, &buf); + + // num_outputs + Read(&num_outputs, &buf); + + // num_modes + Read(&num_modes, &buf); + + // names_len + Read(&names_len, &buf); + + // pad1 + Pad(&buf, 8); + + // crtcs + crtcs.resize(num_crtcs); + for (auto& crtcs_elem : crtcs) { + // crtcs_elem + Read(&crtcs_elem, &buf); + } + + // outputs + outputs.resize(num_outputs); + for (auto& outputs_elem : outputs) { + // outputs_elem + Read(&outputs_elem, &buf); + } + + // modes + modes.resize(num_modes); + for (auto& modes_elem : modes) { + // modes_elem + { + auto& id = modes_elem.id; + auto& width = modes_elem.width; + auto& height = modes_elem.height; + auto& dot_clock = modes_elem.dot_clock; + auto& hsync_start = modes_elem.hsync_start; + auto& hsync_end = modes_elem.hsync_end; + auto& htotal = modes_elem.htotal; + auto& hskew = modes_elem.hskew; + auto& vsync_start = modes_elem.vsync_start; + auto& vsync_end = modes_elem.vsync_end; + auto& vtotal = modes_elem.vtotal; + auto& name_len = modes_elem.name_len; + auto& mode_flags = modes_elem.mode_flags; + + // id + Read(&id, &buf); + + // width + Read(&width, &buf); + + // height + Read(&height, &buf); + + // dot_clock + Read(&dot_clock, &buf); + + // hsync_start + Read(&hsync_start, &buf); + + // hsync_end + Read(&hsync_end, &buf); + + // htotal + Read(&htotal, &buf); + + // hskew + Read(&hskew, &buf); + + // vsync_start + Read(&vsync_start, &buf); + + // vsync_end + Read(&vsync_end, &buf); + + // vtotal + Read(&vtotal, &buf); + + // name_len + Read(&name_len, &buf); + + // mode_flags + uint32_t tmp25; + Read(&tmp25, &buf); + mode_flags = static_cast(tmp25); + } + } + + // names + names.resize(names_len); + for (auto& names_elem : names) { + // names_elem + Read(&names_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future RandR::SetCrtcTransform( + const RandR::SetCrtcTransformRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& crtc = request.crtc; + auto& transform = request.transform; + uint16_t filter_len{}; + auto& filter_name = request.filter_name; + size_t filter_name_len = filter_name.size(); + auto& filter_params = request.filter_params; + size_t filter_params_len = filter_params.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 26; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // crtc + buf.Write(&crtc); + + // transform + { + auto& matrix11 = transform.matrix11; + auto& matrix12 = transform.matrix12; + auto& matrix13 = transform.matrix13; + auto& matrix21 = transform.matrix21; + auto& matrix22 = transform.matrix22; + auto& matrix23 = transform.matrix23; + auto& matrix31 = transform.matrix31; + auto& matrix32 = transform.matrix32; + auto& matrix33 = transform.matrix33; + + // matrix11 + buf.Write(&matrix11); + + // matrix12 + buf.Write(&matrix12); + + // matrix13 + buf.Write(&matrix13); + + // matrix21 + buf.Write(&matrix21); + + // matrix22 + buf.Write(&matrix22); + + // matrix23 + buf.Write(&matrix23); + + // matrix31 + buf.Write(&matrix31); + + // matrix32 + buf.Write(&matrix32); + + // matrix33 + buf.Write(&matrix33); + } + + // filter_len + filter_len = filter_name.size(); + buf.Write(&filter_len); + + // pad0 + Pad(&buf, 2); + + // filter_name + DCHECK_EQ(static_cast(filter_len), filter_name.size()); + for (auto& filter_name_elem : filter_name) { + // filter_name_elem + buf.Write(&filter_name_elem); + } + + // pad1 + Align(&buf, 4); + + // filter_params + DCHECK_EQ(static_cast(filter_params_len), filter_params.size()); + for (auto& filter_params_elem : filter_params) { + // filter_params_elem + buf.Write(&filter_params_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "RandR::SetCrtcTransform", false); +} + +Future RandR::SetCrtcTransform( + const Crtc& crtc, + const Render::Transform& transform, + const std::string& filter_name, + const std::vector& filter_params) { + return RandR::SetCrtcTransform(RandR::SetCrtcTransformRequest{ + crtc, transform, filter_name, filter_params}); +} + +Future RandR::GetCrtcTransform( + const RandR::GetCrtcTransformRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& crtc = request.crtc; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 27; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // crtc + buf.Write(&crtc); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "RandR::GetCrtcTransform", false); +} + +Future RandR::GetCrtcTransform(const Crtc& crtc) { + return RandR::GetCrtcTransform(RandR::GetCrtcTransformRequest{crtc}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + RandR::GetCrtcTransformReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& pending_transform = (*reply).pending_transform; + auto& has_transforms = (*reply).has_transforms; + auto& current_transform = (*reply).current_transform; + uint16_t pending_len{}; + uint16_t pending_nparams{}; + uint16_t current_len{}; + uint16_t current_nparams{}; + auto& pending_filter_name = (*reply).pending_filter_name; + size_t pending_filter_name_len = pending_filter_name.size(); + auto& pending_params = (*reply).pending_params; + size_t pending_params_len = pending_params.size(); + auto& current_filter_name = (*reply).current_filter_name; + size_t current_filter_name_len = current_filter_name.size(); + auto& current_params = (*reply).current_params; + size_t current_params_len = current_params.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pending_transform + { + auto& matrix11 = pending_transform.matrix11; + auto& matrix12 = pending_transform.matrix12; + auto& matrix13 = pending_transform.matrix13; + auto& matrix21 = pending_transform.matrix21; + auto& matrix22 = pending_transform.matrix22; + auto& matrix23 = pending_transform.matrix23; + auto& matrix31 = pending_transform.matrix31; + auto& matrix32 = pending_transform.matrix32; + auto& matrix33 = pending_transform.matrix33; + + // matrix11 + Read(&matrix11, &buf); + + // matrix12 + Read(&matrix12, &buf); + + // matrix13 + Read(&matrix13, &buf); + + // matrix21 + Read(&matrix21, &buf); + + // matrix22 + Read(&matrix22, &buf); + + // matrix23 + Read(&matrix23, &buf); + + // matrix31 + Read(&matrix31, &buf); + + // matrix32 + Read(&matrix32, &buf); + + // matrix33 + Read(&matrix33, &buf); + } + + // has_transforms + Read(&has_transforms, &buf); + + // pad1 + Pad(&buf, 3); + + // current_transform + { + auto& matrix11 = current_transform.matrix11; + auto& matrix12 = current_transform.matrix12; + auto& matrix13 = current_transform.matrix13; + auto& matrix21 = current_transform.matrix21; + auto& matrix22 = current_transform.matrix22; + auto& matrix23 = current_transform.matrix23; + auto& matrix31 = current_transform.matrix31; + auto& matrix32 = current_transform.matrix32; + auto& matrix33 = current_transform.matrix33; + + // matrix11 + Read(&matrix11, &buf); + + // matrix12 + Read(&matrix12, &buf); + + // matrix13 + Read(&matrix13, &buf); + + // matrix21 + Read(&matrix21, &buf); + + // matrix22 + Read(&matrix22, &buf); + + // matrix23 + Read(&matrix23, &buf); + + // matrix31 + Read(&matrix31, &buf); + + // matrix32 + Read(&matrix32, &buf); + + // matrix33 + Read(&matrix33, &buf); + } + + // pad2 + Pad(&buf, 4); + + // pending_len + Read(&pending_len, &buf); + + // pending_nparams + Read(&pending_nparams, &buf); + + // current_len + Read(¤t_len, &buf); + + // current_nparams + Read(¤t_nparams, &buf); + + // pending_filter_name + pending_filter_name.resize(pending_len); + for (auto& pending_filter_name_elem : pending_filter_name) { + // pending_filter_name_elem + Read(&pending_filter_name_elem, &buf); + } + + // pad3 + Align(&buf, 4); + + // pending_params + pending_params.resize(pending_nparams); + for (auto& pending_params_elem : pending_params) { + // pending_params_elem + Read(&pending_params_elem, &buf); + } + + // current_filter_name + current_filter_name.resize(current_len); + for (auto& current_filter_name_elem : current_filter_name) { + // current_filter_name_elem + Read(¤t_filter_name_elem, &buf); + } + + // pad4 + Align(&buf, 4); + + // current_params + current_params.resize(current_nparams); + for (auto& current_params_elem : current_params) { + // current_params_elem + Read(¤t_params_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future RandR::GetPanning( + const RandR::GetPanningRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& crtc = request.crtc; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 28; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // crtc + buf.Write(&crtc); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "RandR::GetPanning", false); +} + +Future RandR::GetPanning(const Crtc& crtc) { + return RandR::GetPanning(RandR::GetPanningRequest{crtc}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + RandR::GetPanningReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& status = (*reply).status; + auto& sequence = (*reply).sequence; + auto& timestamp = (*reply).timestamp; + auto& left = (*reply).left; + auto& top = (*reply).top; + auto& width = (*reply).width; + auto& height = (*reply).height; + auto& track_left = (*reply).track_left; + auto& track_top = (*reply).track_top; + auto& track_width = (*reply).track_width; + auto& track_height = (*reply).track_height; + auto& border_left = (*reply).border_left; + auto& border_top = (*reply).border_top; + auto& border_right = (*reply).border_right; + auto& border_bottom = (*reply).border_bottom; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // status + uint8_t tmp26; + Read(&tmp26, &buf); + status = static_cast(tmp26); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // timestamp + Read(×tamp, &buf); + + // left + Read(&left, &buf); + + // top + Read(&top, &buf); + + // width + Read(&width, &buf); + + // height + Read(&height, &buf); + + // track_left + Read(&track_left, &buf); + + // track_top + Read(&track_top, &buf); + + // track_width + Read(&track_width, &buf); + + // track_height + Read(&track_height, &buf); + + // border_left + Read(&border_left, &buf); + + // border_top + Read(&border_top, &buf); + + // border_right + Read(&border_right, &buf); + + // border_bottom + Read(&border_bottom, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future RandR::SetPanning( + const RandR::SetPanningRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& crtc = request.crtc; + auto& timestamp = request.timestamp; + auto& left = request.left; + auto& top = request.top; + auto& width = request.width; + auto& height = request.height; + auto& track_left = request.track_left; + auto& track_top = request.track_top; + auto& track_width = request.track_width; + auto& track_height = request.track_height; + auto& border_left = request.border_left; + auto& border_top = request.border_top; + auto& border_right = request.border_right; + auto& border_bottom = request.border_bottom; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 29; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // crtc + buf.Write(&crtc); + + // timestamp + buf.Write(×tamp); + + // left + buf.Write(&left); + + // top + buf.Write(&top); + + // width + buf.Write(&width); + + // height + buf.Write(&height); + + // track_left + buf.Write(&track_left); + + // track_top + buf.Write(&track_top); + + // track_width + buf.Write(&track_width); + + // track_height + buf.Write(&track_height); + + // border_left + buf.Write(&border_left); + + // border_top + buf.Write(&border_top); + + // border_right + buf.Write(&border_right); + + // border_bottom + buf.Write(&border_bottom); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "RandR::SetPanning", false); +} + +Future RandR::SetPanning(const Crtc& crtc, + const Time& timestamp, + const uint16_t& left, + const uint16_t& top, + const uint16_t& width, + const uint16_t& height, + const uint16_t& track_left, + const uint16_t& track_top, + const uint16_t& track_width, + const uint16_t& track_height, + const int16_t& border_left, + const int16_t& border_top, + const int16_t& border_right, + const int16_t& border_bottom) { + return RandR::SetPanning(RandR::SetPanningRequest{ + crtc, timestamp, left, top, width, height, track_left, track_top, + track_width, track_height, border_left, border_top, border_right, + border_bottom}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + RandR::SetPanningReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& status = (*reply).status; + auto& sequence = (*reply).sequence; + auto& timestamp = (*reply).timestamp; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // status + uint8_t tmp27; + Read(&tmp27, &buf); + status = static_cast(tmp27); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // timestamp + Read(×tamp, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future RandR::SetOutputPrimary( + const RandR::SetOutputPrimaryRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + auto& output = request.output; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 30; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + // output + buf.Write(&output); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "RandR::SetOutputPrimary", false); +} + +Future RandR::SetOutputPrimary(const Window& window, + const Output& output) { + return RandR::SetOutputPrimary( + RandR::SetOutputPrimaryRequest{window, output}); +} + +Future RandR::GetOutputPrimary( + const RandR::GetOutputPrimaryRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 31; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "RandR::GetOutputPrimary", false); +} + +Future RandR::GetOutputPrimary( + const Window& window) { + return RandR::GetOutputPrimary(RandR::GetOutputPrimaryRequest{window}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + RandR::GetOutputPrimaryReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& output = (*reply).output; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // output + Read(&output, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future RandR::GetProviders( + const RandR::GetProvidersRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 32; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "RandR::GetProviders", false); +} + +Future RandR::GetProviders(const Window& window) { + return RandR::GetProviders(RandR::GetProvidersRequest{window}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + RandR::GetProvidersReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& timestamp = (*reply).timestamp; + uint16_t num_providers{}; + auto& providers = (*reply).providers; + size_t providers_len = providers.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // timestamp + Read(×tamp, &buf); + + // num_providers + Read(&num_providers, &buf); + + // pad1 + Pad(&buf, 18); + + // providers + providers.resize(num_providers); + for (auto& providers_elem : providers) { + // providers_elem + Read(&providers_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future RandR::GetProviderInfo( + const RandR::GetProviderInfoRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& provider = request.provider; + auto& config_timestamp = request.config_timestamp; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 33; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // provider + buf.Write(&provider); + + // config_timestamp + buf.Write(&config_timestamp); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "RandR::GetProviderInfo", false); +} + +Future RandR::GetProviderInfo( + const Provider& provider, + const Time& config_timestamp) { + return RandR::GetProviderInfo( + RandR::GetProviderInfoRequest{provider, config_timestamp}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + RandR::GetProviderInfoReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& status = (*reply).status; + auto& sequence = (*reply).sequence; + auto& timestamp = (*reply).timestamp; + auto& capabilities = (*reply).capabilities; + uint16_t num_crtcs{}; + uint16_t num_outputs{}; + uint16_t num_associated_providers{}; + uint16_t name_len{}; + auto& crtcs = (*reply).crtcs; + size_t crtcs_len = crtcs.size(); + auto& outputs = (*reply).outputs; + size_t outputs_len = outputs.size(); + auto& associated_providers = (*reply).associated_providers; + size_t associated_providers_len = associated_providers.size(); + auto& associated_capability = (*reply).associated_capability; + size_t associated_capability_len = associated_capability.size(); + auto& name = (*reply).name; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // status + Read(&status, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // timestamp + Read(×tamp, &buf); + + // capabilities + uint32_t tmp28; + Read(&tmp28, &buf); + capabilities = static_cast(tmp28); + + // num_crtcs + Read(&num_crtcs, &buf); + + // num_outputs + Read(&num_outputs, &buf); + + // num_associated_providers + Read(&num_associated_providers, &buf); + + // name_len + Read(&name_len, &buf); + + // pad0 + Pad(&buf, 8); + + // crtcs + crtcs.resize(num_crtcs); + for (auto& crtcs_elem : crtcs) { + // crtcs_elem + Read(&crtcs_elem, &buf); + } + + // outputs + outputs.resize(num_outputs); + for (auto& outputs_elem : outputs) { + // outputs_elem + Read(&outputs_elem, &buf); + } + + // associated_providers + associated_providers.resize(num_associated_providers); + for (auto& associated_providers_elem : associated_providers) { + // associated_providers_elem + Read(&associated_providers_elem, &buf); + } + + // associated_capability + associated_capability.resize(num_associated_providers); + for (auto& associated_capability_elem : associated_capability) { + // associated_capability_elem + Read(&associated_capability_elem, &buf); + } + + // name + name.resize(name_len); + for (auto& name_elem : name) { + // name_elem + Read(&name_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future RandR::SetProviderOffloadSink( + const RandR::SetProviderOffloadSinkRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& provider = request.provider; + auto& sink_provider = request.sink_provider; + auto& config_timestamp = request.config_timestamp; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 34; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // provider + buf.Write(&provider); + + // sink_provider + buf.Write(&sink_provider); + + // config_timestamp + buf.Write(&config_timestamp); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "RandR::SetProviderOffloadSink", + false); +} + +Future RandR::SetProviderOffloadSink(const Provider& provider, + const Provider& sink_provider, + const Time& config_timestamp) { + return RandR::SetProviderOffloadSink(RandR::SetProviderOffloadSinkRequest{ + provider, sink_provider, config_timestamp}); +} + +Future RandR::SetProviderOutputSource( + const RandR::SetProviderOutputSourceRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& provider = request.provider; + auto& source_provider = request.source_provider; + auto& config_timestamp = request.config_timestamp; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 35; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // provider + buf.Write(&provider); + + // source_provider + buf.Write(&source_provider); + + // config_timestamp + buf.Write(&config_timestamp); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "RandR::SetProviderOutputSource", + false); +} + +Future RandR::SetProviderOutputSource(const Provider& provider, + const Provider& source_provider, + const Time& config_timestamp) { + return RandR::SetProviderOutputSource(RandR::SetProviderOutputSourceRequest{ + provider, source_provider, config_timestamp}); +} + +Future RandR::ListProviderProperties( + const RandR::ListProviderPropertiesRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& provider = request.provider; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 36; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // provider + buf.Write(&provider); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "RandR::ListProviderProperties", false); +} + +Future RandR::ListProviderProperties( + const Provider& provider) { + return RandR::ListProviderProperties( + RandR::ListProviderPropertiesRequest{provider}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + RandR::ListProviderPropertiesReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint16_t num_atoms{}; + auto& atoms = (*reply).atoms; + size_t atoms_len = atoms.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // num_atoms + Read(&num_atoms, &buf); + + // pad1 + Pad(&buf, 22); + + // atoms + atoms.resize(num_atoms); + for (auto& atoms_elem : atoms) { + // atoms_elem + Read(&atoms_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future RandR::QueryProviderProperty( + const RandR::QueryProviderPropertyRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& provider = request.provider; + auto& property = request.property; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 37; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // provider + buf.Write(&provider); + + // property + buf.Write(&property); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "RandR::QueryProviderProperty", false); +} + +Future RandR::QueryProviderProperty( + const Provider& provider, + const Atom& property) { + return RandR::QueryProviderProperty( + RandR::QueryProviderPropertyRequest{provider, property}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + RandR::QueryProviderPropertyReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& pending = (*reply).pending; + auto& range = (*reply).range; + auto& immutable = (*reply).immutable; + auto& valid_values = (*reply).valid_values; + size_t valid_values_len = valid_values.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pending + Read(&pending, &buf); + + // range + Read(&range, &buf); + + // immutable + Read(&immutable, &buf); + + // pad1 + Pad(&buf, 21); + + // valid_values + valid_values.resize(length); + for (auto& valid_values_elem : valid_values) { + // valid_values_elem + Read(&valid_values_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future RandR::ConfigureProviderProperty( + const RandR::ConfigureProviderPropertyRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& provider = request.provider; + auto& property = request.property; + auto& pending = request.pending; + auto& range = request.range; + auto& values = request.values; + size_t values_len = values.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 38; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // provider + buf.Write(&provider); + + // property + buf.Write(&property); + + // pending + buf.Write(&pending); + + // range + buf.Write(&range); + + // pad0 + Pad(&buf, 2); + + // values + DCHECK_EQ(static_cast(values_len), values.size()); + for (auto& values_elem : values) { + // values_elem + buf.Write(&values_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "RandR::ConfigureProviderProperty", false); +} + +Future RandR::ConfigureProviderProperty( + const Provider& provider, + const Atom& property, + const uint8_t& pending, + const uint8_t& range, + const std::vector& values) { + return RandR::ConfigureProviderProperty( + RandR::ConfigureProviderPropertyRequest{provider, property, pending, + range, values}); +} + +Future RandR::ChangeProviderProperty( + const RandR::ChangeProviderPropertyRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& provider = request.provider; + auto& property = request.property; + auto& type = request.type; + auto& format = request.format; + auto& mode = request.mode; + auto& num_items = request.num_items; + auto& data = request.data; + size_t data_len = data ? data->size() : 0; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 39; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // provider + buf.Write(&provider); + + // property + buf.Write(&property); + + // type + buf.Write(&type); + + // format + buf.Write(&format); + + // mode + buf.Write(&mode); + + // pad0 + Pad(&buf, 2); + + // num_items + buf.Write(&num_items); + + // data + buf.AppendBuffer(data, (num_items) * ((format) / (8))); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "RandR::ChangeProviderProperty", + false); +} + +Future RandR::ChangeProviderProperty( + const Provider& provider, + const Atom& property, + const Atom& type, + const uint8_t& format, + const uint8_t& mode, + const uint32_t& num_items, + const scoped_refptr& data) { + return RandR::ChangeProviderProperty(RandR::ChangeProviderPropertyRequest{ + provider, property, type, format, mode, num_items, data}); +} + +Future RandR::DeleteProviderProperty( + const RandR::DeleteProviderPropertyRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& provider = request.provider; + auto& property = request.property; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 40; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // provider + buf.Write(&provider); + + // property + buf.Write(&property); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "RandR::DeleteProviderProperty", + false); +} + +Future RandR::DeleteProviderProperty(const Provider& provider, + const Atom& property) { + return RandR::DeleteProviderProperty( + RandR::DeleteProviderPropertyRequest{provider, property}); +} + +Future RandR::GetProviderProperty( + const RandR::GetProviderPropertyRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& provider = request.provider; + auto& property = request.property; + auto& type = request.type; + auto& long_offset = request.long_offset; + auto& long_length = request.long_length; + auto& c_delete = request.c_delete; + auto& pending = request.pending; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 41; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // provider + buf.Write(&provider); + + // property + buf.Write(&property); + + // type + buf.Write(&type); + + // long_offset + buf.Write(&long_offset); + + // long_length + buf.Write(&long_length); + + // c_delete + buf.Write(&c_delete); + + // pending + buf.Write(&pending); + + // pad0 + Pad(&buf, 2); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "RandR::GetProviderProperty", false); +} + +Future RandR::GetProviderProperty( + const Provider& provider, + const Atom& property, + const Atom& type, + const uint32_t& long_offset, + const uint32_t& long_length, + const uint8_t& c_delete, + const uint8_t& pending) { + return RandR::GetProviderProperty(RandR::GetProviderPropertyRequest{ + provider, property, type, long_offset, long_length, c_delete, pending}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + RandR::GetProviderPropertyReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& format = (*reply).format; + auto& sequence = (*reply).sequence; + auto& type = (*reply).type; + auto& bytes_after = (*reply).bytes_after; + auto& num_items = (*reply).num_items; + auto& data = (*reply).data; + size_t data_len = data ? data->size() : 0; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // format + Read(&format, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // type + Read(&type, &buf); + + // bytes_after + Read(&bytes_after, &buf); + + // num_items + Read(&num_items, &buf); + + // pad0 + Pad(&buf, 12); + + // data + data = buffer->ReadAndAdvance((num_items) * ((format) / (8))); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future RandR::GetMonitors( + const RandR::GetMonitorsRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + auto& get_active = request.get_active; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 42; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + // get_active + buf.Write(&get_active); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "RandR::GetMonitors", false); +} + +Future RandR::GetMonitors(const Window& window, + const uint8_t& get_active) { + return RandR::GetMonitors(RandR::GetMonitorsRequest{window, get_active}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + RandR::GetMonitorsReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& timestamp = (*reply).timestamp; + uint32_t nMonitors{}; + auto& nOutputs = (*reply).nOutputs; + auto& monitors = (*reply).monitors; + size_t monitors_len = monitors.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // timestamp + Read(×tamp, &buf); + + // nMonitors + Read(&nMonitors, &buf); + + // nOutputs + Read(&nOutputs, &buf); + + // pad1 + Pad(&buf, 12); + + // monitors + monitors.resize(nMonitors); + for (auto& monitors_elem : monitors) { + // monitors_elem + { + auto& name = monitors_elem.name; + auto& primary = monitors_elem.primary; + auto& automatic = monitors_elem.automatic; + uint16_t nOutput{}; + auto& x = monitors_elem.x; + auto& y = monitors_elem.y; + auto& width = monitors_elem.width; + auto& height = monitors_elem.height; + auto& width_in_millimeters = monitors_elem.width_in_millimeters; + auto& height_in_millimeters = monitors_elem.height_in_millimeters; + auto& outputs = monitors_elem.outputs; + size_t outputs_len = outputs.size(); + + // name + Read(&name, &buf); + + // primary + Read(&primary, &buf); + + // automatic + Read(&automatic, &buf); + + // nOutput + Read(&nOutput, &buf); + + // x + Read(&x, &buf); + + // y + Read(&y, &buf); + + // width + Read(&width, &buf); + + // height + Read(&height, &buf); + + // width_in_millimeters + Read(&width_in_millimeters, &buf); + + // height_in_millimeters + Read(&height_in_millimeters, &buf); + + // outputs + outputs.resize(nOutput); + for (auto& outputs_elem : outputs) { + // outputs_elem + Read(&outputs_elem, &buf); + } + } + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future RandR::SetMonitor(const RandR::SetMonitorRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + auto& monitorinfo = request.monitorinfo; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 43; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + // monitorinfo + { + auto& name = monitorinfo.name; + auto& primary = monitorinfo.primary; + auto& automatic = monitorinfo.automatic; + uint16_t nOutput{}; + auto& x = monitorinfo.x; + auto& y = monitorinfo.y; + auto& width = monitorinfo.width; + auto& height = monitorinfo.height; + auto& width_in_millimeters = monitorinfo.width_in_millimeters; + auto& height_in_millimeters = monitorinfo.height_in_millimeters; + auto& outputs = monitorinfo.outputs; + size_t outputs_len = outputs.size(); + + // name + buf.Write(&name); + + // primary + buf.Write(&primary); + + // automatic + buf.Write(&automatic); + + // nOutput + nOutput = outputs.size(); + buf.Write(&nOutput); + + // x + buf.Write(&x); + + // y + buf.Write(&y); + + // width + buf.Write(&width); + + // height + buf.Write(&height); + + // width_in_millimeters + buf.Write(&width_in_millimeters); + + // height_in_millimeters + buf.Write(&height_in_millimeters); + + // outputs + DCHECK_EQ(static_cast(nOutput), outputs.size()); + for (auto& outputs_elem : outputs) { + // outputs_elem + buf.Write(&outputs_elem); + } + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "RandR::SetMonitor", false); +} + +Future RandR::SetMonitor(const Window& window, + const MonitorInfo& monitorinfo) { + return RandR::SetMonitor(RandR::SetMonitorRequest{window, monitorinfo}); +} + +Future RandR::DeleteMonitor(const RandR::DeleteMonitorRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + auto& name = request.name; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 44; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + // name + buf.Write(&name); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "RandR::DeleteMonitor", false); +} + +Future RandR::DeleteMonitor(const Window& window, const Atom& name) { + return RandR::DeleteMonitor(RandR::DeleteMonitorRequest{window, name}); +} + +Future RandR::CreateLease( + const RandR::CreateLeaseRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + auto& lid = request.lid; + uint16_t num_crtcs{}; + uint16_t num_outputs{}; + auto& crtcs = request.crtcs; + size_t crtcs_len = crtcs.size(); + auto& outputs = request.outputs; + size_t outputs_len = outputs.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 45; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + // lid + buf.Write(&lid); + + // num_crtcs + num_crtcs = crtcs.size(); + buf.Write(&num_crtcs); + + // num_outputs + num_outputs = outputs.size(); + buf.Write(&num_outputs); + + // crtcs + DCHECK_EQ(static_cast(num_crtcs), crtcs.size()); + for (auto& crtcs_elem : crtcs) { + // crtcs_elem + buf.Write(&crtcs_elem); + } + + // outputs + DCHECK_EQ(static_cast(num_outputs), outputs.size()); + for (auto& outputs_elem : outputs) { + // outputs_elem + buf.Write(&outputs_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "RandR::CreateLease", true); +} + +Future RandR::CreateLease( + const Window& window, + const Lease& lid, + const std::vector& crtcs, + const std::vector& outputs) { + return RandR::CreateLease( + RandR::CreateLeaseRequest{window, lid, crtcs, outputs}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + RandR::CreateLeaseReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& nfd = (*reply).nfd; + auto& sequence = (*reply).sequence; + auto& master_fd = (*reply).master_fd; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // nfd + Read(&nfd, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // master_fd + master_fd = RefCountedFD(buf.TakeFd()); + + // pad0 + Pad(&buf, 24); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future RandR::FreeLease(const RandR::FreeLeaseRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& lid = request.lid; + auto& terminate = request.terminate; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 46; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // lid + buf.Write(&lid); + + // terminate + buf.Write(&terminate); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "RandR::FreeLease", false); +} + +Future RandR::FreeLease(const Lease& lid, const uint8_t& terminate) { + return RandR::FreeLease(RandR::FreeLeaseRequest{lid, terminate}); +} + +} // namespace x11 diff --git a/x/generated_protos/randr.h b/x/generated_protos/randr.h new file mode 100644 index 000000000000..1f420bb79bd1 --- /dev/null +++ b/x/generated_protos/randr.h @@ -0,0 +1,1337 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#ifndef UI_GFX_X_GENERATED_PROTOS_RANDR_H_ +#define UI_GFX_X_GENERATED_PROTOS_RANDR_H_ + +#include +#include +#include +#include +#include + +#include "base/component_export.h" +#include "base/files/scoped_file.h" +#include "base/memory/ref_counted_memory.h" +#include "base/memory/scoped_refptr.h" +#include "render.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/gfx/x/error.h" +#include "ui/gfx/x/ref_counted_fd.h" +#include "xproto.h" + +namespace x11 { + +class Connection; + +template +struct Response; + +template +class Future; + +class COMPONENT_EXPORT(X11) RandR { + public: + static constexpr unsigned major_version = 1; + static constexpr unsigned minor_version = 6; + + RandR(Connection* connection, const x11::QueryExtensionReply& info); + + uint8_t present() const { return info_.present; } + uint8_t major_opcode() const { return info_.major_opcode; } + uint8_t first_event() const { return info_.first_event; } + uint8_t first_error() const { return info_.first_error; } + + Connection* connection() const { return connection_; } + + enum class Mode : uint32_t {}; + + enum class Crtc : uint32_t {}; + + enum class Output : uint32_t {}; + + enum class Provider : uint32_t {}; + + enum class Lease : uint32_t {}; + + enum class Rotation : int { + Rotate_0 = 1 << 0, + Rotate_90 = 1 << 1, + Rotate_180 = 1 << 2, + Rotate_270 = 1 << 3, + Reflect_X = 1 << 4, + Reflect_Y = 1 << 5, + }; + + enum class SetConfig : int { + Success = 0, + InvalidConfigTime = 1, + InvalidTime = 2, + Failed = 3, + }; + + enum class NotifyMask : int { + ScreenChange = 1 << 0, + CrtcChange = 1 << 1, + OutputChange = 1 << 2, + OutputProperty = 1 << 3, + ProviderChange = 1 << 4, + ProviderProperty = 1 << 5, + ResourceChange = 1 << 6, + Lease = 1 << 7, + }; + + enum class ModeFlag : int { + HsyncPositive = 1 << 0, + HsyncNegative = 1 << 1, + VsyncPositive = 1 << 2, + VsyncNegative = 1 << 3, + Interlace = 1 << 4, + DoubleScan = 1 << 5, + Csync = 1 << 6, + CsyncPositive = 1 << 7, + CsyncNegative = 1 << 8, + HskewPresent = 1 << 9, + Bcast = 1 << 10, + PixelMultiplex = 1 << 11, + DoubleClock = 1 << 12, + HalveClock = 1 << 13, + }; + + enum class RandRConnection : int { + Connected = 0, + Disconnected = 1, + Unknown = 2, + }; + + enum class Transform : int { + Unit = 1 << 0, + ScaleUp = 1 << 1, + ScaleDown = 1 << 2, + Projective = 1 << 3, + }; + + enum class ProviderCapability : int { + SourceOutput = 1 << 0, + SinkOutput = 1 << 1, + SourceOffload = 1 << 2, + SinkOffload = 1 << 3, + }; + + enum class Notify : int { + CrtcChange = 0, + OutputChange = 1, + OutputProperty = 2, + ProviderChange = 3, + ProviderProperty = 4, + ResourceChange = 5, + Lease = 6, + }; + + struct BadOutputError : public x11::Error { + uint16_t sequence{}; + + std::string ToString() const override; + }; + + struct BadCrtcError : public x11::Error { + uint16_t sequence{}; + + std::string ToString() const override; + }; + + struct BadModeError : public x11::Error { + uint16_t sequence{}; + + std::string ToString() const override; + }; + + struct BadProviderError : public x11::Error { + uint16_t sequence{}; + + std::string ToString() const override; + }; + + struct ScreenSize { + uint16_t width{}; + uint16_t height{}; + uint16_t mwidth{}; + uint16_t mheight{}; + }; + + struct RefreshRates { + std::vector rates{}; + }; + + struct ModeInfo { + uint32_t id{}; + uint16_t width{}; + uint16_t height{}; + uint32_t dot_clock{}; + uint16_t hsync_start{}; + uint16_t hsync_end{}; + uint16_t htotal{}; + uint16_t hskew{}; + uint16_t vsync_start{}; + uint16_t vsync_end{}; + uint16_t vtotal{}; + uint16_t name_len{}; + ModeFlag mode_flags{}; + }; + + struct ScreenChangeNotifyEvent { + static constexpr int type_id = 11; + static constexpr uint8_t opcode = 0; + bool send_event{}; + Rotation rotation{}; + uint16_t sequence{}; + Time timestamp{}; + Time config_timestamp{}; + Window root{}; + Window request_window{}; + uint16_t sizeID{}; + Render::SubPixel subpixel_order{}; + uint16_t width{}; + uint16_t height{}; + uint16_t mwidth{}; + uint16_t mheight{}; + + x11::Window* GetWindow() { + return reinterpret_cast(&request_window); + } + }; + + struct MonitorInfo { + Atom name{}; + uint8_t primary{}; + uint8_t automatic{}; + int16_t x{}; + int16_t y{}; + uint16_t width{}; + uint16_t height{}; + uint32_t width_in_millimeters{}; + uint32_t height_in_millimeters{}; + std::vector outputs{}; + }; + + struct NotifyEvent { + static constexpr int type_id = 12; + static constexpr uint8_t opcode = 1; + bool send_event{}; + uint16_t sequence{}; + struct Cc { + Time timestamp{}; + Window window{}; + Crtc crtc{}; + Mode mode{}; + Rotation rotation{}; + int16_t x{}; + int16_t y{}; + uint16_t width{}; + uint16_t height{}; + }; + struct Oc { + Time timestamp{}; + Time config_timestamp{}; + Window window{}; + Output output{}; + Crtc crtc{}; + Mode mode{}; + Rotation rotation{}; + RandRConnection connection{}; + Render::SubPixel subpixel_order{}; + }; + struct Op { + Window window{}; + Output output{}; + Atom atom{}; + Time timestamp{}; + Property status{}; + }; + struct Pc { + Time timestamp{}; + Window window{}; + Provider provider{}; + }; + struct Pp { + Window window{}; + Provider provider{}; + Atom atom{}; + Time timestamp{}; + uint8_t state{}; + }; + struct Rc { + Time timestamp{}; + Window window{}; + }; + struct Lc { + Time timestamp{}; + Window window{}; + Lease lease{}; + uint8_t created{}; + }; + absl::optional cc{}; + absl::optional oc{}; + absl::optional op{}; + absl::optional pc{}; + absl::optional pp{}; + absl::optional rc{}; + absl::optional lc{}; + + x11::Window* GetWindow() { return nullptr; } + }; + + struct QueryVersionRequest { + uint32_t major_version{}; + uint32_t minor_version{}; + }; + + struct QueryVersionReply { + uint16_t sequence{}; + uint32_t major_version{}; + uint32_t minor_version{}; + }; + + using QueryVersionResponse = Response; + + Future QueryVersion(const QueryVersionRequest& request); + + Future QueryVersion(const uint32_t& major_version = {}, + const uint32_t& minor_version = {}); + + struct SetScreenConfigRequest { + Window window{}; + Time timestamp{}; + Time config_timestamp{}; + uint16_t sizeID{}; + Rotation rotation{}; + uint16_t rate{}; + }; + + struct SetScreenConfigReply { + SetConfig status{}; + uint16_t sequence{}; + Time new_timestamp{}; + Time config_timestamp{}; + Window root{}; + Render::SubPixel subpixel_order{}; + }; + + using SetScreenConfigResponse = Response; + + Future SetScreenConfig( + const SetScreenConfigRequest& request); + + Future SetScreenConfig( + const Window& window = {}, + const Time& timestamp = {}, + const Time& config_timestamp = {}, + const uint16_t& sizeID = {}, + const Rotation& rotation = {}, + const uint16_t& rate = {}); + + struct SelectInputRequest { + Window window{}; + NotifyMask enable{}; + }; + + using SelectInputResponse = Response; + + Future SelectInput(const SelectInputRequest& request); + + Future SelectInput(const Window& window = {}, + const NotifyMask& enable = {}); + + struct GetScreenInfoRequest { + Window window{}; + }; + + struct GetScreenInfoReply { + Rotation rotations{}; + uint16_t sequence{}; + Window root{}; + Time timestamp{}; + Time config_timestamp{}; + uint16_t sizeID{}; + Rotation rotation{}; + uint16_t rate{}; + uint16_t nInfo{}; + std::vector sizes{}; + std::vector rates{}; + }; + + using GetScreenInfoResponse = Response; + + Future GetScreenInfo(const GetScreenInfoRequest& request); + + Future GetScreenInfo(const Window& window = {}); + + struct GetScreenSizeRangeRequest { + Window window{}; + }; + + struct GetScreenSizeRangeReply { + uint16_t sequence{}; + uint16_t min_width{}; + uint16_t min_height{}; + uint16_t max_width{}; + uint16_t max_height{}; + }; + + using GetScreenSizeRangeResponse = Response; + + Future GetScreenSizeRange( + const GetScreenSizeRangeRequest& request); + + Future GetScreenSizeRange(const Window& window = {}); + + struct SetScreenSizeRequest { + Window window{}; + uint16_t width{}; + uint16_t height{}; + uint32_t mm_width{}; + uint32_t mm_height{}; + }; + + using SetScreenSizeResponse = Response; + + Future SetScreenSize(const SetScreenSizeRequest& request); + + Future SetScreenSize(const Window& window = {}, + const uint16_t& width = {}, + const uint16_t& height = {}, + const uint32_t& mm_width = {}, + const uint32_t& mm_height = {}); + + struct GetScreenResourcesRequest { + Window window{}; + }; + + struct GetScreenResourcesReply { + uint16_t sequence{}; + Time timestamp{}; + Time config_timestamp{}; + std::vector crtcs{}; + std::vector outputs{}; + std::vector modes{}; + std::vector names{}; + }; + + using GetScreenResourcesResponse = Response; + + Future GetScreenResources( + const GetScreenResourcesRequest& request); + + Future GetScreenResources(const Window& window = {}); + + struct GetOutputInfoRequest { + Output output{}; + Time config_timestamp{}; + }; + + struct GetOutputInfoReply { + SetConfig status{}; + uint16_t sequence{}; + Time timestamp{}; + Crtc crtc{}; + uint32_t mm_width{}; + uint32_t mm_height{}; + RandRConnection connection{}; + Render::SubPixel subpixel_order{}; + uint16_t num_preferred{}; + std::vector crtcs{}; + std::vector modes{}; + std::vector clones{}; + std::vector name{}; + }; + + using GetOutputInfoResponse = Response; + + Future GetOutputInfo(const GetOutputInfoRequest& request); + + Future GetOutputInfo(const Output& output = {}, + const Time& config_timestamp = {}); + + struct ListOutputPropertiesRequest { + Output output{}; + }; + + struct ListOutputPropertiesReply { + uint16_t sequence{}; + std::vector atoms{}; + }; + + using ListOutputPropertiesResponse = Response; + + Future ListOutputProperties( + const ListOutputPropertiesRequest& request); + + Future ListOutputProperties( + const Output& output = {}); + + struct QueryOutputPropertyRequest { + Output output{}; + Atom property{}; + }; + + struct QueryOutputPropertyReply { + uint16_t sequence{}; + uint8_t pending{}; + uint8_t range{}; + uint8_t immutable{}; + std::vector validValues{}; + }; + + using QueryOutputPropertyResponse = Response; + + Future QueryOutputProperty( + const QueryOutputPropertyRequest& request); + + Future QueryOutputProperty( + const Output& output = {}, + const Atom& property = {}); + + struct ConfigureOutputPropertyRequest { + Output output{}; + Atom property{}; + uint8_t pending{}; + uint8_t range{}; + std::vector values{}; + }; + + using ConfigureOutputPropertyResponse = Response; + + Future ConfigureOutputProperty( + const ConfigureOutputPropertyRequest& request); + + Future ConfigureOutputProperty(const Output& output = {}, + const Atom& property = {}, + const uint8_t& pending = {}, + const uint8_t& range = {}, + const std::vector& values = {}); + + struct ChangeOutputPropertyRequest { + Output output{}; + Atom property{}; + Atom type{}; + uint8_t format{}; + PropMode mode{}; + uint32_t num_units{}; + scoped_refptr data{}; + }; + + using ChangeOutputPropertyResponse = Response; + + Future ChangeOutputProperty(const ChangeOutputPropertyRequest& request); + + Future ChangeOutputProperty( + const Output& output = {}, + const Atom& property = {}, + const Atom& type = {}, + const uint8_t& format = {}, + const PropMode& mode = {}, + const uint32_t& num_units = {}, + const scoped_refptr& data = {}); + + struct DeleteOutputPropertyRequest { + Output output{}; + Atom property{}; + }; + + using DeleteOutputPropertyResponse = Response; + + Future DeleteOutputProperty(const DeleteOutputPropertyRequest& request); + + Future DeleteOutputProperty(const Output& output = {}, + const Atom& property = {}); + + struct GetOutputPropertyRequest { + Output output{}; + Atom property{}; + Atom type{}; + uint32_t long_offset{}; + uint32_t long_length{}; + uint8_t c_delete{}; + uint8_t pending{}; + }; + + struct GetOutputPropertyReply { + uint8_t format{}; + uint16_t sequence{}; + Atom type{}; + uint32_t bytes_after{}; + uint32_t num_items{}; + std::vector data{}; + }; + + using GetOutputPropertyResponse = Response; + + Future GetOutputProperty( + const GetOutputPropertyRequest& request); + + Future GetOutputProperty( + const Output& output = {}, + const Atom& property = {}, + const Atom& type = {}, + const uint32_t& long_offset = {}, + const uint32_t& long_length = {}, + const uint8_t& c_delete = {}, + const uint8_t& pending = {}); + + struct CreateModeRequest { + Window window{}; + ModeInfo mode_info{}; + std::string name{}; + }; + + struct CreateModeReply { + uint16_t sequence{}; + Mode mode{}; + }; + + using CreateModeResponse = Response; + + Future CreateMode(const CreateModeRequest& request); + + Future CreateMode( + const Window& window = {}, + const ModeInfo& mode_info = + {{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}, + const std::string& name = {}); + + struct DestroyModeRequest { + Mode mode{}; + }; + + using DestroyModeResponse = Response; + + Future DestroyMode(const DestroyModeRequest& request); + + Future DestroyMode(const Mode& mode = {}); + + struct AddOutputModeRequest { + Output output{}; + Mode mode{}; + }; + + using AddOutputModeResponse = Response; + + Future AddOutputMode(const AddOutputModeRequest& request); + + Future AddOutputMode(const Output& output = {}, const Mode& mode = {}); + + struct DeleteOutputModeRequest { + Output output{}; + Mode mode{}; + }; + + using DeleteOutputModeResponse = Response; + + Future DeleteOutputMode(const DeleteOutputModeRequest& request); + + Future DeleteOutputMode(const Output& output = {}, + const Mode& mode = {}); + + struct GetCrtcInfoRequest { + Crtc crtc{}; + Time config_timestamp{}; + }; + + struct GetCrtcInfoReply { + SetConfig status{}; + uint16_t sequence{}; + Time timestamp{}; + int16_t x{}; + int16_t y{}; + uint16_t width{}; + uint16_t height{}; + Mode mode{}; + Rotation rotation{}; + Rotation rotations{}; + std::vector outputs{}; + std::vector possible{}; + }; + + using GetCrtcInfoResponse = Response; + + Future GetCrtcInfo(const GetCrtcInfoRequest& request); + + Future GetCrtcInfo(const Crtc& crtc = {}, + const Time& config_timestamp = {}); + + struct SetCrtcConfigRequest { + Crtc crtc{}; + Time timestamp{}; + Time config_timestamp{}; + int16_t x{}; + int16_t y{}; + Mode mode{}; + Rotation rotation{}; + std::vector outputs{}; + }; + + struct SetCrtcConfigReply { + SetConfig status{}; + uint16_t sequence{}; + Time timestamp{}; + }; + + using SetCrtcConfigResponse = Response; + + Future SetCrtcConfig(const SetCrtcConfigRequest& request); + + Future SetCrtcConfig( + const Crtc& crtc = {}, + const Time& timestamp = {}, + const Time& config_timestamp = {}, + const int16_t& x = {}, + const int16_t& y = {}, + const Mode& mode = {}, + const Rotation& rotation = {}, + const std::vector& outputs = {}); + + struct GetCrtcGammaSizeRequest { + Crtc crtc{}; + }; + + struct GetCrtcGammaSizeReply { + uint16_t sequence{}; + uint16_t size{}; + }; + + using GetCrtcGammaSizeResponse = Response; + + Future GetCrtcGammaSize( + const GetCrtcGammaSizeRequest& request); + + Future GetCrtcGammaSize(const Crtc& crtc = {}); + + struct GetCrtcGammaRequest { + Crtc crtc{}; + }; + + struct GetCrtcGammaReply { + uint16_t sequence{}; + std::vector red{}; + std::vector green{}; + std::vector blue{}; + }; + + using GetCrtcGammaResponse = Response; + + Future GetCrtcGamma(const GetCrtcGammaRequest& request); + + Future GetCrtcGamma(const Crtc& crtc = {}); + + struct SetCrtcGammaRequest { + Crtc crtc{}; + std::vector red{}; + std::vector green{}; + std::vector blue{}; + }; + + using SetCrtcGammaResponse = Response; + + Future SetCrtcGamma(const SetCrtcGammaRequest& request); + + Future SetCrtcGamma(const Crtc& crtc = {}, + const std::vector& red = {}, + const std::vector& green = {}, + const std::vector& blue = {}); + + struct GetScreenResourcesCurrentRequest { + Window window{}; + }; + + struct GetScreenResourcesCurrentReply { + uint16_t sequence{}; + Time timestamp{}; + Time config_timestamp{}; + std::vector crtcs{}; + std::vector outputs{}; + std::vector modes{}; + std::vector names{}; + }; + + using GetScreenResourcesCurrentResponse = + Response; + + Future GetScreenResourcesCurrent( + const GetScreenResourcesCurrentRequest& request); + + Future GetScreenResourcesCurrent( + const Window& window = {}); + + struct SetCrtcTransformRequest { + Crtc crtc{}; + Render::Transform transform{}; + std::string filter_name{}; + std::vector filter_params{}; + }; + + using SetCrtcTransformResponse = Response; + + Future SetCrtcTransform(const SetCrtcTransformRequest& request); + + Future SetCrtcTransform( + const Crtc& crtc = {}, + const Render::Transform& transform = {{}, {}, {}, {}, {}, {}, {}, {}, {}}, + const std::string& filter_name = {}, + const std::vector& filter_params = {}); + + struct GetCrtcTransformRequest { + Crtc crtc{}; + }; + + struct GetCrtcTransformReply { + uint16_t sequence{}; + Render::Transform pending_transform{}; + uint8_t has_transforms{}; + Render::Transform current_transform{}; + std::string pending_filter_name{}; + std::vector pending_params{}; + std::string current_filter_name{}; + std::vector current_params{}; + }; + + using GetCrtcTransformResponse = Response; + + Future GetCrtcTransform( + const GetCrtcTransformRequest& request); + + Future GetCrtcTransform(const Crtc& crtc = {}); + + struct GetPanningRequest { + Crtc crtc{}; + }; + + struct GetPanningReply { + SetConfig status{}; + uint16_t sequence{}; + Time timestamp{}; + uint16_t left{}; + uint16_t top{}; + uint16_t width{}; + uint16_t height{}; + uint16_t track_left{}; + uint16_t track_top{}; + uint16_t track_width{}; + uint16_t track_height{}; + int16_t border_left{}; + int16_t border_top{}; + int16_t border_right{}; + int16_t border_bottom{}; + }; + + using GetPanningResponse = Response; + + Future GetPanning(const GetPanningRequest& request); + + Future GetPanning(const Crtc& crtc = {}); + + struct SetPanningRequest { + Crtc crtc{}; + Time timestamp{}; + uint16_t left{}; + uint16_t top{}; + uint16_t width{}; + uint16_t height{}; + uint16_t track_left{}; + uint16_t track_top{}; + uint16_t track_width{}; + uint16_t track_height{}; + int16_t border_left{}; + int16_t border_top{}; + int16_t border_right{}; + int16_t border_bottom{}; + }; + + struct SetPanningReply { + SetConfig status{}; + uint16_t sequence{}; + Time timestamp{}; + }; + + using SetPanningResponse = Response; + + Future SetPanning(const SetPanningRequest& request); + + Future SetPanning(const Crtc& crtc = {}, + const Time& timestamp = {}, + const uint16_t& left = {}, + const uint16_t& top = {}, + const uint16_t& width = {}, + const uint16_t& height = {}, + const uint16_t& track_left = {}, + const uint16_t& track_top = {}, + const uint16_t& track_width = {}, + const uint16_t& track_height = {}, + const int16_t& border_left = {}, + const int16_t& border_top = {}, + const int16_t& border_right = {}, + const int16_t& border_bottom = {}); + + struct SetOutputPrimaryRequest { + Window window{}; + Output output{}; + }; + + using SetOutputPrimaryResponse = Response; + + Future SetOutputPrimary(const SetOutputPrimaryRequest& request); + + Future SetOutputPrimary(const Window& window = {}, + const Output& output = {}); + + struct GetOutputPrimaryRequest { + Window window{}; + }; + + struct GetOutputPrimaryReply { + uint16_t sequence{}; + Output output{}; + }; + + using GetOutputPrimaryResponse = Response; + + Future GetOutputPrimary( + const GetOutputPrimaryRequest& request); + + Future GetOutputPrimary(const Window& window = {}); + + struct GetProvidersRequest { + Window window{}; + }; + + struct GetProvidersReply { + uint16_t sequence{}; + Time timestamp{}; + std::vector providers{}; + }; + + using GetProvidersResponse = Response; + + Future GetProviders(const GetProvidersRequest& request); + + Future GetProviders(const Window& window = {}); + + struct GetProviderInfoRequest { + Provider provider{}; + Time config_timestamp{}; + }; + + struct GetProviderInfoReply { + uint8_t status{}; + uint16_t sequence{}; + Time timestamp{}; + ProviderCapability capabilities{}; + std::vector crtcs{}; + std::vector outputs{}; + std::vector associated_providers{}; + std::vector associated_capability{}; + std::string name{}; + }; + + using GetProviderInfoResponse = Response; + + Future GetProviderInfo( + const GetProviderInfoRequest& request); + + Future GetProviderInfo( + const Provider& provider = {}, + const Time& config_timestamp = {}); + + struct SetProviderOffloadSinkRequest { + Provider provider{}; + Provider sink_provider{}; + Time config_timestamp{}; + }; + + using SetProviderOffloadSinkResponse = Response; + + Future SetProviderOffloadSink( + const SetProviderOffloadSinkRequest& request); + + Future SetProviderOffloadSink(const Provider& provider = {}, + const Provider& sink_provider = {}, + const Time& config_timestamp = {}); + + struct SetProviderOutputSourceRequest { + Provider provider{}; + Provider source_provider{}; + Time config_timestamp{}; + }; + + using SetProviderOutputSourceResponse = Response; + + Future SetProviderOutputSource( + const SetProviderOutputSourceRequest& request); + + Future SetProviderOutputSource(const Provider& provider = {}, + const Provider& source_provider = {}, + const Time& config_timestamp = {}); + + struct ListProviderPropertiesRequest { + Provider provider{}; + }; + + struct ListProviderPropertiesReply { + uint16_t sequence{}; + std::vector atoms{}; + }; + + using ListProviderPropertiesResponse = Response; + + Future ListProviderProperties( + const ListProviderPropertiesRequest& request); + + Future ListProviderProperties( + const Provider& provider = {}); + + struct QueryProviderPropertyRequest { + Provider provider{}; + Atom property{}; + }; + + struct QueryProviderPropertyReply { + uint16_t sequence{}; + uint8_t pending{}; + uint8_t range{}; + uint8_t immutable{}; + std::vector valid_values{}; + }; + + using QueryProviderPropertyResponse = Response; + + Future QueryProviderProperty( + const QueryProviderPropertyRequest& request); + + Future QueryProviderProperty( + const Provider& provider = {}, + const Atom& property = {}); + + struct ConfigureProviderPropertyRequest { + Provider provider{}; + Atom property{}; + uint8_t pending{}; + uint8_t range{}; + std::vector values{}; + }; + + using ConfigureProviderPropertyResponse = Response; + + Future ConfigureProviderProperty( + const ConfigureProviderPropertyRequest& request); + + Future ConfigureProviderProperty( + const Provider& provider = {}, + const Atom& property = {}, + const uint8_t& pending = {}, + const uint8_t& range = {}, + const std::vector& values = {}); + + struct ChangeProviderPropertyRequest { + Provider provider{}; + Atom property{}; + Atom type{}; + uint8_t format{}; + uint8_t mode{}; + uint32_t num_items{}; + scoped_refptr data{}; + }; + + using ChangeProviderPropertyResponse = Response; + + Future ChangeProviderProperty( + const ChangeProviderPropertyRequest& request); + + Future ChangeProviderProperty( + const Provider& provider = {}, + const Atom& property = {}, + const Atom& type = {}, + const uint8_t& format = {}, + const uint8_t& mode = {}, + const uint32_t& num_items = {}, + const scoped_refptr& data = {}); + + struct DeleteProviderPropertyRequest { + Provider provider{}; + Atom property{}; + }; + + using DeleteProviderPropertyResponse = Response; + + Future DeleteProviderProperty( + const DeleteProviderPropertyRequest& request); + + Future DeleteProviderProperty(const Provider& provider = {}, + const Atom& property = {}); + + struct GetProviderPropertyRequest { + Provider provider{}; + Atom property{}; + Atom type{}; + uint32_t long_offset{}; + uint32_t long_length{}; + uint8_t c_delete{}; + uint8_t pending{}; + }; + + struct GetProviderPropertyReply { + uint8_t format{}; + uint16_t sequence{}; + Atom type{}; + uint32_t bytes_after{}; + uint32_t num_items{}; + scoped_refptr data{}; + }; + + using GetProviderPropertyResponse = Response; + + Future GetProviderProperty( + const GetProviderPropertyRequest& request); + + Future GetProviderProperty( + const Provider& provider = {}, + const Atom& property = {}, + const Atom& type = {}, + const uint32_t& long_offset = {}, + const uint32_t& long_length = {}, + const uint8_t& c_delete = {}, + const uint8_t& pending = {}); + + struct GetMonitorsRequest { + Window window{}; + uint8_t get_active{}; + }; + + struct GetMonitorsReply { + uint16_t sequence{}; + Time timestamp{}; + uint32_t nOutputs{}; + std::vector monitors{}; + }; + + using GetMonitorsResponse = Response; + + Future GetMonitors(const GetMonitorsRequest& request); + + Future GetMonitors(const Window& window = {}, + const uint8_t& get_active = {}); + + struct SetMonitorRequest { + Window window{}; + MonitorInfo monitorinfo{}; + }; + + using SetMonitorResponse = Response; + + Future SetMonitor(const SetMonitorRequest& request); + + Future SetMonitor(const Window& window = {}, + const MonitorInfo& monitorinfo = + {{}, {}, {}, {}, {}, {}, {}, {}, {}, {}}); + + struct DeleteMonitorRequest { + Window window{}; + Atom name{}; + }; + + using DeleteMonitorResponse = Response; + + Future DeleteMonitor(const DeleteMonitorRequest& request); + + Future DeleteMonitor(const Window& window = {}, const Atom& name = {}); + + struct CreateLeaseRequest { + Window window{}; + Lease lid{}; + std::vector crtcs{}; + std::vector outputs{}; + }; + + struct CreateLeaseReply { + uint8_t nfd{}; + uint16_t sequence{}; + RefCountedFD master_fd{}; + }; + + using CreateLeaseResponse = Response; + + Future CreateLease(const CreateLeaseRequest& request); + + Future CreateLease(const Window& window = {}, + const Lease& lid = {}, + const std::vector& crtcs = {}, + const std::vector& outputs = {}); + + struct FreeLeaseRequest { + Lease lid{}; + uint8_t terminate{}; + }; + + using FreeLeaseResponse = Response; + + Future FreeLease(const FreeLeaseRequest& request); + + Future FreeLease(const Lease& lid = {}, const uint8_t& terminate = {}); + + private: + Connection* const connection_; + x11::QueryExtensionReply info_{}; +}; + +} // namespace x11 + +inline constexpr x11::RandR::Rotation operator|(x11::RandR::Rotation l, + x11::RandR::Rotation r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::RandR::Rotation operator&(x11::RandR::Rotation l, + x11::RandR::Rotation r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +inline constexpr x11::RandR::SetConfig operator|(x11::RandR::SetConfig l, + x11::RandR::SetConfig r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::RandR::SetConfig operator&(x11::RandR::SetConfig l, + x11::RandR::SetConfig r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +inline constexpr x11::RandR::NotifyMask operator|(x11::RandR::NotifyMask l, + x11::RandR::NotifyMask r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::RandR::NotifyMask operator&(x11::RandR::NotifyMask l, + x11::RandR::NotifyMask r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +inline constexpr x11::RandR::ModeFlag operator|(x11::RandR::ModeFlag l, + x11::RandR::ModeFlag r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::RandR::ModeFlag operator&(x11::RandR::ModeFlag l, + x11::RandR::ModeFlag r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +inline constexpr x11::RandR::RandRConnection operator|( + x11::RandR::RandRConnection l, + x11::RandR::RandRConnection r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::RandR::RandRConnection operator&( + x11::RandR::RandRConnection l, + x11::RandR::RandRConnection r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +inline constexpr x11::RandR::Transform operator|(x11::RandR::Transform l, + x11::RandR::Transform r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::RandR::Transform operator&(x11::RandR::Transform l, + x11::RandR::Transform r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +inline constexpr x11::RandR::ProviderCapability operator|( + x11::RandR::ProviderCapability l, + x11::RandR::ProviderCapability r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::RandR::ProviderCapability operator&( + x11::RandR::ProviderCapability l, + x11::RandR::ProviderCapability r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +inline constexpr x11::RandR::Notify operator|(x11::RandR::Notify l, + x11::RandR::Notify r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | static_cast(r)); +} + +inline constexpr x11::RandR::Notify operator&(x11::RandR::Notify l, + x11::RandR::Notify r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & static_cast(r)); +} + +#endif // UI_GFX_X_GENERATED_PROTOS_RANDR_H_ diff --git a/x/generated_protos/read_error.cc b/x/generated_protos/read_error.cc new file mode 100644 index 000000000000..1414180562df --- /dev/null +++ b/x/generated_protos/read_error.cc @@ -0,0 +1,530 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#include "ui/gfx/x/connection.h" +#include "ui/gfx/x/error.h" +#include "ui/gfx/x/xproto_internal.h" + +#include "ui/gfx/x/bigreq.h" +#include "ui/gfx/x/composite.h" +#include "ui/gfx/x/damage.h" +#include "ui/gfx/x/dpms.h" +#include "ui/gfx/x/dri2.h" +#include "ui/gfx/x/dri3.h" +#include "ui/gfx/x/ge.h" +#include "ui/gfx/x/glx.h" +#include "ui/gfx/x/present.h" +#include "ui/gfx/x/randr.h" +#include "ui/gfx/x/record.h" +#include "ui/gfx/x/render.h" +#include "ui/gfx/x/res.h" +#include "ui/gfx/x/screensaver.h" +#include "ui/gfx/x/shape.h" +#include "ui/gfx/x/shm.h" +#include "ui/gfx/x/sync.h" +#include "ui/gfx/x/xc_misc.h" +#include "ui/gfx/x/xevie.h" +#include "ui/gfx/x/xf86dri.h" +#include "ui/gfx/x/xf86vidmode.h" +#include "ui/gfx/x/xfixes.h" +#include "ui/gfx/x/xinerama.h" +#include "ui/gfx/x/xinput.h" +#include "ui/gfx/x/xkb.h" +#include "ui/gfx/x/xprint.h" +#include "ui/gfx/x/xproto.h" +#include "ui/gfx/x/xselinux.h" +#include "ui/gfx/x/xtest.h" +#include "ui/gfx/x/xv.h" +#include "ui/gfx/x/xvmc.h" + +namespace x11 { + +namespace { + +template +std::unique_ptr MakeError(Connection::RawError error_) { + ReadBuffer buf(error_); + auto error = std::make_unique(); + ReadError(error.get(), &buf); + return error; +} + +} // namespace + +void Connection::InitErrorParsers() { + uint8_t first_errors[256]; + memset(first_errors, 0, sizeof(first_errors)); + + auto add_parser = [&](uint8_t error_code, uint8_t first_error, + ErrorParser parser) { + if (!error_parsers_[error_code] || first_error > first_errors[error_code]) { + first_errors[error_code] = error_code; + error_parsers_[error_code] = parser; + } + }; + + if (damage().present()) { + uint8_t first_error = damage().first_error(); + { + auto error_code = first_error + 0; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + } + + if (glx().present()) { + uint8_t first_error = glx().first_error(); + { + auto error_code = first_error + 0; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 1; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 2; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 3; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 4; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 5; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 6; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 7; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 8; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 9; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 10; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 11; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 12; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 13; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + } + + if (randr().present()) { + uint8_t first_error = randr().first_error(); + { + auto error_code = first_error + 0; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 1; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 2; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 3; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + } + + if (record().present()) { + uint8_t first_error = record().first_error(); + { + auto error_code = first_error + 0; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + } + + if (render().present()) { + uint8_t first_error = render().first_error(); + { + auto error_code = first_error + 0; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 1; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 2; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 3; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 4; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + } + + if (shm().present()) { + uint8_t first_error = shm().first_error(); + { + auto error_code = first_error + 0; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 2; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 3; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 4; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 5; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 6; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 7; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 9; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 12; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 13; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 14; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + } + + if (sync().present()) { + uint8_t first_error = sync().first_error(); + { + auto error_code = first_error + 0; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 1; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + } + + if (xf86vidmode().present()) { + uint8_t first_error = xf86vidmode().first_error(); + { + auto error_code = first_error + 0; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 1; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 2; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 3; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 4; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 5; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 6; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + } + + if (xfixes().present()) { + uint8_t first_error = xfixes().first_error(); + { + auto error_code = first_error + 0; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + } + + if (xinput().present()) { + uint8_t first_error = xinput().first_error(); + { + auto error_code = first_error + 0; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 1; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 2; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 3; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 4; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + } + + if (xkb().present()) { + uint8_t first_error = xkb().first_error(); + { + auto error_code = first_error + 0; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + } + + if (xprint().present()) { + uint8_t first_error = xprint().first_error(); + { + auto error_code = first_error + 0; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 1; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + } + + { + uint8_t first_error = 0; + { + auto error_code = first_error + 1; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 2; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 3; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 4; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 5; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 6; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 7; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 8; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 9; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 10; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 11; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 12; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 13; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 14; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 15; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 16; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 17; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + } + + if (xv().present()) { + uint8_t first_error = xv().first_error(); + { + auto error_code = first_error + 0; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 1; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + { + auto error_code = first_error + 2; + auto parse = MakeError; + add_parser(error_code, first_error, parse); + } + } +} + +} // namespace x11 diff --git a/x/generated_protos/read_event.cc b/x/generated_protos/read_event.cc new file mode 100644 index 000000000000..a6a308f60031 --- /dev/null +++ b/x/generated_protos/read_event.cc @@ -0,0 +1,1260 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#include "ui/gfx/x/event.h" + +#include + +#include "ui/gfx/x/bigreq.h" +#include "ui/gfx/x/composite.h" +#include "ui/gfx/x/connection.h" +#include "ui/gfx/x/damage.h" +#include "ui/gfx/x/dpms.h" +#include "ui/gfx/x/dri2.h" +#include "ui/gfx/x/dri3.h" +#include "ui/gfx/x/ge.h" +#include "ui/gfx/x/glx.h" +#include "ui/gfx/x/present.h" +#include "ui/gfx/x/randr.h" +#include "ui/gfx/x/record.h" +#include "ui/gfx/x/render.h" +#include "ui/gfx/x/res.h" +#include "ui/gfx/x/screensaver.h" +#include "ui/gfx/x/shape.h" +#include "ui/gfx/x/shm.h" +#include "ui/gfx/x/sync.h" +#include "ui/gfx/x/xc_misc.h" +#include "ui/gfx/x/xevie.h" +#include "ui/gfx/x/xf86dri.h" +#include "ui/gfx/x/xf86vidmode.h" +#include "ui/gfx/x/xfixes.h" +#include "ui/gfx/x/xinerama.h" +#include "ui/gfx/x/xinput.h" +#include "ui/gfx/x/xkb.h" +#include "ui/gfx/x/xprint.h" +#include "ui/gfx/x/xproto.h" +#include "ui/gfx/x/xproto_types.h" +#include "ui/gfx/x/xselinux.h" +#include "ui/gfx/x/xtest.h" +#include "ui/gfx/x/xv.h" +#include "ui/gfx/x/xvmc.h" + +namespace x11 { + +void ReadEvent(Event* event, Connection* conn, ReadBuffer* buffer) { + auto* buf = buffer->data->data(); + auto* ev = reinterpret_cast(buf); + auto* ge = reinterpret_cast(buf); + auto evtype = ev->response_type & ~kSendEventMask; + bool send_event = ev->response_type & kSendEventMask; + + if (conn->damage().present() && + evtype - conn->damage().first_event() == Damage::NotifyEvent::opcode) { + event->type_id_ = 1; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Damage::NotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (conn->dri2().present() && evtype - conn->dri2().first_event() == + Dri2::BufferSwapCompleteEvent::opcode) { + event->type_id_ = 2; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Dri2::BufferSwapCompleteEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (conn->dri2().present() && evtype - conn->dri2().first_event() == + Dri2::InvalidateBuffersEvent::opcode) { + event->type_id_ = 3; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Dri2::InvalidateBuffersEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (conn->glx().present() && + evtype - conn->glx().first_event() == Glx::PbufferClobberEvent::opcode) { + event->type_id_ = 4; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Glx::PbufferClobberEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (conn->glx().present() && evtype - conn->glx().first_event() == + Glx::BufferSwapCompleteEvent::opcode) { + event->type_id_ = 5; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Glx::BufferSwapCompleteEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (conn->present().present() && + evtype - conn->present().first_event() == Present::GenericEvent::opcode) { + event->type_id_ = 6; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Present::GenericEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (evtype == GeGenericEvent::opcode && conn->present().present() && + ge->extension == conn->present().major_opcode() && + ge->event_type == Present::ConfigureNotifyEvent::opcode) { + event->type_id_ = 7; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Present::ConfigureNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (evtype == GeGenericEvent::opcode && conn->present().present() && + ge->extension == conn->present().major_opcode() && + ge->event_type == Present::CompleteNotifyEvent::opcode) { + event->type_id_ = 8; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Present::CompleteNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (evtype == GeGenericEvent::opcode && conn->present().present() && + ge->extension == conn->present().major_opcode() && + ge->event_type == Present::IdleNotifyEvent::opcode) { + event->type_id_ = 9; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Present::IdleNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (evtype == GeGenericEvent::opcode && conn->present().present() && + ge->extension == conn->present().major_opcode() && + ge->event_type == Present::RedirectNotifyEvent::opcode) { + event->type_id_ = 10; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Present::RedirectNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (conn->randr().present() && evtype - conn->randr().first_event() == + RandR::ScreenChangeNotifyEvent::opcode) { + event->type_id_ = 11; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new RandR::ScreenChangeNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (conn->randr().present() && + evtype - conn->randr().first_event() == RandR::NotifyEvent::opcode) { + event->type_id_ = 12; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new RandR::NotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (conn->screensaver().present() && + evtype - conn->screensaver().first_event() == + ScreenSaver::NotifyEvent::opcode) { + event->type_id_ = 13; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new ScreenSaver::NotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (conn->shape().present() && + evtype - conn->shape().first_event() == Shape::NotifyEvent::opcode) { + event->type_id_ = 14; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Shape::NotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (conn->shm().present() && + evtype - conn->shm().first_event() == Shm::CompletionEvent::opcode) { + event->type_id_ = 15; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Shm::CompletionEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (conn->sync().present() && + evtype - conn->sync().first_event() == Sync::CounterNotifyEvent::opcode) { + event->type_id_ = 16; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Sync::CounterNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (conn->sync().present() && + evtype - conn->sync().first_event() == Sync::AlarmNotifyEvent::opcode) { + event->type_id_ = 17; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Sync::AlarmNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (conn->xfixes().present() && evtype - conn->xfixes().first_event() == + XFixes::SelectionNotifyEvent::opcode) { + event->type_id_ = 18; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new XFixes::SelectionNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (conn->xfixes().present() && evtype - conn->xfixes().first_event() == + XFixes::CursorNotifyEvent::opcode) { + event->type_id_ = 19; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new XFixes::CursorNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (conn->xinput().present() && evtype - conn->xinput().first_event() == + Input::DeviceValuatorEvent::opcode) { + event->type_id_ = 20; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Input::DeviceValuatorEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (conn->xinput().present() && + (evtype - conn->xinput().first_event() == + Input::LegacyDeviceEvent::DeviceButtonRelease || + evtype - conn->xinput().first_event() == + Input::LegacyDeviceEvent::ProximityIn || + evtype - conn->xinput().first_event() == + Input::LegacyDeviceEvent::DeviceKeyRelease || + evtype - conn->xinput().first_event() == + Input::LegacyDeviceEvent::DeviceButtonPress || + evtype - conn->xinput().first_event() == + Input::LegacyDeviceEvent::ProximityOut || + evtype - conn->xinput().first_event() == + Input::LegacyDeviceEvent::DeviceKeyPress || + evtype - conn->xinput().first_event() == + Input::LegacyDeviceEvent::DeviceMotionNotify)) { + event->type_id_ = 21; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Input::LegacyDeviceEvent; + ReadEvent(event_, buffer); + event_->opcode = static_castopcode)>( + evtype - conn->xinput().first_event()); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (conn->xinput().present() && + (evtype - conn->xinput().first_event() == Input::DeviceFocusEvent::In || + evtype - conn->xinput().first_event() == Input::DeviceFocusEvent::Out)) { + event->type_id_ = 22; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Input::DeviceFocusEvent; + ReadEvent(event_, buffer); + event_->opcode = static_castopcode)>( + evtype - conn->xinput().first_event()); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (conn->xinput().present() && evtype - conn->xinput().first_event() == + Input::DeviceStateNotifyEvent::opcode) { + event->type_id_ = 23; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Input::DeviceStateNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (conn->xinput().present() && evtype - conn->xinput().first_event() == + Input::DeviceMappingNotifyEvent::opcode) { + event->type_id_ = 24; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Input::DeviceMappingNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (conn->xinput().present() && evtype - conn->xinput().first_event() == + Input::ChangeDeviceNotifyEvent::opcode) { + event->type_id_ = 25; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Input::ChangeDeviceNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (conn->xinput().present() && + evtype - conn->xinput().first_event() == + Input::DeviceKeyStateNotifyEvent::opcode) { + event->type_id_ = 26; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Input::DeviceKeyStateNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (conn->xinput().present() && + evtype - conn->xinput().first_event() == + Input::DeviceButtonStateNotifyEvent::opcode) { + event->type_id_ = 27; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Input::DeviceButtonStateNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (conn->xinput().present() && + evtype - conn->xinput().first_event() == + Input::DevicePresenceNotifyEvent::opcode) { + event->type_id_ = 28; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Input::DevicePresenceNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (conn->xinput().present() && + evtype - conn->xinput().first_event() == + Input::DevicePropertyNotifyEvent::opcode) { + event->type_id_ = 29; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Input::DevicePropertyNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (evtype == GeGenericEvent::opcode && conn->xinput().present() && + ge->extension == conn->xinput().major_opcode() && + ge->event_type == Input::DeviceChangedEvent::opcode) { + event->type_id_ = 30; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Input::DeviceChangedEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (evtype == GeGenericEvent::opcode && conn->xinput().present() && + ge->extension == conn->xinput().major_opcode() && + (ge->event_type == Input::DeviceEvent::ButtonPress || + ge->event_type == Input::DeviceEvent::TouchEnd || + ge->event_type == Input::DeviceEvent::KeyRelease || + ge->event_type == Input::DeviceEvent::TouchUpdate || + ge->event_type == Input::DeviceEvent::KeyPress || + ge->event_type == Input::DeviceEvent::Motion || + ge->event_type == Input::DeviceEvent::TouchBegin || + ge->event_type == Input::DeviceEvent::ButtonRelease)) { + event->type_id_ = 31; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Input::DeviceEvent; + ReadEvent(event_, buffer); + event_->opcode = static_castopcode)>(ge->event_type); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (evtype == GeGenericEvent::opcode && conn->xinput().present() && + ge->extension == conn->xinput().major_opcode() && + (ge->event_type == Input::CrossingEvent::Leave || + ge->event_type == Input::CrossingEvent::FocusIn || + ge->event_type == Input::CrossingEvent::FocusOut || + ge->event_type == Input::CrossingEvent::Enter)) { + event->type_id_ = 32; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Input::CrossingEvent; + ReadEvent(event_, buffer); + event_->opcode = static_castopcode)>(ge->event_type); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (evtype == GeGenericEvent::opcode && conn->xinput().present() && + ge->extension == conn->xinput().major_opcode() && + ge->event_type == Input::HierarchyEvent::opcode) { + event->type_id_ = 33; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Input::HierarchyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (evtype == GeGenericEvent::opcode && conn->xinput().present() && + ge->extension == conn->xinput().major_opcode() && + ge->event_type == Input::PropertyEvent::opcode) { + event->type_id_ = 34; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Input::PropertyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (evtype == GeGenericEvent::opcode && conn->xinput().present() && + ge->extension == conn->xinput().major_opcode() && + (ge->event_type == Input::RawDeviceEvent::RawTouchEnd || + ge->event_type == Input::RawDeviceEvent::RawTouchUpdate || + ge->event_type == Input::RawDeviceEvent::RawTouchBegin || + ge->event_type == Input::RawDeviceEvent::RawButtonRelease || + ge->event_type == Input::RawDeviceEvent::RawMotion || + ge->event_type == Input::RawDeviceEvent::RawButtonPress || + ge->event_type == Input::RawDeviceEvent::RawKeyRelease || + ge->event_type == Input::RawDeviceEvent::RawKeyPress)) { + event->type_id_ = 35; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Input::RawDeviceEvent; + ReadEvent(event_, buffer); + event_->opcode = static_castopcode)>(ge->event_type); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (evtype == GeGenericEvent::opcode && conn->xinput().present() && + ge->extension == conn->xinput().major_opcode() && + ge->event_type == Input::TouchOwnershipEvent::opcode) { + event->type_id_ = 36; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Input::TouchOwnershipEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (evtype == GeGenericEvent::opcode && conn->xinput().present() && + ge->extension == conn->xinput().major_opcode() && + (ge->event_type == Input::BarrierEvent::Leave || + ge->event_type == Input::BarrierEvent::Hit)) { + event->type_id_ = 37; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Input::BarrierEvent; + ReadEvent(event_, buffer); + event_->opcode = static_castopcode)>(ge->event_type); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (conn->xkb().present() && evtype - conn->xkb().first_event() == + Xkb::NewKeyboardNotifyEvent::opcode) { + event->type_id_ = 38; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Xkb::NewKeyboardNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (conn->xkb().present() && + evtype - conn->xkb().first_event() == Xkb::MapNotifyEvent::opcode) { + event->type_id_ = 39; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Xkb::MapNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (conn->xkb().present() && + evtype - conn->xkb().first_event() == Xkb::StateNotifyEvent::opcode) { + event->type_id_ = 40; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Xkb::StateNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (conn->xkb().present() && + evtype - conn->xkb().first_event() == Xkb::ControlsNotifyEvent::opcode) { + event->type_id_ = 41; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Xkb::ControlsNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (conn->xkb().present() && evtype - conn->xkb().first_event() == + Xkb::IndicatorStateNotifyEvent::opcode) { + event->type_id_ = 42; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Xkb::IndicatorStateNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (conn->xkb().present() && evtype - conn->xkb().first_event() == + Xkb::IndicatorMapNotifyEvent::opcode) { + event->type_id_ = 43; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Xkb::IndicatorMapNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (conn->xkb().present() && + evtype - conn->xkb().first_event() == Xkb::NamesNotifyEvent::opcode) { + event->type_id_ = 44; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Xkb::NamesNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (conn->xkb().present() && + evtype - conn->xkb().first_event() == Xkb::CompatMapNotifyEvent::opcode) { + event->type_id_ = 45; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Xkb::CompatMapNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (conn->xkb().present() && + evtype - conn->xkb().first_event() == Xkb::BellNotifyEvent::opcode) { + event->type_id_ = 46; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Xkb::BellNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (conn->xkb().present() && + evtype - conn->xkb().first_event() == Xkb::ActionMessageEvent::opcode) { + event->type_id_ = 47; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Xkb::ActionMessageEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (conn->xkb().present() && + evtype - conn->xkb().first_event() == Xkb::AccessXNotifyEvent::opcode) { + event->type_id_ = 48; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Xkb::AccessXNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (conn->xkb().present() && evtype - conn->xkb().first_event() == + Xkb::ExtensionDeviceNotifyEvent::opcode) { + event->type_id_ = 49; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Xkb::ExtensionDeviceNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (conn->xprint().present() && + evtype - conn->xprint().first_event() == XPrint::NotifyEvent::opcode) { + event->type_id_ = 50; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new XPrint::NotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (conn->xprint().present() && evtype - conn->xprint().first_event() == + XPrint::AttributNotifyEvent::opcode) { + event->type_id_ = 51; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new XPrint::AttributNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if ((evtype == KeyEvent::Release || evtype == KeyEvent::Press)) { + event->type_id_ = 52; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new KeyEvent; + ReadEvent(event_, buffer); + event_->opcode = static_castopcode)>(evtype); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if ((evtype == ButtonEvent::Release || evtype == ButtonEvent::Press)) { + event->type_id_ = 53; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new ButtonEvent; + ReadEvent(event_, buffer); + event_->opcode = static_castopcode)>(evtype); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (evtype == MotionNotifyEvent::opcode) { + event->type_id_ = 54; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new MotionNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if ((evtype == CrossingEvent::EnterNotify || + evtype == CrossingEvent::LeaveNotify)) { + event->type_id_ = 55; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new CrossingEvent; + ReadEvent(event_, buffer); + event_->opcode = static_castopcode)>(evtype); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if ((evtype == FocusEvent::Out || evtype == FocusEvent::In)) { + event->type_id_ = 56; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new FocusEvent; + ReadEvent(event_, buffer); + event_->opcode = static_castopcode)>(evtype); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (evtype == KeymapNotifyEvent::opcode) { + event->type_id_ = 57; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new KeymapNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (evtype == ExposeEvent::opcode) { + event->type_id_ = 58; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new ExposeEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (evtype == GraphicsExposureEvent::opcode) { + event->type_id_ = 59; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new GraphicsExposureEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (evtype == NoExposureEvent::opcode) { + event->type_id_ = 60; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new NoExposureEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (evtype == VisibilityNotifyEvent::opcode) { + event->type_id_ = 61; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new VisibilityNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (evtype == CreateNotifyEvent::opcode) { + event->type_id_ = 62; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new CreateNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (evtype == DestroyNotifyEvent::opcode) { + event->type_id_ = 63; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new DestroyNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (evtype == UnmapNotifyEvent::opcode) { + event->type_id_ = 64; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new UnmapNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (evtype == MapNotifyEvent::opcode) { + event->type_id_ = 65; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new MapNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (evtype == MapRequestEvent::opcode) { + event->type_id_ = 66; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new MapRequestEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (evtype == ReparentNotifyEvent::opcode) { + event->type_id_ = 67; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new ReparentNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (evtype == ConfigureNotifyEvent::opcode) { + event->type_id_ = 68; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new ConfigureNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (evtype == ConfigureRequestEvent::opcode) { + event->type_id_ = 69; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new ConfigureRequestEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (evtype == GravityNotifyEvent::opcode) { + event->type_id_ = 70; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new GravityNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (evtype == ResizeRequestEvent::opcode) { + event->type_id_ = 71; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new ResizeRequestEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if ((evtype == CirculateEvent::Request || evtype == CirculateEvent::Notify)) { + event->type_id_ = 72; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new CirculateEvent; + ReadEvent(event_, buffer); + event_->opcode = static_castopcode)>(evtype); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (evtype == PropertyNotifyEvent::opcode) { + event->type_id_ = 73; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new PropertyNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (evtype == SelectionClearEvent::opcode) { + event->type_id_ = 74; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new SelectionClearEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (evtype == SelectionRequestEvent::opcode) { + event->type_id_ = 75; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new SelectionRequestEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (evtype == SelectionNotifyEvent::opcode) { + event->type_id_ = 76; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new SelectionNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (evtype == ColormapNotifyEvent::opcode) { + event->type_id_ = 77; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new ColormapNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (evtype == ClientMessageEvent::opcode) { + event->type_id_ = 78; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new ClientMessageEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (evtype == MappingNotifyEvent::opcode) { + event->type_id_ = 79; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new MappingNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (conn->xv().present() && + evtype - conn->xv().first_event() == Xv::VideoNotifyEvent::opcode) { + event->type_id_ = 81; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Xv::VideoNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (conn->xv().present() && + evtype - conn->xv().first_event() == Xv::PortNotifyEvent::opcode) { + event->type_id_ = 82; + event->deleter_ = [](void* event) { + delete reinterpret_cast(event); + }; + auto* event_ = new Xv::PortNotifyEvent; + ReadEvent(event_, buffer); + event_->send_event = send_event; + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + NOTREACHED(); +} + +} // namespace x11 diff --git a/x/generated_protos/record.cc b/x/generated_protos/record.cc new file mode 100644 index 000000000000..3976979f6714 --- /dev/null +++ b/x/generated_protos/record.cc @@ -0,0 +1,1040 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#include "record.h" + +#include +#include + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "ui/gfx/x/xproto_internal.h" + +namespace x11 { + +Record::Record(Connection* connection, const x11::QueryExtensionReply& info) + : connection_(connection), info_(info) {} + +std::string Record::BadContextError::ToString() const { + std::stringstream ss_; + ss_ << "Record::BadContextError{"; + ss_ << ".sequence = " << static_cast(sequence) << ", "; + ss_ << ".invalid_record = " << static_cast(invalid_record); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError(Record::BadContextError* error_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + auto& invalid_record = (*error_).invalid_record; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + // invalid_record + Read(&invalid_record, &buf); + + DCHECK_LE(buf.offset, 32ul); +} +Future Record::QueryVersion( + const Record::QueryVersionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& major_version = request.major_version; + auto& minor_version = request.minor_version; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 0; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // major_version + buf.Write(&major_version); + + // minor_version + buf.Write(&minor_version); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Record::QueryVersion", false); +} + +Future Record::QueryVersion( + const uint16_t& major_version, + const uint16_t& minor_version) { + return Record::QueryVersion( + Record::QueryVersionRequest{major_version, minor_version}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Record::QueryVersionReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& major_version = (*reply).major_version; + auto& minor_version = (*reply).minor_version; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // major_version + Read(&major_version, &buf); + + // minor_version + Read(&minor_version, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Record::CreateContext( + const Record::CreateContextRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context = request.context; + auto& element_header = request.element_header; + uint32_t num_client_specs{}; + uint32_t num_ranges{}; + auto& client_specs = request.client_specs; + size_t client_specs_len = client_specs.size(); + auto& ranges = request.ranges; + size_t ranges_len = ranges.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 1; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context + buf.Write(&context); + + // element_header + buf.Write(&element_header); + + // pad0 + Pad(&buf, 3); + + // num_client_specs + num_client_specs = client_specs.size(); + buf.Write(&num_client_specs); + + // num_ranges + num_ranges = ranges.size(); + buf.Write(&num_ranges); + + // client_specs + DCHECK_EQ(static_cast(num_client_specs), client_specs.size()); + for (auto& client_specs_elem : client_specs) { + // client_specs_elem + buf.Write(&client_specs_elem); + } + + // ranges + DCHECK_EQ(static_cast(num_ranges), ranges.size()); + for (auto& ranges_elem : ranges) { + // ranges_elem + { + auto& core_requests = ranges_elem.core_requests; + auto& core_replies = ranges_elem.core_replies; + auto& ext_requests = ranges_elem.ext_requests; + auto& ext_replies = ranges_elem.ext_replies; + auto& delivered_events = ranges_elem.delivered_events; + auto& device_events = ranges_elem.device_events; + auto& errors = ranges_elem.errors; + auto& client_started = ranges_elem.client_started; + auto& client_died = ranges_elem.client_died; + + // core_requests + { + auto& first = core_requests.first; + auto& last = core_requests.last; + + // first + buf.Write(&first); + + // last + buf.Write(&last); + } + + // core_replies + { + auto& first = core_replies.first; + auto& last = core_replies.last; + + // first + buf.Write(&first); + + // last + buf.Write(&last); + } + + // ext_requests + { + auto& major = ext_requests.major; + auto& minor = ext_requests.minor; + + // major + { + auto& first = major.first; + auto& last = major.last; + + // first + buf.Write(&first); + + // last + buf.Write(&last); + } + + // minor + { + auto& first = minor.first; + auto& last = minor.last; + + // first + buf.Write(&first); + + // last + buf.Write(&last); + } + } + + // ext_replies + { + auto& major = ext_replies.major; + auto& minor = ext_replies.minor; + + // major + { + auto& first = major.first; + auto& last = major.last; + + // first + buf.Write(&first); + + // last + buf.Write(&last); + } + + // minor + { + auto& first = minor.first; + auto& last = minor.last; + + // first + buf.Write(&first); + + // last + buf.Write(&last); + } + } + + // delivered_events + { + auto& first = delivered_events.first; + auto& last = delivered_events.last; + + // first + buf.Write(&first); + + // last + buf.Write(&last); + } + + // device_events + { + auto& first = device_events.first; + auto& last = device_events.last; + + // first + buf.Write(&first); + + // last + buf.Write(&last); + } + + // errors + { + auto& first = errors.first; + auto& last = errors.last; + + // first + buf.Write(&first); + + // last + buf.Write(&last); + } + + // client_started + buf.Write(&client_started); + + // client_died + buf.Write(&client_died); + } + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Record::CreateContext", false); +} + +Future Record::CreateContext(const Context& context, + const ElementHeader& element_header, + const std::vector& client_specs, + const std::vector& ranges) { + return Record::CreateContext(Record::CreateContextRequest{ + context, element_header, client_specs, ranges}); +} + +Future Record::RegisterClients( + const Record::RegisterClientsRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context = request.context; + auto& element_header = request.element_header; + uint32_t num_client_specs{}; + uint32_t num_ranges{}; + auto& client_specs = request.client_specs; + size_t client_specs_len = client_specs.size(); + auto& ranges = request.ranges; + size_t ranges_len = ranges.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 2; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context + buf.Write(&context); + + // element_header + buf.Write(&element_header); + + // pad0 + Pad(&buf, 3); + + // num_client_specs + num_client_specs = client_specs.size(); + buf.Write(&num_client_specs); + + // num_ranges + num_ranges = ranges.size(); + buf.Write(&num_ranges); + + // client_specs + DCHECK_EQ(static_cast(num_client_specs), client_specs.size()); + for (auto& client_specs_elem : client_specs) { + // client_specs_elem + buf.Write(&client_specs_elem); + } + + // ranges + DCHECK_EQ(static_cast(num_ranges), ranges.size()); + for (auto& ranges_elem : ranges) { + // ranges_elem + { + auto& core_requests = ranges_elem.core_requests; + auto& core_replies = ranges_elem.core_replies; + auto& ext_requests = ranges_elem.ext_requests; + auto& ext_replies = ranges_elem.ext_replies; + auto& delivered_events = ranges_elem.delivered_events; + auto& device_events = ranges_elem.device_events; + auto& errors = ranges_elem.errors; + auto& client_started = ranges_elem.client_started; + auto& client_died = ranges_elem.client_died; + + // core_requests + { + auto& first = core_requests.first; + auto& last = core_requests.last; + + // first + buf.Write(&first); + + // last + buf.Write(&last); + } + + // core_replies + { + auto& first = core_replies.first; + auto& last = core_replies.last; + + // first + buf.Write(&first); + + // last + buf.Write(&last); + } + + // ext_requests + { + auto& major = ext_requests.major; + auto& minor = ext_requests.minor; + + // major + { + auto& first = major.first; + auto& last = major.last; + + // first + buf.Write(&first); + + // last + buf.Write(&last); + } + + // minor + { + auto& first = minor.first; + auto& last = minor.last; + + // first + buf.Write(&first); + + // last + buf.Write(&last); + } + } + + // ext_replies + { + auto& major = ext_replies.major; + auto& minor = ext_replies.minor; + + // major + { + auto& first = major.first; + auto& last = major.last; + + // first + buf.Write(&first); + + // last + buf.Write(&last); + } + + // minor + { + auto& first = minor.first; + auto& last = minor.last; + + // first + buf.Write(&first); + + // last + buf.Write(&last); + } + } + + // delivered_events + { + auto& first = delivered_events.first; + auto& last = delivered_events.last; + + // first + buf.Write(&first); + + // last + buf.Write(&last); + } + + // device_events + { + auto& first = device_events.first; + auto& last = device_events.last; + + // first + buf.Write(&first); + + // last + buf.Write(&last); + } + + // errors + { + auto& first = errors.first; + auto& last = errors.last; + + // first + buf.Write(&first); + + // last + buf.Write(&last); + } + + // client_started + buf.Write(&client_started); + + // client_died + buf.Write(&client_died); + } + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Record::RegisterClients", false); +} + +Future Record::RegisterClients( + const Context& context, + const ElementHeader& element_header, + const std::vector& client_specs, + const std::vector& ranges) { + return Record::RegisterClients(Record::RegisterClientsRequest{ + context, element_header, client_specs, ranges}); +} + +Future Record::UnregisterClients( + const Record::UnregisterClientsRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context = request.context; + uint32_t num_client_specs{}; + auto& client_specs = request.client_specs; + size_t client_specs_len = client_specs.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 3; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context + buf.Write(&context); + + // num_client_specs + num_client_specs = client_specs.size(); + buf.Write(&num_client_specs); + + // client_specs + DCHECK_EQ(static_cast(num_client_specs), client_specs.size()); + for (auto& client_specs_elem : client_specs) { + // client_specs_elem + buf.Write(&client_specs_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Record::UnregisterClients", + false); +} + +Future Record::UnregisterClients( + const Context& context, + const std::vector& client_specs) { + return Record::UnregisterClients( + Record::UnregisterClientsRequest{context, client_specs}); +} + +Future Record::GetContext( + const Record::GetContextRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context = request.context; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 4; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context + buf.Write(&context); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Record::GetContext", false); +} + +Future Record::GetContext(const Context& context) { + return Record::GetContext(Record::GetContextRequest{context}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Record::GetContextReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& enabled = (*reply).enabled; + auto& sequence = (*reply).sequence; + auto& element_header = (*reply).element_header; + uint32_t num_intercepted_clients{}; + auto& intercepted_clients = (*reply).intercepted_clients; + size_t intercepted_clients_len = intercepted_clients.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // enabled + Read(&enabled, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // element_header + Read(&element_header, &buf); + + // pad0 + Pad(&buf, 3); + + // num_intercepted_clients + Read(&num_intercepted_clients, &buf); + + // pad1 + Pad(&buf, 16); + + // intercepted_clients + intercepted_clients.resize(num_intercepted_clients); + for (auto& intercepted_clients_elem : intercepted_clients) { + // intercepted_clients_elem + { + auto& client_resource = intercepted_clients_elem.client_resource; + uint32_t num_ranges{}; + auto& ranges = intercepted_clients_elem.ranges; + size_t ranges_len = ranges.size(); + + // client_resource + Read(&client_resource, &buf); + + // num_ranges + Read(&num_ranges, &buf); + + // ranges + ranges.resize(num_ranges); + for (auto& ranges_elem : ranges) { + // ranges_elem + { + auto& core_requests = ranges_elem.core_requests; + auto& core_replies = ranges_elem.core_replies; + auto& ext_requests = ranges_elem.ext_requests; + auto& ext_replies = ranges_elem.ext_replies; + auto& delivered_events = ranges_elem.delivered_events; + auto& device_events = ranges_elem.device_events; + auto& errors = ranges_elem.errors; + auto& client_started = ranges_elem.client_started; + auto& client_died = ranges_elem.client_died; + + // core_requests + { + auto& first = core_requests.first; + auto& last = core_requests.last; + + // first + Read(&first, &buf); + + // last + Read(&last, &buf); + } + + // core_replies + { + auto& first = core_replies.first; + auto& last = core_replies.last; + + // first + Read(&first, &buf); + + // last + Read(&last, &buf); + } + + // ext_requests + { + auto& major = ext_requests.major; + auto& minor = ext_requests.minor; + + // major + { + auto& first = major.first; + auto& last = major.last; + + // first + Read(&first, &buf); + + // last + Read(&last, &buf); + } + + // minor + { + auto& first = minor.first; + auto& last = minor.last; + + // first + Read(&first, &buf); + + // last + Read(&last, &buf); + } + } + + // ext_replies + { + auto& major = ext_replies.major; + auto& minor = ext_replies.minor; + + // major + { + auto& first = major.first; + auto& last = major.last; + + // first + Read(&first, &buf); + + // last + Read(&last, &buf); + } + + // minor + { + auto& first = minor.first; + auto& last = minor.last; + + // first + Read(&first, &buf); + + // last + Read(&last, &buf); + } + } + + // delivered_events + { + auto& first = delivered_events.first; + auto& last = delivered_events.last; + + // first + Read(&first, &buf); + + // last + Read(&last, &buf); + } + + // device_events + { + auto& first = device_events.first; + auto& last = device_events.last; + + // first + Read(&first, &buf); + + // last + Read(&last, &buf); + } + + // errors + { + auto& first = errors.first; + auto& last = errors.last; + + // first + Read(&first, &buf); + + // last + Read(&last, &buf); + } + + // client_started + Read(&client_started, &buf); + + // client_died + Read(&client_died, &buf); + } + } + } + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Record::EnableContext( + const Record::EnableContextRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context = request.context; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 5; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context + buf.Write(&context); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Record::EnableContext", false); +} + +Future Record::EnableContext( + const Context& context) { + return Record::EnableContext(Record::EnableContextRequest{context}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Record::EnableContextReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& category = (*reply).category; + auto& sequence = (*reply).sequence; + auto& element_header = (*reply).element_header; + auto& client_swapped = (*reply).client_swapped; + auto& xid_base = (*reply).xid_base; + auto& server_time = (*reply).server_time; + auto& rec_sequence_num = (*reply).rec_sequence_num; + auto& data = (*reply).data; + size_t data_len = data.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // category + Read(&category, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // element_header + Read(&element_header, &buf); + + // client_swapped + Read(&client_swapped, &buf); + + // pad0 + Pad(&buf, 2); + + // xid_base + Read(&xid_base, &buf); + + // server_time + Read(&server_time, &buf); + + // rec_sequence_num + Read(&rec_sequence_num, &buf); + + // pad1 + Pad(&buf, 8); + + // data + data.resize((length) * (4)); + for (auto& data_elem : data) { + // data_elem + Read(&data_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Record::DisableContext( + const Record::DisableContextRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context = request.context; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 6; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context + buf.Write(&context); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Record::DisableContext", false); +} + +Future Record::DisableContext(const Context& context) { + return Record::DisableContext(Record::DisableContextRequest{context}); +} + +Future Record::FreeContext(const Record::FreeContextRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& context = request.context; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 7; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // context + buf.Write(&context); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Record::FreeContext", false); +} + +Future Record::FreeContext(const Context& context) { + return Record::FreeContext(Record::FreeContextRequest{context}); +} + +} // namespace x11 diff --git a/x/generated_protos/record.h b/x/generated_protos/record.h new file mode 100644 index 000000000000..123f200cad59 --- /dev/null +++ b/x/generated_protos/record.h @@ -0,0 +1,292 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#ifndef UI_GFX_X_GENERATED_PROTOS_RECORD_H_ +#define UI_GFX_X_GENERATED_PROTOS_RECORD_H_ + +#include +#include +#include +#include +#include + +#include "base/component_export.h" +#include "base/files/scoped_file.h" +#include "base/memory/ref_counted_memory.h" +#include "base/memory/scoped_refptr.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/gfx/x/error.h" +#include "ui/gfx/x/ref_counted_fd.h" +#include "xproto.h" + +namespace x11 { + +class Connection; + +template +struct Response; + +template +class Future; + +class COMPONENT_EXPORT(X11) Record { + public: + static constexpr unsigned major_version = 1; + static constexpr unsigned minor_version = 13; + + Record(Connection* connection, const x11::QueryExtensionReply& info); + + uint8_t present() const { return info_.present; } + uint8_t major_opcode() const { return info_.major_opcode; } + uint8_t first_event() const { return info_.first_event; } + uint8_t first_error() const { return info_.first_error; } + + Connection* connection() const { return connection_; } + + enum class Context : uint32_t {}; + + enum class ElementHeader : uint8_t {}; + + enum class HType : int { + FromServerTime = 1 << 0, + FromClientTime = 1 << 1, + FromClientSequence = 1 << 2, + }; + + enum class ClientSpec : uint32_t { + CurrentClients = 1, + FutureClients = 2, + AllClients = 3, + }; + + struct Range8 { + uint8_t first{}; + uint8_t last{}; + }; + + struct Range16 { + uint16_t first{}; + uint16_t last{}; + }; + + struct ExtRange { + Range8 major{}; + Range16 minor{}; + }; + + struct Range { + Range8 core_requests{}; + Range8 core_replies{}; + ExtRange ext_requests{}; + ExtRange ext_replies{}; + Range8 delivered_events{}; + Range8 device_events{}; + Range8 errors{}; + uint8_t client_started{}; + uint8_t client_died{}; + }; + + struct ClientInfo { + ClientSpec client_resource{}; + std::vector ranges{}; + }; + + struct BadContextError : public x11::Error { + uint16_t sequence{}; + uint32_t invalid_record{}; + + std::string ToString() const override; + }; + + struct QueryVersionRequest { + uint16_t major_version{}; + uint16_t minor_version{}; + }; + + struct QueryVersionReply { + uint16_t sequence{}; + uint16_t major_version{}; + uint16_t minor_version{}; + }; + + using QueryVersionResponse = Response; + + Future QueryVersion(const QueryVersionRequest& request); + + Future QueryVersion(const uint16_t& major_version = {}, + const uint16_t& minor_version = {}); + + struct CreateContextRequest { + Context context{}; + ElementHeader element_header{}; + std::vector client_specs{}; + std::vector ranges{}; + }; + + using CreateContextResponse = Response; + + Future CreateContext(const CreateContextRequest& request); + + Future CreateContext(const Context& context = {}, + const ElementHeader& element_header = {}, + const std::vector& client_specs = {}, + const std::vector& ranges = {}); + + struct RegisterClientsRequest { + Context context{}; + ElementHeader element_header{}; + std::vector client_specs{}; + std::vector ranges{}; + }; + + using RegisterClientsResponse = Response; + + Future RegisterClients(const RegisterClientsRequest& request); + + Future RegisterClients(const Context& context = {}, + const ElementHeader& element_header = {}, + const std::vector& client_specs = {}, + const std::vector& ranges = {}); + + struct UnregisterClientsRequest { + Context context{}; + std::vector client_specs{}; + }; + + using UnregisterClientsResponse = Response; + + Future UnregisterClients(const UnregisterClientsRequest& request); + + Future UnregisterClients( + const Context& context = {}, + const std::vector& client_specs = {}); + + struct GetContextRequest { + Context context{}; + }; + + struct GetContextReply { + uint8_t enabled{}; + uint16_t sequence{}; + ElementHeader element_header{}; + std::vector intercepted_clients{}; + }; + + using GetContextResponse = Response; + + Future GetContext(const GetContextRequest& request); + + Future GetContext(const Context& context = {}); + + struct EnableContextRequest { + Context context{}; + }; + + struct EnableContextReply { + uint8_t category{}; + uint16_t sequence{}; + ElementHeader element_header{}; + uint8_t client_swapped{}; + uint32_t xid_base{}; + uint32_t server_time{}; + uint32_t rec_sequence_num{}; + std::vector data{}; + }; + + using EnableContextResponse = Response; + + Future EnableContext(const EnableContextRequest& request); + + Future EnableContext(const Context& context = {}); + + struct DisableContextRequest { + Context context{}; + }; + + using DisableContextResponse = Response; + + Future DisableContext(const DisableContextRequest& request); + + Future DisableContext(const Context& context = {}); + + struct FreeContextRequest { + Context context{}; + }; + + using FreeContextResponse = Response; + + Future FreeContext(const FreeContextRequest& request); + + Future FreeContext(const Context& context = {}); + + private: + Connection* const connection_; + x11::QueryExtensionReply info_{}; +}; + +} // namespace x11 + +inline constexpr x11::Record::HType operator|(x11::Record::HType l, + x11::Record::HType r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | static_cast(r)); +} + +inline constexpr x11::Record::HType operator&(x11::Record::HType l, + x11::Record::HType r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & static_cast(r)); +} + +inline constexpr x11::Record::ClientSpec operator|(x11::Record::ClientSpec l, + x11::Record::ClientSpec r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::Record::ClientSpec operator&(x11::Record::ClientSpec l, + x11::Record::ClientSpec r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +#endif // UI_GFX_X_GENERATED_PROTOS_RECORD_H_ diff --git a/x/generated_protos/render.cc b/x/generated_protos/render.cc new file mode 100644 index 000000000000..553afbd9dd10 --- /dev/null +++ b/x/generated_protos/render.cc @@ -0,0 +1,2960 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#include "render.h" + +#include +#include + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "ui/gfx/x/xproto_internal.h" + +namespace x11 { + +Render::Render(Connection* connection, const x11::QueryExtensionReply& info) + : connection_(connection), info_(info) {} + +std::string Render::PictFormatError::ToString() const { + std::stringstream ss_; + ss_ << "Render::PictFormatError{"; + ss_ << ".sequence = " << static_cast(sequence); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError(Render::PictFormatError* error_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + DCHECK_LE(buf.offset, 32ul); +} +std::string Render::PictureError::ToString() const { + std::stringstream ss_; + ss_ << "Render::PictureError{"; + ss_ << ".sequence = " << static_cast(sequence); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError(Render::PictureError* error_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + DCHECK_LE(buf.offset, 32ul); +} +std::string Render::PictOpError::ToString() const { + std::stringstream ss_; + ss_ << "Render::PictOpError{"; + ss_ << ".sequence = " << static_cast(sequence); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError(Render::PictOpError* error_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + DCHECK_LE(buf.offset, 32ul); +} +std::string Render::GlyphSetError::ToString() const { + std::stringstream ss_; + ss_ << "Render::GlyphSetError{"; + ss_ << ".sequence = " << static_cast(sequence); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError(Render::GlyphSetError* error_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + DCHECK_LE(buf.offset, 32ul); +} +std::string Render::GlyphError::ToString() const { + std::stringstream ss_; + ss_ << "Render::GlyphError{"; + ss_ << ".sequence = " << static_cast(sequence); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError(Render::GlyphError* error_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + DCHECK_LE(buf.offset, 32ul); +} +Future Render::QueryVersion( + const Render::QueryVersionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& client_major_version = request.client_major_version; + auto& client_minor_version = request.client_minor_version; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 0; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // client_major_version + buf.Write(&client_major_version); + + // client_minor_version + buf.Write(&client_minor_version); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Render::QueryVersion", false); +} + +Future Render::QueryVersion( + const uint32_t& client_major_version, + const uint32_t& client_minor_version) { + return Render::QueryVersion( + Render::QueryVersionRequest{client_major_version, client_minor_version}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Render::QueryVersionReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& major_version = (*reply).major_version; + auto& minor_version = (*reply).minor_version; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // major_version + Read(&major_version, &buf); + + // minor_version + Read(&minor_version, &buf); + + // pad1 + Pad(&buf, 16); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Render::QueryPictFormats( + const Render::QueryPictFormatsRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 1; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Render::QueryPictFormats", false); +} + +Future Render::QueryPictFormats() { + return Render::QueryPictFormats(Render::QueryPictFormatsRequest{}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Render::QueryPictFormatsReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t num_formats{}; + uint32_t num_screens{}; + auto& num_depths = (*reply).num_depths; + auto& num_visuals = (*reply).num_visuals; + uint32_t num_subpixel{}; + auto& formats = (*reply).formats; + size_t formats_len = formats.size(); + auto& screens = (*reply).screens; + size_t screens_len = screens.size(); + auto& subpixels = (*reply).subpixels; + size_t subpixels_len = subpixels.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // num_formats + Read(&num_formats, &buf); + + // num_screens + Read(&num_screens, &buf); + + // num_depths + Read(&num_depths, &buf); + + // num_visuals + Read(&num_visuals, &buf); + + // num_subpixel + Read(&num_subpixel, &buf); + + // pad1 + Pad(&buf, 4); + + // formats + formats.resize(num_formats); + for (auto& formats_elem : formats) { + // formats_elem + { + auto& id = formats_elem.id; + auto& type = formats_elem.type; + auto& depth = formats_elem.depth; + auto& direct = formats_elem.direct; + auto& colormap = formats_elem.colormap; + + // id + Read(&id, &buf); + + // type + uint8_t tmp0; + Read(&tmp0, &buf); + type = static_cast(tmp0); + + // depth + Read(&depth, &buf); + + // pad0 + Pad(&buf, 2); + + // direct + { + auto& red_shift = direct.red_shift; + auto& red_mask = direct.red_mask; + auto& green_shift = direct.green_shift; + auto& green_mask = direct.green_mask; + auto& blue_shift = direct.blue_shift; + auto& blue_mask = direct.blue_mask; + auto& alpha_shift = direct.alpha_shift; + auto& alpha_mask = direct.alpha_mask; + + // red_shift + Read(&red_shift, &buf); + + // red_mask + Read(&red_mask, &buf); + + // green_shift + Read(&green_shift, &buf); + + // green_mask + Read(&green_mask, &buf); + + // blue_shift + Read(&blue_shift, &buf); + + // blue_mask + Read(&blue_mask, &buf); + + // alpha_shift + Read(&alpha_shift, &buf); + + // alpha_mask + Read(&alpha_mask, &buf); + } + + // colormap + Read(&colormap, &buf); + } + } + + // screens + screens.resize(num_screens); + for (auto& screens_elem : screens) { + // screens_elem + { + uint32_t num_depths{}; + auto& fallback = screens_elem.fallback; + auto& depths = screens_elem.depths; + size_t depths_len = depths.size(); + + // num_depths + Read(&num_depths, &buf); + + // fallback + Read(&fallback, &buf); + + // depths + depths.resize(num_depths); + for (auto& depths_elem : depths) { + // depths_elem + { + auto& depth = depths_elem.depth; + uint16_t num_visuals{}; + auto& visuals = depths_elem.visuals; + size_t visuals_len = visuals.size(); + + // depth + Read(&depth, &buf); + + // pad0 + Pad(&buf, 1); + + // num_visuals + Read(&num_visuals, &buf); + + // pad1 + Pad(&buf, 4); + + // visuals + visuals.resize(num_visuals); + for (auto& visuals_elem : visuals) { + // visuals_elem + { + auto& visual = visuals_elem.visual; + auto& format = visuals_elem.format; + + // visual + Read(&visual, &buf); + + // format + Read(&format, &buf); + } + } + } + } + } + } + + // subpixels + subpixels.resize(num_subpixel); + for (auto& subpixels_elem : subpixels) { + // subpixels_elem + uint32_t tmp1; + Read(&tmp1, &buf); + subpixels_elem = static_cast(tmp1); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Render::QueryPictIndexValues( + const Render::QueryPictIndexValuesRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& format = request.format; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 2; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // format + buf.Write(&format); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Render::QueryPictIndexValues", false); +} + +Future Render::QueryPictIndexValues( + const PictFormat& format) { + return Render::QueryPictIndexValues( + Render::QueryPictIndexValuesRequest{format}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Render::QueryPictIndexValuesReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t num_values{}; + auto& values = (*reply).values; + size_t values_len = values.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // num_values + Read(&num_values, &buf); + + // pad1 + Pad(&buf, 20); + + // values + values.resize(num_values); + for (auto& values_elem : values) { + // values_elem + { + auto& pixel = values_elem.pixel; + auto& red = values_elem.red; + auto& green = values_elem.green; + auto& blue = values_elem.blue; + auto& alpha = values_elem.alpha; + + // pixel + Read(&pixel, &buf); + + // red + Read(&red, &buf); + + // green + Read(&green, &buf); + + // blue + Read(&blue, &buf); + + // alpha + Read(&alpha, &buf); + } + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Render::CreatePicture( + const Render::CreatePictureRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& pid = request.pid; + auto& drawable = request.drawable; + auto& format = request.format; + CreatePictureAttribute value_mask{}; + auto& value_list = request; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 4; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // pid + buf.Write(&pid); + + // drawable + buf.Write(&drawable); + + // format + buf.Write(&format); + + // value_mask + SwitchVar(CreatePictureAttribute::Repeat, value_list.repeat.has_value(), true, + &value_mask); + SwitchVar(CreatePictureAttribute::AlphaMap, value_list.alphamap.has_value(), + true, &value_mask); + SwitchVar(CreatePictureAttribute::AlphaXOrigin, + value_list.alphaxorigin.has_value(), true, &value_mask); + SwitchVar(CreatePictureAttribute::AlphaYOrigin, + value_list.alphayorigin.has_value(), true, &value_mask); + SwitchVar(CreatePictureAttribute::ClipXOrigin, + value_list.clipxorigin.has_value(), true, &value_mask); + SwitchVar(CreatePictureAttribute::ClipYOrigin, + value_list.clipyorigin.has_value(), true, &value_mask); + SwitchVar(CreatePictureAttribute::ClipMask, value_list.clipmask.has_value(), + true, &value_mask); + SwitchVar(CreatePictureAttribute::GraphicsExposure, + value_list.graphicsexposure.has_value(), true, &value_mask); + SwitchVar(CreatePictureAttribute::SubwindowMode, + value_list.subwindowmode.has_value(), true, &value_mask); + SwitchVar(CreatePictureAttribute::PolyEdge, value_list.polyedge.has_value(), + true, &value_mask); + SwitchVar(CreatePictureAttribute::PolyMode, value_list.polymode.has_value(), + true, &value_mask); + SwitchVar(CreatePictureAttribute::Dither, value_list.dither.has_value(), true, + &value_mask); + SwitchVar(CreatePictureAttribute::ComponentAlpha, + value_list.componentalpha.has_value(), true, &value_mask); + uint32_t tmp2; + tmp2 = static_cast(value_mask); + buf.Write(&tmp2); + + // value_list + auto value_list_expr = value_mask; + if (CaseAnd(value_list_expr, CreatePictureAttribute::Repeat)) { + auto& repeat = *value_list.repeat; + + // repeat + uint32_t tmp3; + tmp3 = static_cast(repeat); + buf.Write(&tmp3); + } + if (CaseAnd(value_list_expr, CreatePictureAttribute::AlphaMap)) { + auto& alphamap = *value_list.alphamap; + + // alphamap + buf.Write(&alphamap); + } + if (CaseAnd(value_list_expr, CreatePictureAttribute::AlphaXOrigin)) { + auto& alphaxorigin = *value_list.alphaxorigin; + + // alphaxorigin + buf.Write(&alphaxorigin); + } + if (CaseAnd(value_list_expr, CreatePictureAttribute::AlphaYOrigin)) { + auto& alphayorigin = *value_list.alphayorigin; + + // alphayorigin + buf.Write(&alphayorigin); + } + if (CaseAnd(value_list_expr, CreatePictureAttribute::ClipXOrigin)) { + auto& clipxorigin = *value_list.clipxorigin; + + // clipxorigin + buf.Write(&clipxorigin); + } + if (CaseAnd(value_list_expr, CreatePictureAttribute::ClipYOrigin)) { + auto& clipyorigin = *value_list.clipyorigin; + + // clipyorigin + buf.Write(&clipyorigin); + } + if (CaseAnd(value_list_expr, CreatePictureAttribute::ClipMask)) { + auto& clipmask = *value_list.clipmask; + + // clipmask + buf.Write(&clipmask); + } + if (CaseAnd(value_list_expr, CreatePictureAttribute::GraphicsExposure)) { + auto& graphicsexposure = *value_list.graphicsexposure; + + // graphicsexposure + buf.Write(&graphicsexposure); + } + if (CaseAnd(value_list_expr, CreatePictureAttribute::SubwindowMode)) { + auto& subwindowmode = *value_list.subwindowmode; + + // subwindowmode + uint32_t tmp4; + tmp4 = static_cast(subwindowmode); + buf.Write(&tmp4); + } + if (CaseAnd(value_list_expr, CreatePictureAttribute::PolyEdge)) { + auto& polyedge = *value_list.polyedge; + + // polyedge + uint32_t tmp5; + tmp5 = static_cast(polyedge); + buf.Write(&tmp5); + } + if (CaseAnd(value_list_expr, CreatePictureAttribute::PolyMode)) { + auto& polymode = *value_list.polymode; + + // polymode + uint32_t tmp6; + tmp6 = static_cast(polymode); + buf.Write(&tmp6); + } + if (CaseAnd(value_list_expr, CreatePictureAttribute::Dither)) { + auto& dither = *value_list.dither; + + // dither + buf.Write(&dither); + } + if (CaseAnd(value_list_expr, CreatePictureAttribute::ComponentAlpha)) { + auto& componentalpha = *value_list.componentalpha; + + // componentalpha + buf.Write(&componentalpha); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Render::CreatePicture", false); +} + +Future Render::CreatePicture( + const Picture& pid, + const Drawable& drawable, + const PictFormat& format, + const absl::optional& repeat, + const absl::optional& alphamap, + const absl::optional& alphaxorigin, + const absl::optional& alphayorigin, + const absl::optional& clipxorigin, + const absl::optional& clipyorigin, + const absl::optional& clipmask, + const absl::optional& graphicsexposure, + const absl::optional& subwindowmode, + const absl::optional& polyedge, + const absl::optional& polymode, + const absl::optional& dither, + const absl::optional& componentalpha) { + return Render::CreatePicture(Render::CreatePictureRequest{ + pid, drawable, format, repeat, alphamap, alphaxorigin, alphayorigin, + clipxorigin, clipyorigin, clipmask, graphicsexposure, subwindowmode, + polyedge, polymode, dither, componentalpha}); +} + +Future Render::ChangePicture( + const Render::ChangePictureRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& picture = request.picture; + CreatePictureAttribute value_mask{}; + auto& value_list = request; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 5; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // picture + buf.Write(&picture); + + // value_mask + SwitchVar(CreatePictureAttribute::Repeat, value_list.repeat.has_value(), true, + &value_mask); + SwitchVar(CreatePictureAttribute::AlphaMap, value_list.alphamap.has_value(), + true, &value_mask); + SwitchVar(CreatePictureAttribute::AlphaXOrigin, + value_list.alphaxorigin.has_value(), true, &value_mask); + SwitchVar(CreatePictureAttribute::AlphaYOrigin, + value_list.alphayorigin.has_value(), true, &value_mask); + SwitchVar(CreatePictureAttribute::ClipXOrigin, + value_list.clipxorigin.has_value(), true, &value_mask); + SwitchVar(CreatePictureAttribute::ClipYOrigin, + value_list.clipyorigin.has_value(), true, &value_mask); + SwitchVar(CreatePictureAttribute::ClipMask, value_list.clipmask.has_value(), + true, &value_mask); + SwitchVar(CreatePictureAttribute::GraphicsExposure, + value_list.graphicsexposure.has_value(), true, &value_mask); + SwitchVar(CreatePictureAttribute::SubwindowMode, + value_list.subwindowmode.has_value(), true, &value_mask); + SwitchVar(CreatePictureAttribute::PolyEdge, value_list.polyedge.has_value(), + true, &value_mask); + SwitchVar(CreatePictureAttribute::PolyMode, value_list.polymode.has_value(), + true, &value_mask); + SwitchVar(CreatePictureAttribute::Dither, value_list.dither.has_value(), true, + &value_mask); + SwitchVar(CreatePictureAttribute::ComponentAlpha, + value_list.componentalpha.has_value(), true, &value_mask); + uint32_t tmp7; + tmp7 = static_cast(value_mask); + buf.Write(&tmp7); + + // value_list + auto value_list_expr = value_mask; + if (CaseAnd(value_list_expr, CreatePictureAttribute::Repeat)) { + auto& repeat = *value_list.repeat; + + // repeat + uint32_t tmp8; + tmp8 = static_cast(repeat); + buf.Write(&tmp8); + } + if (CaseAnd(value_list_expr, CreatePictureAttribute::AlphaMap)) { + auto& alphamap = *value_list.alphamap; + + // alphamap + buf.Write(&alphamap); + } + if (CaseAnd(value_list_expr, CreatePictureAttribute::AlphaXOrigin)) { + auto& alphaxorigin = *value_list.alphaxorigin; + + // alphaxorigin + buf.Write(&alphaxorigin); + } + if (CaseAnd(value_list_expr, CreatePictureAttribute::AlphaYOrigin)) { + auto& alphayorigin = *value_list.alphayorigin; + + // alphayorigin + buf.Write(&alphayorigin); + } + if (CaseAnd(value_list_expr, CreatePictureAttribute::ClipXOrigin)) { + auto& clipxorigin = *value_list.clipxorigin; + + // clipxorigin + buf.Write(&clipxorigin); + } + if (CaseAnd(value_list_expr, CreatePictureAttribute::ClipYOrigin)) { + auto& clipyorigin = *value_list.clipyorigin; + + // clipyorigin + buf.Write(&clipyorigin); + } + if (CaseAnd(value_list_expr, CreatePictureAttribute::ClipMask)) { + auto& clipmask = *value_list.clipmask; + + // clipmask + buf.Write(&clipmask); + } + if (CaseAnd(value_list_expr, CreatePictureAttribute::GraphicsExposure)) { + auto& graphicsexposure = *value_list.graphicsexposure; + + // graphicsexposure + buf.Write(&graphicsexposure); + } + if (CaseAnd(value_list_expr, CreatePictureAttribute::SubwindowMode)) { + auto& subwindowmode = *value_list.subwindowmode; + + // subwindowmode + uint32_t tmp9; + tmp9 = static_cast(subwindowmode); + buf.Write(&tmp9); + } + if (CaseAnd(value_list_expr, CreatePictureAttribute::PolyEdge)) { + auto& polyedge = *value_list.polyedge; + + // polyedge + uint32_t tmp10; + tmp10 = static_cast(polyedge); + buf.Write(&tmp10); + } + if (CaseAnd(value_list_expr, CreatePictureAttribute::PolyMode)) { + auto& polymode = *value_list.polymode; + + // polymode + uint32_t tmp11; + tmp11 = static_cast(polymode); + buf.Write(&tmp11); + } + if (CaseAnd(value_list_expr, CreatePictureAttribute::Dither)) { + auto& dither = *value_list.dither; + + // dither + buf.Write(&dither); + } + if (CaseAnd(value_list_expr, CreatePictureAttribute::ComponentAlpha)) { + auto& componentalpha = *value_list.componentalpha; + + // componentalpha + buf.Write(&componentalpha); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Render::ChangePicture", false); +} + +Future Render::ChangePicture( + const Picture& picture, + const absl::optional& repeat, + const absl::optional& alphamap, + const absl::optional& alphaxorigin, + const absl::optional& alphayorigin, + const absl::optional& clipxorigin, + const absl::optional& clipyorigin, + const absl::optional& clipmask, + const absl::optional& graphicsexposure, + const absl::optional& subwindowmode, + const absl::optional& polyedge, + const absl::optional& polymode, + const absl::optional& dither, + const absl::optional& componentalpha) { + return Render::ChangePicture(Render::ChangePictureRequest{ + picture, repeat, alphamap, alphaxorigin, alphayorigin, clipxorigin, + clipyorigin, clipmask, graphicsexposure, subwindowmode, polyedge, + polymode, dither, componentalpha}); +} + +Future Render::SetPictureClipRectangles( + const Render::SetPictureClipRectanglesRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& picture = request.picture; + auto& clip_x_origin = request.clip_x_origin; + auto& clip_y_origin = request.clip_y_origin; + auto& rectangles = request.rectangles; + size_t rectangles_len = rectangles.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 6; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // picture + buf.Write(&picture); + + // clip_x_origin + buf.Write(&clip_x_origin); + + // clip_y_origin + buf.Write(&clip_y_origin); + + // rectangles + DCHECK_EQ(static_cast(rectangles_len), rectangles.size()); + for (auto& rectangles_elem : rectangles) { + // rectangles_elem + { + auto& x = rectangles_elem.x; + auto& y = rectangles_elem.y; + auto& width = rectangles_elem.width; + auto& height = rectangles_elem.height; + + // x + buf.Write(&x); + + // y + buf.Write(&y); + + // width + buf.Write(&width); + + // height + buf.Write(&height); + } + } + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Render::SetPictureClipRectangles", false); +} + +Future Render::SetPictureClipRectangles( + const Picture& picture, + const int16_t& clip_x_origin, + const int16_t& clip_y_origin, + const std::vector& rectangles) { + return Render::SetPictureClipRectangles( + Render::SetPictureClipRectanglesRequest{picture, clip_x_origin, + clip_y_origin, rectangles}); +} + +Future Render::FreePicture(const Render::FreePictureRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& picture = request.picture; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 7; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // picture + buf.Write(&picture); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Render::FreePicture", false); +} + +Future Render::FreePicture(const Picture& picture) { + return Render::FreePicture(Render::FreePictureRequest{picture}); +} + +Future Render::Composite(const Render::CompositeRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& op = request.op; + auto& src = request.src; + auto& mask = request.mask; + auto& dst = request.dst; + auto& src_x = request.src_x; + auto& src_y = request.src_y; + auto& mask_x = request.mask_x; + auto& mask_y = request.mask_y; + auto& dst_x = request.dst_x; + auto& dst_y = request.dst_y; + auto& width = request.width; + auto& height = request.height; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 8; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // op + uint8_t tmp12; + tmp12 = static_cast(op); + buf.Write(&tmp12); + + // pad0 + Pad(&buf, 3); + + // src + buf.Write(&src); + + // mask + buf.Write(&mask); + + // dst + buf.Write(&dst); + + // src_x + buf.Write(&src_x); + + // src_y + buf.Write(&src_y); + + // mask_x + buf.Write(&mask_x); + + // mask_y + buf.Write(&mask_y); + + // dst_x + buf.Write(&dst_x); + + // dst_y + buf.Write(&dst_y); + + // width + buf.Write(&width); + + // height + buf.Write(&height); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Render::Composite", false); +} + +Future Render::Composite(const PictOp& op, + const Picture& src, + const Picture& mask, + const Picture& dst, + const int16_t& src_x, + const int16_t& src_y, + const int16_t& mask_x, + const int16_t& mask_y, + const int16_t& dst_x, + const int16_t& dst_y, + const uint16_t& width, + const uint16_t& height) { + return Render::Composite( + Render::CompositeRequest{op, src, mask, dst, src_x, src_y, mask_x, mask_y, + dst_x, dst_y, width, height}); +} + +Future Render::Trapezoids(const Render::TrapezoidsRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& op = request.op; + auto& src = request.src; + auto& dst = request.dst; + auto& mask_format = request.mask_format; + auto& src_x = request.src_x; + auto& src_y = request.src_y; + auto& traps = request.traps; + size_t traps_len = traps.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 10; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // op + uint8_t tmp13; + tmp13 = static_cast(op); + buf.Write(&tmp13); + + // pad0 + Pad(&buf, 3); + + // src + buf.Write(&src); + + // dst + buf.Write(&dst); + + // mask_format + buf.Write(&mask_format); + + // src_x + buf.Write(&src_x); + + // src_y + buf.Write(&src_y); + + // traps + DCHECK_EQ(static_cast(traps_len), traps.size()); + for (auto& traps_elem : traps) { + // traps_elem + { + auto& top = traps_elem.top; + auto& bottom = traps_elem.bottom; + auto& left = traps_elem.left; + auto& right = traps_elem.right; + + // top + buf.Write(&top); + + // bottom + buf.Write(&bottom); + + // left + { + auto& p1 = left.p1; + auto& p2 = left.p2; + + // p1 + { + auto& x = p1.x; + auto& y = p1.y; + + // x + buf.Write(&x); + + // y + buf.Write(&y); + } + + // p2 + { + auto& x = p2.x; + auto& y = p2.y; + + // x + buf.Write(&x); + + // y + buf.Write(&y); + } + } + + // right + { + auto& p1 = right.p1; + auto& p2 = right.p2; + + // p1 + { + auto& x = p1.x; + auto& y = p1.y; + + // x + buf.Write(&x); + + // y + buf.Write(&y); + } + + // p2 + { + auto& x = p2.x; + auto& y = p2.y; + + // x + buf.Write(&x); + + // y + buf.Write(&y); + } + } + } + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Render::Trapezoids", false); +} + +Future Render::Trapezoids(const PictOp& op, + const Picture& src, + const Picture& dst, + const PictFormat& mask_format, + const int16_t& src_x, + const int16_t& src_y, + const std::vector& traps) { + return Render::Trapezoids(Render::TrapezoidsRequest{op, src, dst, mask_format, + src_x, src_y, traps}); +} + +Future Render::Triangles(const Render::TrianglesRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& op = request.op; + auto& src = request.src; + auto& dst = request.dst; + auto& mask_format = request.mask_format; + auto& src_x = request.src_x; + auto& src_y = request.src_y; + auto& triangles = request.triangles; + size_t triangles_len = triangles.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 11; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // op + uint8_t tmp14; + tmp14 = static_cast(op); + buf.Write(&tmp14); + + // pad0 + Pad(&buf, 3); + + // src + buf.Write(&src); + + // dst + buf.Write(&dst); + + // mask_format + buf.Write(&mask_format); + + // src_x + buf.Write(&src_x); + + // src_y + buf.Write(&src_y); + + // triangles + DCHECK_EQ(static_cast(triangles_len), triangles.size()); + for (auto& triangles_elem : triangles) { + // triangles_elem + { + auto& p1 = triangles_elem.p1; + auto& p2 = triangles_elem.p2; + auto& p3 = triangles_elem.p3; + + // p1 + { + auto& x = p1.x; + auto& y = p1.y; + + // x + buf.Write(&x); + + // y + buf.Write(&y); + } + + // p2 + { + auto& x = p2.x; + auto& y = p2.y; + + // x + buf.Write(&x); + + // y + buf.Write(&y); + } + + // p3 + { + auto& x = p3.x; + auto& y = p3.y; + + // x + buf.Write(&x); + + // y + buf.Write(&y); + } + } + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Render::Triangles", false); +} + +Future Render::Triangles(const PictOp& op, + const Picture& src, + const Picture& dst, + const PictFormat& mask_format, + const int16_t& src_x, + const int16_t& src_y, + const std::vector& triangles) { + return Render::Triangles(Render::TrianglesRequest{op, src, dst, mask_format, + src_x, src_y, triangles}); +} + +Future Render::TriStrip(const Render::TriStripRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& op = request.op; + auto& src = request.src; + auto& dst = request.dst; + auto& mask_format = request.mask_format; + auto& src_x = request.src_x; + auto& src_y = request.src_y; + auto& points = request.points; + size_t points_len = points.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 12; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // op + uint8_t tmp15; + tmp15 = static_cast(op); + buf.Write(&tmp15); + + // pad0 + Pad(&buf, 3); + + // src + buf.Write(&src); + + // dst + buf.Write(&dst); + + // mask_format + buf.Write(&mask_format); + + // src_x + buf.Write(&src_x); + + // src_y + buf.Write(&src_y); + + // points + DCHECK_EQ(static_cast(points_len), points.size()); + for (auto& points_elem : points) { + // points_elem + { + auto& x = points_elem.x; + auto& y = points_elem.y; + + // x + buf.Write(&x); + + // y + buf.Write(&y); + } + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Render::TriStrip", false); +} + +Future Render::TriStrip(const PictOp& op, + const Picture& src, + const Picture& dst, + const PictFormat& mask_format, + const int16_t& src_x, + const int16_t& src_y, + const std::vector& points) { + return Render::TriStrip( + Render::TriStripRequest{op, src, dst, mask_format, src_x, src_y, points}); +} + +Future Render::TriFan(const Render::TriFanRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& op = request.op; + auto& src = request.src; + auto& dst = request.dst; + auto& mask_format = request.mask_format; + auto& src_x = request.src_x; + auto& src_y = request.src_y; + auto& points = request.points; + size_t points_len = points.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 13; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // op + uint8_t tmp16; + tmp16 = static_cast(op); + buf.Write(&tmp16); + + // pad0 + Pad(&buf, 3); + + // src + buf.Write(&src); + + // dst + buf.Write(&dst); + + // mask_format + buf.Write(&mask_format); + + // src_x + buf.Write(&src_x); + + // src_y + buf.Write(&src_y); + + // points + DCHECK_EQ(static_cast(points_len), points.size()); + for (auto& points_elem : points) { + // points_elem + { + auto& x = points_elem.x; + auto& y = points_elem.y; + + // x + buf.Write(&x); + + // y + buf.Write(&y); + } + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Render::TriFan", false); +} + +Future Render::TriFan(const PictOp& op, + const Picture& src, + const Picture& dst, + const PictFormat& mask_format, + const int16_t& src_x, + const int16_t& src_y, + const std::vector& points) { + return Render::TriFan( + Render::TriFanRequest{op, src, dst, mask_format, src_x, src_y, points}); +} + +Future Render::CreateGlyphSet( + const Render::CreateGlyphSetRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& gsid = request.gsid; + auto& format = request.format; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 17; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // gsid + buf.Write(&gsid); + + // format + buf.Write(&format); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Render::CreateGlyphSet", false); +} + +Future Render::CreateGlyphSet(const GlyphSet& gsid, + const PictFormat& format) { + return Render::CreateGlyphSet(Render::CreateGlyphSetRequest{gsid, format}); +} + +Future Render::ReferenceGlyphSet( + const Render::ReferenceGlyphSetRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& gsid = request.gsid; + auto& existing = request.existing; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 18; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // gsid + buf.Write(&gsid); + + // existing + buf.Write(&existing); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Render::ReferenceGlyphSet", + false); +} + +Future Render::ReferenceGlyphSet(const GlyphSet& gsid, + const GlyphSet& existing) { + return Render::ReferenceGlyphSet( + Render::ReferenceGlyphSetRequest{gsid, existing}); +} + +Future Render::FreeGlyphSet(const Render::FreeGlyphSetRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& glyphset = request.glyphset; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 19; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // glyphset + buf.Write(&glyphset); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Render::FreeGlyphSet", false); +} + +Future Render::FreeGlyphSet(const GlyphSet& glyphset) { + return Render::FreeGlyphSet(Render::FreeGlyphSetRequest{glyphset}); +} + +Future Render::AddGlyphs(const Render::AddGlyphsRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& glyphset = request.glyphset; + uint32_t glyphs_len{}; + auto& glyphids = request.glyphids; + size_t glyphids_len = glyphids.size(); + auto& glyphs = request.glyphs; + auto& data = request.data; + size_t data_len = data.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 20; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // glyphset + buf.Write(&glyphset); + + // glyphs_len + glyphs_len = glyphs.size(); + buf.Write(&glyphs_len); + + // glyphids + DCHECK_EQ(static_cast(glyphs_len), glyphids.size()); + for (auto& glyphids_elem : glyphids) { + // glyphids_elem + buf.Write(&glyphids_elem); + } + + // glyphs + DCHECK_EQ(static_cast(glyphs_len), glyphs.size()); + for (auto& glyphs_elem : glyphs) { + // glyphs_elem + { + auto& width = glyphs_elem.width; + auto& height = glyphs_elem.height; + auto& x = glyphs_elem.x; + auto& y = glyphs_elem.y; + auto& x_off = glyphs_elem.x_off; + auto& y_off = glyphs_elem.y_off; + + // width + buf.Write(&width); + + // height + buf.Write(&height); + + // x + buf.Write(&x); + + // y + buf.Write(&y); + + // x_off + buf.Write(&x_off); + + // y_off + buf.Write(&y_off); + } + } + + // data + DCHECK_EQ(static_cast(data_len), data.size()); + for (auto& data_elem : data) { + // data_elem + buf.Write(&data_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Render::AddGlyphs", false); +} + +Future Render::AddGlyphs(const GlyphSet& glyphset, + const std::vector& glyphids, + const std::vector& glyphs, + const std::vector& data) { + return Render::AddGlyphs( + Render::AddGlyphsRequest{glyphset, glyphids, glyphs, data}); +} + +Future Render::FreeGlyphs(const Render::FreeGlyphsRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& glyphset = request.glyphset; + auto& glyphs = request.glyphs; + size_t glyphs_len = glyphs.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 22; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // glyphset + buf.Write(&glyphset); + + // glyphs + DCHECK_EQ(static_cast(glyphs_len), glyphs.size()); + for (auto& glyphs_elem : glyphs) { + // glyphs_elem + buf.Write(&glyphs_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Render::FreeGlyphs", false); +} + +Future Render::FreeGlyphs(const GlyphSet& glyphset, + const std::vector& glyphs) { + return Render::FreeGlyphs(Render::FreeGlyphsRequest{glyphset, glyphs}); +} + +Future Render::CompositeGlyphs8( + const Render::CompositeGlyphs8Request& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& op = request.op; + auto& src = request.src; + auto& dst = request.dst; + auto& mask_format = request.mask_format; + auto& glyphset = request.glyphset; + auto& src_x = request.src_x; + auto& src_y = request.src_y; + auto& glyphcmds = request.glyphcmds; + size_t glyphcmds_len = glyphcmds.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 23; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // op + uint8_t tmp17; + tmp17 = static_cast(op); + buf.Write(&tmp17); + + // pad0 + Pad(&buf, 3); + + // src + buf.Write(&src); + + // dst + buf.Write(&dst); + + // mask_format + buf.Write(&mask_format); + + // glyphset + buf.Write(&glyphset); + + // src_x + buf.Write(&src_x); + + // src_y + buf.Write(&src_y); + + // glyphcmds + DCHECK_EQ(static_cast(glyphcmds_len), glyphcmds.size()); + for (auto& glyphcmds_elem : glyphcmds) { + // glyphcmds_elem + buf.Write(&glyphcmds_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Render::CompositeGlyphs8", + false); +} + +Future Render::CompositeGlyphs8(const PictOp& op, + const Picture& src, + const Picture& dst, + const PictFormat& mask_format, + const GlyphSet& glyphset, + const int16_t& src_x, + const int16_t& src_y, + const std::vector& glyphcmds) { + return Render::CompositeGlyphs8(Render::CompositeGlyphs8Request{ + op, src, dst, mask_format, glyphset, src_x, src_y, glyphcmds}); +} + +Future Render::CompositeGlyphs16( + const Render::CompositeGlyphs16Request& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& op = request.op; + auto& src = request.src; + auto& dst = request.dst; + auto& mask_format = request.mask_format; + auto& glyphset = request.glyphset; + auto& src_x = request.src_x; + auto& src_y = request.src_y; + auto& glyphcmds = request.glyphcmds; + size_t glyphcmds_len = glyphcmds.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 24; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // op + uint8_t tmp18; + tmp18 = static_cast(op); + buf.Write(&tmp18); + + // pad0 + Pad(&buf, 3); + + // src + buf.Write(&src); + + // dst + buf.Write(&dst); + + // mask_format + buf.Write(&mask_format); + + // glyphset + buf.Write(&glyphset); + + // src_x + buf.Write(&src_x); + + // src_y + buf.Write(&src_y); + + // glyphcmds + DCHECK_EQ(static_cast(glyphcmds_len), glyphcmds.size()); + for (auto& glyphcmds_elem : glyphcmds) { + // glyphcmds_elem + buf.Write(&glyphcmds_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Render::CompositeGlyphs16", + false); +} + +Future Render::CompositeGlyphs16(const PictOp& op, + const Picture& src, + const Picture& dst, + const PictFormat& mask_format, + const GlyphSet& glyphset, + const int16_t& src_x, + const int16_t& src_y, + const std::vector& glyphcmds) { + return Render::CompositeGlyphs16(Render::CompositeGlyphs16Request{ + op, src, dst, mask_format, glyphset, src_x, src_y, glyphcmds}); +} + +Future Render::CompositeGlyphs32( + const Render::CompositeGlyphs32Request& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& op = request.op; + auto& src = request.src; + auto& dst = request.dst; + auto& mask_format = request.mask_format; + auto& glyphset = request.glyphset; + auto& src_x = request.src_x; + auto& src_y = request.src_y; + auto& glyphcmds = request.glyphcmds; + size_t glyphcmds_len = glyphcmds.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 25; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // op + uint8_t tmp19; + tmp19 = static_cast(op); + buf.Write(&tmp19); + + // pad0 + Pad(&buf, 3); + + // src + buf.Write(&src); + + // dst + buf.Write(&dst); + + // mask_format + buf.Write(&mask_format); + + // glyphset + buf.Write(&glyphset); + + // src_x + buf.Write(&src_x); + + // src_y + buf.Write(&src_y); + + // glyphcmds + DCHECK_EQ(static_cast(glyphcmds_len), glyphcmds.size()); + for (auto& glyphcmds_elem : glyphcmds) { + // glyphcmds_elem + buf.Write(&glyphcmds_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Render::CompositeGlyphs32", + false); +} + +Future Render::CompositeGlyphs32(const PictOp& op, + const Picture& src, + const Picture& dst, + const PictFormat& mask_format, + const GlyphSet& glyphset, + const int16_t& src_x, + const int16_t& src_y, + const std::vector& glyphcmds) { + return Render::CompositeGlyphs32(Render::CompositeGlyphs32Request{ + op, src, dst, mask_format, glyphset, src_x, src_y, glyphcmds}); +} + +Future Render::FillRectangles( + const Render::FillRectanglesRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& op = request.op; + auto& dst = request.dst; + auto& color = request.color; + auto& rects = request.rects; + size_t rects_len = rects.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 26; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // op + uint8_t tmp20; + tmp20 = static_cast(op); + buf.Write(&tmp20); + + // pad0 + Pad(&buf, 3); + + // dst + buf.Write(&dst); + + // color + { + auto& red = color.red; + auto& green = color.green; + auto& blue = color.blue; + auto& alpha = color.alpha; + + // red + buf.Write(&red); + + // green + buf.Write(&green); + + // blue + buf.Write(&blue); + + // alpha + buf.Write(&alpha); + } + + // rects + DCHECK_EQ(static_cast(rects_len), rects.size()); + for (auto& rects_elem : rects) { + // rects_elem + { + auto& x = rects_elem.x; + auto& y = rects_elem.y; + auto& width = rects_elem.width; + auto& height = rects_elem.height; + + // x + buf.Write(&x); + + // y + buf.Write(&y); + + // width + buf.Write(&width); + + // height + buf.Write(&height); + } + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Render::FillRectangles", false); +} + +Future Render::FillRectangles(const PictOp& op, + const Picture& dst, + const Color& color, + const std::vector& rects) { + return Render::FillRectangles( + Render::FillRectanglesRequest{op, dst, color, rects}); +} + +Future Render::CreateCursor(const Render::CreateCursorRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& cid = request.cid; + auto& source = request.source; + auto& x = request.x; + auto& y = request.y; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 27; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // cid + buf.Write(&cid); + + // source + buf.Write(&source); + + // x + buf.Write(&x); + + // y + buf.Write(&y); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Render::CreateCursor", false); +} + +Future Render::CreateCursor(const Cursor& cid, + const Picture& source, + const uint16_t& x, + const uint16_t& y) { + return Render::CreateCursor(Render::CreateCursorRequest{cid, source, x, y}); +} + +Future Render::SetPictureTransform( + const Render::SetPictureTransformRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& picture = request.picture; + auto& transform = request.transform; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 28; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // picture + buf.Write(&picture); + + // transform + { + auto& matrix11 = transform.matrix11; + auto& matrix12 = transform.matrix12; + auto& matrix13 = transform.matrix13; + auto& matrix21 = transform.matrix21; + auto& matrix22 = transform.matrix22; + auto& matrix23 = transform.matrix23; + auto& matrix31 = transform.matrix31; + auto& matrix32 = transform.matrix32; + auto& matrix33 = transform.matrix33; + + // matrix11 + buf.Write(&matrix11); + + // matrix12 + buf.Write(&matrix12); + + // matrix13 + buf.Write(&matrix13); + + // matrix21 + buf.Write(&matrix21); + + // matrix22 + buf.Write(&matrix22); + + // matrix23 + buf.Write(&matrix23); + + // matrix31 + buf.Write(&matrix31); + + // matrix32 + buf.Write(&matrix32); + + // matrix33 + buf.Write(&matrix33); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Render::SetPictureTransform", + false); +} + +Future Render::SetPictureTransform(const Picture& picture, + const Transform& transform) { + return Render::SetPictureTransform( + Render::SetPictureTransformRequest{picture, transform}); +} + +Future Render::QueryFilters( + const Render::QueryFiltersRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& drawable = request.drawable; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 29; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // drawable + buf.Write(&drawable); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Render::QueryFilters", false); +} + +Future Render::QueryFilters( + const Drawable& drawable) { + return Render::QueryFilters(Render::QueryFiltersRequest{drawable}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Render::QueryFiltersReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t num_aliases{}; + uint32_t num_filters{}; + auto& aliases = (*reply).aliases; + size_t aliases_len = aliases.size(); + auto& filters = (*reply).filters; + size_t filters_len = filters.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // num_aliases + Read(&num_aliases, &buf); + + // num_filters + Read(&num_filters, &buf); + + // pad1 + Pad(&buf, 16); + + // aliases + aliases.resize(num_aliases); + for (auto& aliases_elem : aliases) { + // aliases_elem + Read(&aliases_elem, &buf); + } + + // filters + filters.resize(num_filters); + for (auto& filters_elem : filters) { + // filters_elem + { + uint8_t name_len{}; + auto& name = filters_elem.name; + + // name_len + Read(&name_len, &buf); + + // name + name.resize(name_len); + for (auto& name_elem : name) { + // name_elem + Read(&name_elem, &buf); + } + } + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Render::SetPictureFilter( + const Render::SetPictureFilterRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& picture = request.picture; + uint16_t filter_len{}; + auto& filter = request.filter; + auto& values = request.values; + size_t values_len = values.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 30; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // picture + buf.Write(&picture); + + // filter_len + filter_len = filter.size(); + buf.Write(&filter_len); + + // pad0 + Pad(&buf, 2); + + // filter + DCHECK_EQ(static_cast(filter_len), filter.size()); + for (auto& filter_elem : filter) { + // filter_elem + buf.Write(&filter_elem); + } + + // pad1 + Align(&buf, 4); + + // values + DCHECK_EQ(static_cast(values_len), values.size()); + for (auto& values_elem : values) { + // values_elem + buf.Write(&values_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Render::SetPictureFilter", + false); +} + +Future Render::SetPictureFilter(const Picture& picture, + const std::string& filter, + const std::vector& values) { + return Render::SetPictureFilter( + Render::SetPictureFilterRequest{picture, filter, values}); +} + +Future Render::CreateAnimCursor( + const Render::CreateAnimCursorRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& cid = request.cid; + auto& cursors = request.cursors; + size_t cursors_len = cursors.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 31; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // cid + buf.Write(&cid); + + // cursors + DCHECK_EQ(static_cast(cursors_len), cursors.size()); + for (auto& cursors_elem : cursors) { + // cursors_elem + { + auto& cursor = cursors_elem.cursor; + auto& delay = cursors_elem.delay; + + // cursor + buf.Write(&cursor); + + // delay + buf.Write(&delay); + } + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Render::CreateAnimCursor", + false); +} + +Future Render::CreateAnimCursor( + const Cursor& cid, + const std::vector& cursors) { + return Render::CreateAnimCursor( + Render::CreateAnimCursorRequest{cid, cursors}); +} + +Future Render::AddTraps(const Render::AddTrapsRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& picture = request.picture; + auto& x_off = request.x_off; + auto& y_off = request.y_off; + auto& traps = request.traps; + size_t traps_len = traps.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 32; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // picture + buf.Write(&picture); + + // x_off + buf.Write(&x_off); + + // y_off + buf.Write(&y_off); + + // traps + DCHECK_EQ(static_cast(traps_len), traps.size()); + for (auto& traps_elem : traps) { + // traps_elem + { + auto& top = traps_elem.top; + auto& bot = traps_elem.bot; + + // top + { + auto& l = top.l; + auto& r = top.r; + auto& y = top.y; + + // l + buf.Write(&l); + + // r + buf.Write(&r); + + // y + buf.Write(&y); + } + + // bot + { + auto& l = bot.l; + auto& r = bot.r; + auto& y = bot.y; + + // l + buf.Write(&l); + + // r + buf.Write(&r); + + // y + buf.Write(&y); + } + } + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Render::AddTraps", false); +} + +Future Render::AddTraps(const Picture& picture, + const int16_t& x_off, + const int16_t& y_off, + const std::vector& traps) { + return Render::AddTraps( + Render::AddTrapsRequest{picture, x_off, y_off, traps}); +} + +Future Render::CreateSolidFill( + const Render::CreateSolidFillRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& picture = request.picture; + auto& color = request.color; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 33; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // picture + buf.Write(&picture); + + // color + { + auto& red = color.red; + auto& green = color.green; + auto& blue = color.blue; + auto& alpha = color.alpha; + + // red + buf.Write(&red); + + // green + buf.Write(&green); + + // blue + buf.Write(&blue); + + // alpha + buf.Write(&alpha); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Render::CreateSolidFill", false); +} + +Future Render::CreateSolidFill(const Picture& picture, + const Color& color) { + return Render::CreateSolidFill( + Render::CreateSolidFillRequest{picture, color}); +} + +Future Render::CreateLinearGradient( + const Render::CreateLinearGradientRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& picture = request.picture; + auto& p1 = request.p1; + auto& p2 = request.p2; + uint32_t num_stops{}; + auto& stops = request.stops; + size_t stops_len = stops.size(); + auto& colors = request.colors; + size_t colors_len = colors.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 34; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // picture + buf.Write(&picture); + + // p1 + { + auto& x = p1.x; + auto& y = p1.y; + + // x + buf.Write(&x); + + // y + buf.Write(&y); + } + + // p2 + { + auto& x = p2.x; + auto& y = p2.y; + + // x + buf.Write(&x); + + // y + buf.Write(&y); + } + + // num_stops + num_stops = stops.size(); + buf.Write(&num_stops); + + // stops + DCHECK_EQ(static_cast(num_stops), stops.size()); + for (auto& stops_elem : stops) { + // stops_elem + buf.Write(&stops_elem); + } + + // colors + DCHECK_EQ(static_cast(num_stops), colors.size()); + for (auto& colors_elem : colors) { + // colors_elem + { + auto& red = colors_elem.red; + auto& green = colors_elem.green; + auto& blue = colors_elem.blue; + auto& alpha = colors_elem.alpha; + + // red + buf.Write(&red); + + // green + buf.Write(&green); + + // blue + buf.Write(&blue); + + // alpha + buf.Write(&alpha); + } + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Render::CreateLinearGradient", + false); +} + +Future Render::CreateLinearGradient(const Picture& picture, + const PointFix& p1, + const PointFix& p2, + const std::vector& stops, + const std::vector& colors) { + return Render::CreateLinearGradient( + Render::CreateLinearGradientRequest{picture, p1, p2, stops, colors}); +} + +Future Render::CreateRadialGradient( + const Render::CreateRadialGradientRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& picture = request.picture; + auto& inner = request.inner; + auto& outer = request.outer; + auto& inner_radius = request.inner_radius; + auto& outer_radius = request.outer_radius; + uint32_t num_stops{}; + auto& stops = request.stops; + size_t stops_len = stops.size(); + auto& colors = request.colors; + size_t colors_len = colors.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 35; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // picture + buf.Write(&picture); + + // inner + { + auto& x = inner.x; + auto& y = inner.y; + + // x + buf.Write(&x); + + // y + buf.Write(&y); + } + + // outer + { + auto& x = outer.x; + auto& y = outer.y; + + // x + buf.Write(&x); + + // y + buf.Write(&y); + } + + // inner_radius + buf.Write(&inner_radius); + + // outer_radius + buf.Write(&outer_radius); + + // num_stops + num_stops = stops.size(); + buf.Write(&num_stops); + + // stops + DCHECK_EQ(static_cast(num_stops), stops.size()); + for (auto& stops_elem : stops) { + // stops_elem + buf.Write(&stops_elem); + } + + // colors + DCHECK_EQ(static_cast(num_stops), colors.size()); + for (auto& colors_elem : colors) { + // colors_elem + { + auto& red = colors_elem.red; + auto& green = colors_elem.green; + auto& blue = colors_elem.blue; + auto& alpha = colors_elem.alpha; + + // red + buf.Write(&red); + + // green + buf.Write(&green); + + // blue + buf.Write(&blue); + + // alpha + buf.Write(&alpha); + } + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Render::CreateRadialGradient", + false); +} + +Future Render::CreateRadialGradient(const Picture& picture, + const PointFix& inner, + const PointFix& outer, + const Fixed& inner_radius, + const Fixed& outer_radius, + const std::vector& stops, + const std::vector& colors) { + return Render::CreateRadialGradient(Render::CreateRadialGradientRequest{ + picture, inner, outer, inner_radius, outer_radius, stops, colors}); +} + +Future Render::CreateConicalGradient( + const Render::CreateConicalGradientRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& picture = request.picture; + auto& center = request.center; + auto& angle = request.angle; + uint32_t num_stops{}; + auto& stops = request.stops; + size_t stops_len = stops.size(); + auto& colors = request.colors; + size_t colors_len = colors.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 36; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // picture + buf.Write(&picture); + + // center + { + auto& x = center.x; + auto& y = center.y; + + // x + buf.Write(&x); + + // y + buf.Write(&y); + } + + // angle + buf.Write(&angle); + + // num_stops + num_stops = stops.size(); + buf.Write(&num_stops); + + // stops + DCHECK_EQ(static_cast(num_stops), stops.size()); + for (auto& stops_elem : stops) { + // stops_elem + buf.Write(&stops_elem); + } + + // colors + DCHECK_EQ(static_cast(num_stops), colors.size()); + for (auto& colors_elem : colors) { + // colors_elem + { + auto& red = colors_elem.red; + auto& green = colors_elem.green; + auto& blue = colors_elem.blue; + auto& alpha = colors_elem.alpha; + + // red + buf.Write(&red); + + // green + buf.Write(&green); + + // blue + buf.Write(&blue); + + // alpha + buf.Write(&alpha); + } + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Render::CreateConicalGradient", + false); +} + +Future Render::CreateConicalGradient(const Picture& picture, + const PointFix& center, + const Fixed& angle, + const std::vector& stops, + const std::vector& colors) { + return Render::CreateConicalGradient(Render::CreateConicalGradientRequest{ + picture, center, angle, stops, colors}); +} + +} // namespace x11 diff --git a/x/generated_protos/render.h b/x/generated_protos/render.h new file mode 100644 index 000000000000..62d784fc1e11 --- /dev/null +++ b/x/generated_protos/render.h @@ -0,0 +1,1047 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#ifndef UI_GFX_X_GENERATED_PROTOS_RENDER_H_ +#define UI_GFX_X_GENERATED_PROTOS_RENDER_H_ + +#include +#include +#include +#include +#include + +#include "base/component_export.h" +#include "base/files/scoped_file.h" +#include "base/memory/ref_counted_memory.h" +#include "base/memory/scoped_refptr.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/gfx/x/error.h" +#include "ui/gfx/x/ref_counted_fd.h" +#include "xproto.h" + +namespace x11 { + +class Connection; + +template +struct Response; + +template +class Future; + +class COMPONENT_EXPORT(X11) Render { + public: + static constexpr unsigned major_version = 0; + static constexpr unsigned minor_version = 11; + + Render(Connection* connection, const x11::QueryExtensionReply& info); + + uint8_t present() const { return info_.present; } + uint8_t major_opcode() const { return info_.major_opcode; } + uint8_t first_event() const { return info_.first_event; } + uint8_t first_error() const { return info_.first_error; } + + Connection* connection() const { return connection_; } + + enum class PictType : int { + Indexed = 0, + Direct = 1, + }; + + enum class Picture : uint32_t { + None = 0, + }; + + enum class PictOp : int { + Clear = 0, + Src = 1, + Dst = 2, + Over = 3, + OverReverse = 4, + In = 5, + InReverse = 6, + Out = 7, + OutReverse = 8, + Atop = 9, + AtopReverse = 10, + Xor = 11, + Add = 12, + Saturate = 13, + DisjointClear = 16, + DisjointSrc = 17, + DisjointDst = 18, + DisjointOver = 19, + DisjointOverReverse = 20, + DisjointIn = 21, + DisjointInReverse = 22, + DisjointOut = 23, + DisjointOutReverse = 24, + DisjointAtop = 25, + DisjointAtopReverse = 26, + DisjointXor = 27, + ConjointClear = 32, + ConjointSrc = 33, + ConjointDst = 34, + ConjointOver = 35, + ConjointOverReverse = 36, + ConjointIn = 37, + ConjointInReverse = 38, + ConjointOut = 39, + ConjointOutReverse = 40, + ConjointAtop = 41, + ConjointAtopReverse = 42, + ConjointXor = 43, + Multiply = 48, + Screen = 49, + Overlay = 50, + Darken = 51, + Lighten = 52, + ColorDodge = 53, + ColorBurn = 54, + HardLight = 55, + SoftLight = 56, + Difference = 57, + Exclusion = 58, + HSLHue = 59, + HSLSaturation = 60, + HSLColor = 61, + HSLLuminosity = 62, + }; + + enum class PolyEdge : int { + Sharp = 0, + Smooth = 1, + }; + + enum class PolyMode : int { + Precise = 0, + Imprecise = 1, + }; + + enum class CreatePictureAttribute : int { + Repeat = 1 << 0, + AlphaMap = 1 << 1, + AlphaXOrigin = 1 << 2, + AlphaYOrigin = 1 << 3, + ClipXOrigin = 1 << 4, + ClipYOrigin = 1 << 5, + ClipMask = 1 << 6, + GraphicsExposure = 1 << 7, + SubwindowMode = 1 << 8, + PolyEdge = 1 << 9, + PolyMode = 1 << 10, + Dither = 1 << 11, + ComponentAlpha = 1 << 12, + }; + + enum class SubPixel : int { + Unknown = 0, + HorizontalRGB = 1, + HorizontalBGR = 2, + VerticalRGB = 3, + VerticalBGR = 4, + None = 5, + }; + + enum class Repeat : int { + None = 0, + Normal = 1, + Pad = 2, + Reflect = 3, + }; + + enum class Glyph : uint32_t {}; + + enum class GlyphSet : uint32_t {}; + + enum class PictFormat : uint32_t {}; + + enum class Fixed : int32_t {}; + + struct PictFormatError : public x11::Error { + uint16_t sequence{}; + + std::string ToString() const override; + }; + + struct PictureError : public x11::Error { + uint16_t sequence{}; + + std::string ToString() const override; + }; + + struct PictOpError : public x11::Error { + uint16_t sequence{}; + + std::string ToString() const override; + }; + + struct GlyphSetError : public x11::Error { + uint16_t sequence{}; + + std::string ToString() const override; + }; + + struct GlyphError : public x11::Error { + uint16_t sequence{}; + + std::string ToString() const override; + }; + + struct DirectFormat { + uint16_t red_shift{}; + uint16_t red_mask{}; + uint16_t green_shift{}; + uint16_t green_mask{}; + uint16_t blue_shift{}; + uint16_t blue_mask{}; + uint16_t alpha_shift{}; + uint16_t alpha_mask{}; + }; + + struct PictFormInfo { + PictFormat id{}; + PictType type{}; + uint8_t depth{}; + DirectFormat direct{}; + ColorMap colormap{}; + }; + + struct PictVisual { + VisualId visual{}; + PictFormat format{}; + }; + + struct PictDepth { + uint8_t depth{}; + std::vector visuals{}; + }; + + struct PictScreen { + PictFormat fallback{}; + std::vector depths{}; + }; + + struct IndexValue { + uint32_t pixel{}; + uint16_t red{}; + uint16_t green{}; + uint16_t blue{}; + uint16_t alpha{}; + }; + + struct Color { + uint16_t red{}; + uint16_t green{}; + uint16_t blue{}; + uint16_t alpha{}; + }; + + struct PointFix { + Fixed x{}; + Fixed y{}; + }; + + struct LineFix { + PointFix p1{}; + PointFix p2{}; + }; + + struct Triangle { + PointFix p1{}; + PointFix p2{}; + PointFix p3{}; + }; + + struct Trapezoid { + Fixed top{}; + Fixed bottom{}; + LineFix left{}; + LineFix right{}; + }; + + struct GlyphInfo { + uint16_t width{}; + uint16_t height{}; + int16_t x{}; + int16_t y{}; + int16_t x_off{}; + int16_t y_off{}; + }; + + struct Transform { + Fixed matrix11{}; + Fixed matrix12{}; + Fixed matrix13{}; + Fixed matrix21{}; + Fixed matrix22{}; + Fixed matrix23{}; + Fixed matrix31{}; + Fixed matrix32{}; + Fixed matrix33{}; + }; + + struct AnimationCursorElement { + Cursor cursor{}; + uint32_t delay{}; + }; + + struct SpanFix { + Fixed l{}; + Fixed r{}; + Fixed y{}; + }; + + struct Trap { + SpanFix top{}; + SpanFix bot{}; + }; + + struct QueryVersionRequest { + uint32_t client_major_version{}; + uint32_t client_minor_version{}; + }; + + struct QueryVersionReply { + uint16_t sequence{}; + uint32_t major_version{}; + uint32_t minor_version{}; + }; + + using QueryVersionResponse = Response; + + Future QueryVersion(const QueryVersionRequest& request); + + Future QueryVersion( + const uint32_t& client_major_version = {}, + const uint32_t& client_minor_version = {}); + + struct QueryPictFormatsRequest {}; + + struct QueryPictFormatsReply { + uint16_t sequence{}; + uint32_t num_depths{}; + uint32_t num_visuals{}; + std::vector formats{}; + std::vector screens{}; + std::vector subpixels{}; + }; + + using QueryPictFormatsResponse = Response; + + Future QueryPictFormats( + const QueryPictFormatsRequest& request); + + Future QueryPictFormats(); + + struct QueryPictIndexValuesRequest { + PictFormat format{}; + }; + + struct QueryPictIndexValuesReply { + uint16_t sequence{}; + std::vector values{}; + }; + + using QueryPictIndexValuesResponse = Response; + + Future QueryPictIndexValues( + const QueryPictIndexValuesRequest& request); + + Future QueryPictIndexValues( + const PictFormat& format = {}); + + struct CreatePictureRequest { + Picture pid{}; + Drawable drawable{}; + PictFormat format{}; + absl::optional repeat{}; + absl::optional alphamap{}; + absl::optional alphaxorigin{}; + absl::optional alphayorigin{}; + absl::optional clipxorigin{}; + absl::optional clipyorigin{}; + absl::optional clipmask{}; + absl::optional graphicsexposure{}; + absl::optional subwindowmode{}; + absl::optional polyedge{}; + absl::optional polymode{}; + absl::optional dither{}; + absl::optional componentalpha{}; + }; + + using CreatePictureResponse = Response; + + Future CreatePicture(const CreatePictureRequest& request); + + Future CreatePicture( + const Picture& pid = {}, + const Drawable& drawable = {}, + const PictFormat& format = {}, + const absl::optional& repeat = absl::nullopt, + const absl::optional& alphamap = absl::nullopt, + const absl::optional& alphaxorigin = absl::nullopt, + const absl::optional& alphayorigin = absl::nullopt, + const absl::optional& clipxorigin = absl::nullopt, + const absl::optional& clipyorigin = absl::nullopt, + const absl::optional& clipmask = absl::nullopt, + const absl::optional& graphicsexposure = absl::nullopt, + const absl::optional& subwindowmode = absl::nullopt, + const absl::optional& polyedge = absl::nullopt, + const absl::optional& polymode = absl::nullopt, + const absl::optional& dither = absl::nullopt, + const absl::optional& componentalpha = absl::nullopt); + + struct ChangePictureRequest { + Picture picture{}; + absl::optional repeat{}; + absl::optional alphamap{}; + absl::optional alphaxorigin{}; + absl::optional alphayorigin{}; + absl::optional clipxorigin{}; + absl::optional clipyorigin{}; + absl::optional clipmask{}; + absl::optional graphicsexposure{}; + absl::optional subwindowmode{}; + absl::optional polyedge{}; + absl::optional polymode{}; + absl::optional dither{}; + absl::optional componentalpha{}; + }; + + using ChangePictureResponse = Response; + + Future ChangePicture(const ChangePictureRequest& request); + + Future ChangePicture( + const Picture& picture = {}, + const absl::optional& repeat = absl::nullopt, + const absl::optional& alphamap = absl::nullopt, + const absl::optional& alphaxorigin = absl::nullopt, + const absl::optional& alphayorigin = absl::nullopt, + const absl::optional& clipxorigin = absl::nullopt, + const absl::optional& clipyorigin = absl::nullopt, + const absl::optional& clipmask = absl::nullopt, + const absl::optional& graphicsexposure = absl::nullopt, + const absl::optional& subwindowmode = absl::nullopt, + const absl::optional& polyedge = absl::nullopt, + const absl::optional& polymode = absl::nullopt, + const absl::optional& dither = absl::nullopt, + const absl::optional& componentalpha = absl::nullopt); + + struct SetPictureClipRectanglesRequest { + Picture picture{}; + int16_t clip_x_origin{}; + int16_t clip_y_origin{}; + std::vector rectangles{}; + }; + + using SetPictureClipRectanglesResponse = Response; + + Future SetPictureClipRectangles( + const SetPictureClipRectanglesRequest& request); + + Future SetPictureClipRectangles( + const Picture& picture = {}, + const int16_t& clip_x_origin = {}, + const int16_t& clip_y_origin = {}, + const std::vector& rectangles = {}); + + struct FreePictureRequest { + Picture picture{}; + }; + + using FreePictureResponse = Response; + + Future FreePicture(const FreePictureRequest& request); + + Future FreePicture(const Picture& picture = {}); + + struct CompositeRequest { + PictOp op{}; + Picture src{}; + Picture mask{}; + Picture dst{}; + int16_t src_x{}; + int16_t src_y{}; + int16_t mask_x{}; + int16_t mask_y{}; + int16_t dst_x{}; + int16_t dst_y{}; + uint16_t width{}; + uint16_t height{}; + }; + + using CompositeResponse = Response; + + Future Composite(const CompositeRequest& request); + + Future Composite(const PictOp& op = {}, + const Picture& src = {}, + const Picture& mask = {}, + const Picture& dst = {}, + const int16_t& src_x = {}, + const int16_t& src_y = {}, + const int16_t& mask_x = {}, + const int16_t& mask_y = {}, + const int16_t& dst_x = {}, + const int16_t& dst_y = {}, + const uint16_t& width = {}, + const uint16_t& height = {}); + + struct TrapezoidsRequest { + PictOp op{}; + Picture src{}; + Picture dst{}; + PictFormat mask_format{}; + int16_t src_x{}; + int16_t src_y{}; + std::vector traps{}; + }; + + using TrapezoidsResponse = Response; + + Future Trapezoids(const TrapezoidsRequest& request); + + Future Trapezoids(const PictOp& op = {}, + const Picture& src = {}, + const Picture& dst = {}, + const PictFormat& mask_format = {}, + const int16_t& src_x = {}, + const int16_t& src_y = {}, + const std::vector& traps = {}); + + struct TrianglesRequest { + PictOp op{}; + Picture src{}; + Picture dst{}; + PictFormat mask_format{}; + int16_t src_x{}; + int16_t src_y{}; + std::vector triangles{}; + }; + + using TrianglesResponse = Response; + + Future Triangles(const TrianglesRequest& request); + + Future Triangles(const PictOp& op = {}, + const Picture& src = {}, + const Picture& dst = {}, + const PictFormat& mask_format = {}, + const int16_t& src_x = {}, + const int16_t& src_y = {}, + const std::vector& triangles = {}); + + struct TriStripRequest { + PictOp op{}; + Picture src{}; + Picture dst{}; + PictFormat mask_format{}; + int16_t src_x{}; + int16_t src_y{}; + std::vector points{}; + }; + + using TriStripResponse = Response; + + Future TriStrip(const TriStripRequest& request); + + Future TriStrip(const PictOp& op = {}, + const Picture& src = {}, + const Picture& dst = {}, + const PictFormat& mask_format = {}, + const int16_t& src_x = {}, + const int16_t& src_y = {}, + const std::vector& points = {}); + + struct TriFanRequest { + PictOp op{}; + Picture src{}; + Picture dst{}; + PictFormat mask_format{}; + int16_t src_x{}; + int16_t src_y{}; + std::vector points{}; + }; + + using TriFanResponse = Response; + + Future TriFan(const TriFanRequest& request); + + Future TriFan(const PictOp& op = {}, + const Picture& src = {}, + const Picture& dst = {}, + const PictFormat& mask_format = {}, + const int16_t& src_x = {}, + const int16_t& src_y = {}, + const std::vector& points = {}); + + struct CreateGlyphSetRequest { + GlyphSet gsid{}; + PictFormat format{}; + }; + + using CreateGlyphSetResponse = Response; + + Future CreateGlyphSet(const CreateGlyphSetRequest& request); + + Future CreateGlyphSet(const GlyphSet& gsid = {}, + const PictFormat& format = {}); + + struct ReferenceGlyphSetRequest { + GlyphSet gsid{}; + GlyphSet existing{}; + }; + + using ReferenceGlyphSetResponse = Response; + + Future ReferenceGlyphSet(const ReferenceGlyphSetRequest& request); + + Future ReferenceGlyphSet(const GlyphSet& gsid = {}, + const GlyphSet& existing = {}); + + struct FreeGlyphSetRequest { + GlyphSet glyphset{}; + }; + + using FreeGlyphSetResponse = Response; + + Future FreeGlyphSet(const FreeGlyphSetRequest& request); + + Future FreeGlyphSet(const GlyphSet& glyphset = {}); + + struct AddGlyphsRequest { + GlyphSet glyphset{}; + std::vector glyphids{}; + std::vector glyphs{}; + std::vector data{}; + }; + + using AddGlyphsResponse = Response; + + Future AddGlyphs(const AddGlyphsRequest& request); + + Future AddGlyphs(const GlyphSet& glyphset = {}, + const std::vector& glyphids = {}, + const std::vector& glyphs = {}, + const std::vector& data = {}); + + struct FreeGlyphsRequest { + GlyphSet glyphset{}; + std::vector glyphs{}; + }; + + using FreeGlyphsResponse = Response; + + Future FreeGlyphs(const FreeGlyphsRequest& request); + + Future FreeGlyphs(const GlyphSet& glyphset = {}, + const std::vector& glyphs = {}); + + struct CompositeGlyphs8Request { + PictOp op{}; + Picture src{}; + Picture dst{}; + PictFormat mask_format{}; + GlyphSet glyphset{}; + int16_t src_x{}; + int16_t src_y{}; + std::vector glyphcmds{}; + }; + + using CompositeGlyphs8Response = Response; + + Future CompositeGlyphs8(const CompositeGlyphs8Request& request); + + Future CompositeGlyphs8(const PictOp& op = {}, + const Picture& src = {}, + const Picture& dst = {}, + const PictFormat& mask_format = {}, + const GlyphSet& glyphset = {}, + const int16_t& src_x = {}, + const int16_t& src_y = {}, + const std::vector& glyphcmds = {}); + + struct CompositeGlyphs16Request { + PictOp op{}; + Picture src{}; + Picture dst{}; + PictFormat mask_format{}; + GlyphSet glyphset{}; + int16_t src_x{}; + int16_t src_y{}; + std::vector glyphcmds{}; + }; + + using CompositeGlyphs16Response = Response; + + Future CompositeGlyphs16(const CompositeGlyphs16Request& request); + + Future CompositeGlyphs16(const PictOp& op = {}, + const Picture& src = {}, + const Picture& dst = {}, + const PictFormat& mask_format = {}, + const GlyphSet& glyphset = {}, + const int16_t& src_x = {}, + const int16_t& src_y = {}, + const std::vector& glyphcmds = {}); + + struct CompositeGlyphs32Request { + PictOp op{}; + Picture src{}; + Picture dst{}; + PictFormat mask_format{}; + GlyphSet glyphset{}; + int16_t src_x{}; + int16_t src_y{}; + std::vector glyphcmds{}; + }; + + using CompositeGlyphs32Response = Response; + + Future CompositeGlyphs32(const CompositeGlyphs32Request& request); + + Future CompositeGlyphs32(const PictOp& op = {}, + const Picture& src = {}, + const Picture& dst = {}, + const PictFormat& mask_format = {}, + const GlyphSet& glyphset = {}, + const int16_t& src_x = {}, + const int16_t& src_y = {}, + const std::vector& glyphcmds = {}); + + struct FillRectanglesRequest { + PictOp op{}; + Picture dst{}; + Color color{}; + std::vector rects{}; + }; + + using FillRectanglesResponse = Response; + + Future FillRectangles(const FillRectanglesRequest& request); + + Future FillRectangles(const PictOp& op = {}, + const Picture& dst = {}, + const Color& color = {{}, {}, {}, {}}, + const std::vector& rects = {}); + + struct CreateCursorRequest { + Cursor cid{}; + Picture source{}; + uint16_t x{}; + uint16_t y{}; + }; + + using CreateCursorResponse = Response; + + Future CreateCursor(const CreateCursorRequest& request); + + Future CreateCursor(const Cursor& cid = {}, + const Picture& source = {}, + const uint16_t& x = {}, + const uint16_t& y = {}); + + struct SetPictureTransformRequest { + Picture picture{}; + Transform transform{}; + }; + + using SetPictureTransformResponse = Response; + + Future SetPictureTransform(const SetPictureTransformRequest& request); + + Future SetPictureTransform( + const Picture& picture = {}, + const Transform& transform = {{}, {}, {}, {}, {}, {}, {}, {}, {}}); + + struct QueryFiltersRequest { + Drawable drawable{}; + }; + + struct QueryFiltersReply { + uint16_t sequence{}; + std::vector aliases{}; + std::vector filters{}; + }; + + using QueryFiltersResponse = Response; + + Future QueryFilters(const QueryFiltersRequest& request); + + Future QueryFilters(const Drawable& drawable = {}); + + struct SetPictureFilterRequest { + Picture picture{}; + std::string filter{}; + std::vector values{}; + }; + + using SetPictureFilterResponse = Response; + + Future SetPictureFilter(const SetPictureFilterRequest& request); + + Future SetPictureFilter(const Picture& picture = {}, + const std::string& filter = {}, + const std::vector& values = {}); + + struct CreateAnimCursorRequest { + Cursor cid{}; + std::vector cursors{}; + }; + + using CreateAnimCursorResponse = Response; + + Future CreateAnimCursor(const CreateAnimCursorRequest& request); + + Future CreateAnimCursor( + const Cursor& cid = {}, + const std::vector& cursors = {}); + + struct AddTrapsRequest { + Picture picture{}; + int16_t x_off{}; + int16_t y_off{}; + std::vector traps{}; + }; + + using AddTrapsResponse = Response; + + Future AddTraps(const AddTrapsRequest& request); + + Future AddTraps(const Picture& picture = {}, + const int16_t& x_off = {}, + const int16_t& y_off = {}, + const std::vector& traps = {}); + + struct CreateSolidFillRequest { + Picture picture{}; + Color color{}; + }; + + using CreateSolidFillResponse = Response; + + Future CreateSolidFill(const CreateSolidFillRequest& request); + + Future CreateSolidFill(const Picture& picture = {}, + const Color& color = {{}, {}, {}, {}}); + + struct CreateLinearGradientRequest { + Picture picture{}; + PointFix p1{}; + PointFix p2{}; + std::vector stops{}; + std::vector colors{}; + }; + + using CreateLinearGradientResponse = Response; + + Future CreateLinearGradient(const CreateLinearGradientRequest& request); + + Future CreateLinearGradient(const Picture& picture = {}, + const PointFix& p1 = {{}, {}}, + const PointFix& p2 = {{}, {}}, + const std::vector& stops = {}, + const std::vector& colors = {}); + + struct CreateRadialGradientRequest { + Picture picture{}; + PointFix inner{}; + PointFix outer{}; + Fixed inner_radius{}; + Fixed outer_radius{}; + std::vector stops{}; + std::vector colors{}; + }; + + using CreateRadialGradientResponse = Response; + + Future CreateRadialGradient(const CreateRadialGradientRequest& request); + + Future CreateRadialGradient(const Picture& picture = {}, + const PointFix& inner = {{}, {}}, + const PointFix& outer = {{}, {}}, + const Fixed& inner_radius = {}, + const Fixed& outer_radius = {}, + const std::vector& stops = {}, + const std::vector& colors = {}); + + struct CreateConicalGradientRequest { + Picture picture{}; + PointFix center{}; + Fixed angle{}; + std::vector stops{}; + std::vector colors{}; + }; + + using CreateConicalGradientResponse = Response; + + Future CreateConicalGradient( + const CreateConicalGradientRequest& request); + + Future CreateConicalGradient(const Picture& picture = {}, + const PointFix& center = {{}, {}}, + const Fixed& angle = {}, + const std::vector& stops = {}, + const std::vector& colors = {}); + + private: + Connection* const connection_; + x11::QueryExtensionReply info_{}; +}; + +} // namespace x11 + +inline constexpr x11::Render::PictType operator|(x11::Render::PictType l, + x11::Render::PictType r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::Render::PictType operator&(x11::Render::PictType l, + x11::Render::PictType r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +inline constexpr x11::Render::Picture operator|(x11::Render::Picture l, + x11::Render::Picture r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::Render::Picture operator&(x11::Render::Picture l, + x11::Render::Picture r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +inline constexpr x11::Render::PictOp operator|(x11::Render::PictOp l, + x11::Render::PictOp r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::Render::PictOp operator&(x11::Render::PictOp l, + x11::Render::PictOp r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +inline constexpr x11::Render::PolyEdge operator|(x11::Render::PolyEdge l, + x11::Render::PolyEdge r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::Render::PolyEdge operator&(x11::Render::PolyEdge l, + x11::Render::PolyEdge r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +inline constexpr x11::Render::PolyMode operator|(x11::Render::PolyMode l, + x11::Render::PolyMode r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::Render::PolyMode operator&(x11::Render::PolyMode l, + x11::Render::PolyMode r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +inline constexpr x11::Render::CreatePictureAttribute operator|( + x11::Render::CreatePictureAttribute l, + x11::Render::CreatePictureAttribute r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::Render::CreatePictureAttribute operator&( + x11::Render::CreatePictureAttribute l, + x11::Render::CreatePictureAttribute r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +inline constexpr x11::Render::SubPixel operator|(x11::Render::SubPixel l, + x11::Render::SubPixel r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::Render::SubPixel operator&(x11::Render::SubPixel l, + x11::Render::SubPixel r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +inline constexpr x11::Render::Repeat operator|(x11::Render::Repeat l, + x11::Render::Repeat r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::Render::Repeat operator&(x11::Render::Repeat l, + x11::Render::Repeat r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +#endif // UI_GFX_X_GENERATED_PROTOS_RENDER_H_ diff --git a/x/generated_protos/res.cc b/x/generated_protos/res.cc new file mode 100644 index 000000000000..a17dcfff9e51 --- /dev/null +++ b/x/generated_protos/res.cc @@ -0,0 +1,680 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#include "res.h" + +#include +#include + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "ui/gfx/x/xproto_internal.h" + +namespace x11 { + +Res::Res(Connection* connection, const x11::QueryExtensionReply& info) + : connection_(connection), info_(info) {} + +Future Res::QueryVersion( + const Res::QueryVersionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& client_major = request.client_major; + auto& client_minor = request.client_minor; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 0; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // client_major + buf.Write(&client_major); + + // client_minor + buf.Write(&client_minor); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Res::QueryVersion", false); +} + +Future Res::QueryVersion(const uint8_t& client_major, + const uint8_t& client_minor) { + return Res::QueryVersion( + Res::QueryVersionRequest{client_major, client_minor}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Res::QueryVersionReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& server_major = (*reply).server_major; + auto& server_minor = (*reply).server_minor; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // server_major + Read(&server_major, &buf); + + // server_minor + Read(&server_minor, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Res::QueryClients( + const Res::QueryClientsRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 1; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Res::QueryClients", false); +} + +Future Res::QueryClients() { + return Res::QueryClients(Res::QueryClientsRequest{}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Res::QueryClientsReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t num_clients{}; + auto& clients = (*reply).clients; + size_t clients_len = clients.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // num_clients + Read(&num_clients, &buf); + + // pad1 + Pad(&buf, 20); + + // clients + clients.resize(num_clients); + for (auto& clients_elem : clients) { + // clients_elem + { + auto& resource_base = clients_elem.resource_base; + auto& resource_mask = clients_elem.resource_mask; + + // resource_base + Read(&resource_base, &buf); + + // resource_mask + Read(&resource_mask, &buf); + } + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Res::QueryClientResources( + const Res::QueryClientResourcesRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& xid = request.xid; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 2; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // xid + buf.Write(&xid); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Res::QueryClientResources", false); +} + +Future Res::QueryClientResources( + const uint32_t& xid) { + return Res::QueryClientResources(Res::QueryClientResourcesRequest{xid}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Res::QueryClientResourcesReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t num_types{}; + auto& types = (*reply).types; + size_t types_len = types.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // num_types + Read(&num_types, &buf); + + // pad1 + Pad(&buf, 20); + + // types + types.resize(num_types); + for (auto& types_elem : types) { + // types_elem + { + auto& resource_type = types_elem.resource_type; + auto& count = types_elem.count; + + // resource_type + Read(&resource_type, &buf); + + // count + Read(&count, &buf); + } + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Res::QueryClientPixmapBytes( + const Res::QueryClientPixmapBytesRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& xid = request.xid; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 3; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // xid + buf.Write(&xid); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Res::QueryClientPixmapBytes", false); +} + +Future Res::QueryClientPixmapBytes( + const uint32_t& xid) { + return Res::QueryClientPixmapBytes(Res::QueryClientPixmapBytesRequest{xid}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Res::QueryClientPixmapBytesReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& bytes = (*reply).bytes; + auto& bytes_overflow = (*reply).bytes_overflow; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // bytes + Read(&bytes, &buf); + + // bytes_overflow + Read(&bytes_overflow, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Res::QueryClientIds( + const Res::QueryClientIdsRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + uint32_t num_specs{}; + auto& specs = request.specs; + size_t specs_len = specs.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 4; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // num_specs + num_specs = specs.size(); + buf.Write(&num_specs); + + // specs + DCHECK_EQ(static_cast(num_specs), specs.size()); + for (auto& specs_elem : specs) { + // specs_elem + { + auto& client = specs_elem.client; + auto& mask = specs_elem.mask; + + // client + buf.Write(&client); + + // mask + uint32_t tmp0; + tmp0 = static_cast(mask); + buf.Write(&tmp0); + } + } + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Res::QueryClientIds", false); +} + +Future Res::QueryClientIds( + const std::vector& specs) { + return Res::QueryClientIds(Res::QueryClientIdsRequest{specs}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Res::QueryClientIdsReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t num_ids{}; + auto& ids = (*reply).ids; + size_t ids_len = ids.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // num_ids + Read(&num_ids, &buf); + + // pad1 + Pad(&buf, 20); + + // ids + ids.resize(num_ids); + for (auto& ids_elem : ids) { + // ids_elem + { + auto& spec = ids_elem.spec; + auto& length = ids_elem.length; + auto& value = ids_elem.value; + size_t value_len = value.size(); + + // spec + { + auto& client = spec.client; + auto& mask = spec.mask; + + // client + Read(&client, &buf); + + // mask + uint32_t tmp1; + Read(&tmp1, &buf); + mask = static_cast(tmp1); + } + + // length + Read(&length, &buf); + + // value + value.resize((length) / (4)); + for (auto& value_elem : value) { + // value_elem + Read(&value_elem, &buf); + } + } + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Res::QueryResourceBytes( + const Res::QueryResourceBytesRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& client = request.client; + uint32_t num_specs{}; + auto& specs = request.specs; + size_t specs_len = specs.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 5; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // client + buf.Write(&client); + + // num_specs + num_specs = specs.size(); + buf.Write(&num_specs); + + // specs + DCHECK_EQ(static_cast(num_specs), specs.size()); + for (auto& specs_elem : specs) { + // specs_elem + { + auto& resource = specs_elem.resource; + auto& type = specs_elem.type; + + // resource + buf.Write(&resource); + + // type + buf.Write(&type); + } + } + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Res::QueryResourceBytes", false); +} + +Future Res::QueryResourceBytes( + const uint32_t& client, + const std::vector& specs) { + return Res::QueryResourceBytes(Res::QueryResourceBytesRequest{client, specs}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Res::QueryResourceBytesReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t num_sizes{}; + auto& sizes = (*reply).sizes; + size_t sizes_len = sizes.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // num_sizes + Read(&num_sizes, &buf); + + // pad1 + Pad(&buf, 20); + + // sizes + sizes.resize(num_sizes); + for (auto& sizes_elem : sizes) { + // sizes_elem + { + auto& size = sizes_elem.size; + uint32_t num_cross_references{}; + auto& cross_references = sizes_elem.cross_references; + size_t cross_references_len = cross_references.size(); + + // size + { + auto& spec = size.spec; + auto& bytes = size.bytes; + auto& ref_count = size.ref_count; + auto& use_count = size.use_count; + + // spec + { + auto& resource = spec.resource; + auto& type = spec.type; + + // resource + Read(&resource, &buf); + + // type + Read(&type, &buf); + } + + // bytes + Read(&bytes, &buf); + + // ref_count + Read(&ref_count, &buf); + + // use_count + Read(&use_count, &buf); + } + + // num_cross_references + Read(&num_cross_references, &buf); + + // cross_references + cross_references.resize(num_cross_references); + for (auto& cross_references_elem : cross_references) { + // cross_references_elem + { + auto& spec = cross_references_elem.spec; + auto& bytes = cross_references_elem.bytes; + auto& ref_count = cross_references_elem.ref_count; + auto& use_count = cross_references_elem.use_count; + + // spec + { + auto& resource = spec.resource; + auto& type = spec.type; + + // resource + Read(&resource, &buf); + + // type + Read(&type, &buf); + } + + // bytes + Read(&bytes, &buf); + + // ref_count + Read(&ref_count, &buf); + + // use_count + Read(&use_count, &buf); + } + } + } + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +} // namespace x11 diff --git a/x/generated_protos/res.h b/x/generated_protos/res.h new file mode 100644 index 000000000000..11bd31088fd0 --- /dev/null +++ b/x/generated_protos/res.h @@ -0,0 +1,249 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#ifndef UI_GFX_X_GENERATED_PROTOS_RES_H_ +#define UI_GFX_X_GENERATED_PROTOS_RES_H_ + +#include +#include +#include +#include +#include + +#include "base/component_export.h" +#include "base/files/scoped_file.h" +#include "base/memory/ref_counted_memory.h" +#include "base/memory/scoped_refptr.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/gfx/x/error.h" +#include "ui/gfx/x/ref_counted_fd.h" +#include "xproto.h" + +namespace x11 { + +class Connection; + +template +struct Response; + +template +class Future; + +class COMPONENT_EXPORT(X11) Res { + public: + static constexpr unsigned major_version = 1; + static constexpr unsigned minor_version = 2; + + Res(Connection* connection, const x11::QueryExtensionReply& info); + + uint8_t present() const { return info_.present; } + uint8_t major_opcode() const { return info_.major_opcode; } + uint8_t first_event() const { return info_.first_event; } + uint8_t first_error() const { return info_.first_error; } + + Connection* connection() const { return connection_; } + + enum class ClientIdMask : int { + ClientXID = 1 << 0, + LocalClientPID = 1 << 1, + }; + + struct Client { + uint32_t resource_base{}; + uint32_t resource_mask{}; + }; + + struct Type { + Atom resource_type{}; + uint32_t count{}; + }; + + struct ClientIdSpec { + uint32_t client{}; + ClientIdMask mask{}; + }; + + struct ClientIdValue { + ClientIdSpec spec{}; + uint32_t length{}; + std::vector value{}; + }; + + struct ResourceIdSpec { + uint32_t resource{}; + uint32_t type{}; + }; + + struct ResourceSizeSpec { + ResourceIdSpec spec{}; + uint32_t bytes{}; + uint32_t ref_count{}; + uint32_t use_count{}; + }; + + struct ResourceSizeValue { + ResourceSizeSpec size{}; + std::vector cross_references{}; + }; + + struct QueryVersionRequest { + uint8_t client_major{}; + uint8_t client_minor{}; + }; + + struct QueryVersionReply { + uint16_t sequence{}; + uint16_t server_major{}; + uint16_t server_minor{}; + }; + + using QueryVersionResponse = Response; + + Future QueryVersion(const QueryVersionRequest& request); + + Future QueryVersion(const uint8_t& client_major = {}, + const uint8_t& client_minor = {}); + + struct QueryClientsRequest {}; + + struct QueryClientsReply { + uint16_t sequence{}; + std::vector clients{}; + }; + + using QueryClientsResponse = Response; + + Future QueryClients(const QueryClientsRequest& request); + + Future QueryClients(); + + struct QueryClientResourcesRequest { + uint32_t xid{}; + }; + + struct QueryClientResourcesReply { + uint16_t sequence{}; + std::vector types{}; + }; + + using QueryClientResourcesResponse = Response; + + Future QueryClientResources( + const QueryClientResourcesRequest& request); + + Future QueryClientResources( + const uint32_t& xid = {}); + + struct QueryClientPixmapBytesRequest { + uint32_t xid{}; + }; + + struct QueryClientPixmapBytesReply { + uint16_t sequence{}; + uint32_t bytes{}; + uint32_t bytes_overflow{}; + }; + + using QueryClientPixmapBytesResponse = Response; + + Future QueryClientPixmapBytes( + const QueryClientPixmapBytesRequest& request); + + Future QueryClientPixmapBytes( + const uint32_t& xid = {}); + + struct QueryClientIdsRequest { + std::vector specs{}; + }; + + struct QueryClientIdsReply { + uint16_t sequence{}; + std::vector ids{}; + }; + + using QueryClientIdsResponse = Response; + + Future QueryClientIds( + const QueryClientIdsRequest& request); + + Future QueryClientIds( + const std::vector& specs = {}); + + struct QueryResourceBytesRequest { + uint32_t client{}; + std::vector specs{}; + }; + + struct QueryResourceBytesReply { + uint16_t sequence{}; + std::vector sizes{}; + }; + + using QueryResourceBytesResponse = Response; + + Future QueryResourceBytes( + const QueryResourceBytesRequest& request); + + Future QueryResourceBytes( + const uint32_t& client = {}, + const std::vector& specs = {}); + + private: + Connection* const connection_; + x11::QueryExtensionReply info_{}; +}; + +} // namespace x11 + +inline constexpr x11::Res::ClientIdMask operator|(x11::Res::ClientIdMask l, + x11::Res::ClientIdMask r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::Res::ClientIdMask operator&(x11::Res::ClientIdMask l, + x11::Res::ClientIdMask r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +#endif // UI_GFX_X_GENERATED_PROTOS_RES_H_ diff --git a/x/generated_protos/screensaver.cc b/x/generated_protos/screensaver.cc new file mode 100644 index 000000000000..b28f6632d686 --- /dev/null +++ b/x/generated_protos/screensaver.cc @@ -0,0 +1,644 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#include "screensaver.h" + +#include +#include + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "ui/gfx/x/xproto_internal.h" + +namespace x11 { + +ScreenSaver::ScreenSaver(Connection* connection, + const x11::QueryExtensionReply& info) + : connection_(connection), info_(info) {} + +template <> +COMPONENT_EXPORT(X11) +void ReadEvent(ScreenSaver::NotifyEvent* event_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& state = (*event_).state; + auto& sequence = (*event_).sequence; + auto& time = (*event_).time; + auto& root = (*event_).root; + auto& window = (*event_).window; + auto& kind = (*event_).kind; + auto& forced = (*event_).forced; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // state + uint8_t tmp0; + Read(&tmp0, &buf); + state = static_cast(tmp0); + + // sequence + Read(&sequence, &buf); + + // time + Read(&time, &buf); + + // root + Read(&root, &buf); + + // window + Read(&window, &buf); + + // kind + uint8_t tmp1; + Read(&tmp1, &buf); + kind = static_cast(tmp1); + + // forced + Read(&forced, &buf); + + // pad0 + Pad(&buf, 14); + + DCHECK_LE(buf.offset, 32ul); +} + +Future ScreenSaver::QueryVersion( + const ScreenSaver::QueryVersionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& client_major_version = request.client_major_version; + auto& client_minor_version = request.client_minor_version; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 0; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // client_major_version + buf.Write(&client_major_version); + + // client_minor_version + buf.Write(&client_minor_version); + + // pad0 + Pad(&buf, 2); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "ScreenSaver::QueryVersion", false); +} + +Future ScreenSaver::QueryVersion( + const uint8_t& client_major_version, + const uint8_t& client_minor_version) { + return ScreenSaver::QueryVersion(ScreenSaver::QueryVersionRequest{ + client_major_version, client_minor_version}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + ScreenSaver::QueryVersionReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& server_major_version = (*reply).server_major_version; + auto& server_minor_version = (*reply).server_minor_version; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // server_major_version + Read(&server_major_version, &buf); + + // server_minor_version + Read(&server_minor_version, &buf); + + // pad1 + Pad(&buf, 20); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future ScreenSaver::QueryInfo( + const ScreenSaver::QueryInfoRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& drawable = request.drawable; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 1; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // drawable + buf.Write(&drawable); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "ScreenSaver::QueryInfo", false); +} + +Future ScreenSaver::QueryInfo( + const Drawable& drawable) { + return ScreenSaver::QueryInfo(ScreenSaver::QueryInfoRequest{drawable}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + ScreenSaver::QueryInfoReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& state = (*reply).state; + auto& sequence = (*reply).sequence; + auto& saver_window = (*reply).saver_window; + auto& ms_until_server = (*reply).ms_until_server; + auto& ms_since_user_input = (*reply).ms_since_user_input; + auto& event_mask = (*reply).event_mask; + auto& kind = (*reply).kind; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // state + Read(&state, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // saver_window + Read(&saver_window, &buf); + + // ms_until_server + Read(&ms_until_server, &buf); + + // ms_since_user_input + Read(&ms_since_user_input, &buf); + + // event_mask + Read(&event_mask, &buf); + + // kind + uint8_t tmp2; + Read(&tmp2, &buf); + kind = static_cast(tmp2); + + // pad0 + Pad(&buf, 7); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future ScreenSaver::SelectInput( + const ScreenSaver::SelectInputRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& drawable = request.drawable; + auto& event_mask = request.event_mask; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 2; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // drawable + buf.Write(&drawable); + + // event_mask + uint32_t tmp3; + tmp3 = static_cast(event_mask); + buf.Write(&tmp3); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "ScreenSaver::SelectInput", + false); +} + +Future ScreenSaver::SelectInput(const Drawable& drawable, + const Event& event_mask) { + return ScreenSaver::SelectInput( + ScreenSaver::SelectInputRequest{drawable, event_mask}); +} + +Future ScreenSaver::SetAttributes( + const ScreenSaver::SetAttributesRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& drawable = request.drawable; + auto& x = request.x; + auto& y = request.y; + auto& width = request.width; + auto& height = request.height; + auto& border_width = request.border_width; + auto& c_class = request.c_class; + auto& depth = request.depth; + auto& visual = request.visual; + CreateWindowAttribute value_mask{}; + auto& value_list = request; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 3; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // drawable + buf.Write(&drawable); + + // x + buf.Write(&x); + + // y + buf.Write(&y); + + // width + buf.Write(&width); + + // height + buf.Write(&height); + + // border_width + buf.Write(&border_width); + + // c_class + uint8_t tmp4; + tmp4 = static_cast(c_class); + buf.Write(&tmp4); + + // depth + buf.Write(&depth); + + // visual + buf.Write(&visual); + + // value_mask + SwitchVar(CreateWindowAttribute::BackPixmap, + value_list.background_pixmap.has_value(), true, &value_mask); + SwitchVar(CreateWindowAttribute::BackPixel, + value_list.background_pixel.has_value(), true, &value_mask); + SwitchVar(CreateWindowAttribute::BorderPixmap, + value_list.border_pixmap.has_value(), true, &value_mask); + SwitchVar(CreateWindowAttribute::BorderPixel, + value_list.border_pixel.has_value(), true, &value_mask); + SwitchVar(CreateWindowAttribute::BitGravity, + value_list.bit_gravity.has_value(), true, &value_mask); + SwitchVar(CreateWindowAttribute::WinGravity, + value_list.win_gravity.has_value(), true, &value_mask); + SwitchVar(CreateWindowAttribute::BackingStore, + value_list.backing_store.has_value(), true, &value_mask); + SwitchVar(CreateWindowAttribute::BackingPlanes, + value_list.backing_planes.has_value(), true, &value_mask); + SwitchVar(CreateWindowAttribute::BackingPixel, + value_list.backing_pixel.has_value(), true, &value_mask); + SwitchVar(CreateWindowAttribute::OverrideRedirect, + value_list.override_redirect.has_value(), true, &value_mask); + SwitchVar(CreateWindowAttribute::SaveUnder, value_list.save_under.has_value(), + true, &value_mask); + SwitchVar(CreateWindowAttribute::EventMask, value_list.event_mask.has_value(), + true, &value_mask); + SwitchVar(CreateWindowAttribute::DontPropagate, + value_list.do_not_propogate_mask.has_value(), true, &value_mask); + SwitchVar(CreateWindowAttribute::Colormap, value_list.colormap.has_value(), + true, &value_mask); + SwitchVar(CreateWindowAttribute::Cursor, value_list.cursor.has_value(), true, + &value_mask); + uint32_t tmp5; + tmp5 = static_cast(value_mask); + buf.Write(&tmp5); + + // value_list + auto value_list_expr = value_mask; + if (CaseAnd(value_list_expr, CreateWindowAttribute::BackPixmap)) { + auto& background_pixmap = *value_list.background_pixmap; + + // background_pixmap + buf.Write(&background_pixmap); + } + if (CaseAnd(value_list_expr, CreateWindowAttribute::BackPixel)) { + auto& background_pixel = *value_list.background_pixel; + + // background_pixel + buf.Write(&background_pixel); + } + if (CaseAnd(value_list_expr, CreateWindowAttribute::BorderPixmap)) { + auto& border_pixmap = *value_list.border_pixmap; + + // border_pixmap + buf.Write(&border_pixmap); + } + if (CaseAnd(value_list_expr, CreateWindowAttribute::BorderPixel)) { + auto& border_pixel = *value_list.border_pixel; + + // border_pixel + buf.Write(&border_pixel); + } + if (CaseAnd(value_list_expr, CreateWindowAttribute::BitGravity)) { + auto& bit_gravity = *value_list.bit_gravity; + + // bit_gravity + uint32_t tmp6; + tmp6 = static_cast(bit_gravity); + buf.Write(&tmp6); + } + if (CaseAnd(value_list_expr, CreateWindowAttribute::WinGravity)) { + auto& win_gravity = *value_list.win_gravity; + + // win_gravity + uint32_t tmp7; + tmp7 = static_cast(win_gravity); + buf.Write(&tmp7); + } + if (CaseAnd(value_list_expr, CreateWindowAttribute::BackingStore)) { + auto& backing_store = *value_list.backing_store; + + // backing_store + uint32_t tmp8; + tmp8 = static_cast(backing_store); + buf.Write(&tmp8); + } + if (CaseAnd(value_list_expr, CreateWindowAttribute::BackingPlanes)) { + auto& backing_planes = *value_list.backing_planes; + + // backing_planes + buf.Write(&backing_planes); + } + if (CaseAnd(value_list_expr, CreateWindowAttribute::BackingPixel)) { + auto& backing_pixel = *value_list.backing_pixel; + + // backing_pixel + buf.Write(&backing_pixel); + } + if (CaseAnd(value_list_expr, CreateWindowAttribute::OverrideRedirect)) { + auto& override_redirect = *value_list.override_redirect; + + // override_redirect + buf.Write(&override_redirect); + } + if (CaseAnd(value_list_expr, CreateWindowAttribute::SaveUnder)) { + auto& save_under = *value_list.save_under; + + // save_under + buf.Write(&save_under); + } + if (CaseAnd(value_list_expr, CreateWindowAttribute::EventMask)) { + auto& event_mask = *value_list.event_mask; + + // event_mask + uint32_t tmp9; + tmp9 = static_cast(event_mask); + buf.Write(&tmp9); + } + if (CaseAnd(value_list_expr, CreateWindowAttribute::DontPropagate)) { + auto& do_not_propogate_mask = *value_list.do_not_propogate_mask; + + // do_not_propogate_mask + uint32_t tmp10; + tmp10 = static_cast(do_not_propogate_mask); + buf.Write(&tmp10); + } + if (CaseAnd(value_list_expr, CreateWindowAttribute::Colormap)) { + auto& colormap = *value_list.colormap; + + // colormap + buf.Write(&colormap); + } + if (CaseAnd(value_list_expr, CreateWindowAttribute::Cursor)) { + auto& cursor = *value_list.cursor; + + // cursor + buf.Write(&cursor); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "ScreenSaver::SetAttributes", + false); +} + +Future ScreenSaver::SetAttributes( + const Drawable& drawable, + const int16_t& x, + const int16_t& y, + const uint16_t& width, + const uint16_t& height, + const uint16_t& border_width, + const WindowClass& c_class, + const uint8_t& depth, + const VisualId& visual, + const absl::optional& background_pixmap, + const absl::optional& background_pixel, + const absl::optional& border_pixmap, + const absl::optional& border_pixel, + const absl::optional& bit_gravity, + const absl::optional& win_gravity, + const absl::optional& backing_store, + const absl::optional& backing_planes, + const absl::optional& backing_pixel, + const absl::optional& override_redirect, + const absl::optional& save_under, + const absl::optional& event_mask, + const absl::optional& do_not_propogate_mask, + const absl::optional& colormap, + const absl::optional& cursor) { + return ScreenSaver::SetAttributes( + ScreenSaver::SetAttributesRequest{drawable, + x, + y, + width, + height, + border_width, + c_class, + depth, + visual, + background_pixmap, + background_pixel, + border_pixmap, + border_pixel, + bit_gravity, + win_gravity, + backing_store, + backing_planes, + backing_pixel, + override_redirect, + save_under, + event_mask, + do_not_propogate_mask, + colormap, + cursor}); +} + +Future ScreenSaver::UnsetAttributes( + const ScreenSaver::UnsetAttributesRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& drawable = request.drawable; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 4; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // drawable + buf.Write(&drawable); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "ScreenSaver::UnsetAttributes", + false); +} + +Future ScreenSaver::UnsetAttributes(const Drawable& drawable) { + return ScreenSaver::UnsetAttributes( + ScreenSaver::UnsetAttributesRequest{drawable}); +} + +Future ScreenSaver::Suspend(const ScreenSaver::SuspendRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& suspend = request.suspend; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 5; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // suspend + buf.Write(&suspend); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "ScreenSaver::Suspend", false); +} + +Future ScreenSaver::Suspend(const uint32_t& suspend) { + return ScreenSaver::Suspend(ScreenSaver::SuspendRequest{suspend}); +} + +} // namespace x11 diff --git a/x/generated_protos/screensaver.h b/x/generated_protos/screensaver.h new file mode 100644 index 000000000000..7f43bcd7d25c --- /dev/null +++ b/x/generated_protos/screensaver.h @@ -0,0 +1,293 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#ifndef UI_GFX_X_GENERATED_PROTOS_SCREENSAVER_H_ +#define UI_GFX_X_GENERATED_PROTOS_SCREENSAVER_H_ + +#include +#include +#include +#include +#include + +#include "base/component_export.h" +#include "base/files/scoped_file.h" +#include "base/memory/ref_counted_memory.h" +#include "base/memory/scoped_refptr.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/gfx/x/error.h" +#include "ui/gfx/x/ref_counted_fd.h" +#include "xproto.h" + +namespace x11 { + +class Connection; + +template +struct Response; + +template +class Future; + +class COMPONENT_EXPORT(X11) ScreenSaver { + public: + static constexpr unsigned major_version = 1; + static constexpr unsigned minor_version = 1; + + ScreenSaver(Connection* connection, const x11::QueryExtensionReply& info); + + uint8_t present() const { return info_.present; } + uint8_t major_opcode() const { return info_.major_opcode; } + uint8_t first_event() const { return info_.first_event; } + uint8_t first_error() const { return info_.first_error; } + + Connection* connection() const { return connection_; } + + enum class Kind : int { + Blanked = 0, + Internal = 1, + External = 2, + }; + + enum class Event : int { + NotifyMask = 1 << 0, + CycleMask = 1 << 1, + }; + + enum class State : int { + Off = 0, + On = 1, + Cycle = 2, + Disabled = 3, + }; + + struct NotifyEvent { + static constexpr int type_id = 13; + static constexpr uint8_t opcode = 0; + bool send_event{}; + State state{}; + uint16_t sequence{}; + Time time{}; + Window root{}; + Window window{}; + Kind kind{}; + uint8_t forced{}; + + x11::Window* GetWindow() { return reinterpret_cast(&window); } + }; + + struct QueryVersionRequest { + uint8_t client_major_version{}; + uint8_t client_minor_version{}; + }; + + struct QueryVersionReply { + uint16_t sequence{}; + uint16_t server_major_version{}; + uint16_t server_minor_version{}; + }; + + using QueryVersionResponse = Response; + + Future QueryVersion(const QueryVersionRequest& request); + + Future QueryVersion( + const uint8_t& client_major_version = {}, + const uint8_t& client_minor_version = {}); + + struct QueryInfoRequest { + Drawable drawable{}; + }; + + struct QueryInfoReply { + uint8_t state{}; + uint16_t sequence{}; + Window saver_window{}; + uint32_t ms_until_server{}; + uint32_t ms_since_user_input{}; + uint32_t event_mask{}; + Kind kind{}; + }; + + using QueryInfoResponse = Response; + + Future QueryInfo(const QueryInfoRequest& request); + + Future QueryInfo(const Drawable& drawable = {}); + + struct SelectInputRequest { + Drawable drawable{}; + Event event_mask{}; + }; + + using SelectInputResponse = Response; + + Future SelectInput(const SelectInputRequest& request); + + Future SelectInput(const Drawable& drawable = {}, + const Event& event_mask = {}); + + struct SetAttributesRequest { + Drawable drawable{}; + int16_t x{}; + int16_t y{}; + uint16_t width{}; + uint16_t height{}; + uint16_t border_width{}; + WindowClass c_class{}; + uint8_t depth{}; + VisualId visual{}; + absl::optional background_pixmap{}; + absl::optional background_pixel{}; + absl::optional border_pixmap{}; + absl::optional border_pixel{}; + absl::optional bit_gravity{}; + absl::optional win_gravity{}; + absl::optional backing_store{}; + absl::optional backing_planes{}; + absl::optional backing_pixel{}; + absl::optional override_redirect{}; + absl::optional save_under{}; + absl::optional event_mask{}; + absl::optional do_not_propogate_mask{}; + absl::optional colormap{}; + absl::optional cursor{}; + }; + + using SetAttributesResponse = Response; + + Future SetAttributes(const SetAttributesRequest& request); + + Future SetAttributes( + const Drawable& drawable = {}, + const int16_t& x = {}, + const int16_t& y = {}, + const uint16_t& width = {}, + const uint16_t& height = {}, + const uint16_t& border_width = {}, + const WindowClass& c_class = {}, + const uint8_t& depth = {}, + const VisualId& visual = {}, + const absl::optional& background_pixmap = absl::nullopt, + const absl::optional& background_pixel = absl::nullopt, + const absl::optional& border_pixmap = absl::nullopt, + const absl::optional& border_pixel = absl::nullopt, + const absl::optional& bit_gravity = absl::nullopt, + const absl::optional& win_gravity = absl::nullopt, + const absl::optional& backing_store = absl::nullopt, + const absl::optional& backing_planes = absl::nullopt, + const absl::optional& backing_pixel = absl::nullopt, + const absl::optional& override_redirect = absl::nullopt, + const absl::optional& save_under = absl::nullopt, + const absl::optional& event_mask = absl::nullopt, + const absl::optional& do_not_propogate_mask = absl::nullopt, + const absl::optional& colormap = absl::nullopt, + const absl::optional& cursor = absl::nullopt); + + struct UnsetAttributesRequest { + Drawable drawable{}; + }; + + using UnsetAttributesResponse = Response; + + Future UnsetAttributes(const UnsetAttributesRequest& request); + + Future UnsetAttributes(const Drawable& drawable = {}); + + struct SuspendRequest { + uint32_t suspend{}; + }; + + using SuspendResponse = Response; + + Future Suspend(const SuspendRequest& request); + + Future Suspend(const uint32_t& suspend = {}); + + private: + Connection* const connection_; + x11::QueryExtensionReply info_{}; +}; + +} // namespace x11 + +inline constexpr x11::ScreenSaver::Kind operator|(x11::ScreenSaver::Kind l, + x11::ScreenSaver::Kind r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::ScreenSaver::Kind operator&(x11::ScreenSaver::Kind l, + x11::ScreenSaver::Kind r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +inline constexpr x11::ScreenSaver::Event operator|(x11::ScreenSaver::Event l, + x11::ScreenSaver::Event r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::ScreenSaver::Event operator&(x11::ScreenSaver::Event l, + x11::ScreenSaver::Event r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +inline constexpr x11::ScreenSaver::State operator|(x11::ScreenSaver::State l, + x11::ScreenSaver::State r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::ScreenSaver::State operator&(x11::ScreenSaver::State l, + x11::ScreenSaver::State r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +#endif // UI_GFX_X_GENERATED_PROTOS_SCREENSAVER_H_ diff --git a/x/generated_protos/shape.cc b/x/generated_protos/shape.cc new file mode 100644 index 000000000000..946080af03d4 --- /dev/null +++ b/x/generated_protos/shape.cc @@ -0,0 +1,784 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#include "shape.h" + +#include +#include + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "ui/gfx/x/xproto_internal.h" + +namespace x11 { + +Shape::Shape(Connection* connection, const x11::QueryExtensionReply& info) + : connection_(connection), info_(info) {} + +template <> +COMPONENT_EXPORT(X11) +void ReadEvent(Shape::NotifyEvent* event_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& shape_kind = (*event_).shape_kind; + auto& sequence = (*event_).sequence; + auto& affected_window = (*event_).affected_window; + auto& extents_x = (*event_).extents_x; + auto& extents_y = (*event_).extents_y; + auto& extents_width = (*event_).extents_width; + auto& extents_height = (*event_).extents_height; + auto& server_time = (*event_).server_time; + auto& shaped = (*event_).shaped; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // shape_kind + uint8_t tmp0; + Read(&tmp0, &buf); + shape_kind = static_cast(tmp0); + + // sequence + Read(&sequence, &buf); + + // affected_window + Read(&affected_window, &buf); + + // extents_x + Read(&extents_x, &buf); + + // extents_y + Read(&extents_y, &buf); + + // extents_width + Read(&extents_width, &buf); + + // extents_height + Read(&extents_height, &buf); + + // server_time + Read(&server_time, &buf); + + // shaped + Read(&shaped, &buf); + + // pad0 + Pad(&buf, 11); + + DCHECK_LE(buf.offset, 32ul); +} + +Future Shape::QueryVersion( + const Shape::QueryVersionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 0; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Shape::QueryVersion", false); +} + +Future Shape::QueryVersion() { + return Shape::QueryVersion(Shape::QueryVersionRequest{}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Shape::QueryVersionReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& major_version = (*reply).major_version; + auto& minor_version = (*reply).minor_version; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // major_version + Read(&major_version, &buf); + + // minor_version + Read(&minor_version, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Shape::Rectangles(const Shape::RectanglesRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& operation = request.operation; + auto& destination_kind = request.destination_kind; + auto& ordering = request.ordering; + auto& destination_window = request.destination_window; + auto& x_offset = request.x_offset; + auto& y_offset = request.y_offset; + auto& rectangles = request.rectangles; + size_t rectangles_len = rectangles.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 1; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // operation + uint8_t tmp1; + tmp1 = static_cast(operation); + buf.Write(&tmp1); + + // destination_kind + uint8_t tmp2; + tmp2 = static_cast(destination_kind); + buf.Write(&tmp2); + + // ordering + uint8_t tmp3; + tmp3 = static_cast(ordering); + buf.Write(&tmp3); + + // pad0 + Pad(&buf, 1); + + // destination_window + buf.Write(&destination_window); + + // x_offset + buf.Write(&x_offset); + + // y_offset + buf.Write(&y_offset); + + // rectangles + DCHECK_EQ(static_cast(rectangles_len), rectangles.size()); + for (auto& rectangles_elem : rectangles) { + // rectangles_elem + { + auto& x = rectangles_elem.x; + auto& y = rectangles_elem.y; + auto& width = rectangles_elem.width; + auto& height = rectangles_elem.height; + + // x + buf.Write(&x); + + // y + buf.Write(&y); + + // width + buf.Write(&width); + + // height + buf.Write(&height); + } + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Shape::Rectangles", false); +} + +Future Shape::Rectangles(const So& operation, + const Sk& destination_kind, + const ClipOrdering& ordering, + const Window& destination_window, + const int16_t& x_offset, + const int16_t& y_offset, + const std::vector& rectangles) { + return Shape::Rectangles(Shape::RectanglesRequest{ + operation, destination_kind, ordering, destination_window, x_offset, + y_offset, rectangles}); +} + +Future Shape::Mask(const Shape::MaskRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& operation = request.operation; + auto& destination_kind = request.destination_kind; + auto& destination_window = request.destination_window; + auto& x_offset = request.x_offset; + auto& y_offset = request.y_offset; + auto& source_bitmap = request.source_bitmap; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 2; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // operation + uint8_t tmp4; + tmp4 = static_cast(operation); + buf.Write(&tmp4); + + // destination_kind + uint8_t tmp5; + tmp5 = static_cast(destination_kind); + buf.Write(&tmp5); + + // pad0 + Pad(&buf, 2); + + // destination_window + buf.Write(&destination_window); + + // x_offset + buf.Write(&x_offset); + + // y_offset + buf.Write(&y_offset); + + // source_bitmap + buf.Write(&source_bitmap); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Shape::Mask", false); +} + +Future Shape::Mask(const So& operation, + const Sk& destination_kind, + const Window& destination_window, + const int16_t& x_offset, + const int16_t& y_offset, + const Pixmap& source_bitmap) { + return Shape::Mask(Shape::MaskRequest{operation, destination_kind, + destination_window, x_offset, y_offset, + source_bitmap}); +} + +Future Shape::Combine(const Shape::CombineRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& operation = request.operation; + auto& destination_kind = request.destination_kind; + auto& source_kind = request.source_kind; + auto& destination_window = request.destination_window; + auto& x_offset = request.x_offset; + auto& y_offset = request.y_offset; + auto& source_window = request.source_window; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 3; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // operation + uint8_t tmp6; + tmp6 = static_cast(operation); + buf.Write(&tmp6); + + // destination_kind + uint8_t tmp7; + tmp7 = static_cast(destination_kind); + buf.Write(&tmp7); + + // source_kind + uint8_t tmp8; + tmp8 = static_cast(source_kind); + buf.Write(&tmp8); + + // pad0 + Pad(&buf, 1); + + // destination_window + buf.Write(&destination_window); + + // x_offset + buf.Write(&x_offset); + + // y_offset + buf.Write(&y_offset); + + // source_window + buf.Write(&source_window); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Shape::Combine", false); +} + +Future Shape::Combine(const So& operation, + const Sk& destination_kind, + const Sk& source_kind, + const Window& destination_window, + const int16_t& x_offset, + const int16_t& y_offset, + const Window& source_window) { + return Shape::Combine(Shape::CombineRequest{ + operation, destination_kind, source_kind, destination_window, x_offset, + y_offset, source_window}); +} + +Future Shape::Offset(const Shape::OffsetRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& destination_kind = request.destination_kind; + auto& destination_window = request.destination_window; + auto& x_offset = request.x_offset; + auto& y_offset = request.y_offset; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 4; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // destination_kind + uint8_t tmp9; + tmp9 = static_cast(destination_kind); + buf.Write(&tmp9); + + // pad0 + Pad(&buf, 3); + + // destination_window + buf.Write(&destination_window); + + // x_offset + buf.Write(&x_offset); + + // y_offset + buf.Write(&y_offset); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Shape::Offset", false); +} + +Future Shape::Offset(const Sk& destination_kind, + const Window& destination_window, + const int16_t& x_offset, + const int16_t& y_offset) { + return Shape::Offset(Shape::OffsetRequest{ + destination_kind, destination_window, x_offset, y_offset}); +} + +Future Shape::QueryExtents( + const Shape::QueryExtentsRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& destination_window = request.destination_window; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 5; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // destination_window + buf.Write(&destination_window); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Shape::QueryExtents", false); +} + +Future Shape::QueryExtents( + const Window& destination_window) { + return Shape::QueryExtents(Shape::QueryExtentsRequest{destination_window}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Shape::QueryExtentsReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& bounding_shaped = (*reply).bounding_shaped; + auto& clip_shaped = (*reply).clip_shaped; + auto& bounding_shape_extents_x = (*reply).bounding_shape_extents_x; + auto& bounding_shape_extents_y = (*reply).bounding_shape_extents_y; + auto& bounding_shape_extents_width = (*reply).bounding_shape_extents_width; + auto& bounding_shape_extents_height = (*reply).bounding_shape_extents_height; + auto& clip_shape_extents_x = (*reply).clip_shape_extents_x; + auto& clip_shape_extents_y = (*reply).clip_shape_extents_y; + auto& clip_shape_extents_width = (*reply).clip_shape_extents_width; + auto& clip_shape_extents_height = (*reply).clip_shape_extents_height; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // bounding_shaped + Read(&bounding_shaped, &buf); + + // clip_shaped + Read(&clip_shaped, &buf); + + // pad1 + Pad(&buf, 2); + + // bounding_shape_extents_x + Read(&bounding_shape_extents_x, &buf); + + // bounding_shape_extents_y + Read(&bounding_shape_extents_y, &buf); + + // bounding_shape_extents_width + Read(&bounding_shape_extents_width, &buf); + + // bounding_shape_extents_height + Read(&bounding_shape_extents_height, &buf); + + // clip_shape_extents_x + Read(&clip_shape_extents_x, &buf); + + // clip_shape_extents_y + Read(&clip_shape_extents_y, &buf); + + // clip_shape_extents_width + Read(&clip_shape_extents_width, &buf); + + // clip_shape_extents_height + Read(&clip_shape_extents_height, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Shape::SelectInput(const Shape::SelectInputRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& destination_window = request.destination_window; + auto& enable = request.enable; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 6; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // destination_window + buf.Write(&destination_window); + + // enable + buf.Write(&enable); + + // pad0 + Pad(&buf, 3); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Shape::SelectInput", false); +} + +Future Shape::SelectInput(const Window& destination_window, + const uint8_t& enable) { + return Shape::SelectInput( + Shape::SelectInputRequest{destination_window, enable}); +} + +Future Shape::InputSelected( + const Shape::InputSelectedRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& destination_window = request.destination_window; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 7; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // destination_window + buf.Write(&destination_window); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Shape::InputSelected", false); +} + +Future Shape::InputSelected( + const Window& destination_window) { + return Shape::InputSelected(Shape::InputSelectedRequest{destination_window}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Shape::InputSelectedReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& enabled = (*reply).enabled; + auto& sequence = (*reply).sequence; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // enabled + Read(&enabled, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Shape::GetRectangles( + const Shape::GetRectanglesRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + auto& source_kind = request.source_kind; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 8; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + // source_kind + uint8_t tmp10; + tmp10 = static_cast(source_kind); + buf.Write(&tmp10); + + // pad0 + Pad(&buf, 3); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Shape::GetRectangles", false); +} + +Future Shape::GetRectangles(const Window& window, + const Sk& source_kind) { + return Shape::GetRectangles(Shape::GetRectanglesRequest{window, source_kind}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Shape::GetRectanglesReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& ordering = (*reply).ordering; + auto& sequence = (*reply).sequence; + uint32_t rectangles_len{}; + auto& rectangles = (*reply).rectangles; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // ordering + uint8_t tmp11; + Read(&tmp11, &buf); + ordering = static_cast(tmp11); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // rectangles_len + Read(&rectangles_len, &buf); + + // pad0 + Pad(&buf, 20); + + // rectangles + rectangles.resize(rectangles_len); + for (auto& rectangles_elem : rectangles) { + // rectangles_elem + { + auto& x = rectangles_elem.x; + auto& y = rectangles_elem.y; + auto& width = rectangles_elem.width; + auto& height = rectangles_elem.height; + + // x + Read(&x, &buf); + + // y + Read(&y, &buf); + + // width + Read(&width, &buf); + + // height + Read(&height, &buf); + } + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +} // namespace x11 diff --git a/x/generated_protos/shape.h b/x/generated_protos/shape.h new file mode 100644 index 000000000000..7262c397778c --- /dev/null +++ b/x/generated_protos/shape.h @@ -0,0 +1,311 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#ifndef UI_GFX_X_GENERATED_PROTOS_SHAPE_H_ +#define UI_GFX_X_GENERATED_PROTOS_SHAPE_H_ + +#include +#include +#include +#include +#include + +#include "base/component_export.h" +#include "base/files/scoped_file.h" +#include "base/memory/ref_counted_memory.h" +#include "base/memory/scoped_refptr.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/gfx/x/error.h" +#include "ui/gfx/x/ref_counted_fd.h" +#include "xproto.h" + +namespace x11 { + +class Connection; + +template +struct Response; + +template +class Future; + +class COMPONENT_EXPORT(X11) Shape { + public: + static constexpr unsigned major_version = 1; + static constexpr unsigned minor_version = 1; + + Shape(Connection* connection, const x11::QueryExtensionReply& info); + + uint8_t present() const { return info_.present; } + uint8_t major_opcode() const { return info_.major_opcode; } + uint8_t first_event() const { return info_.first_event; } + uint8_t first_error() const { return info_.first_error; } + + Connection* connection() const { return connection_; } + + enum class Operation : uint8_t {}; + + enum class Kind : uint8_t {}; + + enum class So : int { + Set = 0, + Union = 1, + Intersect = 2, + Subtract = 3, + Invert = 4, + }; + + enum class Sk : int { + Bounding = 0, + Clip = 1, + Input = 2, + }; + + struct NotifyEvent { + static constexpr int type_id = 14; + static constexpr uint8_t opcode = 0; + bool send_event{}; + Sk shape_kind{}; + uint16_t sequence{}; + Window affected_window{}; + int16_t extents_x{}; + int16_t extents_y{}; + uint16_t extents_width{}; + uint16_t extents_height{}; + Time server_time{}; + uint8_t shaped{}; + + x11::Window* GetWindow() { + return reinterpret_cast(&affected_window); + } + }; + + struct QueryVersionRequest {}; + + struct QueryVersionReply { + uint16_t sequence{}; + uint16_t major_version{}; + uint16_t minor_version{}; + }; + + using QueryVersionResponse = Response; + + Future QueryVersion(const QueryVersionRequest& request); + + Future QueryVersion(); + + struct RectanglesRequest { + So operation{}; + Sk destination_kind{}; + ClipOrdering ordering{}; + Window destination_window{}; + int16_t x_offset{}; + int16_t y_offset{}; + std::vector rectangles{}; + }; + + using RectanglesResponse = Response; + + Future Rectangles(const RectanglesRequest& request); + + Future Rectangles(const So& operation = {}, + const Sk& destination_kind = {}, + const ClipOrdering& ordering = {}, + const Window& destination_window = {}, + const int16_t& x_offset = {}, + const int16_t& y_offset = {}, + const std::vector& rectangles = {}); + + struct MaskRequest { + So operation{}; + Sk destination_kind{}; + Window destination_window{}; + int16_t x_offset{}; + int16_t y_offset{}; + Pixmap source_bitmap{}; + }; + + using MaskResponse = Response; + + Future Mask(const MaskRequest& request); + + Future Mask(const So& operation = {}, + const Sk& destination_kind = {}, + const Window& destination_window = {}, + const int16_t& x_offset = {}, + const int16_t& y_offset = {}, + const Pixmap& source_bitmap = {}); + + struct CombineRequest { + So operation{}; + Sk destination_kind{}; + Sk source_kind{}; + Window destination_window{}; + int16_t x_offset{}; + int16_t y_offset{}; + Window source_window{}; + }; + + using CombineResponse = Response; + + Future Combine(const CombineRequest& request); + + Future Combine(const So& operation = {}, + const Sk& destination_kind = {}, + const Sk& source_kind = {}, + const Window& destination_window = {}, + const int16_t& x_offset = {}, + const int16_t& y_offset = {}, + const Window& source_window = {}); + + struct OffsetRequest { + Sk destination_kind{}; + Window destination_window{}; + int16_t x_offset{}; + int16_t y_offset{}; + }; + + using OffsetResponse = Response; + + Future Offset(const OffsetRequest& request); + + Future Offset(const Sk& destination_kind = {}, + const Window& destination_window = {}, + const int16_t& x_offset = {}, + const int16_t& y_offset = {}); + + struct QueryExtentsRequest { + Window destination_window{}; + }; + + struct QueryExtentsReply { + uint16_t sequence{}; + uint8_t bounding_shaped{}; + uint8_t clip_shaped{}; + int16_t bounding_shape_extents_x{}; + int16_t bounding_shape_extents_y{}; + uint16_t bounding_shape_extents_width{}; + uint16_t bounding_shape_extents_height{}; + int16_t clip_shape_extents_x{}; + int16_t clip_shape_extents_y{}; + uint16_t clip_shape_extents_width{}; + uint16_t clip_shape_extents_height{}; + }; + + using QueryExtentsResponse = Response; + + Future QueryExtents(const QueryExtentsRequest& request); + + Future QueryExtents(const Window& destination_window = {}); + + struct SelectInputRequest { + Window destination_window{}; + uint8_t enable{}; + }; + + using SelectInputResponse = Response; + + Future SelectInput(const SelectInputRequest& request); + + Future SelectInput(const Window& destination_window = {}, + const uint8_t& enable = {}); + + struct InputSelectedRequest { + Window destination_window{}; + }; + + struct InputSelectedReply { + uint8_t enabled{}; + uint16_t sequence{}; + }; + + using InputSelectedResponse = Response; + + Future InputSelected(const InputSelectedRequest& request); + + Future InputSelected( + const Window& destination_window = {}); + + struct GetRectanglesRequest { + Window window{}; + Sk source_kind{}; + }; + + struct GetRectanglesReply { + ClipOrdering ordering{}; + uint16_t sequence{}; + std::vector rectangles{}; + }; + + using GetRectanglesResponse = Response; + + Future GetRectangles(const GetRectanglesRequest& request); + + Future GetRectangles(const Window& window = {}, + const Sk& source_kind = {}); + + private: + Connection* const connection_; + x11::QueryExtensionReply info_{}; +}; + +} // namespace x11 + +inline constexpr x11::Shape::So operator|(x11::Shape::So l, x11::Shape::So r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | static_cast(r)); +} + +inline constexpr x11::Shape::So operator&(x11::Shape::So l, x11::Shape::So r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & static_cast(r)); +} + +inline constexpr x11::Shape::Sk operator|(x11::Shape::Sk l, x11::Shape::Sk r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | static_cast(r)); +} + +inline constexpr x11::Shape::Sk operator&(x11::Shape::Sk l, x11::Shape::Sk r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & static_cast(r)); +} + +#endif // UI_GFX_X_GENERATED_PROTOS_SHAPE_H_ diff --git a/x/generated_protos/shm.cc b/x/generated_protos/shm.cc new file mode 100644 index 000000000000..fc795c661da9 --- /dev/null +++ b/x/generated_protos/shm.cc @@ -0,0 +1,722 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#include "shm.h" + +#include +#include + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "ui/gfx/x/xproto_internal.h" + +namespace x11 { + +Shm::Shm(Connection* connection, const x11::QueryExtensionReply& info) + : connection_(connection), info_(info) {} + +template <> +COMPONENT_EXPORT(X11) +void ReadEvent(Shm::CompletionEvent* event_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*event_).sequence; + auto& drawable = (*event_).drawable; + auto& minor_event = (*event_).minor_event; + auto& major_event = (*event_).major_event; + auto& shmseg = (*event_).shmseg; + auto& offset = (*event_).offset; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // drawable + Read(&drawable, &buf); + + // minor_event + Read(&minor_event, &buf); + + // major_event + Read(&major_event, &buf); + + // pad1 + Pad(&buf, 1); + + // shmseg + Read(&shmseg, &buf); + + // offset + Read(&offset, &buf); + + DCHECK_LE(buf.offset, 32ul); +} + +std::string Shm::BadSegError::ToString() const { + std::stringstream ss_; + ss_ << "Shm::BadSegError{"; + ss_ << ".sequence = " << static_cast(sequence) << ", "; + ss_ << ".bad_value = " << static_cast(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast(major_opcode); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError(Shm::BadSegError* error_, ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + + // pad0 + Pad(&buf, 1); + + DCHECK_LE(buf.offset, 32ul); +} +Future Shm::QueryVersion( + const Shm::QueryVersionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 0; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Shm::QueryVersion", false); +} + +Future Shm::QueryVersion() { + return Shm::QueryVersion(Shm::QueryVersionRequest{}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Shm::QueryVersionReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& shared_pixmaps = (*reply).shared_pixmaps; + auto& sequence = (*reply).sequence; + auto& major_version = (*reply).major_version; + auto& minor_version = (*reply).minor_version; + auto& uid = (*reply).uid; + auto& gid = (*reply).gid; + auto& pixmap_format = (*reply).pixmap_format; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // shared_pixmaps + Read(&shared_pixmaps, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // major_version + Read(&major_version, &buf); + + // minor_version + Read(&minor_version, &buf); + + // uid + Read(&uid, &buf); + + // gid + Read(&gid, &buf); + + // pixmap_format + Read(&pixmap_format, &buf); + + // pad0 + Pad(&buf, 15); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Shm::Attach(const Shm::AttachRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& shmseg = request.shmseg; + auto& shmid = request.shmid; + auto& read_only = request.read_only; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 1; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // shmseg + buf.Write(&shmseg); + + // shmid + buf.Write(&shmid); + + // read_only + buf.Write(&read_only); + + // pad0 + Pad(&buf, 3); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Shm::Attach", false); +} + +Future Shm::Attach(const Seg& shmseg, + const uint32_t& shmid, + const uint8_t& read_only) { + return Shm::Attach(Shm::AttachRequest{shmseg, shmid, read_only}); +} + +Future Shm::Detach(const Shm::DetachRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& shmseg = request.shmseg; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 2; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // shmseg + buf.Write(&shmseg); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Shm::Detach", false); +} + +Future Shm::Detach(const Seg& shmseg) { + return Shm::Detach(Shm::DetachRequest{shmseg}); +} + +Future Shm::PutImage(const Shm::PutImageRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& drawable = request.drawable; + auto& gc = request.gc; + auto& total_width = request.total_width; + auto& total_height = request.total_height; + auto& src_x = request.src_x; + auto& src_y = request.src_y; + auto& src_width = request.src_width; + auto& src_height = request.src_height; + auto& dst_x = request.dst_x; + auto& dst_y = request.dst_y; + auto& depth = request.depth; + auto& format = request.format; + auto& send_event = request.send_event; + auto& shmseg = request.shmseg; + auto& offset = request.offset; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 3; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // drawable + buf.Write(&drawable); + + // gc + buf.Write(&gc); + + // total_width + buf.Write(&total_width); + + // total_height + buf.Write(&total_height); + + // src_x + buf.Write(&src_x); + + // src_y + buf.Write(&src_y); + + // src_width + buf.Write(&src_width); + + // src_height + buf.Write(&src_height); + + // dst_x + buf.Write(&dst_x); + + // dst_y + buf.Write(&dst_y); + + // depth + buf.Write(&depth); + + // format + uint8_t tmp0; + tmp0 = static_cast(format); + buf.Write(&tmp0); + + // send_event + buf.Write(&send_event); + + // pad0 + Pad(&buf, 1); + + // shmseg + buf.Write(&shmseg); + + // offset + buf.Write(&offset); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Shm::PutImage", false); +} + +Future Shm::PutImage(const Drawable& drawable, + const GraphicsContext& gc, + const uint16_t& total_width, + const uint16_t& total_height, + const uint16_t& src_x, + const uint16_t& src_y, + const uint16_t& src_width, + const uint16_t& src_height, + const int16_t& dst_x, + const int16_t& dst_y, + const uint8_t& depth, + const ImageFormat& format, + const uint8_t& send_event, + const Seg& shmseg, + const uint32_t& offset) { + return Shm::PutImage(Shm::PutImageRequest{ + drawable, gc, total_width, total_height, src_x, src_y, src_width, + src_height, dst_x, dst_y, depth, format, send_event, shmseg, offset}); +} + +Future Shm::GetImage(const Shm::GetImageRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& drawable = request.drawable; + auto& x = request.x; + auto& y = request.y; + auto& width = request.width; + auto& height = request.height; + auto& plane_mask = request.plane_mask; + auto& format = request.format; + auto& shmseg = request.shmseg; + auto& offset = request.offset; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 4; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // drawable + buf.Write(&drawable); + + // x + buf.Write(&x); + + // y + buf.Write(&y); + + // width + buf.Write(&width); + + // height + buf.Write(&height); + + // plane_mask + buf.Write(&plane_mask); + + // format + buf.Write(&format); + + // pad0 + Pad(&buf, 3); + + // shmseg + buf.Write(&shmseg); + + // offset + buf.Write(&offset); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Shm::GetImage", + false); +} + +Future Shm::GetImage(const Drawable& drawable, + const int16_t& x, + const int16_t& y, + const uint16_t& width, + const uint16_t& height, + const uint32_t& plane_mask, + const uint8_t& format, + const Seg& shmseg, + const uint32_t& offset) { + return Shm::GetImage(Shm::GetImageRequest{ + drawable, x, y, width, height, plane_mask, format, shmseg, offset}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& depth = (*reply).depth; + auto& sequence = (*reply).sequence; + auto& visual = (*reply).visual; + auto& size = (*reply).size; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // depth + Read(&depth, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // visual + Read(&visual, &buf); + + // size + Read(&size, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Shm::CreatePixmap(const Shm::CreatePixmapRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& pid = request.pid; + auto& drawable = request.drawable; + auto& width = request.width; + auto& height = request.height; + auto& depth = request.depth; + auto& shmseg = request.shmseg; + auto& offset = request.offset; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 5; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // pid + buf.Write(&pid); + + // drawable + buf.Write(&drawable); + + // width + buf.Write(&width); + + // height + buf.Write(&height); + + // depth + buf.Write(&depth); + + // pad0 + Pad(&buf, 3); + + // shmseg + buf.Write(&shmseg); + + // offset + buf.Write(&offset); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Shm::CreatePixmap", false); +} + +Future Shm::CreatePixmap(const Pixmap& pid, + const Drawable& drawable, + const uint16_t& width, + const uint16_t& height, + const uint8_t& depth, + const Seg& shmseg, + const uint32_t& offset) { + return Shm::CreatePixmap(Shm::CreatePixmapRequest{ + pid, drawable, width, height, depth, shmseg, offset}); +} + +Future Shm::AttachFd(const Shm::AttachFdRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& shmseg = request.shmseg; + auto& shm_fd = request.shm_fd; + auto& read_only = request.read_only; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 6; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // shmseg + buf.Write(&shmseg); + + // shm_fd + buf.fds().push_back(HANDLE_EINTR(dup(shm_fd.get()))); + + // read_only + buf.Write(&read_only); + + // pad0 + Pad(&buf, 3); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Shm::AttachFd", false); +} + +Future Shm::AttachFd(const Seg& shmseg, + const RefCountedFD& shm_fd, + const uint8_t& read_only) { + return Shm::AttachFd(Shm::AttachFdRequest{shmseg, shm_fd, read_only}); +} + +Future Shm::CreateSegment( + const Shm::CreateSegmentRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& shmseg = request.shmseg; + auto& size = request.size; + auto& read_only = request.read_only; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 7; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // shmseg + buf.Write(&shmseg); + + // size + buf.Write(&size); + + // read_only + buf.Write(&read_only); + + // pad0 + Pad(&buf, 3); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Shm::CreateSegment", true); +} + +Future Shm::CreateSegment(const Seg& shmseg, + const uint32_t& size, + const uint8_t& read_only) { + return Shm::CreateSegment(Shm::CreateSegmentRequest{shmseg, size, read_only}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Shm::CreateSegmentReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& nfd = (*reply).nfd; + auto& sequence = (*reply).sequence; + auto& shm_fd = (*reply).shm_fd; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // nfd + Read(&nfd, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // shm_fd + shm_fd = RefCountedFD(buf.TakeFd()); + + // pad0 + Pad(&buf, 24); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +} // namespace x11 diff --git a/x/generated_protos/shm.h b/x/generated_protos/shm.h new file mode 100644 index 000000000000..6df3185de600 --- /dev/null +++ b/x/generated_protos/shm.h @@ -0,0 +1,286 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#ifndef UI_GFX_X_GENERATED_PROTOS_SHM_H_ +#define UI_GFX_X_GENERATED_PROTOS_SHM_H_ + +#include +#include +#include +#include +#include + +#include "base/component_export.h" +#include "base/files/scoped_file.h" +#include "base/memory/ref_counted_memory.h" +#include "base/memory/scoped_refptr.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/gfx/x/error.h" +#include "ui/gfx/x/ref_counted_fd.h" +#include "xproto.h" + +namespace x11 { + +class Connection; + +template +struct Response; + +template +class Future; + +class COMPONENT_EXPORT(X11) Shm { + public: + static constexpr unsigned major_version = 1; + static constexpr unsigned minor_version = 2; + + Shm(Connection* connection, const x11::QueryExtensionReply& info); + + uint8_t present() const { return info_.present; } + uint8_t major_opcode() const { return info_.major_opcode; } + uint8_t first_event() const { return info_.first_event; } + uint8_t first_error() const { return info_.first_error; } + + Connection* connection() const { return connection_; } + + enum class Seg : uint32_t {}; + + struct CompletionEvent { + static constexpr int type_id = 15; + static constexpr uint8_t opcode = 0; + bool send_event{}; + uint16_t sequence{}; + Drawable drawable{}; + uint16_t minor_event{}; + uint8_t major_event{}; + Seg shmseg{}; + uint32_t offset{}; + + x11::Window* GetWindow() { + return reinterpret_cast(&drawable); + } + }; + + struct BadSegError : public x11::Error { + uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; + + std::string ToString() const override; + }; + + struct QueryVersionRequest {}; + + struct QueryVersionReply { + uint8_t shared_pixmaps{}; + uint16_t sequence{}; + uint16_t major_version{}; + uint16_t minor_version{}; + uint16_t uid{}; + uint16_t gid{}; + uint8_t pixmap_format{}; + }; + + using QueryVersionResponse = Response; + + Future QueryVersion(const QueryVersionRequest& request); + + Future QueryVersion(); + + struct AttachRequest { + Seg shmseg{}; + uint32_t shmid{}; + uint8_t read_only{}; + }; + + using AttachResponse = Response; + + Future Attach(const AttachRequest& request); + + Future Attach(const Seg& shmseg = {}, + const uint32_t& shmid = {}, + const uint8_t& read_only = {}); + + struct DetachRequest { + Seg shmseg{}; + }; + + using DetachResponse = Response; + + Future Detach(const DetachRequest& request); + + Future Detach(const Seg& shmseg = {}); + + struct PutImageRequest { + Drawable drawable{}; + GraphicsContext gc{}; + uint16_t total_width{}; + uint16_t total_height{}; + uint16_t src_x{}; + uint16_t src_y{}; + uint16_t src_width{}; + uint16_t src_height{}; + int16_t dst_x{}; + int16_t dst_y{}; + uint8_t depth{}; + ImageFormat format{}; + uint8_t send_event{}; + Seg shmseg{}; + uint32_t offset{}; + }; + + using PutImageResponse = Response; + + Future PutImage(const PutImageRequest& request); + + Future PutImage(const Drawable& drawable = {}, + const GraphicsContext& gc = {}, + const uint16_t& total_width = {}, + const uint16_t& total_height = {}, + const uint16_t& src_x = {}, + const uint16_t& src_y = {}, + const uint16_t& src_width = {}, + const uint16_t& src_height = {}, + const int16_t& dst_x = {}, + const int16_t& dst_y = {}, + const uint8_t& depth = {}, + const ImageFormat& format = {}, + const uint8_t& send_event = {}, + const Seg& shmseg = {}, + const uint32_t& offset = {}); + + struct GetImageRequest { + Drawable drawable{}; + int16_t x{}; + int16_t y{}; + uint16_t width{}; + uint16_t height{}; + uint32_t plane_mask{}; + uint8_t format{}; + Seg shmseg{}; + uint32_t offset{}; + }; + + struct GetImageReply { + uint8_t depth{}; + uint16_t sequence{}; + VisualId visual{}; + uint32_t size{}; + }; + + using GetImageResponse = Response; + + Future GetImage(const GetImageRequest& request); + + Future GetImage(const Drawable& drawable = {}, + const int16_t& x = {}, + const int16_t& y = {}, + const uint16_t& width = {}, + const uint16_t& height = {}, + const uint32_t& plane_mask = {}, + const uint8_t& format = {}, + const Seg& shmseg = {}, + const uint32_t& offset = {}); + + struct CreatePixmapRequest { + Pixmap pid{}; + Drawable drawable{}; + uint16_t width{}; + uint16_t height{}; + uint8_t depth{}; + Seg shmseg{}; + uint32_t offset{}; + }; + + using CreatePixmapResponse = Response; + + Future CreatePixmap(const CreatePixmapRequest& request); + + Future CreatePixmap(const Pixmap& pid = {}, + const Drawable& drawable = {}, + const uint16_t& width = {}, + const uint16_t& height = {}, + const uint8_t& depth = {}, + const Seg& shmseg = {}, + const uint32_t& offset = {}); + + struct AttachFdRequest { + Seg shmseg{}; + RefCountedFD shm_fd{}; + uint8_t read_only{}; + }; + + using AttachFdResponse = Response; + + Future AttachFd(const AttachFdRequest& request); + + Future AttachFd(const Seg& shmseg = {}, + const RefCountedFD& shm_fd = {}, + const uint8_t& read_only = {}); + + struct CreateSegmentRequest { + Seg shmseg{}; + uint32_t size{}; + uint8_t read_only{}; + }; + + struct CreateSegmentReply { + uint8_t nfd{}; + uint16_t sequence{}; + RefCountedFD shm_fd{}; + }; + + using CreateSegmentResponse = Response; + + Future CreateSegment(const CreateSegmentRequest& request); + + Future CreateSegment(const Seg& shmseg = {}, + const uint32_t& size = {}, + const uint8_t& read_only = {}); + + private: + Connection* const connection_; + x11::QueryExtensionReply info_{}; +}; + +} // namespace x11 + +#endif // UI_GFX_X_GENERATED_PROTOS_SHM_H_ diff --git a/x/generated_protos/sync.cc b/x/generated_protos/sync.cc new file mode 100644 index 000000000000..f175c29b22b1 --- /dev/null +++ b/x/generated_protos/sync.cc @@ -0,0 +1,1530 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#include "sync.h" + +#include +#include + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "ui/gfx/x/xproto_internal.h" + +namespace x11 { + +Sync::Sync(Connection* connection, const x11::QueryExtensionReply& info) + : connection_(connection), info_(info) {} + +std::string Sync::CounterError::ToString() const { + std::stringstream ss_; + ss_ << "Sync::CounterError{"; + ss_ << ".sequence = " << static_cast(sequence) << ", "; + ss_ << ".bad_counter = " << static_cast(bad_counter) << ", "; + ss_ << ".minor_opcode = " << static_cast(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast(major_opcode); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError(Sync::CounterError* error_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + auto& bad_counter = (*error_).bad_counter; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + // bad_counter + Read(&bad_counter, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + + DCHECK_LE(buf.offset, 32ul); +} +std::string Sync::AlarmError::ToString() const { + std::stringstream ss_; + ss_ << "Sync::AlarmError{"; + ss_ << ".sequence = " << static_cast(sequence) << ", "; + ss_ << ".bad_alarm = " << static_cast(bad_alarm) << ", "; + ss_ << ".minor_opcode = " << static_cast(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast(major_opcode); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError(Sync::AlarmError* error_, ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + auto& bad_alarm = (*error_).bad_alarm; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + // bad_alarm + Read(&bad_alarm, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + + DCHECK_LE(buf.offset, 32ul); +} +template <> +COMPONENT_EXPORT(X11) +void ReadEvent(Sync::CounterNotifyEvent* event_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& kind = (*event_).kind; + auto& sequence = (*event_).sequence; + auto& counter = (*event_).counter; + auto& wait_value = (*event_).wait_value; + auto& counter_value = (*event_).counter_value; + auto& timestamp = (*event_).timestamp; + auto& count = (*event_).count; + auto& destroyed = (*event_).destroyed; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // kind + Read(&kind, &buf); + + // sequence + Read(&sequence, &buf); + + // counter + Read(&counter, &buf); + + // wait_value + { + auto& hi = wait_value.hi; + auto& lo = wait_value.lo; + + // hi + Read(&hi, &buf); + + // lo + Read(&lo, &buf); + } + + // counter_value + { + auto& hi = counter_value.hi; + auto& lo = counter_value.lo; + + // hi + Read(&hi, &buf); + + // lo + Read(&lo, &buf); + } + + // timestamp + Read(×tamp, &buf); + + // count + Read(&count, &buf); + + // destroyed + Read(&destroyed, &buf); + + // pad0 + Pad(&buf, 1); + + DCHECK_LE(buf.offset, 32ul); +} + +template <> +COMPONENT_EXPORT(X11) +void ReadEvent(Sync::AlarmNotifyEvent* event_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& kind = (*event_).kind; + auto& sequence = (*event_).sequence; + auto& alarm = (*event_).alarm; + auto& counter_value = (*event_).counter_value; + auto& alarm_value = (*event_).alarm_value; + auto& timestamp = (*event_).timestamp; + auto& state = (*event_).state; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // kind + Read(&kind, &buf); + + // sequence + Read(&sequence, &buf); + + // alarm + Read(&alarm, &buf); + + // counter_value + { + auto& hi = counter_value.hi; + auto& lo = counter_value.lo; + + // hi + Read(&hi, &buf); + + // lo + Read(&lo, &buf); + } + + // alarm_value + { + auto& hi = alarm_value.hi; + auto& lo = alarm_value.lo; + + // hi + Read(&hi, &buf); + + // lo + Read(&lo, &buf); + } + + // timestamp + Read(×tamp, &buf); + + // state + uint8_t tmp0; + Read(&tmp0, &buf); + state = static_cast(tmp0); + + // pad0 + Pad(&buf, 3); + + DCHECK_LE(buf.offset, 32ul); +} + +Future Sync::Initialize( + const Sync::InitializeRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& desired_major_version = request.desired_major_version; + auto& desired_minor_version = request.desired_minor_version; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 0; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // desired_major_version + buf.Write(&desired_major_version); + + // desired_minor_version + buf.Write(&desired_minor_version); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Sync::Initialize", false); +} + +Future Sync::Initialize( + const uint8_t& desired_major_version, + const uint8_t& desired_minor_version) { + return Sync::Initialize( + Sync::InitializeRequest{desired_major_version, desired_minor_version}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& major_version = (*reply).major_version; + auto& minor_version = (*reply).minor_version; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // major_version + Read(&major_version, &buf); + + // minor_version + Read(&minor_version, &buf); + + // pad1 + Pad(&buf, 22); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Sync::ListSystemCounters( + const Sync::ListSystemCountersRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 1; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Sync::ListSystemCounters", false); +} + +Future Sync::ListSystemCounters() { + return Sync::ListSystemCounters(Sync::ListSystemCountersRequest{}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Sync::ListSystemCountersReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t counters_len{}; + auto& counters = (*reply).counters; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // counters_len + Read(&counters_len, &buf); + + // pad1 + Pad(&buf, 20); + + // counters + counters.resize(counters_len); + for (auto& counters_elem : counters) { + // counters_elem + { + auto& counter = counters_elem.counter; + auto& resolution = counters_elem.resolution; + uint16_t name_len{}; + auto& name = counters_elem.name; + + // counter + Read(&counter, &buf); + + // resolution + { + auto& hi = resolution.hi; + auto& lo = resolution.lo; + + // hi + Read(&hi, &buf); + + // lo + Read(&lo, &buf); + } + + // name_len + Read(&name_len, &buf); + + // name + name.resize(name_len); + for (auto& name_elem : name) { + // name_elem + Read(&name_elem, &buf); + } + + // pad0 + Align(&buf, 4); + } + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Sync::CreateCounter(const Sync::CreateCounterRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& id = request.id; + auto& initial_value = request.initial_value; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 2; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // id + buf.Write(&id); + + // initial_value + { + auto& hi = initial_value.hi; + auto& lo = initial_value.lo; + + // hi + buf.Write(&hi); + + // lo + buf.Write(&lo); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Sync::CreateCounter", false); +} + +Future Sync::CreateCounter(const Counter& id, + const Int64& initial_value) { + return Sync::CreateCounter(Sync::CreateCounterRequest{id, initial_value}); +} + +Future Sync::DestroyCounter(const Sync::DestroyCounterRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& counter = request.counter; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 6; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // counter + buf.Write(&counter); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Sync::DestroyCounter", false); +} + +Future Sync::DestroyCounter(const Counter& counter) { + return Sync::DestroyCounter(Sync::DestroyCounterRequest{counter}); +} + +Future Sync::QueryCounter( + const Sync::QueryCounterRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& counter = request.counter; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 5; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // counter + buf.Write(&counter); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Sync::QueryCounter", false); +} + +Future Sync::QueryCounter(const Counter& counter) { + return Sync::QueryCounter(Sync::QueryCounterRequest{counter}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Sync::QueryCounterReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& counter_value = (*reply).counter_value; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // counter_value + { + auto& hi = counter_value.hi; + auto& lo = counter_value.lo; + + // hi + Read(&hi, &buf); + + // lo + Read(&lo, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Sync::Await(const Sync::AwaitRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& wait_list = request.wait_list; + size_t wait_list_len = wait_list.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 7; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // wait_list + DCHECK_EQ(static_cast(wait_list_len), wait_list.size()); + for (auto& wait_list_elem : wait_list) { + // wait_list_elem + { + auto& trigger = wait_list_elem.trigger; + auto& event_threshold = wait_list_elem.event_threshold; + + // trigger + { + auto& counter = trigger.counter; + auto& wait_type = trigger.wait_type; + auto& wait_value = trigger.wait_value; + auto& test_type = trigger.test_type; + + // counter + buf.Write(&counter); + + // wait_type + uint32_t tmp1; + tmp1 = static_cast(wait_type); + buf.Write(&tmp1); + + // wait_value + { + auto& hi = wait_value.hi; + auto& lo = wait_value.lo; + + // hi + buf.Write(&hi); + + // lo + buf.Write(&lo); + } + + // test_type + uint32_t tmp2; + tmp2 = static_cast(test_type); + buf.Write(&tmp2); + } + + // event_threshold + { + auto& hi = event_threshold.hi; + auto& lo = event_threshold.lo; + + // hi + buf.Write(&hi); + + // lo + buf.Write(&lo); + } + } + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Sync::Await", false); +} + +Future Sync::Await(const std::vector& wait_list) { + return Sync::Await(Sync::AwaitRequest{wait_list}); +} + +Future Sync::ChangeCounter(const Sync::ChangeCounterRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& counter = request.counter; + auto& amount = request.amount; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 4; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // counter + buf.Write(&counter); + + // amount + { + auto& hi = amount.hi; + auto& lo = amount.lo; + + // hi + buf.Write(&hi); + + // lo + buf.Write(&lo); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Sync::ChangeCounter", false); +} + +Future Sync::ChangeCounter(const Counter& counter, const Int64& amount) { + return Sync::ChangeCounter(Sync::ChangeCounterRequest{counter, amount}); +} + +Future Sync::SetCounter(const Sync::SetCounterRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& counter = request.counter; + auto& value = request.value; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 3; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // counter + buf.Write(&counter); + + // value + { + auto& hi = value.hi; + auto& lo = value.lo; + + // hi + buf.Write(&hi); + + // lo + buf.Write(&lo); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Sync::SetCounter", false); +} + +Future Sync::SetCounter(const Counter& counter, const Int64& value) { + return Sync::SetCounter(Sync::SetCounterRequest{counter, value}); +} + +Future Sync::CreateAlarm(const Sync::CreateAlarmRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& id = request.id; + ChangeAlarmAttribute value_mask{}; + auto& value_list = request; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 8; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // id + buf.Write(&id); + + // value_mask + SwitchVar(ChangeAlarmAttribute::Counter, value_list.counter.has_value(), true, + &value_mask); + SwitchVar(ChangeAlarmAttribute::ValueType, value_list.valueType.has_value(), + true, &value_mask); + SwitchVar(ChangeAlarmAttribute::Value, value_list.value.has_value(), true, + &value_mask); + SwitchVar(ChangeAlarmAttribute::TestType, value_list.testType.has_value(), + true, &value_mask); + SwitchVar(ChangeAlarmAttribute::Delta, value_list.delta.has_value(), true, + &value_mask); + SwitchVar(ChangeAlarmAttribute::Events, value_list.events.has_value(), true, + &value_mask); + uint32_t tmp3; + tmp3 = static_cast(value_mask); + buf.Write(&tmp3); + + // value_list + auto value_list_expr = value_mask; + if (CaseAnd(value_list_expr, ChangeAlarmAttribute::Counter)) { + auto& counter = *value_list.counter; + + // counter + buf.Write(&counter); + } + if (CaseAnd(value_list_expr, ChangeAlarmAttribute::ValueType)) { + auto& valueType = *value_list.valueType; + + // valueType + uint32_t tmp4; + tmp4 = static_cast(valueType); + buf.Write(&tmp4); + } + if (CaseAnd(value_list_expr, ChangeAlarmAttribute::Value)) { + auto& value = *value_list.value; + + // value + { + auto& hi = value.hi; + auto& lo = value.lo; + + // hi + buf.Write(&hi); + + // lo + buf.Write(&lo); + } + } + if (CaseAnd(value_list_expr, ChangeAlarmAttribute::TestType)) { + auto& testType = *value_list.testType; + + // testType + uint32_t tmp5; + tmp5 = static_cast(testType); + buf.Write(&tmp5); + } + if (CaseAnd(value_list_expr, ChangeAlarmAttribute::Delta)) { + auto& delta = *value_list.delta; + + // delta + { + auto& hi = delta.hi; + auto& lo = delta.lo; + + // hi + buf.Write(&hi); + + // lo + buf.Write(&lo); + } + } + if (CaseAnd(value_list_expr, ChangeAlarmAttribute::Events)) { + auto& events = *value_list.events; + + // events + buf.Write(&events); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Sync::CreateAlarm", false); +} + +Future Sync::CreateAlarm(const Alarm& id, + const absl::optional& counter, + const absl::optional& valueType, + const absl::optional& value, + const absl::optional& testType, + const absl::optional& delta, + const absl::optional& events) { + return Sync::CreateAlarm(Sync::CreateAlarmRequest{ + id, counter, valueType, value, testType, delta, events}); +} + +Future Sync::ChangeAlarm(const Sync::ChangeAlarmRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& id = request.id; + ChangeAlarmAttribute value_mask{}; + auto& value_list = request; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 9; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // id + buf.Write(&id); + + // value_mask + SwitchVar(ChangeAlarmAttribute::Counter, value_list.counter.has_value(), true, + &value_mask); + SwitchVar(ChangeAlarmAttribute::ValueType, value_list.valueType.has_value(), + true, &value_mask); + SwitchVar(ChangeAlarmAttribute::Value, value_list.value.has_value(), true, + &value_mask); + SwitchVar(ChangeAlarmAttribute::TestType, value_list.testType.has_value(), + true, &value_mask); + SwitchVar(ChangeAlarmAttribute::Delta, value_list.delta.has_value(), true, + &value_mask); + SwitchVar(ChangeAlarmAttribute::Events, value_list.events.has_value(), true, + &value_mask); + uint32_t tmp6; + tmp6 = static_cast(value_mask); + buf.Write(&tmp6); + + // value_list + auto value_list_expr = value_mask; + if (CaseAnd(value_list_expr, ChangeAlarmAttribute::Counter)) { + auto& counter = *value_list.counter; + + // counter + buf.Write(&counter); + } + if (CaseAnd(value_list_expr, ChangeAlarmAttribute::ValueType)) { + auto& valueType = *value_list.valueType; + + // valueType + uint32_t tmp7; + tmp7 = static_cast(valueType); + buf.Write(&tmp7); + } + if (CaseAnd(value_list_expr, ChangeAlarmAttribute::Value)) { + auto& value = *value_list.value; + + // value + { + auto& hi = value.hi; + auto& lo = value.lo; + + // hi + buf.Write(&hi); + + // lo + buf.Write(&lo); + } + } + if (CaseAnd(value_list_expr, ChangeAlarmAttribute::TestType)) { + auto& testType = *value_list.testType; + + // testType + uint32_t tmp8; + tmp8 = static_cast(testType); + buf.Write(&tmp8); + } + if (CaseAnd(value_list_expr, ChangeAlarmAttribute::Delta)) { + auto& delta = *value_list.delta; + + // delta + { + auto& hi = delta.hi; + auto& lo = delta.lo; + + // hi + buf.Write(&hi); + + // lo + buf.Write(&lo); + } + } + if (CaseAnd(value_list_expr, ChangeAlarmAttribute::Events)) { + auto& events = *value_list.events; + + // events + buf.Write(&events); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Sync::ChangeAlarm", false); +} + +Future Sync::ChangeAlarm(const Alarm& id, + const absl::optional& counter, + const absl::optional& valueType, + const absl::optional& value, + const absl::optional& testType, + const absl::optional& delta, + const absl::optional& events) { + return Sync::ChangeAlarm(Sync::ChangeAlarmRequest{ + id, counter, valueType, value, testType, delta, events}); +} + +Future Sync::DestroyAlarm(const Sync::DestroyAlarmRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& alarm = request.alarm; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 11; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // alarm + buf.Write(&alarm); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Sync::DestroyAlarm", false); +} + +Future Sync::DestroyAlarm(const Alarm& alarm) { + return Sync::DestroyAlarm(Sync::DestroyAlarmRequest{alarm}); +} + +Future Sync::QueryAlarm( + const Sync::QueryAlarmRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& alarm = request.alarm; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 10; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // alarm + buf.Write(&alarm); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Sync::QueryAlarm", false); +} + +Future Sync::QueryAlarm(const Alarm& alarm) { + return Sync::QueryAlarm(Sync::QueryAlarmRequest{alarm}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& trigger = (*reply).trigger; + auto& delta = (*reply).delta; + auto& events = (*reply).events; + auto& state = (*reply).state; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // trigger + { + auto& counter = trigger.counter; + auto& wait_type = trigger.wait_type; + auto& wait_value = trigger.wait_value; + auto& test_type = trigger.test_type; + + // counter + Read(&counter, &buf); + + // wait_type + uint32_t tmp9; + Read(&tmp9, &buf); + wait_type = static_cast(tmp9); + + // wait_value + { + auto& hi = wait_value.hi; + auto& lo = wait_value.lo; + + // hi + Read(&hi, &buf); + + // lo + Read(&lo, &buf); + } + + // test_type + uint32_t tmp10; + Read(&tmp10, &buf); + test_type = static_cast(tmp10); + } + + // delta + { + auto& hi = delta.hi; + auto& lo = delta.lo; + + // hi + Read(&hi, &buf); + + // lo + Read(&lo, &buf); + } + + // events + Read(&events, &buf); + + // state + uint8_t tmp11; + Read(&tmp11, &buf); + state = static_cast(tmp11); + + // pad1 + Pad(&buf, 2); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Sync::SetPriority(const Sync::SetPriorityRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& id = request.id; + auto& priority = request.priority; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 12; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // id + buf.Write(&id); + + // priority + buf.Write(&priority); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Sync::SetPriority", false); +} + +Future Sync::SetPriority(const uint32_t& id, const int32_t& priority) { + return Sync::SetPriority(Sync::SetPriorityRequest{id, priority}); +} + +Future Sync::GetPriority( + const Sync::GetPriorityRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& id = request.id; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 13; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // id + buf.Write(&id); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Sync::GetPriority", false); +} + +Future Sync::GetPriority(const uint32_t& id) { + return Sync::GetPriority(Sync::GetPriorityRequest{id}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Sync::GetPriorityReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& priority = (*reply).priority; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // priority + Read(&priority, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Sync::CreateFence(const Sync::CreateFenceRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& drawable = request.drawable; + auto& fence = request.fence; + auto& initially_triggered = request.initially_triggered; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 14; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // drawable + buf.Write(&drawable); + + // fence + buf.Write(&fence); + + // initially_triggered + buf.Write(&initially_triggered); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Sync::CreateFence", false); +} + +Future Sync::CreateFence(const Drawable& drawable, + const Fence& fence, + const uint8_t& initially_triggered) { + return Sync::CreateFence( + Sync::CreateFenceRequest{drawable, fence, initially_triggered}); +} + +Future Sync::TriggerFence(const Sync::TriggerFenceRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& fence = request.fence; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 15; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // fence + buf.Write(&fence); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Sync::TriggerFence", false); +} + +Future Sync::TriggerFence(const Fence& fence) { + return Sync::TriggerFence(Sync::TriggerFenceRequest{fence}); +} + +Future Sync::ResetFence(const Sync::ResetFenceRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& fence = request.fence; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 16; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // fence + buf.Write(&fence); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Sync::ResetFence", false); +} + +Future Sync::ResetFence(const Fence& fence) { + return Sync::ResetFence(Sync::ResetFenceRequest{fence}); +} + +Future Sync::DestroyFence(const Sync::DestroyFenceRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& fence = request.fence; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 17; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // fence + buf.Write(&fence); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Sync::DestroyFence", false); +} + +Future Sync::DestroyFence(const Fence& fence) { + return Sync::DestroyFence(Sync::DestroyFenceRequest{fence}); +} + +Future Sync::QueryFence( + const Sync::QueryFenceRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& fence = request.fence; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 18; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // fence + buf.Write(&fence); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Sync::QueryFence", false); +} + +Future Sync::QueryFence(const Fence& fence) { + return Sync::QueryFence(Sync::QueryFenceRequest{fence}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& triggered = (*reply).triggered; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // triggered + Read(&triggered, &buf); + + // pad1 + Pad(&buf, 23); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Sync::AwaitFence(const Sync::AwaitFenceRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& fence_list = request.fence_list; + size_t fence_list_len = fence_list.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 19; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // fence_list + DCHECK_EQ(static_cast(fence_list_len), fence_list.size()); + for (auto& fence_list_elem : fence_list) { + // fence_list_elem + buf.Write(&fence_list_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Sync::AwaitFence", false); +} + +Future Sync::AwaitFence(const std::vector& fence_list) { + return Sync::AwaitFence(Sync::AwaitFenceRequest{fence_list}); +} + +} // namespace x11 diff --git a/x/generated_protos/sync.h b/x/generated_protos/sync.h new file mode 100644 index 000000000000..85ef3d14f527 --- /dev/null +++ b/x/generated_protos/sync.h @@ -0,0 +1,526 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#ifndef UI_GFX_X_GENERATED_PROTOS_SYNC_H_ +#define UI_GFX_X_GENERATED_PROTOS_SYNC_H_ + +#include +#include +#include +#include +#include + +#include "base/component_export.h" +#include "base/files/scoped_file.h" +#include "base/memory/ref_counted_memory.h" +#include "base/memory/scoped_refptr.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/gfx/x/error.h" +#include "ui/gfx/x/ref_counted_fd.h" +#include "xproto.h" + +namespace x11 { + +class Connection; + +template +struct Response; + +template +class Future; + +class COMPONENT_EXPORT(X11) Sync { + public: + static constexpr unsigned major_version = 3; + static constexpr unsigned minor_version = 1; + + Sync(Connection* connection, const x11::QueryExtensionReply& info); + + uint8_t present() const { return info_.present; } + uint8_t major_opcode() const { return info_.major_opcode; } + uint8_t first_event() const { return info_.first_event; } + uint8_t first_error() const { return info_.first_error; } + + Connection* connection() const { return connection_; } + + enum class Alarm : uint32_t {}; + + enum class Alarmstate : int { + Active = 0, + Inactive = 1, + Destroyed = 2, + }; + + enum class Counter : uint32_t {}; + + enum class Fence : uint32_t {}; + + enum class Testtype : int { + PositiveTransition = 0, + NegativeTransition = 1, + PositiveComparison = 2, + NegativeComparison = 3, + }; + + enum class Valuetype : int { + Absolute = 0, + Relative = 1, + }; + + enum class ChangeAlarmAttribute : int { + Counter = 1 << 0, + ValueType = 1 << 1, + Value = 1 << 2, + TestType = 1 << 3, + Delta = 1 << 4, + Events = 1 << 5, + }; + + struct Int64 { + int32_t hi{}; + uint32_t lo{}; + }; + + struct SystemCounter { + Counter counter{}; + Int64 resolution{}; + std::string name{}; + }; + + struct Trigger { + Counter counter{}; + Valuetype wait_type{}; + Int64 wait_value{}; + Testtype test_type{}; + }; + + struct WaitCondition { + Trigger trigger{}; + Int64 event_threshold{}; + }; + + struct CounterError : public x11::Error { + uint16_t sequence{}; + uint32_t bad_counter{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; + + std::string ToString() const override; + }; + + struct AlarmError : public x11::Error { + uint16_t sequence{}; + uint32_t bad_alarm{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; + + std::string ToString() const override; + }; + + struct CounterNotifyEvent { + static constexpr int type_id = 16; + static constexpr uint8_t opcode = 0; + bool send_event{}; + uint8_t kind{}; + uint16_t sequence{}; + Counter counter{}; + Int64 wait_value{}; + Int64 counter_value{}; + Time timestamp{}; + uint16_t count{}; + uint8_t destroyed{}; + + x11::Window* GetWindow() { return nullptr; } + }; + + struct AlarmNotifyEvent { + static constexpr int type_id = 17; + static constexpr uint8_t opcode = 1; + bool send_event{}; + uint8_t kind{}; + uint16_t sequence{}; + Alarm alarm{}; + Int64 counter_value{}; + Int64 alarm_value{}; + Time timestamp{}; + Alarmstate state{}; + + x11::Window* GetWindow() { return nullptr; } + }; + + struct InitializeRequest { + uint8_t desired_major_version{}; + uint8_t desired_minor_version{}; + }; + + struct InitializeReply { + uint16_t sequence{}; + uint8_t major_version{}; + uint8_t minor_version{}; + }; + + using InitializeResponse = Response; + + Future Initialize(const InitializeRequest& request); + + Future Initialize(const uint8_t& desired_major_version = {}, + const uint8_t& desired_minor_version = {}); + + struct ListSystemCountersRequest {}; + + struct ListSystemCountersReply { + uint16_t sequence{}; + std::vector counters{}; + }; + + using ListSystemCountersResponse = Response; + + Future ListSystemCounters( + const ListSystemCountersRequest& request); + + Future ListSystemCounters(); + + struct CreateCounterRequest { + Counter id{}; + Int64 initial_value{}; + }; + + using CreateCounterResponse = Response; + + Future CreateCounter(const CreateCounterRequest& request); + + Future CreateCounter(const Counter& id = {}, + const Int64& initial_value = {{}, {}}); + + struct DestroyCounterRequest { + Counter counter{}; + }; + + using DestroyCounterResponse = Response; + + Future DestroyCounter(const DestroyCounterRequest& request); + + Future DestroyCounter(const Counter& counter = {}); + + struct QueryCounterRequest { + Counter counter{}; + }; + + struct QueryCounterReply { + uint16_t sequence{}; + Int64 counter_value{}; + }; + + using QueryCounterResponse = Response; + + Future QueryCounter(const QueryCounterRequest& request); + + Future QueryCounter(const Counter& counter = {}); + + struct AwaitRequest { + std::vector wait_list{}; + }; + + using AwaitResponse = Response; + + Future Await(const AwaitRequest& request); + + Future Await(const std::vector& wait_list = {}); + + struct ChangeCounterRequest { + Counter counter{}; + Int64 amount{}; + }; + + using ChangeCounterResponse = Response; + + Future ChangeCounter(const ChangeCounterRequest& request); + + Future ChangeCounter(const Counter& counter = {}, + const Int64& amount = {{}, {}}); + + struct SetCounterRequest { + Counter counter{}; + Int64 value{}; + }; + + using SetCounterResponse = Response; + + Future SetCounter(const SetCounterRequest& request); + + Future SetCounter(const Counter& counter = {}, + const Int64& value = {{}, {}}); + + struct CreateAlarmRequest { + Alarm id{}; + absl::optional counter{}; + absl::optional valueType{}; + absl::optional value{}; + absl::optional testType{}; + absl::optional delta{}; + absl::optional events{}; + }; + + using CreateAlarmResponse = Response; + + Future CreateAlarm(const CreateAlarmRequest& request); + + Future CreateAlarm( + const Alarm& id = {}, + const absl::optional& counter = absl::nullopt, + const absl::optional& valueType = absl::nullopt, + const absl::optional& value = absl::nullopt, + const absl::optional& testType = absl::nullopt, + const absl::optional& delta = absl::nullopt, + const absl::optional& events = absl::nullopt); + + struct ChangeAlarmRequest { + Alarm id{}; + absl::optional counter{}; + absl::optional valueType{}; + absl::optional value{}; + absl::optional testType{}; + absl::optional delta{}; + absl::optional events{}; + }; + + using ChangeAlarmResponse = Response; + + Future ChangeAlarm(const ChangeAlarmRequest& request); + + Future ChangeAlarm( + const Alarm& id = {}, + const absl::optional& counter = absl::nullopt, + const absl::optional& valueType = absl::nullopt, + const absl::optional& value = absl::nullopt, + const absl::optional& testType = absl::nullopt, + const absl::optional& delta = absl::nullopt, + const absl::optional& events = absl::nullopt); + + struct DestroyAlarmRequest { + Alarm alarm{}; + }; + + using DestroyAlarmResponse = Response; + + Future DestroyAlarm(const DestroyAlarmRequest& request); + + Future DestroyAlarm(const Alarm& alarm = {}); + + struct QueryAlarmRequest { + Alarm alarm{}; + }; + + struct QueryAlarmReply { + uint16_t sequence{}; + Trigger trigger{}; + Int64 delta{}; + uint8_t events{}; + Alarmstate state{}; + }; + + using QueryAlarmResponse = Response; + + Future QueryAlarm(const QueryAlarmRequest& request); + + Future QueryAlarm(const Alarm& alarm = {}); + + struct SetPriorityRequest { + uint32_t id{}; + int32_t priority{}; + }; + + using SetPriorityResponse = Response; + + Future SetPriority(const SetPriorityRequest& request); + + Future SetPriority(const uint32_t& id = {}, + const int32_t& priority = {}); + + struct GetPriorityRequest { + uint32_t id{}; + }; + + struct GetPriorityReply { + uint16_t sequence{}; + int32_t priority{}; + }; + + using GetPriorityResponse = Response; + + Future GetPriority(const GetPriorityRequest& request); + + Future GetPriority(const uint32_t& id = {}); + + struct CreateFenceRequest { + Drawable drawable{}; + Fence fence{}; + uint8_t initially_triggered{}; + }; + + using CreateFenceResponse = Response; + + Future CreateFence(const CreateFenceRequest& request); + + Future CreateFence(const Drawable& drawable = {}, + const Fence& fence = {}, + const uint8_t& initially_triggered = {}); + + struct TriggerFenceRequest { + Fence fence{}; + }; + + using TriggerFenceResponse = Response; + + Future TriggerFence(const TriggerFenceRequest& request); + + Future TriggerFence(const Fence& fence = {}); + + struct ResetFenceRequest { + Fence fence{}; + }; + + using ResetFenceResponse = Response; + + Future ResetFence(const ResetFenceRequest& request); + + Future ResetFence(const Fence& fence = {}); + + struct DestroyFenceRequest { + Fence fence{}; + }; + + using DestroyFenceResponse = Response; + + Future DestroyFence(const DestroyFenceRequest& request); + + Future DestroyFence(const Fence& fence = {}); + + struct QueryFenceRequest { + Fence fence{}; + }; + + struct QueryFenceReply { + uint16_t sequence{}; + uint8_t triggered{}; + }; + + using QueryFenceResponse = Response; + + Future QueryFence(const QueryFenceRequest& request); + + Future QueryFence(const Fence& fence = {}); + + struct AwaitFenceRequest { + std::vector fence_list{}; + }; + + using AwaitFenceResponse = Response; + + Future AwaitFence(const AwaitFenceRequest& request); + + Future AwaitFence(const std::vector& fence_list = {}); + + private: + Connection* const connection_; + x11::QueryExtensionReply info_{}; +}; + +} // namespace x11 + +inline constexpr x11::Sync::Alarmstate operator|(x11::Sync::Alarmstate l, + x11::Sync::Alarmstate r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::Sync::Alarmstate operator&(x11::Sync::Alarmstate l, + x11::Sync::Alarmstate r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +inline constexpr x11::Sync::Testtype operator|(x11::Sync::Testtype l, + x11::Sync::Testtype r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::Sync::Testtype operator&(x11::Sync::Testtype l, + x11::Sync::Testtype r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +inline constexpr x11::Sync::Valuetype operator|(x11::Sync::Valuetype l, + x11::Sync::Valuetype r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::Sync::Valuetype operator&(x11::Sync::Valuetype l, + x11::Sync::Valuetype r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +inline constexpr x11::Sync::ChangeAlarmAttribute operator|( + x11::Sync::ChangeAlarmAttribute l, + x11::Sync::ChangeAlarmAttribute r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::Sync::ChangeAlarmAttribute operator&( + x11::Sync::ChangeAlarmAttribute l, + x11::Sync::ChangeAlarmAttribute r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +#endif // UI_GFX_X_GENERATED_PROTOS_SYNC_H_ diff --git a/x/generated_protos/xc_misc.cc b/x/generated_protos/xc_misc.cc new file mode 100644 index 000000000000..e75c0a82b990 --- /dev/null +++ b/x/generated_protos/xc_misc.cc @@ -0,0 +1,277 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#include "xc_misc.h" + +#include +#include + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "ui/gfx/x/xproto_internal.h" + +namespace x11 { + +XCMisc::XCMisc(Connection* connection, const x11::QueryExtensionReply& info) + : connection_(connection), info_(info) {} + +Future XCMisc::GetVersion( + const XCMisc::GetVersionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& client_major_version = request.client_major_version; + auto& client_minor_version = request.client_minor_version; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 0; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // client_major_version + buf.Write(&client_major_version); + + // client_minor_version + buf.Write(&client_minor_version); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "XCMisc::GetVersion", false); +} + +Future XCMisc::GetVersion( + const uint16_t& client_major_version, + const uint16_t& client_minor_version) { + return XCMisc::GetVersion( + XCMisc::GetVersionRequest{client_major_version, client_minor_version}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + XCMisc::GetVersionReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& server_major_version = (*reply).server_major_version; + auto& server_minor_version = (*reply).server_minor_version; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // server_major_version + Read(&server_major_version, &buf); + + // server_minor_version + Read(&server_minor_version, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future XCMisc::GetXIDRange( + const XCMisc::GetXIDRangeRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 1; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "XCMisc::GetXIDRange", false); +} + +Future XCMisc::GetXIDRange() { + return XCMisc::GetXIDRange(XCMisc::GetXIDRangeRequest{}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + XCMisc::GetXIDRangeReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& start_id = (*reply).start_id; + auto& count = (*reply).count; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // start_id + Read(&start_id, &buf); + + // count + Read(&count, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future XCMisc::GetXIDList( + const XCMisc::GetXIDListRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& count = request.count; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 2; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // count + buf.Write(&count); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "XCMisc::GetXIDList", false); +} + +Future XCMisc::GetXIDList(const uint32_t& count) { + return XCMisc::GetXIDList(XCMisc::GetXIDListRequest{count}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + XCMisc::GetXIDListReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t ids_len{}; + auto& ids = (*reply).ids; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // ids_len + Read(&ids_len, &buf); + + // pad1 + Pad(&buf, 20); + + // ids + ids.resize(ids_len); + for (auto& ids_elem : ids) { + // ids_elem + Read(&ids_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +} // namespace x11 diff --git a/x/generated_protos/xc_misc.h b/x/generated_protos/xc_misc.h new file mode 100644 index 000000000000..ea890feb62a3 --- /dev/null +++ b/x/generated_protos/xc_misc.h @@ -0,0 +1,137 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#ifndef UI_GFX_X_GENERATED_PROTOS_XC_MISC_H_ +#define UI_GFX_X_GENERATED_PROTOS_XC_MISC_H_ + +#include +#include +#include +#include +#include + +#include "base/component_export.h" +#include "base/files/scoped_file.h" +#include "base/memory/ref_counted_memory.h" +#include "base/memory/scoped_refptr.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/gfx/x/error.h" +#include "ui/gfx/x/ref_counted_fd.h" +#include "xproto.h" + +namespace x11 { + +class Connection; + +template +struct Response; + +template +class Future; + +class COMPONENT_EXPORT(X11) XCMisc { + public: + static constexpr unsigned major_version = 1; + static constexpr unsigned minor_version = 1; + + XCMisc(Connection* connection, const x11::QueryExtensionReply& info); + + uint8_t present() const { return info_.present; } + uint8_t major_opcode() const { return info_.major_opcode; } + uint8_t first_event() const { return info_.first_event; } + uint8_t first_error() const { return info_.first_error; } + + Connection* connection() const { return connection_; } + + struct GetVersionRequest { + uint16_t client_major_version{}; + uint16_t client_minor_version{}; + }; + + struct GetVersionReply { + uint16_t sequence{}; + uint16_t server_major_version{}; + uint16_t server_minor_version{}; + }; + + using GetVersionResponse = Response; + + Future GetVersion(const GetVersionRequest& request); + + Future GetVersion(const uint16_t& client_major_version = {}, + const uint16_t& client_minor_version = {}); + + struct GetXIDRangeRequest {}; + + struct GetXIDRangeReply { + uint16_t sequence{}; + uint32_t start_id{}; + uint32_t count{}; + }; + + using GetXIDRangeResponse = Response; + + Future GetXIDRange(const GetXIDRangeRequest& request); + + Future GetXIDRange(); + + struct GetXIDListRequest { + uint32_t count{}; + }; + + struct GetXIDListReply { + uint16_t sequence{}; + std::vector ids{}; + }; + + using GetXIDListResponse = Response; + + Future GetXIDList(const GetXIDListRequest& request); + + Future GetXIDList(const uint32_t& count = {}); + + private: + Connection* const connection_; + x11::QueryExtensionReply info_{}; +}; + +} // namespace x11 + +#endif // UI_GFX_X_GENERATED_PROTOS_XC_MISC_H_ diff --git a/x/generated_protos/xevie.cc b/x/generated_protos/xevie.cc new file mode 100644 index 000000000000..8eeb44dc8ad1 --- /dev/null +++ b/x/generated_protos/xevie.cc @@ -0,0 +1,406 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#include "xevie.h" + +#include +#include + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "ui/gfx/x/xproto_internal.h" + +namespace x11 { + +Xevie::Xevie(Connection* connection, const x11::QueryExtensionReply& info) + : connection_(connection), info_(info) {} + +Future Xevie::QueryVersion( + const Xevie::QueryVersionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& client_major_version = request.client_major_version; + auto& client_minor_version = request.client_minor_version; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 0; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // client_major_version + buf.Write(&client_major_version); + + // client_minor_version + buf.Write(&client_minor_version); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Xevie::QueryVersion", false); +} + +Future Xevie::QueryVersion( + const uint16_t& client_major_version, + const uint16_t& client_minor_version) { + return Xevie::QueryVersion( + Xevie::QueryVersionRequest{client_major_version, client_minor_version}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Xevie::QueryVersionReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& server_major_version = (*reply).server_major_version; + auto& server_minor_version = (*reply).server_minor_version; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // server_major_version + Read(&server_major_version, &buf); + + // server_minor_version + Read(&server_minor_version, &buf); + + // pad1 + Pad(&buf, 20); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Xevie::Start(const Xevie::StartRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& screen = request.screen; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 1; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // screen + buf.Write(&screen); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Xevie::Start", + false); +} + +Future Xevie::Start(const uint32_t& screen) { + return Xevie::Start(Xevie::StartRequest{screen}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 24); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Xevie::End(const Xevie::EndRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& cmap = request.cmap; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 2; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // cmap + buf.Write(&cmap); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Xevie::End", false); +} + +Future Xevie::End(const uint32_t& cmap) { + return Xevie::End(Xevie::EndRequest{cmap}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 24); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Xevie::Send(const Xevie::SendRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& event = request.event; + auto& data_type = request.data_type; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 3; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // event + { + // pad0 + Pad(&buf, 32); + } + + // data_type + buf.Write(&data_type); + + // pad0 + Pad(&buf, 64); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Xevie::Send", false); +} + +Future Xevie::Send(const Event& event, + const uint32_t& data_type) { + return Xevie::Send(Xevie::SendRequest{event, data_type}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply( + ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 24); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Xevie::SelectInput( + const Xevie::SelectInputRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& event_mask = request.event_mask; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 4; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // event_mask + buf.Write(&event_mask); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Xevie::SelectInput", false); +} + +Future Xevie::SelectInput(const uint32_t& event_mask) { + return Xevie::SelectInput(Xevie::SelectInputRequest{event_mask}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Xevie::SelectInputReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // pad1 + Pad(&buf, 24); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +} // namespace x11 diff --git a/x/generated_protos/xevie.h b/x/generated_protos/xevie.h new file mode 100644 index 000000000000..be6723c69aa4 --- /dev/null +++ b/x/generated_protos/xevie.h @@ -0,0 +1,188 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#ifndef UI_GFX_X_GENERATED_PROTOS_XEVIE_H_ +#define UI_GFX_X_GENERATED_PROTOS_XEVIE_H_ + +#include +#include +#include +#include +#include + +#include "base/component_export.h" +#include "base/files/scoped_file.h" +#include "base/memory/ref_counted_memory.h" +#include "base/memory/scoped_refptr.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/gfx/x/error.h" +#include "ui/gfx/x/ref_counted_fd.h" +#include "xproto.h" + +namespace x11 { + +class Connection; + +template +struct Response; + +template +class Future; + +class COMPONENT_EXPORT(X11) Xevie { + public: + static constexpr unsigned major_version = 1; + static constexpr unsigned minor_version = 0; + + Xevie(Connection* connection, const x11::QueryExtensionReply& info); + + uint8_t present() const { return info_.present; } + uint8_t major_opcode() const { return info_.major_opcode; } + uint8_t first_event() const { return info_.first_event; } + uint8_t first_error() const { return info_.first_error; } + + Connection* connection() const { return connection_; } + + enum class Datatype : int { + Unmodified = 0, + Modified = 1, + }; + + struct Event {}; + + struct QueryVersionRequest { + uint16_t client_major_version{}; + uint16_t client_minor_version{}; + }; + + struct QueryVersionReply { + uint16_t sequence{}; + uint16_t server_major_version{}; + uint16_t server_minor_version{}; + }; + + using QueryVersionResponse = Response; + + Future QueryVersion(const QueryVersionRequest& request); + + Future QueryVersion( + const uint16_t& client_major_version = {}, + const uint16_t& client_minor_version = {}); + + struct StartRequest { + uint32_t screen{}; + }; + + struct StartReply { + uint16_t sequence{}; + }; + + using StartResponse = Response; + + Future Start(const StartRequest& request); + + Future Start(const uint32_t& screen = {}); + + struct EndRequest { + uint32_t cmap{}; + }; + + struct EndReply { + uint16_t sequence{}; + }; + + using EndResponse = Response; + + Future End(const EndRequest& request); + + Future End(const uint32_t& cmap = {}); + + struct SendRequest { + Event event{}; + uint32_t data_type{}; + }; + + struct SendReply { + uint16_t sequence{}; + }; + + using SendResponse = Response; + + Future Send(const SendRequest& request); + + Future Send(const Event& event = {}, + const uint32_t& data_type = {}); + + struct SelectInputRequest { + uint32_t event_mask{}; + }; + + struct SelectInputReply { + uint16_t sequence{}; + }; + + using SelectInputResponse = Response; + + Future SelectInput(const SelectInputRequest& request); + + Future SelectInput(const uint32_t& event_mask = {}); + + private: + Connection* const connection_; + x11::QueryExtensionReply info_{}; +}; + +} // namespace x11 + +inline constexpr x11::Xevie::Datatype operator|(x11::Xevie::Datatype l, + x11::Xevie::Datatype r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::Xevie::Datatype operator&(x11::Xevie::Datatype l, + x11::Xevie::Datatype r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +#endif // UI_GFX_X_GENERATED_PROTOS_XEVIE_H_ diff --git a/x/generated_protos/xf86dri.cc b/x/generated_protos/xf86dri.cc new file mode 100644 index 000000000000..4acebbeb22d5 --- /dev/null +++ b/x/generated_protos/xf86dri.cc @@ -0,0 +1,972 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#include "xf86dri.h" + +#include +#include + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "ui/gfx/x/xproto_internal.h" + +namespace x11 { + +XF86Dri::XF86Dri(Connection* connection, const x11::QueryExtensionReply& info) + : connection_(connection), info_(info) {} + +Future XF86Dri::QueryVersion( + const XF86Dri::QueryVersionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 0; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "XF86Dri::QueryVersion", false); +} + +Future XF86Dri::QueryVersion() { + return XF86Dri::QueryVersion(XF86Dri::QueryVersionRequest{}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + XF86Dri::QueryVersionReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& dri_major_version = (*reply).dri_major_version; + auto& dri_minor_version = (*reply).dri_minor_version; + auto& dri_minor_patch = (*reply).dri_minor_patch; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // dri_major_version + Read(&dri_major_version, &buf); + + // dri_minor_version + Read(&dri_minor_version, &buf); + + // dri_minor_patch + Read(&dri_minor_patch, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future +XF86Dri::QueryDirectRenderingCapable( + const XF86Dri::QueryDirectRenderingCapableRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& screen = request.screen; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 1; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // screen + buf.Write(&screen); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "XF86Dri::QueryDirectRenderingCapable", false); +} + +Future +XF86Dri::QueryDirectRenderingCapable(const uint32_t& screen) { + return XF86Dri::QueryDirectRenderingCapable( + XF86Dri::QueryDirectRenderingCapableRequest{screen}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + XF86Dri::QueryDirectRenderingCapableReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& is_capable = (*reply).is_capable; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // is_capable + Read(&is_capable, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future XF86Dri::OpenConnection( + const XF86Dri::OpenConnectionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& screen = request.screen; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 2; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // screen + buf.Write(&screen); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "XF86Dri::OpenConnection", false); +} + +Future XF86Dri::OpenConnection( + const uint32_t& screen) { + return XF86Dri::OpenConnection(XF86Dri::OpenConnectionRequest{screen}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + XF86Dri::OpenConnectionReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& sarea_handle_low = (*reply).sarea_handle_low; + auto& sarea_handle_high = (*reply).sarea_handle_high; + uint32_t bus_id_len{}; + auto& bus_id = (*reply).bus_id; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // sarea_handle_low + Read(&sarea_handle_low, &buf); + + // sarea_handle_high + Read(&sarea_handle_high, &buf); + + // bus_id_len + Read(&bus_id_len, &buf); + + // pad1 + Pad(&buf, 12); + + // bus_id + bus_id.resize(bus_id_len); + for (auto& bus_id_elem : bus_id) { + // bus_id_elem + Read(&bus_id_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future XF86Dri::CloseConnection( + const XF86Dri::CloseConnectionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& screen = request.screen; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 3; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // screen + buf.Write(&screen); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "XF86Dri::CloseConnection", + false); +} + +Future XF86Dri::CloseConnection(const uint32_t& screen) { + return XF86Dri::CloseConnection(XF86Dri::CloseConnectionRequest{screen}); +} + +Future XF86Dri::GetClientDriverName( + const XF86Dri::GetClientDriverNameRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& screen = request.screen; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 4; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // screen + buf.Write(&screen); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "XF86Dri::GetClientDriverName", false); +} + +Future XF86Dri::GetClientDriverName( + const uint32_t& screen) { + return XF86Dri::GetClientDriverName( + XF86Dri::GetClientDriverNameRequest{screen}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + XF86Dri::GetClientDriverNameReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& client_driver_major_version = (*reply).client_driver_major_version; + auto& client_driver_minor_version = (*reply).client_driver_minor_version; + auto& client_driver_patch_version = (*reply).client_driver_patch_version; + uint32_t client_driver_name_len{}; + auto& client_driver_name = (*reply).client_driver_name; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // client_driver_major_version + Read(&client_driver_major_version, &buf); + + // client_driver_minor_version + Read(&client_driver_minor_version, &buf); + + // client_driver_patch_version + Read(&client_driver_patch_version, &buf); + + // client_driver_name_len + Read(&client_driver_name_len, &buf); + + // pad1 + Pad(&buf, 8); + + // client_driver_name + client_driver_name.resize(client_driver_name_len); + for (auto& client_driver_name_elem : client_driver_name) { + // client_driver_name_elem + Read(&client_driver_name_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future XF86Dri::CreateContext( + const XF86Dri::CreateContextRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& screen = request.screen; + auto& visual = request.visual; + auto& context = request.context; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 5; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // screen + buf.Write(&screen); + + // visual + buf.Write(&visual); + + // context + buf.Write(&context); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "XF86Dri::CreateContext", false); +} + +Future XF86Dri::CreateContext( + const uint32_t& screen, + const uint32_t& visual, + const uint32_t& context) { + return XF86Dri::CreateContext( + XF86Dri::CreateContextRequest{screen, visual, context}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + XF86Dri::CreateContextReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& hw_context = (*reply).hw_context; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // hw_context + Read(&hw_context, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future XF86Dri::DestroyContext( + const XF86Dri::DestroyContextRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& screen = request.screen; + auto& context = request.context; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 6; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // screen + buf.Write(&screen); + + // context + buf.Write(&context); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "XF86Dri::DestroyContext", false); +} + +Future XF86Dri::DestroyContext(const uint32_t& screen, + const uint32_t& context) { + return XF86Dri::DestroyContext( + XF86Dri::DestroyContextRequest{screen, context}); +} + +Future XF86Dri::CreateDrawable( + const XF86Dri::CreateDrawableRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& screen = request.screen; + auto& drawable = request.drawable; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 7; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // screen + buf.Write(&screen); + + // drawable + buf.Write(&drawable); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "XF86Dri::CreateDrawable", false); +} + +Future XF86Dri::CreateDrawable( + const uint32_t& screen, + const uint32_t& drawable) { + return XF86Dri::CreateDrawable( + XF86Dri::CreateDrawableRequest{screen, drawable}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + XF86Dri::CreateDrawableReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& hw_drawable_handle = (*reply).hw_drawable_handle; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // hw_drawable_handle + Read(&hw_drawable_handle, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future XF86Dri::DestroyDrawable( + const XF86Dri::DestroyDrawableRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& screen = request.screen; + auto& drawable = request.drawable; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 8; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // screen + buf.Write(&screen); + + // drawable + buf.Write(&drawable); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "XF86Dri::DestroyDrawable", + false); +} + +Future XF86Dri::DestroyDrawable(const uint32_t& screen, + const uint32_t& drawable) { + return XF86Dri::DestroyDrawable( + XF86Dri::DestroyDrawableRequest{screen, drawable}); +} + +Future XF86Dri::GetDrawableInfo( + const XF86Dri::GetDrawableInfoRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& screen = request.screen; + auto& drawable = request.drawable; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 9; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // screen + buf.Write(&screen); + + // drawable + buf.Write(&drawable); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "XF86Dri::GetDrawableInfo", false); +} + +Future XF86Dri::GetDrawableInfo( + const uint32_t& screen, + const uint32_t& drawable) { + return XF86Dri::GetDrawableInfo( + XF86Dri::GetDrawableInfoRequest{screen, drawable}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + XF86Dri::GetDrawableInfoReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& drawable_table_index = (*reply).drawable_table_index; + auto& drawable_table_stamp = (*reply).drawable_table_stamp; + auto& drawable_origin_X = (*reply).drawable_origin_X; + auto& drawable_origin_Y = (*reply).drawable_origin_Y; + auto& drawable_size_W = (*reply).drawable_size_W; + auto& drawable_size_H = (*reply).drawable_size_H; + uint32_t num_clip_rects{}; + auto& back_x = (*reply).back_x; + auto& back_y = (*reply).back_y; + uint32_t num_back_clip_rects{}; + auto& clip_rects = (*reply).clip_rects; + size_t clip_rects_len = clip_rects.size(); + auto& back_clip_rects = (*reply).back_clip_rects; + size_t back_clip_rects_len = back_clip_rects.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // drawable_table_index + Read(&drawable_table_index, &buf); + + // drawable_table_stamp + Read(&drawable_table_stamp, &buf); + + // drawable_origin_X + Read(&drawable_origin_X, &buf); + + // drawable_origin_Y + Read(&drawable_origin_Y, &buf); + + // drawable_size_W + Read(&drawable_size_W, &buf); + + // drawable_size_H + Read(&drawable_size_H, &buf); + + // num_clip_rects + Read(&num_clip_rects, &buf); + + // back_x + Read(&back_x, &buf); + + // back_y + Read(&back_y, &buf); + + // num_back_clip_rects + Read(&num_back_clip_rects, &buf); + + // clip_rects + clip_rects.resize(num_clip_rects); + for (auto& clip_rects_elem : clip_rects) { + // clip_rects_elem + { + auto& x1 = clip_rects_elem.x1; + auto& y1 = clip_rects_elem.y1; + auto& x2 = clip_rects_elem.x2; + auto& x3 = clip_rects_elem.x3; + + // x1 + Read(&x1, &buf); + + // y1 + Read(&y1, &buf); + + // x2 + Read(&x2, &buf); + + // x3 + Read(&x3, &buf); + } + } + + // back_clip_rects + back_clip_rects.resize(num_back_clip_rects); + for (auto& back_clip_rects_elem : back_clip_rects) { + // back_clip_rects_elem + { + auto& x1 = back_clip_rects_elem.x1; + auto& y1 = back_clip_rects_elem.y1; + auto& x2 = back_clip_rects_elem.x2; + auto& x3 = back_clip_rects_elem.x3; + + // x1 + Read(&x1, &buf); + + // y1 + Read(&y1, &buf); + + // x2 + Read(&x2, &buf); + + // x3 + Read(&x3, &buf); + } + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future XF86Dri::GetDeviceInfo( + const XF86Dri::GetDeviceInfoRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& screen = request.screen; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 10; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // screen + buf.Write(&screen); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "XF86Dri::GetDeviceInfo", false); +} + +Future XF86Dri::GetDeviceInfo( + const uint32_t& screen) { + return XF86Dri::GetDeviceInfo(XF86Dri::GetDeviceInfoRequest{screen}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + XF86Dri::GetDeviceInfoReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& framebuffer_handle_low = (*reply).framebuffer_handle_low; + auto& framebuffer_handle_high = (*reply).framebuffer_handle_high; + auto& framebuffer_origin_offset = (*reply).framebuffer_origin_offset; + auto& framebuffer_size = (*reply).framebuffer_size; + auto& framebuffer_stride = (*reply).framebuffer_stride; + uint32_t device_private_size{}; + auto& device_private = (*reply).device_private; + size_t device_private_len = device_private.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // framebuffer_handle_low + Read(&framebuffer_handle_low, &buf); + + // framebuffer_handle_high + Read(&framebuffer_handle_high, &buf); + + // framebuffer_origin_offset + Read(&framebuffer_origin_offset, &buf); + + // framebuffer_size + Read(&framebuffer_size, &buf); + + // framebuffer_stride + Read(&framebuffer_stride, &buf); + + // device_private_size + Read(&device_private_size, &buf); + + // device_private + device_private.resize(device_private_size); + for (auto& device_private_elem : device_private) { + // device_private_elem + Read(&device_private_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future XF86Dri::AuthConnection( + const XF86Dri::AuthConnectionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& screen = request.screen; + auto& magic = request.magic; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 11; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // screen + buf.Write(&screen); + + // magic + buf.Write(&magic); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "XF86Dri::AuthConnection", false); +} + +Future XF86Dri::AuthConnection( + const uint32_t& screen, + const uint32_t& magic) { + return XF86Dri::AuthConnection(XF86Dri::AuthConnectionRequest{screen, magic}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + XF86Dri::AuthConnectionReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& authenticated = (*reply).authenticated; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // authenticated + Read(&authenticated, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +} // namespace x11 diff --git a/x/generated_protos/xf86dri.h b/x/generated_protos/xf86dri.h new file mode 100644 index 000000000000..42cbd474f513 --- /dev/null +++ b/x/generated_protos/xf86dri.h @@ -0,0 +1,304 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#ifndef UI_GFX_X_GENERATED_PROTOS_XF86DRI_H_ +#define UI_GFX_X_GENERATED_PROTOS_XF86DRI_H_ + +#include +#include +#include +#include +#include + +#include "base/component_export.h" +#include "base/files/scoped_file.h" +#include "base/memory/ref_counted_memory.h" +#include "base/memory/scoped_refptr.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/gfx/x/error.h" +#include "ui/gfx/x/ref_counted_fd.h" +#include "xproto.h" + +namespace x11 { + +class Connection; + +template +struct Response; + +template +class Future; + +class COMPONENT_EXPORT(X11) XF86Dri { + public: + static constexpr unsigned major_version = 4; + static constexpr unsigned minor_version = 1; + + XF86Dri(Connection* connection, const x11::QueryExtensionReply& info); + + uint8_t present() const { return info_.present; } + uint8_t major_opcode() const { return info_.major_opcode; } + uint8_t first_event() const { return info_.first_event; } + uint8_t first_error() const { return info_.first_error; } + + Connection* connection() const { return connection_; } + + struct DrmClipRect { + int16_t x1{}; + int16_t y1{}; + int16_t x2{}; + int16_t x3{}; + }; + + struct QueryVersionRequest {}; + + struct QueryVersionReply { + uint16_t sequence{}; + uint16_t dri_major_version{}; + uint16_t dri_minor_version{}; + uint32_t dri_minor_patch{}; + }; + + using QueryVersionResponse = Response; + + Future QueryVersion(const QueryVersionRequest& request); + + Future QueryVersion(); + + struct QueryDirectRenderingCapableRequest { + uint32_t screen{}; + }; + + struct QueryDirectRenderingCapableReply { + uint16_t sequence{}; + uint8_t is_capable{}; + }; + + using QueryDirectRenderingCapableResponse = + Response; + + Future QueryDirectRenderingCapable( + const QueryDirectRenderingCapableRequest& request); + + Future QueryDirectRenderingCapable( + const uint32_t& screen = {}); + + struct OpenConnectionRequest { + uint32_t screen{}; + }; + + struct OpenConnectionReply { + uint16_t sequence{}; + uint32_t sarea_handle_low{}; + uint32_t sarea_handle_high{}; + std::string bus_id{}; + }; + + using OpenConnectionResponse = Response; + + Future OpenConnection( + const OpenConnectionRequest& request); + + Future OpenConnection(const uint32_t& screen = {}); + + struct CloseConnectionRequest { + uint32_t screen{}; + }; + + using CloseConnectionResponse = Response; + + Future CloseConnection(const CloseConnectionRequest& request); + + Future CloseConnection(const uint32_t& screen = {}); + + struct GetClientDriverNameRequest { + uint32_t screen{}; + }; + + struct GetClientDriverNameReply { + uint16_t sequence{}; + uint32_t client_driver_major_version{}; + uint32_t client_driver_minor_version{}; + uint32_t client_driver_patch_version{}; + std::string client_driver_name{}; + }; + + using GetClientDriverNameResponse = Response; + + Future GetClientDriverName( + const GetClientDriverNameRequest& request); + + Future GetClientDriverName( + const uint32_t& screen = {}); + + struct CreateContextRequest { + uint32_t screen{}; + uint32_t visual{}; + uint32_t context{}; + }; + + struct CreateContextReply { + uint16_t sequence{}; + uint32_t hw_context{}; + }; + + using CreateContextResponse = Response; + + Future CreateContext(const CreateContextRequest& request); + + Future CreateContext(const uint32_t& screen = {}, + const uint32_t& visual = {}, + const uint32_t& context = {}); + + struct DestroyContextRequest { + uint32_t screen{}; + uint32_t context{}; + }; + + using DestroyContextResponse = Response; + + Future DestroyContext(const DestroyContextRequest& request); + + Future DestroyContext(const uint32_t& screen = {}, + const uint32_t& context = {}); + + struct CreateDrawableRequest { + uint32_t screen{}; + uint32_t drawable{}; + }; + + struct CreateDrawableReply { + uint16_t sequence{}; + uint32_t hw_drawable_handle{}; + }; + + using CreateDrawableResponse = Response; + + Future CreateDrawable( + const CreateDrawableRequest& request); + + Future CreateDrawable(const uint32_t& screen = {}, + const uint32_t& drawable = {}); + + struct DestroyDrawableRequest { + uint32_t screen{}; + uint32_t drawable{}; + }; + + using DestroyDrawableResponse = Response; + + Future DestroyDrawable(const DestroyDrawableRequest& request); + + Future DestroyDrawable(const uint32_t& screen = {}, + const uint32_t& drawable = {}); + + struct GetDrawableInfoRequest { + uint32_t screen{}; + uint32_t drawable{}; + }; + + struct GetDrawableInfoReply { + uint16_t sequence{}; + uint32_t drawable_table_index{}; + uint32_t drawable_table_stamp{}; + int16_t drawable_origin_X{}; + int16_t drawable_origin_Y{}; + int16_t drawable_size_W{}; + int16_t drawable_size_H{}; + int16_t back_x{}; + int16_t back_y{}; + std::vector clip_rects{}; + std::vector back_clip_rects{}; + }; + + using GetDrawableInfoResponse = Response; + + Future GetDrawableInfo( + const GetDrawableInfoRequest& request); + + Future GetDrawableInfo(const uint32_t& screen = {}, + const uint32_t& drawable = {}); + + struct GetDeviceInfoRequest { + uint32_t screen{}; + }; + + struct GetDeviceInfoReply { + uint16_t sequence{}; + uint32_t framebuffer_handle_low{}; + uint32_t framebuffer_handle_high{}; + uint32_t framebuffer_origin_offset{}; + uint32_t framebuffer_size{}; + uint32_t framebuffer_stride{}; + std::vector device_private{}; + }; + + using GetDeviceInfoResponse = Response; + + Future GetDeviceInfo(const GetDeviceInfoRequest& request); + + Future GetDeviceInfo(const uint32_t& screen = {}); + + struct AuthConnectionRequest { + uint32_t screen{}; + uint32_t magic{}; + }; + + struct AuthConnectionReply { + uint16_t sequence{}; + uint32_t authenticated{}; + }; + + using AuthConnectionResponse = Response; + + Future AuthConnection( + const AuthConnectionRequest& request); + + Future AuthConnection(const uint32_t& screen = {}, + const uint32_t& magic = {}); + + private: + Connection* const connection_; + x11::QueryExtensionReply info_{}; +}; + +} // namespace x11 + +#endif // UI_GFX_X_GENERATED_PROTOS_XF86DRI_H_ diff --git a/x/generated_protos/xf86vidmode.cc b/x/generated_protos/xf86vidmode.cc new file mode 100644 index 000000000000..c08c3363d73a --- /dev/null +++ b/x/generated_protos/xf86vidmode.cc @@ -0,0 +1,2197 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#include "xf86vidmode.h" + +#include +#include + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "ui/gfx/x/xproto_internal.h" + +namespace x11 { + +XF86VidMode::XF86VidMode(Connection* connection, + const x11::QueryExtensionReply& info) + : connection_(connection), info_(info) {} + +std::string XF86VidMode::BadClockError::ToString() const { + std::stringstream ss_; + ss_ << "XF86VidMode::BadClockError{"; + ss_ << ".sequence = " << static_cast(sequence); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError(XF86VidMode::BadClockError* error_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + DCHECK_LE(buf.offset, 32ul); +} +std::string XF86VidMode::BadHTimingsError::ToString() const { + std::stringstream ss_; + ss_ << "XF86VidMode::BadHTimingsError{"; + ss_ << ".sequence = " << static_cast(sequence); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError( + XF86VidMode::BadHTimingsError* error_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + DCHECK_LE(buf.offset, 32ul); +} +std::string XF86VidMode::BadVTimingsError::ToString() const { + std::stringstream ss_; + ss_ << "XF86VidMode::BadVTimingsError{"; + ss_ << ".sequence = " << static_cast(sequence); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError( + XF86VidMode::BadVTimingsError* error_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + DCHECK_LE(buf.offset, 32ul); +} +std::string XF86VidMode::ModeUnsuitableError::ToString() const { + std::stringstream ss_; + ss_ << "XF86VidMode::ModeUnsuitableError{"; + ss_ << ".sequence = " << static_cast(sequence); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError( + XF86VidMode::ModeUnsuitableError* error_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + DCHECK_LE(buf.offset, 32ul); +} +std::string XF86VidMode::ExtensionDisabledError::ToString() const { + std::stringstream ss_; + ss_ << "XF86VidMode::ExtensionDisabledError{"; + ss_ << ".sequence = " << static_cast(sequence); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError( + XF86VidMode::ExtensionDisabledError* error_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + DCHECK_LE(buf.offset, 32ul); +} +std::string XF86VidMode::ClientNotLocalError::ToString() const { + std::stringstream ss_; + ss_ << "XF86VidMode::ClientNotLocalError{"; + ss_ << ".sequence = " << static_cast(sequence); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError( + XF86VidMode::ClientNotLocalError* error_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + DCHECK_LE(buf.offset, 32ul); +} +std::string XF86VidMode::ZoomLockedError::ToString() const { + std::stringstream ss_; + ss_ << "XF86VidMode::ZoomLockedError{"; + ss_ << ".sequence = " << static_cast(sequence); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError( + XF86VidMode::ZoomLockedError* error_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + DCHECK_LE(buf.offset, 32ul); +} +Future XF86VidMode::QueryVersion( + const XF86VidMode::QueryVersionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 0; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "XF86VidMode::QueryVersion", false); +} + +Future XF86VidMode::QueryVersion() { + return XF86VidMode::QueryVersion(XF86VidMode::QueryVersionRequest{}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + XF86VidMode::QueryVersionReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& major_version = (*reply).major_version; + auto& minor_version = (*reply).minor_version; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // major_version + Read(&major_version, &buf); + + // minor_version + Read(&minor_version, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future XF86VidMode::GetModeLine( + const XF86VidMode::GetModeLineRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& screen = request.screen; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 1; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // screen + buf.Write(&screen); + + // pad0 + Pad(&buf, 2); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "XF86VidMode::GetModeLine", false); +} + +Future XF86VidMode::GetModeLine( + const uint16_t& screen) { + return XF86VidMode::GetModeLine(XF86VidMode::GetModeLineRequest{screen}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + XF86VidMode::GetModeLineReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& dotclock = (*reply).dotclock; + auto& hdisplay = (*reply).hdisplay; + auto& hsyncstart = (*reply).hsyncstart; + auto& hsyncend = (*reply).hsyncend; + auto& htotal = (*reply).htotal; + auto& hskew = (*reply).hskew; + auto& vdisplay = (*reply).vdisplay; + auto& vsyncstart = (*reply).vsyncstart; + auto& vsyncend = (*reply).vsyncend; + auto& vtotal = (*reply).vtotal; + auto& flags = (*reply).flags; + uint32_t privsize{}; + auto& c_private = (*reply).c_private; + size_t c_private_len = c_private.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // dotclock + Read(&dotclock, &buf); + + // hdisplay + Read(&hdisplay, &buf); + + // hsyncstart + Read(&hsyncstart, &buf); + + // hsyncend + Read(&hsyncend, &buf); + + // htotal + Read(&htotal, &buf); + + // hskew + Read(&hskew, &buf); + + // vdisplay + Read(&vdisplay, &buf); + + // vsyncstart + Read(&vsyncstart, &buf); + + // vsyncend + Read(&vsyncend, &buf); + + // vtotal + Read(&vtotal, &buf); + + // pad1 + Pad(&buf, 2); + + // flags + uint32_t tmp0; + Read(&tmp0, &buf); + flags = static_cast(tmp0); + + // pad2 + Pad(&buf, 12); + + // privsize + Read(&privsize, &buf); + + // c_private + c_private.resize(privsize); + for (auto& c_private_elem : c_private) { + // c_private_elem + Read(&c_private_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future XF86VidMode::ModModeLine( + const XF86VidMode::ModModeLineRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& screen = request.screen; + auto& hdisplay = request.hdisplay; + auto& hsyncstart = request.hsyncstart; + auto& hsyncend = request.hsyncend; + auto& htotal = request.htotal; + auto& hskew = request.hskew; + auto& vdisplay = request.vdisplay; + auto& vsyncstart = request.vsyncstart; + auto& vsyncend = request.vsyncend; + auto& vtotal = request.vtotal; + auto& flags = request.flags; + uint32_t privsize{}; + auto& c_private = request.c_private; + size_t c_private_len = c_private.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 2; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // screen + buf.Write(&screen); + + // hdisplay + buf.Write(&hdisplay); + + // hsyncstart + buf.Write(&hsyncstart); + + // hsyncend + buf.Write(&hsyncend); + + // htotal + buf.Write(&htotal); + + // hskew + buf.Write(&hskew); + + // vdisplay + buf.Write(&vdisplay); + + // vsyncstart + buf.Write(&vsyncstart); + + // vsyncend + buf.Write(&vsyncend); + + // vtotal + buf.Write(&vtotal); + + // pad0 + Pad(&buf, 2); + + // flags + uint32_t tmp1; + tmp1 = static_cast(flags); + buf.Write(&tmp1); + + // pad1 + Pad(&buf, 12); + + // privsize + privsize = c_private.size(); + buf.Write(&privsize); + + // c_private + DCHECK_EQ(static_cast(privsize), c_private.size()); + for (auto& c_private_elem : c_private) { + // c_private_elem + buf.Write(&c_private_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "XF86VidMode::ModModeLine", + false); +} + +Future XF86VidMode::ModModeLine(const uint32_t& screen, + const uint16_t& hdisplay, + const uint16_t& hsyncstart, + const uint16_t& hsyncend, + const uint16_t& htotal, + const uint16_t& hskew, + const uint16_t& vdisplay, + const uint16_t& vsyncstart, + const uint16_t& vsyncend, + const uint16_t& vtotal, + const ModeFlag& flags, + const std::vector& c_private) { + return XF86VidMode::ModModeLine(XF86VidMode::ModModeLineRequest{ + screen, hdisplay, hsyncstart, hsyncend, htotal, hskew, vdisplay, + vsyncstart, vsyncend, vtotal, flags, c_private}); +} + +Future XF86VidMode::SwitchMode( + const XF86VidMode::SwitchModeRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& screen = request.screen; + auto& zoom = request.zoom; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 3; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // screen + buf.Write(&screen); + + // zoom + buf.Write(&zoom); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "XF86VidMode::SwitchMode", false); +} + +Future XF86VidMode::SwitchMode(const uint16_t& screen, + const uint16_t& zoom) { + return XF86VidMode::SwitchMode(XF86VidMode::SwitchModeRequest{screen, zoom}); +} + +Future XF86VidMode::GetMonitor( + const XF86VidMode::GetMonitorRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& screen = request.screen; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 4; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // screen + buf.Write(&screen); + + // pad0 + Pad(&buf, 2); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "XF86VidMode::GetMonitor", false); +} + +Future XF86VidMode::GetMonitor( + const uint16_t& screen) { + return XF86VidMode::GetMonitor(XF86VidMode::GetMonitorRequest{screen}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + XF86VidMode::GetMonitorReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint8_t vendor_length{}; + uint8_t model_length{}; + uint8_t num_hsync{}; + uint8_t num_vsync{}; + auto& hsync = (*reply).hsync; + size_t hsync_len = hsync.size(); + auto& vsync = (*reply).vsync; + size_t vsync_len = vsync.size(); + auto& vendor = (*reply).vendor; + size_t vendor_len = vendor.size(); + auto& alignment_pad = (*reply).alignment_pad; + size_t alignment_pad_len = alignment_pad ? alignment_pad->size() : 0; + auto& model = (*reply).model; + size_t model_len = model.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // vendor_length + Read(&vendor_length, &buf); + + // model_length + Read(&model_length, &buf); + + // num_hsync + Read(&num_hsync, &buf); + + // num_vsync + Read(&num_vsync, &buf); + + // pad1 + Pad(&buf, 20); + + // hsync + hsync.resize(num_hsync); + for (auto& hsync_elem : hsync) { + // hsync_elem + Read(&hsync_elem, &buf); + } + + // vsync + vsync.resize(num_vsync); + for (auto& vsync_elem : vsync) { + // vsync_elem + Read(&vsync_elem, &buf); + } + + // vendor + vendor.resize(vendor_length); + for (auto& vendor_elem : vendor) { + // vendor_elem + Read(&vendor_elem, &buf); + } + + // alignment_pad + alignment_pad = buffer->ReadAndAdvance( + (BitAnd((vendor_length) + (3), BitNot(3))) - (vendor_length)); + + // model + model.resize(model_length); + for (auto& model_elem : model) { + // model_elem + Read(&model_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future XF86VidMode::LockModeSwitch( + const XF86VidMode::LockModeSwitchRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& screen = request.screen; + auto& lock = request.lock; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 5; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // screen + buf.Write(&screen); + + // lock + buf.Write(&lock); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "XF86VidMode::LockModeSwitch", + false); +} + +Future XF86VidMode::LockModeSwitch(const uint16_t& screen, + const uint16_t& lock) { + return XF86VidMode::LockModeSwitch( + XF86VidMode::LockModeSwitchRequest{screen, lock}); +} + +Future XF86VidMode::GetAllModeLines( + const XF86VidMode::GetAllModeLinesRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& screen = request.screen; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 6; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // screen + buf.Write(&screen); + + // pad0 + Pad(&buf, 2); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "XF86VidMode::GetAllModeLines", false); +} + +Future XF86VidMode::GetAllModeLines( + const uint16_t& screen) { + return XF86VidMode::GetAllModeLines( + XF86VidMode::GetAllModeLinesRequest{screen}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + XF86VidMode::GetAllModeLinesReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t modecount{}; + auto& modeinfo = (*reply).modeinfo; + size_t modeinfo_len = modeinfo.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // modecount + Read(&modecount, &buf); + + // pad1 + Pad(&buf, 20); + + // modeinfo + modeinfo.resize(modecount); + for (auto& modeinfo_elem : modeinfo) { + // modeinfo_elem + { + auto& dotclock = modeinfo_elem.dotclock; + auto& hdisplay = modeinfo_elem.hdisplay; + auto& hsyncstart = modeinfo_elem.hsyncstart; + auto& hsyncend = modeinfo_elem.hsyncend; + auto& htotal = modeinfo_elem.htotal; + auto& hskew = modeinfo_elem.hskew; + auto& vdisplay = modeinfo_elem.vdisplay; + auto& vsyncstart = modeinfo_elem.vsyncstart; + auto& vsyncend = modeinfo_elem.vsyncend; + auto& vtotal = modeinfo_elem.vtotal; + auto& flags = modeinfo_elem.flags; + auto& privsize = modeinfo_elem.privsize; + + // dotclock + Read(&dotclock, &buf); + + // hdisplay + Read(&hdisplay, &buf); + + // hsyncstart + Read(&hsyncstart, &buf); + + // hsyncend + Read(&hsyncend, &buf); + + // htotal + Read(&htotal, &buf); + + // hskew + Read(&hskew, &buf); + + // vdisplay + Read(&vdisplay, &buf); + + // vsyncstart + Read(&vsyncstart, &buf); + + // vsyncend + Read(&vsyncend, &buf); + + // vtotal + Read(&vtotal, &buf); + + // pad0 + Pad(&buf, 4); + + // flags + uint32_t tmp2; + Read(&tmp2, &buf); + flags = static_cast(tmp2); + + // pad1 + Pad(&buf, 12); + + // privsize + Read(&privsize, &buf); + } + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future XF86VidMode::AddModeLine( + const XF86VidMode::AddModeLineRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& screen = request.screen; + auto& dotclock = request.dotclock; + auto& hdisplay = request.hdisplay; + auto& hsyncstart = request.hsyncstart; + auto& hsyncend = request.hsyncend; + auto& htotal = request.htotal; + auto& hskew = request.hskew; + auto& vdisplay = request.vdisplay; + auto& vsyncstart = request.vsyncstart; + auto& vsyncend = request.vsyncend; + auto& vtotal = request.vtotal; + auto& flags = request.flags; + uint32_t privsize{}; + auto& after_dotclock = request.after_dotclock; + auto& after_hdisplay = request.after_hdisplay; + auto& after_hsyncstart = request.after_hsyncstart; + auto& after_hsyncend = request.after_hsyncend; + auto& after_htotal = request.after_htotal; + auto& after_hskew = request.after_hskew; + auto& after_vdisplay = request.after_vdisplay; + auto& after_vsyncstart = request.after_vsyncstart; + auto& after_vsyncend = request.after_vsyncend; + auto& after_vtotal = request.after_vtotal; + auto& after_flags = request.after_flags; + auto& c_private = request.c_private; + size_t c_private_len = c_private.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 7; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // screen + buf.Write(&screen); + + // dotclock + buf.Write(&dotclock); + + // hdisplay + buf.Write(&hdisplay); + + // hsyncstart + buf.Write(&hsyncstart); + + // hsyncend + buf.Write(&hsyncend); + + // htotal + buf.Write(&htotal); + + // hskew + buf.Write(&hskew); + + // vdisplay + buf.Write(&vdisplay); + + // vsyncstart + buf.Write(&vsyncstart); + + // vsyncend + buf.Write(&vsyncend); + + // vtotal + buf.Write(&vtotal); + + // pad0 + Pad(&buf, 2); + + // flags + uint32_t tmp3; + tmp3 = static_cast(flags); + buf.Write(&tmp3); + + // pad1 + Pad(&buf, 12); + + // privsize + privsize = c_private.size(); + buf.Write(&privsize); + + // after_dotclock + buf.Write(&after_dotclock); + + // after_hdisplay + buf.Write(&after_hdisplay); + + // after_hsyncstart + buf.Write(&after_hsyncstart); + + // after_hsyncend + buf.Write(&after_hsyncend); + + // after_htotal + buf.Write(&after_htotal); + + // after_hskew + buf.Write(&after_hskew); + + // after_vdisplay + buf.Write(&after_vdisplay); + + // after_vsyncstart + buf.Write(&after_vsyncstart); + + // after_vsyncend + buf.Write(&after_vsyncend); + + // after_vtotal + buf.Write(&after_vtotal); + + // pad2 + Pad(&buf, 2); + + // after_flags + uint32_t tmp4; + tmp4 = static_cast(after_flags); + buf.Write(&tmp4); + + // pad3 + Pad(&buf, 12); + + // c_private + DCHECK_EQ(static_cast(privsize), c_private.size()); + for (auto& c_private_elem : c_private) { + // c_private_elem + buf.Write(&c_private_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "XF86VidMode::AddModeLine", + false); +} + +Future XF86VidMode::AddModeLine(const uint32_t& screen, + const DotClock& dotclock, + const uint16_t& hdisplay, + const uint16_t& hsyncstart, + const uint16_t& hsyncend, + const uint16_t& htotal, + const uint16_t& hskew, + const uint16_t& vdisplay, + const uint16_t& vsyncstart, + const uint16_t& vsyncend, + const uint16_t& vtotal, + const ModeFlag& flags, + const DotClock& after_dotclock, + const uint16_t& after_hdisplay, + const uint16_t& after_hsyncstart, + const uint16_t& after_hsyncend, + const uint16_t& after_htotal, + const uint16_t& after_hskew, + const uint16_t& after_vdisplay, + const uint16_t& after_vsyncstart, + const uint16_t& after_vsyncend, + const uint16_t& after_vtotal, + const ModeFlag& after_flags, + const std::vector& c_private) { + return XF86VidMode::AddModeLine(XF86VidMode::AddModeLineRequest{ + screen, dotclock, hdisplay, + hsyncstart, hsyncend, htotal, + hskew, vdisplay, vsyncstart, + vsyncend, vtotal, flags, + after_dotclock, after_hdisplay, after_hsyncstart, + after_hsyncend, after_htotal, after_hskew, + after_vdisplay, after_vsyncstart, after_vsyncend, + after_vtotal, after_flags, c_private}); +} + +Future XF86VidMode::DeleteModeLine( + const XF86VidMode::DeleteModeLineRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& screen = request.screen; + auto& dotclock = request.dotclock; + auto& hdisplay = request.hdisplay; + auto& hsyncstart = request.hsyncstart; + auto& hsyncend = request.hsyncend; + auto& htotal = request.htotal; + auto& hskew = request.hskew; + auto& vdisplay = request.vdisplay; + auto& vsyncstart = request.vsyncstart; + auto& vsyncend = request.vsyncend; + auto& vtotal = request.vtotal; + auto& flags = request.flags; + uint32_t privsize{}; + auto& c_private = request.c_private; + size_t c_private_len = c_private.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 8; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // screen + buf.Write(&screen); + + // dotclock + buf.Write(&dotclock); + + // hdisplay + buf.Write(&hdisplay); + + // hsyncstart + buf.Write(&hsyncstart); + + // hsyncend + buf.Write(&hsyncend); + + // htotal + buf.Write(&htotal); + + // hskew + buf.Write(&hskew); + + // vdisplay + buf.Write(&vdisplay); + + // vsyncstart + buf.Write(&vsyncstart); + + // vsyncend + buf.Write(&vsyncend); + + // vtotal + buf.Write(&vtotal); + + // pad0 + Pad(&buf, 2); + + // flags + uint32_t tmp5; + tmp5 = static_cast(flags); + buf.Write(&tmp5); + + // pad1 + Pad(&buf, 12); + + // privsize + privsize = c_private.size(); + buf.Write(&privsize); + + // c_private + DCHECK_EQ(static_cast(privsize), c_private.size()); + for (auto& c_private_elem : c_private) { + // c_private_elem + buf.Write(&c_private_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "XF86VidMode::DeleteModeLine", + false); +} + +Future XF86VidMode::DeleteModeLine( + const uint32_t& screen, + const DotClock& dotclock, + const uint16_t& hdisplay, + const uint16_t& hsyncstart, + const uint16_t& hsyncend, + const uint16_t& htotal, + const uint16_t& hskew, + const uint16_t& vdisplay, + const uint16_t& vsyncstart, + const uint16_t& vsyncend, + const uint16_t& vtotal, + const ModeFlag& flags, + const std::vector& c_private) { + return XF86VidMode::DeleteModeLine(XF86VidMode::DeleteModeLineRequest{ + screen, dotclock, hdisplay, hsyncstart, hsyncend, htotal, hskew, vdisplay, + vsyncstart, vsyncend, vtotal, flags, c_private}); +} + +Future XF86VidMode::ValidateModeLine( + const XF86VidMode::ValidateModeLineRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& screen = request.screen; + auto& dotclock = request.dotclock; + auto& hdisplay = request.hdisplay; + auto& hsyncstart = request.hsyncstart; + auto& hsyncend = request.hsyncend; + auto& htotal = request.htotal; + auto& hskew = request.hskew; + auto& vdisplay = request.vdisplay; + auto& vsyncstart = request.vsyncstart; + auto& vsyncend = request.vsyncend; + auto& vtotal = request.vtotal; + auto& flags = request.flags; + uint32_t privsize{}; + auto& c_private = request.c_private; + size_t c_private_len = c_private.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 9; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // screen + buf.Write(&screen); + + // dotclock + buf.Write(&dotclock); + + // hdisplay + buf.Write(&hdisplay); + + // hsyncstart + buf.Write(&hsyncstart); + + // hsyncend + buf.Write(&hsyncend); + + // htotal + buf.Write(&htotal); + + // hskew + buf.Write(&hskew); + + // vdisplay + buf.Write(&vdisplay); + + // vsyncstart + buf.Write(&vsyncstart); + + // vsyncend + buf.Write(&vsyncend); + + // vtotal + buf.Write(&vtotal); + + // pad0 + Pad(&buf, 2); + + // flags + uint32_t tmp6; + tmp6 = static_cast(flags); + buf.Write(&tmp6); + + // pad1 + Pad(&buf, 12); + + // privsize + privsize = c_private.size(); + buf.Write(&privsize); + + // c_private + DCHECK_EQ(static_cast(privsize), c_private.size()); + for (auto& c_private_elem : c_private) { + // c_private_elem + buf.Write(&c_private_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "XF86VidMode::ValidateModeLine", false); +} + +Future XF86VidMode::ValidateModeLine( + const uint32_t& screen, + const DotClock& dotclock, + const uint16_t& hdisplay, + const uint16_t& hsyncstart, + const uint16_t& hsyncend, + const uint16_t& htotal, + const uint16_t& hskew, + const uint16_t& vdisplay, + const uint16_t& vsyncstart, + const uint16_t& vsyncend, + const uint16_t& vtotal, + const ModeFlag& flags, + const std::vector& c_private) { + return XF86VidMode::ValidateModeLine(XF86VidMode::ValidateModeLineRequest{ + screen, dotclock, hdisplay, hsyncstart, hsyncend, htotal, hskew, vdisplay, + vsyncstart, vsyncend, vtotal, flags, c_private}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + XF86VidMode::ValidateModeLineReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& status = (*reply).status; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // status + Read(&status, &buf); + + // pad1 + Pad(&buf, 20); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future XF86VidMode::SwitchToMode( + const XF86VidMode::SwitchToModeRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& screen = request.screen; + auto& dotclock = request.dotclock; + auto& hdisplay = request.hdisplay; + auto& hsyncstart = request.hsyncstart; + auto& hsyncend = request.hsyncend; + auto& htotal = request.htotal; + auto& hskew = request.hskew; + auto& vdisplay = request.vdisplay; + auto& vsyncstart = request.vsyncstart; + auto& vsyncend = request.vsyncend; + auto& vtotal = request.vtotal; + auto& flags = request.flags; + uint32_t privsize{}; + auto& c_private = request.c_private; + size_t c_private_len = c_private.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 10; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // screen + buf.Write(&screen); + + // dotclock + buf.Write(&dotclock); + + // hdisplay + buf.Write(&hdisplay); + + // hsyncstart + buf.Write(&hsyncstart); + + // hsyncend + buf.Write(&hsyncend); + + // htotal + buf.Write(&htotal); + + // hskew + buf.Write(&hskew); + + // vdisplay + buf.Write(&vdisplay); + + // vsyncstart + buf.Write(&vsyncstart); + + // vsyncend + buf.Write(&vsyncend); + + // vtotal + buf.Write(&vtotal); + + // pad0 + Pad(&buf, 2); + + // flags + uint32_t tmp7; + tmp7 = static_cast(flags); + buf.Write(&tmp7); + + // pad1 + Pad(&buf, 12); + + // privsize + privsize = c_private.size(); + buf.Write(&privsize); + + // c_private + DCHECK_EQ(static_cast(privsize), c_private.size()); + for (auto& c_private_elem : c_private) { + // c_private_elem + buf.Write(&c_private_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "XF86VidMode::SwitchToMode", + false); +} + +Future XF86VidMode::SwitchToMode(const uint32_t& screen, + const DotClock& dotclock, + const uint16_t& hdisplay, + const uint16_t& hsyncstart, + const uint16_t& hsyncend, + const uint16_t& htotal, + const uint16_t& hskew, + const uint16_t& vdisplay, + const uint16_t& vsyncstart, + const uint16_t& vsyncend, + const uint16_t& vtotal, + const ModeFlag& flags, + const std::vector& c_private) { + return XF86VidMode::SwitchToMode(XF86VidMode::SwitchToModeRequest{ + screen, dotclock, hdisplay, hsyncstart, hsyncend, htotal, hskew, vdisplay, + vsyncstart, vsyncend, vtotal, flags, c_private}); +} + +Future XF86VidMode::GetViewPort( + const XF86VidMode::GetViewPortRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& screen = request.screen; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 11; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // screen + buf.Write(&screen); + + // pad0 + Pad(&buf, 2); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "XF86VidMode::GetViewPort", false); +} + +Future XF86VidMode::GetViewPort( + const uint16_t& screen) { + return XF86VidMode::GetViewPort(XF86VidMode::GetViewPortRequest{screen}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + XF86VidMode::GetViewPortReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& x = (*reply).x; + auto& y = (*reply).y; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // x + Read(&x, &buf); + + // y + Read(&y, &buf); + + // pad1 + Pad(&buf, 16); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future XF86VidMode::SetViewPort( + const XF86VidMode::SetViewPortRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& screen = request.screen; + auto& x = request.x; + auto& y = request.y; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 12; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // screen + buf.Write(&screen); + + // pad0 + Pad(&buf, 2); + + // x + buf.Write(&x); + + // y + buf.Write(&y); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "XF86VidMode::SetViewPort", + false); +} + +Future XF86VidMode::SetViewPort(const uint16_t& screen, + const uint32_t& x, + const uint32_t& y) { + return XF86VidMode::SetViewPort( + XF86VidMode::SetViewPortRequest{screen, x, y}); +} + +Future XF86VidMode::GetDotClocks( + const XF86VidMode::GetDotClocksRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& screen = request.screen; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 13; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // screen + buf.Write(&screen); + + // pad0 + Pad(&buf, 2); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "XF86VidMode::GetDotClocks", false); +} + +Future XF86VidMode::GetDotClocks( + const uint16_t& screen) { + return XF86VidMode::GetDotClocks(XF86VidMode::GetDotClocksRequest{screen}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + XF86VidMode::GetDotClocksReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& flags = (*reply).flags; + auto& clocks = (*reply).clocks; + auto& maxclocks = (*reply).maxclocks; + auto& clock = (*reply).clock; + size_t clock_len = clock.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // flags + uint32_t tmp8; + Read(&tmp8, &buf); + flags = static_cast(tmp8); + + // clocks + Read(&clocks, &buf); + + // maxclocks + Read(&maxclocks, &buf); + + // pad1 + Pad(&buf, 12); + + // clock + clock.resize(((1) - (BitAnd(flags, 1))) * (clocks)); + for (auto& clock_elem : clock) { + // clock_elem + Read(&clock_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future XF86VidMode::SetClientVersion( + const XF86VidMode::SetClientVersionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& major = request.major; + auto& minor = request.minor; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 14; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // major + buf.Write(&major); + + // minor + buf.Write(&minor); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "XF86VidMode::SetClientVersion", + false); +} + +Future XF86VidMode::SetClientVersion(const uint16_t& major, + const uint16_t& minor) { + return XF86VidMode::SetClientVersion( + XF86VidMode::SetClientVersionRequest{major, minor}); +} + +Future XF86VidMode::SetGamma( + const XF86VidMode::SetGammaRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& screen = request.screen; + auto& red = request.red; + auto& green = request.green; + auto& blue = request.blue; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 15; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // screen + buf.Write(&screen); + + // pad0 + Pad(&buf, 2); + + // red + buf.Write(&red); + + // green + buf.Write(&green); + + // blue + buf.Write(&blue); + + // pad1 + Pad(&buf, 12); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "XF86VidMode::SetGamma", false); +} + +Future XF86VidMode::SetGamma(const uint16_t& screen, + const uint32_t& red, + const uint32_t& green, + const uint32_t& blue) { + return XF86VidMode::SetGamma( + XF86VidMode::SetGammaRequest{screen, red, green, blue}); +} + +Future XF86VidMode::GetGamma( + const XF86VidMode::GetGammaRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& screen = request.screen; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 16; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // screen + buf.Write(&screen); + + // pad0 + Pad(&buf, 26); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "XF86VidMode::GetGamma", false); +} + +Future XF86VidMode::GetGamma( + const uint16_t& screen) { + return XF86VidMode::GetGamma(XF86VidMode::GetGammaRequest{screen}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + XF86VidMode::GetGammaReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& red = (*reply).red; + auto& green = (*reply).green; + auto& blue = (*reply).blue; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // red + Read(&red, &buf); + + // green + Read(&green, &buf); + + // blue + Read(&blue, &buf); + + // pad1 + Pad(&buf, 12); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future XF86VidMode::GetGammaRamp( + const XF86VidMode::GetGammaRampRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& screen = request.screen; + auto& size = request.size; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 17; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // screen + buf.Write(&screen); + + // size + buf.Write(&size); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "XF86VidMode::GetGammaRamp", false); +} + +Future XF86VidMode::GetGammaRamp( + const uint16_t& screen, + const uint16_t& size) { + return XF86VidMode::GetGammaRamp( + XF86VidMode::GetGammaRampRequest{screen, size}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + XF86VidMode::GetGammaRampReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& size = (*reply).size; + auto& red = (*reply).red; + size_t red_len = red.size(); + auto& green = (*reply).green; + size_t green_len = green.size(); + auto& blue = (*reply).blue; + size_t blue_len = blue.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // size + Read(&size, &buf); + + // pad1 + Pad(&buf, 22); + + // red + red.resize(BitAnd((size) + (1), BitNot(1))); + for (auto& red_elem : red) { + // red_elem + Read(&red_elem, &buf); + } + + // green + green.resize(BitAnd((size) + (1), BitNot(1))); + for (auto& green_elem : green) { + // green_elem + Read(&green_elem, &buf); + } + + // blue + blue.resize(BitAnd((size) + (1), BitNot(1))); + for (auto& blue_elem : blue) { + // blue_elem + Read(&blue_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future XF86VidMode::SetGammaRamp( + const XF86VidMode::SetGammaRampRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& screen = request.screen; + auto& size = request.size; + auto& red = request.red; + size_t red_len = red.size(); + auto& green = request.green; + size_t green_len = green.size(); + auto& blue = request.blue; + size_t blue_len = blue.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 18; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // screen + buf.Write(&screen); + + // size + buf.Write(&size); + + // red + DCHECK_EQ(static_cast(BitAnd((size) + (1), BitNot(1))), red.size()); + for (auto& red_elem : red) { + // red_elem + buf.Write(&red_elem); + } + + // green + DCHECK_EQ(static_cast(BitAnd((size) + (1), BitNot(1))), green.size()); + for (auto& green_elem : green) { + // green_elem + buf.Write(&green_elem); + } + + // blue + DCHECK_EQ(static_cast(BitAnd((size) + (1), BitNot(1))), blue.size()); + for (auto& blue_elem : blue) { + // blue_elem + buf.Write(&blue_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "XF86VidMode::SetGammaRamp", + false); +} + +Future XF86VidMode::SetGammaRamp(const uint16_t& screen, + const uint16_t& size, + const std::vector& red, + const std::vector& green, + const std::vector& blue) { + return XF86VidMode::SetGammaRamp( + XF86VidMode::SetGammaRampRequest{screen, size, red, green, blue}); +} + +Future XF86VidMode::GetGammaRampSize( + const XF86VidMode::GetGammaRampSizeRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& screen = request.screen; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 19; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // screen + buf.Write(&screen); + + // pad0 + Pad(&buf, 2); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "XF86VidMode::GetGammaRampSize", false); +} + +Future XF86VidMode::GetGammaRampSize( + const uint16_t& screen) { + return XF86VidMode::GetGammaRampSize( + XF86VidMode::GetGammaRampSizeRequest{screen}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + XF86VidMode::GetGammaRampSizeReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& size = (*reply).size; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // size + Read(&size, &buf); + + // pad1 + Pad(&buf, 22); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future XF86VidMode::GetPermissions( + const XF86VidMode::GetPermissionsRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& screen = request.screen; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 20; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // screen + buf.Write(&screen); + + // pad0 + Pad(&buf, 2); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "XF86VidMode::GetPermissions", false); +} + +Future XF86VidMode::GetPermissions( + const uint16_t& screen) { + return XF86VidMode::GetPermissions( + XF86VidMode::GetPermissionsRequest{screen}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + XF86VidMode::GetPermissionsReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& permissions = (*reply).permissions; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // permissions + uint32_t tmp9; + Read(&tmp9, &buf); + permissions = static_cast(tmp9); + + // pad1 + Pad(&buf, 20); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +} // namespace x11 diff --git a/x/generated_protos/xf86vidmode.h b/x/generated_protos/xf86vidmode.h new file mode 100644 index 000000000000..fa451a2c22fa --- /dev/null +++ b/x/generated_protos/xf86vidmode.h @@ -0,0 +1,683 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#ifndef UI_GFX_X_GENERATED_PROTOS_XF86VIDMODE_H_ +#define UI_GFX_X_GENERATED_PROTOS_XF86VIDMODE_H_ + +#include +#include +#include +#include +#include + +#include "base/component_export.h" +#include "base/files/scoped_file.h" +#include "base/memory/ref_counted_memory.h" +#include "base/memory/scoped_refptr.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/gfx/x/error.h" +#include "ui/gfx/x/ref_counted_fd.h" +#include "xproto.h" + +namespace x11 { + +class Connection; + +template +struct Response; + +template +class Future; + +class COMPONENT_EXPORT(X11) XF86VidMode { + public: + static constexpr unsigned major_version = 2; + static constexpr unsigned minor_version = 2; + + XF86VidMode(Connection* connection, const x11::QueryExtensionReply& info); + + uint8_t present() const { return info_.present; } + uint8_t major_opcode() const { return info_.major_opcode; } + uint8_t first_event() const { return info_.first_event; } + uint8_t first_error() const { return info_.first_error; } + + Connection* connection() const { return connection_; } + + enum class Syncrange : uint32_t {}; + + enum class DotClock : uint32_t {}; + + enum class ModeFlag : int { + Positive_HSync = 1 << 0, + Negative_HSync = 1 << 1, + Positive_VSync = 1 << 2, + Negative_VSync = 1 << 3, + Interlace = 1 << 4, + Composite_Sync = 1 << 5, + Positive_CSync = 1 << 6, + Negative_CSync = 1 << 7, + HSkew = 1 << 8, + Broadcast = 1 << 9, + Pixmux = 1 << 10, + Double_Clock = 1 << 11, + Half_Clock = 1 << 12, + }; + + enum class ClockFlag : int { + Programable = 1 << 0, + }; + + enum class Permission : int { + Read = 1 << 0, + Write = 1 << 1, + }; + + struct ModeInfo { + DotClock dotclock{}; + uint16_t hdisplay{}; + uint16_t hsyncstart{}; + uint16_t hsyncend{}; + uint16_t htotal{}; + uint32_t hskew{}; + uint16_t vdisplay{}; + uint16_t vsyncstart{}; + uint16_t vsyncend{}; + uint16_t vtotal{}; + ModeFlag flags{}; + uint32_t privsize{}; + }; + + struct BadClockError : public x11::Error { + uint16_t sequence{}; + + std::string ToString() const override; + }; + + struct BadHTimingsError : public x11::Error { + uint16_t sequence{}; + + std::string ToString() const override; + }; + + struct BadVTimingsError : public x11::Error { + uint16_t sequence{}; + + std::string ToString() const override; + }; + + struct ModeUnsuitableError : public x11::Error { + uint16_t sequence{}; + + std::string ToString() const override; + }; + + struct ExtensionDisabledError : public x11::Error { + uint16_t sequence{}; + + std::string ToString() const override; + }; + + struct ClientNotLocalError : public x11::Error { + uint16_t sequence{}; + + std::string ToString() const override; + }; + + struct ZoomLockedError : public x11::Error { + uint16_t sequence{}; + + std::string ToString() const override; + }; + + struct QueryVersionRequest {}; + + struct QueryVersionReply { + uint16_t sequence{}; + uint16_t major_version{}; + uint16_t minor_version{}; + }; + + using QueryVersionResponse = Response; + + Future QueryVersion(const QueryVersionRequest& request); + + Future QueryVersion(); + + struct GetModeLineRequest { + uint16_t screen{}; + }; + + struct GetModeLineReply { + uint16_t sequence{}; + DotClock dotclock{}; + uint16_t hdisplay{}; + uint16_t hsyncstart{}; + uint16_t hsyncend{}; + uint16_t htotal{}; + uint16_t hskew{}; + uint16_t vdisplay{}; + uint16_t vsyncstart{}; + uint16_t vsyncend{}; + uint16_t vtotal{}; + ModeFlag flags{}; + std::vector c_private{}; + }; + + using GetModeLineResponse = Response; + + Future GetModeLine(const GetModeLineRequest& request); + + Future GetModeLine(const uint16_t& screen = {}); + + struct ModModeLineRequest { + uint32_t screen{}; + uint16_t hdisplay{}; + uint16_t hsyncstart{}; + uint16_t hsyncend{}; + uint16_t htotal{}; + uint16_t hskew{}; + uint16_t vdisplay{}; + uint16_t vsyncstart{}; + uint16_t vsyncend{}; + uint16_t vtotal{}; + ModeFlag flags{}; + std::vector c_private{}; + }; + + using ModModeLineResponse = Response; + + Future ModModeLine(const ModModeLineRequest& request); + + Future ModModeLine(const uint32_t& screen = {}, + const uint16_t& hdisplay = {}, + const uint16_t& hsyncstart = {}, + const uint16_t& hsyncend = {}, + const uint16_t& htotal = {}, + const uint16_t& hskew = {}, + const uint16_t& vdisplay = {}, + const uint16_t& vsyncstart = {}, + const uint16_t& vsyncend = {}, + const uint16_t& vtotal = {}, + const ModeFlag& flags = {}, + const std::vector& c_private = {}); + + struct SwitchModeRequest { + uint16_t screen{}; + uint16_t zoom{}; + }; + + using SwitchModeResponse = Response; + + Future SwitchMode(const SwitchModeRequest& request); + + Future SwitchMode(const uint16_t& screen = {}, + const uint16_t& zoom = {}); + + struct GetMonitorRequest { + uint16_t screen{}; + }; + + struct GetMonitorReply { + uint16_t sequence{}; + std::vector hsync{}; + std::vector vsync{}; + std::string vendor{}; + scoped_refptr alignment_pad{}; + std::string model{}; + }; + + using GetMonitorResponse = Response; + + Future GetMonitor(const GetMonitorRequest& request); + + Future GetMonitor(const uint16_t& screen = {}); + + struct LockModeSwitchRequest { + uint16_t screen{}; + uint16_t lock{}; + }; + + using LockModeSwitchResponse = Response; + + Future LockModeSwitch(const LockModeSwitchRequest& request); + + Future LockModeSwitch(const uint16_t& screen = {}, + const uint16_t& lock = {}); + + struct GetAllModeLinesRequest { + uint16_t screen{}; + }; + + struct GetAllModeLinesReply { + uint16_t sequence{}; + std::vector modeinfo{}; + }; + + using GetAllModeLinesResponse = Response; + + Future GetAllModeLines( + const GetAllModeLinesRequest& request); + + Future GetAllModeLines(const uint16_t& screen = {}); + + struct AddModeLineRequest { + uint32_t screen{}; + DotClock dotclock{}; + uint16_t hdisplay{}; + uint16_t hsyncstart{}; + uint16_t hsyncend{}; + uint16_t htotal{}; + uint16_t hskew{}; + uint16_t vdisplay{}; + uint16_t vsyncstart{}; + uint16_t vsyncend{}; + uint16_t vtotal{}; + ModeFlag flags{}; + DotClock after_dotclock{}; + uint16_t after_hdisplay{}; + uint16_t after_hsyncstart{}; + uint16_t after_hsyncend{}; + uint16_t after_htotal{}; + uint16_t after_hskew{}; + uint16_t after_vdisplay{}; + uint16_t after_vsyncstart{}; + uint16_t after_vsyncend{}; + uint16_t after_vtotal{}; + ModeFlag after_flags{}; + std::vector c_private{}; + }; + + using AddModeLineResponse = Response; + + Future AddModeLine(const AddModeLineRequest& request); + + Future AddModeLine(const uint32_t& screen = {}, + const DotClock& dotclock = {}, + const uint16_t& hdisplay = {}, + const uint16_t& hsyncstart = {}, + const uint16_t& hsyncend = {}, + const uint16_t& htotal = {}, + const uint16_t& hskew = {}, + const uint16_t& vdisplay = {}, + const uint16_t& vsyncstart = {}, + const uint16_t& vsyncend = {}, + const uint16_t& vtotal = {}, + const ModeFlag& flags = {}, + const DotClock& after_dotclock = {}, + const uint16_t& after_hdisplay = {}, + const uint16_t& after_hsyncstart = {}, + const uint16_t& after_hsyncend = {}, + const uint16_t& after_htotal = {}, + const uint16_t& after_hskew = {}, + const uint16_t& after_vdisplay = {}, + const uint16_t& after_vsyncstart = {}, + const uint16_t& after_vsyncend = {}, + const uint16_t& after_vtotal = {}, + const ModeFlag& after_flags = {}, + const std::vector& c_private = {}); + + struct DeleteModeLineRequest { + uint32_t screen{}; + DotClock dotclock{}; + uint16_t hdisplay{}; + uint16_t hsyncstart{}; + uint16_t hsyncend{}; + uint16_t htotal{}; + uint16_t hskew{}; + uint16_t vdisplay{}; + uint16_t vsyncstart{}; + uint16_t vsyncend{}; + uint16_t vtotal{}; + ModeFlag flags{}; + std::vector c_private{}; + }; + + using DeleteModeLineResponse = Response; + + Future DeleteModeLine(const DeleteModeLineRequest& request); + + Future DeleteModeLine(const uint32_t& screen = {}, + const DotClock& dotclock = {}, + const uint16_t& hdisplay = {}, + const uint16_t& hsyncstart = {}, + const uint16_t& hsyncend = {}, + const uint16_t& htotal = {}, + const uint16_t& hskew = {}, + const uint16_t& vdisplay = {}, + const uint16_t& vsyncstart = {}, + const uint16_t& vsyncend = {}, + const uint16_t& vtotal = {}, + const ModeFlag& flags = {}, + const std::vector& c_private = {}); + + struct ValidateModeLineRequest { + uint32_t screen{}; + DotClock dotclock{}; + uint16_t hdisplay{}; + uint16_t hsyncstart{}; + uint16_t hsyncend{}; + uint16_t htotal{}; + uint16_t hskew{}; + uint16_t vdisplay{}; + uint16_t vsyncstart{}; + uint16_t vsyncend{}; + uint16_t vtotal{}; + ModeFlag flags{}; + std::vector c_private{}; + }; + + struct ValidateModeLineReply { + uint16_t sequence{}; + uint32_t status{}; + }; + + using ValidateModeLineResponse = Response; + + Future ValidateModeLine( + const ValidateModeLineRequest& request); + + Future ValidateModeLine( + const uint32_t& screen = {}, + const DotClock& dotclock = {}, + const uint16_t& hdisplay = {}, + const uint16_t& hsyncstart = {}, + const uint16_t& hsyncend = {}, + const uint16_t& htotal = {}, + const uint16_t& hskew = {}, + const uint16_t& vdisplay = {}, + const uint16_t& vsyncstart = {}, + const uint16_t& vsyncend = {}, + const uint16_t& vtotal = {}, + const ModeFlag& flags = {}, + const std::vector& c_private = {}); + + struct SwitchToModeRequest { + uint32_t screen{}; + DotClock dotclock{}; + uint16_t hdisplay{}; + uint16_t hsyncstart{}; + uint16_t hsyncend{}; + uint16_t htotal{}; + uint16_t hskew{}; + uint16_t vdisplay{}; + uint16_t vsyncstart{}; + uint16_t vsyncend{}; + uint16_t vtotal{}; + ModeFlag flags{}; + std::vector c_private{}; + }; + + using SwitchToModeResponse = Response; + + Future SwitchToMode(const SwitchToModeRequest& request); + + Future SwitchToMode(const uint32_t& screen = {}, + const DotClock& dotclock = {}, + const uint16_t& hdisplay = {}, + const uint16_t& hsyncstart = {}, + const uint16_t& hsyncend = {}, + const uint16_t& htotal = {}, + const uint16_t& hskew = {}, + const uint16_t& vdisplay = {}, + const uint16_t& vsyncstart = {}, + const uint16_t& vsyncend = {}, + const uint16_t& vtotal = {}, + const ModeFlag& flags = {}, + const std::vector& c_private = {}); + + struct GetViewPortRequest { + uint16_t screen{}; + }; + + struct GetViewPortReply { + uint16_t sequence{}; + uint32_t x{}; + uint32_t y{}; + }; + + using GetViewPortResponse = Response; + + Future GetViewPort(const GetViewPortRequest& request); + + Future GetViewPort(const uint16_t& screen = {}); + + struct SetViewPortRequest { + uint16_t screen{}; + uint32_t x{}; + uint32_t y{}; + }; + + using SetViewPortResponse = Response; + + Future SetViewPort(const SetViewPortRequest& request); + + Future SetViewPort(const uint16_t& screen = {}, + const uint32_t& x = {}, + const uint32_t& y = {}); + + struct GetDotClocksRequest { + uint16_t screen{}; + }; + + struct GetDotClocksReply { + uint16_t sequence{}; + ClockFlag flags{}; + uint32_t clocks{}; + uint32_t maxclocks{}; + std::vector clock{}; + }; + + using GetDotClocksResponse = Response; + + Future GetDotClocks(const GetDotClocksRequest& request); + + Future GetDotClocks(const uint16_t& screen = {}); + + struct SetClientVersionRequest { + uint16_t major{}; + uint16_t minor{}; + }; + + using SetClientVersionResponse = Response; + + Future SetClientVersion(const SetClientVersionRequest& request); + + Future SetClientVersion(const uint16_t& major = {}, + const uint16_t& minor = {}); + + struct SetGammaRequest { + uint16_t screen{}; + uint32_t red{}; + uint32_t green{}; + uint32_t blue{}; + }; + + using SetGammaResponse = Response; + + Future SetGamma(const SetGammaRequest& request); + + Future SetGamma(const uint16_t& screen = {}, + const uint32_t& red = {}, + const uint32_t& green = {}, + const uint32_t& blue = {}); + + struct GetGammaRequest { + uint16_t screen{}; + }; + + struct GetGammaReply { + uint16_t sequence{}; + uint32_t red{}; + uint32_t green{}; + uint32_t blue{}; + }; + + using GetGammaResponse = Response; + + Future GetGamma(const GetGammaRequest& request); + + Future GetGamma(const uint16_t& screen = {}); + + struct GetGammaRampRequest { + uint16_t screen{}; + uint16_t size{}; + }; + + struct GetGammaRampReply { + uint16_t sequence{}; + uint16_t size{}; + std::vector red{}; + std::vector green{}; + std::vector blue{}; + }; + + using GetGammaRampResponse = Response; + + Future GetGammaRamp(const GetGammaRampRequest& request); + + Future GetGammaRamp(const uint16_t& screen = {}, + const uint16_t& size = {}); + + struct SetGammaRampRequest { + uint16_t screen{}; + uint16_t size{}; + std::vector red{}; + std::vector green{}; + std::vector blue{}; + }; + + using SetGammaRampResponse = Response; + + Future SetGammaRamp(const SetGammaRampRequest& request); + + Future SetGammaRamp(const uint16_t& screen = {}, + const uint16_t& size = {}, + const std::vector& red = {}, + const std::vector& green = {}, + const std::vector& blue = {}); + + struct GetGammaRampSizeRequest { + uint16_t screen{}; + }; + + struct GetGammaRampSizeReply { + uint16_t sequence{}; + uint16_t size{}; + }; + + using GetGammaRampSizeResponse = Response; + + Future GetGammaRampSize( + const GetGammaRampSizeRequest& request); + + Future GetGammaRampSize(const uint16_t& screen = {}); + + struct GetPermissionsRequest { + uint16_t screen{}; + }; + + struct GetPermissionsReply { + uint16_t sequence{}; + Permission permissions{}; + }; + + using GetPermissionsResponse = Response; + + Future GetPermissions( + const GetPermissionsRequest& request); + + Future GetPermissions(const uint16_t& screen = {}); + + private: + Connection* const connection_; + x11::QueryExtensionReply info_{}; +}; + +} // namespace x11 + +inline constexpr x11::XF86VidMode::ModeFlag operator|( + x11::XF86VidMode::ModeFlag l, + x11::XF86VidMode::ModeFlag r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::XF86VidMode::ModeFlag operator&( + x11::XF86VidMode::ModeFlag l, + x11::XF86VidMode::ModeFlag r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +inline constexpr x11::XF86VidMode::ClockFlag operator|( + x11::XF86VidMode::ClockFlag l, + x11::XF86VidMode::ClockFlag r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::XF86VidMode::ClockFlag operator&( + x11::XF86VidMode::ClockFlag l, + x11::XF86VidMode::ClockFlag r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +inline constexpr x11::XF86VidMode::Permission operator|( + x11::XF86VidMode::Permission l, + x11::XF86VidMode::Permission r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::XF86VidMode::Permission operator&( + x11::XF86VidMode::Permission l, + x11::XF86VidMode::Permission r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +#endif // UI_GFX_X_GENERATED_PROTOS_XF86VIDMODE_H_ diff --git a/x/generated_protos/xfixes.cc b/x/generated_protos/xfixes.cc new file mode 100644 index 000000000000..88cc3514f420 --- /dev/null +++ b/x/generated_protos/xfixes.cc @@ -0,0 +1,1987 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#include "xfixes.h" + +#include +#include + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "ui/gfx/x/xproto_internal.h" + +namespace x11 { + +XFixes::XFixes(Connection* connection, const x11::QueryExtensionReply& info) + : connection_(connection), info_(info) {} + +template <> +COMPONENT_EXPORT(X11) +void ReadEvent( + XFixes::SelectionNotifyEvent* event_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& subtype = (*event_).subtype; + auto& sequence = (*event_).sequence; + auto& window = (*event_).window; + auto& owner = (*event_).owner; + auto& selection = (*event_).selection; + auto& timestamp = (*event_).timestamp; + auto& selection_timestamp = (*event_).selection_timestamp; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // subtype + uint8_t tmp0; + Read(&tmp0, &buf); + subtype = static_cast(tmp0); + + // sequence + Read(&sequence, &buf); + + // window + Read(&window, &buf); + + // owner + Read(&owner, &buf); + + // selection + Read(&selection, &buf); + + // timestamp + Read(×tamp, &buf); + + // selection_timestamp + Read(&selection_timestamp, &buf); + + // pad0 + Pad(&buf, 8); + + DCHECK_LE(buf.offset, 32ul); +} + +template <> +COMPONENT_EXPORT(X11) +void ReadEvent(XFixes::CursorNotifyEvent* event_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& subtype = (*event_).subtype; + auto& sequence = (*event_).sequence; + auto& window = (*event_).window; + auto& cursor_serial = (*event_).cursor_serial; + auto& timestamp = (*event_).timestamp; + auto& name = (*event_).name; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // subtype + uint8_t tmp1; + Read(&tmp1, &buf); + subtype = static_cast(tmp1); + + // sequence + Read(&sequence, &buf); + + // window + Read(&window, &buf); + + // cursor_serial + Read(&cursor_serial, &buf); + + // timestamp + Read(×tamp, &buf); + + // name + Read(&name, &buf); + + // pad0 + Pad(&buf, 12); + + DCHECK_LE(buf.offset, 32ul); +} + +std::string XFixes::BadRegionError::ToString() const { + std::stringstream ss_; + ss_ << "XFixes::BadRegionError{"; + ss_ << ".sequence = " << static_cast(sequence); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError(XFixes::BadRegionError* error_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + DCHECK_LE(buf.offset, 32ul); +} +Future XFixes::QueryVersion( + const XFixes::QueryVersionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& client_major_version = request.client_major_version; + auto& client_minor_version = request.client_minor_version; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 0; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // client_major_version + buf.Write(&client_major_version); + + // client_minor_version + buf.Write(&client_minor_version); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "XFixes::QueryVersion", false); +} + +Future XFixes::QueryVersion( + const uint32_t& client_major_version, + const uint32_t& client_minor_version) { + return XFixes::QueryVersion( + XFixes::QueryVersionRequest{client_major_version, client_minor_version}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + XFixes::QueryVersionReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& major_version = (*reply).major_version; + auto& minor_version = (*reply).minor_version; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // major_version + Read(&major_version, &buf); + + // minor_version + Read(&minor_version, &buf); + + // pad1 + Pad(&buf, 16); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future XFixes::ChangeSaveSet( + const XFixes::ChangeSaveSetRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& mode = request.mode; + auto& target = request.target; + auto& map = request.map; + auto& window = request.window; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 1; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // mode + uint8_t tmp2; + tmp2 = static_cast(mode); + buf.Write(&tmp2); + + // target + uint8_t tmp3; + tmp3 = static_cast(target); + buf.Write(&tmp3); + + // map + uint8_t tmp4; + tmp4 = static_cast(map); + buf.Write(&tmp4); + + // pad0 + Pad(&buf, 1); + + // window + buf.Write(&window); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "XFixes::ChangeSaveSet", false); +} + +Future XFixes::ChangeSaveSet(const SaveSetMode& mode, + const SaveSetTarget& target, + const SaveSetMapping& map, + const Window& window) { + return XFixes::ChangeSaveSet( + XFixes::ChangeSaveSetRequest{mode, target, map, window}); +} + +Future XFixes::SelectSelectionInput( + const XFixes::SelectSelectionInputRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + auto& selection = request.selection; + auto& event_mask = request.event_mask; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 2; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + // selection + buf.Write(&selection); + + // event_mask + uint32_t tmp5; + tmp5 = static_cast(event_mask); + buf.Write(&tmp5); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "XFixes::SelectSelectionInput", + false); +} + +Future XFixes::SelectSelectionInput( + const Window& window, + const Atom& selection, + const SelectionEventMask& event_mask) { + return XFixes::SelectSelectionInput( + XFixes::SelectSelectionInputRequest{window, selection, event_mask}); +} + +Future XFixes::SelectCursorInput( + const XFixes::SelectCursorInputRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + auto& event_mask = request.event_mask; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 3; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + // event_mask + uint32_t tmp6; + tmp6 = static_cast(event_mask); + buf.Write(&tmp6); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "XFixes::SelectCursorInput", + false); +} + +Future XFixes::SelectCursorInput(const Window& window, + const CursorNotifyMask& event_mask) { + return XFixes::SelectCursorInput( + XFixes::SelectCursorInputRequest{window, event_mask}); +} + +Future XFixes::GetCursorImage( + const XFixes::GetCursorImageRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 4; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "XFixes::GetCursorImage", false); +} + +Future XFixes::GetCursorImage() { + return XFixes::GetCursorImage(XFixes::GetCursorImageRequest{}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + XFixes::GetCursorImageReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& x = (*reply).x; + auto& y = (*reply).y; + auto& width = (*reply).width; + auto& height = (*reply).height; + auto& xhot = (*reply).xhot; + auto& yhot = (*reply).yhot; + auto& cursor_serial = (*reply).cursor_serial; + auto& cursor_image = (*reply).cursor_image; + size_t cursor_image_len = cursor_image.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // x + Read(&x, &buf); + + // y + Read(&y, &buf); + + // width + Read(&width, &buf); + + // height + Read(&height, &buf); + + // xhot + Read(&xhot, &buf); + + // yhot + Read(&yhot, &buf); + + // cursor_serial + Read(&cursor_serial, &buf); + + // pad1 + Pad(&buf, 8); + + // cursor_image + cursor_image.resize((width) * (height)); + for (auto& cursor_image_elem : cursor_image) { + // cursor_image_elem + Read(&cursor_image_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future XFixes::CreateRegion(const XFixes::CreateRegionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& region = request.region; + auto& rectangles = request.rectangles; + size_t rectangles_len = rectangles.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 5; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // region + buf.Write(®ion); + + // rectangles + DCHECK_EQ(static_cast(rectangles_len), rectangles.size()); + for (auto& rectangles_elem : rectangles) { + // rectangles_elem + { + auto& x = rectangles_elem.x; + auto& y = rectangles_elem.y; + auto& width = rectangles_elem.width; + auto& height = rectangles_elem.height; + + // x + buf.Write(&x); + + // y + buf.Write(&y); + + // width + buf.Write(&width); + + // height + buf.Write(&height); + } + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "XFixes::CreateRegion", false); +} + +Future XFixes::CreateRegion(const Region& region, + const std::vector& rectangles) { + return XFixes::CreateRegion(XFixes::CreateRegionRequest{region, rectangles}); +} + +Future XFixes::CreateRegionFromBitmap( + const XFixes::CreateRegionFromBitmapRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& region = request.region; + auto& bitmap = request.bitmap; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 6; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // region + buf.Write(®ion); + + // bitmap + buf.Write(&bitmap); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "XFixes::CreateRegionFromBitmap", + false); +} + +Future XFixes::CreateRegionFromBitmap(const Region& region, + const Pixmap& bitmap) { + return XFixes::CreateRegionFromBitmap( + XFixes::CreateRegionFromBitmapRequest{region, bitmap}); +} + +Future XFixes::CreateRegionFromWindow( + const XFixes::CreateRegionFromWindowRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& region = request.region; + auto& window = request.window; + auto& kind = request.kind; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 7; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // region + buf.Write(®ion); + + // window + buf.Write(&window); + + // kind + uint8_t tmp7; + tmp7 = static_cast(kind); + buf.Write(&tmp7); + + // pad0 + Pad(&buf, 3); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "XFixes::CreateRegionFromWindow", + false); +} + +Future XFixes::CreateRegionFromWindow(const Region& region, + const Window& window, + const Shape::Sk& kind) { + return XFixes::CreateRegionFromWindow( + XFixes::CreateRegionFromWindowRequest{region, window, kind}); +} + +Future XFixes::CreateRegionFromGC( + const XFixes::CreateRegionFromGCRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& region = request.region; + auto& gc = request.gc; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 8; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // region + buf.Write(®ion); + + // gc + buf.Write(&gc); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "XFixes::CreateRegionFromGC", + false); +} + +Future XFixes::CreateRegionFromGC(const Region& region, + const GraphicsContext& gc) { + return XFixes::CreateRegionFromGC( + XFixes::CreateRegionFromGCRequest{region, gc}); +} + +Future XFixes::CreateRegionFromPicture( + const XFixes::CreateRegionFromPictureRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& region = request.region; + auto& picture = request.picture; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 9; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // region + buf.Write(®ion); + + // picture + buf.Write(&picture); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "XFixes::CreateRegionFromPicture", + false); +} + +Future XFixes::CreateRegionFromPicture(const Region& region, + const Render::Picture& picture) { + return XFixes::CreateRegionFromPicture( + XFixes::CreateRegionFromPictureRequest{region, picture}); +} + +Future XFixes::DestroyRegion( + const XFixes::DestroyRegionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& region = request.region; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 10; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // region + buf.Write(®ion); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "XFixes::DestroyRegion", false); +} + +Future XFixes::DestroyRegion(const Region& region) { + return XFixes::DestroyRegion(XFixes::DestroyRegionRequest{region}); +} + +Future XFixes::SetRegion(const XFixes::SetRegionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& region = request.region; + auto& rectangles = request.rectangles; + size_t rectangles_len = rectangles.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 11; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // region + buf.Write(®ion); + + // rectangles + DCHECK_EQ(static_cast(rectangles_len), rectangles.size()); + for (auto& rectangles_elem : rectangles) { + // rectangles_elem + { + auto& x = rectangles_elem.x; + auto& y = rectangles_elem.y; + auto& width = rectangles_elem.width; + auto& height = rectangles_elem.height; + + // x + buf.Write(&x); + + // y + buf.Write(&y); + + // width + buf.Write(&width); + + // height + buf.Write(&height); + } + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "XFixes::SetRegion", false); +} + +Future XFixes::SetRegion(const Region& region, + const std::vector& rectangles) { + return XFixes::SetRegion(XFixes::SetRegionRequest{region, rectangles}); +} + +Future XFixes::CopyRegion(const XFixes::CopyRegionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& source = request.source; + auto& destination = request.destination; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 12; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // source + buf.Write(&source); + + // destination + buf.Write(&destination); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "XFixes::CopyRegion", false); +} + +Future XFixes::CopyRegion(const Region& source, + const Region& destination) { + return XFixes::CopyRegion(XFixes::CopyRegionRequest{source, destination}); +} + +Future XFixes::UnionRegion(const XFixes::UnionRegionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& source1 = request.source1; + auto& source2 = request.source2; + auto& destination = request.destination; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 13; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // source1 + buf.Write(&source1); + + // source2 + buf.Write(&source2); + + // destination + buf.Write(&destination); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "XFixes::UnionRegion", false); +} + +Future XFixes::UnionRegion(const Region& source1, + const Region& source2, + const Region& destination) { + return XFixes::UnionRegion( + XFixes::UnionRegionRequest{source1, source2, destination}); +} + +Future XFixes::IntersectRegion( + const XFixes::IntersectRegionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& source1 = request.source1; + auto& source2 = request.source2; + auto& destination = request.destination; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 14; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // source1 + buf.Write(&source1); + + // source2 + buf.Write(&source2); + + // destination + buf.Write(&destination); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "XFixes::IntersectRegion", false); +} + +Future XFixes::IntersectRegion(const Region& source1, + const Region& source2, + const Region& destination) { + return XFixes::IntersectRegion( + XFixes::IntersectRegionRequest{source1, source2, destination}); +} + +Future XFixes::SubtractRegion( + const XFixes::SubtractRegionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& source1 = request.source1; + auto& source2 = request.source2; + auto& destination = request.destination; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 15; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // source1 + buf.Write(&source1); + + // source2 + buf.Write(&source2); + + // destination + buf.Write(&destination); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "XFixes::SubtractRegion", false); +} + +Future XFixes::SubtractRegion(const Region& source1, + const Region& source2, + const Region& destination) { + return XFixes::SubtractRegion( + XFixes::SubtractRegionRequest{source1, source2, destination}); +} + +Future XFixes::InvertRegion(const XFixes::InvertRegionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& source = request.source; + auto& bounds = request.bounds; + auto& destination = request.destination; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 16; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // source + buf.Write(&source); + + // bounds + { + auto& x = bounds.x; + auto& y = bounds.y; + auto& width = bounds.width; + auto& height = bounds.height; + + // x + buf.Write(&x); + + // y + buf.Write(&y); + + // width + buf.Write(&width); + + // height + buf.Write(&height); + } + + // destination + buf.Write(&destination); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "XFixes::InvertRegion", false); +} + +Future XFixes::InvertRegion(const Region& source, + const Rectangle& bounds, + const Region& destination) { + return XFixes::InvertRegion( + XFixes::InvertRegionRequest{source, bounds, destination}); +} + +Future XFixes::TranslateRegion( + const XFixes::TranslateRegionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& region = request.region; + auto& dx = request.dx; + auto& dy = request.dy; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 17; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // region + buf.Write(®ion); + + // dx + buf.Write(&dx); + + // dy + buf.Write(&dy); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "XFixes::TranslateRegion", false); +} + +Future XFixes::TranslateRegion(const Region& region, + const int16_t& dx, + const int16_t& dy) { + return XFixes::TranslateRegion( + XFixes::TranslateRegionRequest{region, dx, dy}); +} + +Future XFixes::RegionExtents( + const XFixes::RegionExtentsRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& source = request.source; + auto& destination = request.destination; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 18; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // source + buf.Write(&source); + + // destination + buf.Write(&destination); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "XFixes::RegionExtents", false); +} + +Future XFixes::RegionExtents(const Region& source, + const Region& destination) { + return XFixes::RegionExtents( + XFixes::RegionExtentsRequest{source, destination}); +} + +Future XFixes::FetchRegion( + const XFixes::FetchRegionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& region = request.region; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 19; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // region + buf.Write(®ion); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "XFixes::FetchRegion", false); +} + +Future XFixes::FetchRegion(const Region& region) { + return XFixes::FetchRegion(XFixes::FetchRegionRequest{region}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + XFixes::FetchRegionReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& extents = (*reply).extents; + auto& rectangles = (*reply).rectangles; + size_t rectangles_len = rectangles.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // extents + { + auto& x = extents.x; + auto& y = extents.y; + auto& width = extents.width; + auto& height = extents.height; + + // x + Read(&x, &buf); + + // y + Read(&y, &buf); + + // width + Read(&width, &buf); + + // height + Read(&height, &buf); + } + + // pad1 + Pad(&buf, 16); + + // rectangles + rectangles.resize((length) / (2)); + for (auto& rectangles_elem : rectangles) { + // rectangles_elem + { + auto& x = rectangles_elem.x; + auto& y = rectangles_elem.y; + auto& width = rectangles_elem.width; + auto& height = rectangles_elem.height; + + // x + Read(&x, &buf); + + // y + Read(&y, &buf); + + // width + Read(&width, &buf); + + // height + Read(&height, &buf); + } + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future XFixes::SetGCClipRegion( + const XFixes::SetGCClipRegionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& gc = request.gc; + auto& region = request.region; + auto& x_origin = request.x_origin; + auto& y_origin = request.y_origin; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 20; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // gc + buf.Write(&gc); + + // region + buf.Write(®ion); + + // x_origin + buf.Write(&x_origin); + + // y_origin + buf.Write(&y_origin); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "XFixes::SetGCClipRegion", false); +} + +Future XFixes::SetGCClipRegion(const GraphicsContext& gc, + const Region& region, + const int16_t& x_origin, + const int16_t& y_origin) { + return XFixes::SetGCClipRegion( + XFixes::SetGCClipRegionRequest{gc, region, x_origin, y_origin}); +} + +Future XFixes::SetWindowShapeRegion( + const XFixes::SetWindowShapeRegionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& dest = request.dest; + auto& dest_kind = request.dest_kind; + auto& x_offset = request.x_offset; + auto& y_offset = request.y_offset; + auto& region = request.region; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 21; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // dest + buf.Write(&dest); + + // dest_kind + uint8_t tmp8; + tmp8 = static_cast(dest_kind); + buf.Write(&tmp8); + + // pad0 + Pad(&buf, 3); + + // x_offset + buf.Write(&x_offset); + + // y_offset + buf.Write(&y_offset); + + // region + buf.Write(®ion); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "XFixes::SetWindowShapeRegion", + false); +} + +Future XFixes::SetWindowShapeRegion(const Window& dest, + const Shape::Sk& dest_kind, + const int16_t& x_offset, + const int16_t& y_offset, + const Region& region) { + return XFixes::SetWindowShapeRegion(XFixes::SetWindowShapeRegionRequest{ + dest, dest_kind, x_offset, y_offset, region}); +} + +Future XFixes::SetPictureClipRegion( + const XFixes::SetPictureClipRegionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& picture = request.picture; + auto& region = request.region; + auto& x_origin = request.x_origin; + auto& y_origin = request.y_origin; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 22; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // picture + buf.Write(&picture); + + // region + buf.Write(®ion); + + // x_origin + buf.Write(&x_origin); + + // y_origin + buf.Write(&y_origin); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "XFixes::SetPictureClipRegion", + false); +} + +Future XFixes::SetPictureClipRegion(const Render::Picture& picture, + const Region& region, + const int16_t& x_origin, + const int16_t& y_origin) { + return XFixes::SetPictureClipRegion( + XFixes::SetPictureClipRegionRequest{picture, region, x_origin, y_origin}); +} + +Future XFixes::SetCursorName( + const XFixes::SetCursorNameRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& cursor = request.cursor; + uint16_t nbytes{}; + auto& name = request.name; + size_t name_len = name.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 23; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // cursor + buf.Write(&cursor); + + // nbytes + nbytes = name.size(); + buf.Write(&nbytes); + + // pad0 + Pad(&buf, 2); + + // name + DCHECK_EQ(static_cast(nbytes), name.size()); + for (auto& name_elem : name) { + // name_elem + buf.Write(&name_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "XFixes::SetCursorName", false); +} + +Future XFixes::SetCursorName(const Cursor& cursor, + const std::string& name) { + return XFixes::SetCursorName(XFixes::SetCursorNameRequest{cursor, name}); +} + +Future XFixes::GetCursorName( + const XFixes::GetCursorNameRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& cursor = request.cursor; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 24; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // cursor + buf.Write(&cursor); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "XFixes::GetCursorName", false); +} + +Future XFixes::GetCursorName(const Cursor& cursor) { + return XFixes::GetCursorName(XFixes::GetCursorNameRequest{cursor}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + XFixes::GetCursorNameReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& atom = (*reply).atom; + uint16_t nbytes{}; + auto& name = (*reply).name; + size_t name_len = name.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // atom + Read(&atom, &buf); + + // nbytes + Read(&nbytes, &buf); + + // pad1 + Pad(&buf, 18); + + // name + name.resize(nbytes); + for (auto& name_elem : name) { + // name_elem + Read(&name_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future XFixes::GetCursorImageAndName( + const XFixes::GetCursorImageAndNameRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 25; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "XFixes::GetCursorImageAndName", false); +} + +Future XFixes::GetCursorImageAndName() { + return XFixes::GetCursorImageAndName(XFixes::GetCursorImageAndNameRequest{}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + XFixes::GetCursorImageAndNameReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& x = (*reply).x; + auto& y = (*reply).y; + auto& width = (*reply).width; + auto& height = (*reply).height; + auto& xhot = (*reply).xhot; + auto& yhot = (*reply).yhot; + auto& cursor_serial = (*reply).cursor_serial; + auto& cursor_atom = (*reply).cursor_atom; + uint16_t nbytes{}; + auto& cursor_image = (*reply).cursor_image; + size_t cursor_image_len = cursor_image.size(); + auto& name = (*reply).name; + size_t name_len = name.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // x + Read(&x, &buf); + + // y + Read(&y, &buf); + + // width + Read(&width, &buf); + + // height + Read(&height, &buf); + + // xhot + Read(&xhot, &buf); + + // yhot + Read(&yhot, &buf); + + // cursor_serial + Read(&cursor_serial, &buf); + + // cursor_atom + Read(&cursor_atom, &buf); + + // nbytes + Read(&nbytes, &buf); + + // pad1 + Pad(&buf, 2); + + // cursor_image + cursor_image.resize((width) * (height)); + for (auto& cursor_image_elem : cursor_image) { + // cursor_image_elem + Read(&cursor_image_elem, &buf); + } + + // name + name.resize(nbytes); + for (auto& name_elem : name) { + // name_elem + Read(&name_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future XFixes::ChangeCursor(const XFixes::ChangeCursorRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& source = request.source; + auto& destination = request.destination; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 26; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // source + buf.Write(&source); + + // destination + buf.Write(&destination); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "XFixes::ChangeCursor", false); +} + +Future XFixes::ChangeCursor(const Cursor& source, + const Cursor& destination) { + return XFixes::ChangeCursor(XFixes::ChangeCursorRequest{source, destination}); +} + +Future XFixes::ChangeCursorByName( + const XFixes::ChangeCursorByNameRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& src = request.src; + uint16_t nbytes{}; + auto& name = request.name; + size_t name_len = name.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 27; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // src + buf.Write(&src); + + // nbytes + nbytes = name.size(); + buf.Write(&nbytes); + + // pad0 + Pad(&buf, 2); + + // name + DCHECK_EQ(static_cast(nbytes), name.size()); + for (auto& name_elem : name) { + // name_elem + buf.Write(&name_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "XFixes::ChangeCursorByName", + false); +} + +Future XFixes::ChangeCursorByName(const Cursor& src, + const std::string& name) { + return XFixes::ChangeCursorByName( + XFixes::ChangeCursorByNameRequest{src, name}); +} + +Future XFixes::ExpandRegion(const XFixes::ExpandRegionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& source = request.source; + auto& destination = request.destination; + auto& left = request.left; + auto& right = request.right; + auto& top = request.top; + auto& bottom = request.bottom; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 28; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // source + buf.Write(&source); + + // destination + buf.Write(&destination); + + // left + buf.Write(&left); + + // right + buf.Write(&right); + + // top + buf.Write(&top); + + // bottom + buf.Write(&bottom); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "XFixes::ExpandRegion", false); +} + +Future XFixes::ExpandRegion(const Region& source, + const Region& destination, + const uint16_t& left, + const uint16_t& right, + const uint16_t& top, + const uint16_t& bottom) { + return XFixes::ExpandRegion(XFixes::ExpandRegionRequest{ + source, destination, left, right, top, bottom}); +} + +Future XFixes::HideCursor(const XFixes::HideCursorRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 29; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "XFixes::HideCursor", false); +} + +Future XFixes::HideCursor(const Window& window) { + return XFixes::HideCursor(XFixes::HideCursorRequest{window}); +} + +Future XFixes::ShowCursor(const XFixes::ShowCursorRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 30; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "XFixes::ShowCursor", false); +} + +Future XFixes::ShowCursor(const Window& window) { + return XFixes::ShowCursor(XFixes::ShowCursorRequest{window}); +} + +Future XFixes::CreatePointerBarrier( + const XFixes::CreatePointerBarrierRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& barrier = request.barrier; + auto& window = request.window; + auto& x1 = request.x1; + auto& y1 = request.y1; + auto& x2 = request.x2; + auto& y2 = request.y2; + auto& directions = request.directions; + uint16_t num_devices{}; + auto& devices = request.devices; + size_t devices_len = devices.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 31; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // barrier + buf.Write(&barrier); + + // window + buf.Write(&window); + + // x1 + buf.Write(&x1); + + // y1 + buf.Write(&y1); + + // x2 + buf.Write(&x2); + + // y2 + buf.Write(&y2); + + // directions + uint32_t tmp9; + tmp9 = static_cast(directions); + buf.Write(&tmp9); + + // pad0 + Pad(&buf, 2); + + // num_devices + num_devices = devices.size(); + buf.Write(&num_devices); + + // devices + DCHECK_EQ(static_cast(num_devices), devices.size()); + for (auto& devices_elem : devices) { + // devices_elem + buf.Write(&devices_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "XFixes::CreatePointerBarrier", + false); +} + +Future XFixes::CreatePointerBarrier( + const Barrier& barrier, + const Window& window, + const uint16_t& x1, + const uint16_t& y1, + const uint16_t& x2, + const uint16_t& y2, + const BarrierDirections& directions, + const std::vector& devices) { + return XFixes::CreatePointerBarrier(XFixes::CreatePointerBarrierRequest{ + barrier, window, x1, y1, x2, y2, directions, devices}); +} + +Future XFixes::DeletePointerBarrier( + const XFixes::DeletePointerBarrierRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& barrier = request.barrier; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 32; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // barrier + buf.Write(&barrier); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "XFixes::DeletePointerBarrier", + false); +} + +Future XFixes::DeletePointerBarrier(const Barrier& barrier) { + return XFixes::DeletePointerBarrier( + XFixes::DeletePointerBarrierRequest{barrier}); +} + +} // namespace x11 diff --git a/x/generated_protos/xfixes.h b/x/generated_protos/xfixes.h new file mode 100644 index 000000000000..b880acd9946f --- /dev/null +++ b/x/generated_protos/xfixes.h @@ -0,0 +1,795 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#ifndef UI_GFX_X_GENERATED_PROTOS_XFIXES_H_ +#define UI_GFX_X_GENERATED_PROTOS_XFIXES_H_ + +#include +#include +#include +#include +#include + +#include "base/component_export.h" +#include "base/files/scoped_file.h" +#include "base/memory/ref_counted_memory.h" +#include "base/memory/scoped_refptr.h" +#include "render.h" +#include "shape.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/gfx/x/error.h" +#include "ui/gfx/x/ref_counted_fd.h" +#include "xproto.h" + +namespace x11 { + +class Connection; + +template +struct Response; + +template +class Future; + +class COMPONENT_EXPORT(X11) XFixes { + public: + static constexpr unsigned major_version = 5; + static constexpr unsigned minor_version = 0; + + XFixes(Connection* connection, const x11::QueryExtensionReply& info); + + uint8_t present() const { return info_.present; } + uint8_t major_opcode() const { return info_.major_opcode; } + uint8_t first_event() const { return info_.first_event; } + uint8_t first_error() const { return info_.first_error; } + + Connection* connection() const { return connection_; } + + enum class SaveSetMode : int { + Insert = 0, + Delete = 1, + }; + + enum class SaveSetTarget : int { + Nearest = 0, + Root = 1, + }; + + enum class SaveSetMapping : int { + Map = 0, + Unmap = 1, + }; + + enum class SelectionEvent : int { + SetSelectionOwner = 0, + SelectionWindowDestroy = 1, + SelectionClientClose = 2, + }; + + enum class SelectionEventMask : int { + SetSelectionOwner = 1 << 0, + SelectionWindowDestroy = 1 << 1, + SelectionClientClose = 1 << 2, + }; + + enum class CursorNotify : int { + DisplayCursor = 0, + }; + + enum class CursorNotifyMask : int { + DisplayCursor = 1 << 0, + }; + + enum class Region : uint32_t { + None = 0, + }; + + enum class Barrier : uint32_t {}; + + enum class BarrierDirections : int { + PositiveX = 1 << 0, + PositiveY = 1 << 1, + NegativeX = 1 << 2, + NegativeY = 1 << 3, + }; + + struct SelectionNotifyEvent { + static constexpr int type_id = 18; + static constexpr uint8_t opcode = 0; + bool send_event{}; + SelectionEvent subtype{}; + uint16_t sequence{}; + Window window{}; + Window owner{}; + Atom selection{}; + Time timestamp{}; + Time selection_timestamp{}; + + x11::Window* GetWindow() { return reinterpret_cast(&window); } + }; + + struct CursorNotifyEvent { + static constexpr int type_id = 19; + static constexpr uint8_t opcode = 1; + bool send_event{}; + CursorNotify subtype{}; + uint16_t sequence{}; + Window window{}; + uint32_t cursor_serial{}; + Time timestamp{}; + Atom name{}; + + x11::Window* GetWindow() { return reinterpret_cast(&window); } + }; + + struct BadRegionError : public x11::Error { + uint16_t sequence{}; + + std::string ToString() const override; + }; + + struct QueryVersionRequest { + uint32_t client_major_version{}; + uint32_t client_minor_version{}; + }; + + struct QueryVersionReply { + uint16_t sequence{}; + uint32_t major_version{}; + uint32_t minor_version{}; + }; + + using QueryVersionResponse = Response; + + Future QueryVersion(const QueryVersionRequest& request); + + Future QueryVersion( + const uint32_t& client_major_version = {}, + const uint32_t& client_minor_version = {}); + + struct ChangeSaveSetRequest { + SaveSetMode mode{}; + SaveSetTarget target{}; + SaveSetMapping map{}; + Window window{}; + }; + + using ChangeSaveSetResponse = Response; + + Future ChangeSaveSet(const ChangeSaveSetRequest& request); + + Future ChangeSaveSet(const SaveSetMode& mode = {}, + const SaveSetTarget& target = {}, + const SaveSetMapping& map = {}, + const Window& window = {}); + + struct SelectSelectionInputRequest { + Window window{}; + Atom selection{}; + SelectionEventMask event_mask{}; + }; + + using SelectSelectionInputResponse = Response; + + Future SelectSelectionInput(const SelectSelectionInputRequest& request); + + Future SelectSelectionInput(const Window& window = {}, + const Atom& selection = {}, + const SelectionEventMask& event_mask = {}); + + struct SelectCursorInputRequest { + Window window{}; + CursorNotifyMask event_mask{}; + }; + + using SelectCursorInputResponse = Response; + + Future SelectCursorInput(const SelectCursorInputRequest& request); + + Future SelectCursorInput(const Window& window = {}, + const CursorNotifyMask& event_mask = {}); + + struct GetCursorImageRequest {}; + + struct GetCursorImageReply { + uint16_t sequence{}; + int16_t x{}; + int16_t y{}; + uint16_t width{}; + uint16_t height{}; + uint16_t xhot{}; + uint16_t yhot{}; + uint32_t cursor_serial{}; + std::vector cursor_image{}; + }; + + using GetCursorImageResponse = Response; + + Future GetCursorImage( + const GetCursorImageRequest& request); + + Future GetCursorImage(); + + struct CreateRegionRequest { + Region region{}; + std::vector rectangles{}; + }; + + using CreateRegionResponse = Response; + + Future CreateRegion(const CreateRegionRequest& request); + + Future CreateRegion(const Region& region = {}, + const std::vector& rectangles = {}); + + struct CreateRegionFromBitmapRequest { + Region region{}; + Pixmap bitmap{}; + }; + + using CreateRegionFromBitmapResponse = Response; + + Future CreateRegionFromBitmap( + const CreateRegionFromBitmapRequest& request); + + Future CreateRegionFromBitmap(const Region& region = {}, + const Pixmap& bitmap = {}); + + struct CreateRegionFromWindowRequest { + Region region{}; + Window window{}; + Shape::Sk kind{}; + }; + + using CreateRegionFromWindowResponse = Response; + + Future CreateRegionFromWindow( + const CreateRegionFromWindowRequest& request); + + Future CreateRegionFromWindow(const Region& region = {}, + const Window& window = {}, + const Shape::Sk& kind = {}); + + struct CreateRegionFromGCRequest { + Region region{}; + GraphicsContext gc{}; + }; + + using CreateRegionFromGCResponse = Response; + + Future CreateRegionFromGC(const CreateRegionFromGCRequest& request); + + Future CreateRegionFromGC(const Region& region = {}, + const GraphicsContext& gc = {}); + + struct CreateRegionFromPictureRequest { + Region region{}; + Render::Picture picture{}; + }; + + using CreateRegionFromPictureResponse = Response; + + Future CreateRegionFromPicture( + const CreateRegionFromPictureRequest& request); + + Future CreateRegionFromPicture(const Region& region = {}, + const Render::Picture& picture = {}); + + struct DestroyRegionRequest { + Region region{}; + }; + + using DestroyRegionResponse = Response; + + Future DestroyRegion(const DestroyRegionRequest& request); + + Future DestroyRegion(const Region& region = {}); + + struct SetRegionRequest { + Region region{}; + std::vector rectangles{}; + }; + + using SetRegionResponse = Response; + + Future SetRegion(const SetRegionRequest& request); + + Future SetRegion(const Region& region = {}, + const std::vector& rectangles = {}); + + struct CopyRegionRequest { + Region source{}; + Region destination{}; + }; + + using CopyRegionResponse = Response; + + Future CopyRegion(const CopyRegionRequest& request); + + Future CopyRegion(const Region& source = {}, + const Region& destination = {}); + + struct UnionRegionRequest { + Region source1{}; + Region source2{}; + Region destination{}; + }; + + using UnionRegionResponse = Response; + + Future UnionRegion(const UnionRegionRequest& request); + + Future UnionRegion(const Region& source1 = {}, + const Region& source2 = {}, + const Region& destination = {}); + + struct IntersectRegionRequest { + Region source1{}; + Region source2{}; + Region destination{}; + }; + + using IntersectRegionResponse = Response; + + Future IntersectRegion(const IntersectRegionRequest& request); + + Future IntersectRegion(const Region& source1 = {}, + const Region& source2 = {}, + const Region& destination = {}); + + struct SubtractRegionRequest { + Region source1{}; + Region source2{}; + Region destination{}; + }; + + using SubtractRegionResponse = Response; + + Future SubtractRegion(const SubtractRegionRequest& request); + + Future SubtractRegion(const Region& source1 = {}, + const Region& source2 = {}, + const Region& destination = {}); + + struct InvertRegionRequest { + Region source{}; + Rectangle bounds{}; + Region destination{}; + }; + + using InvertRegionResponse = Response; + + Future InvertRegion(const InvertRegionRequest& request); + + Future InvertRegion(const Region& source = {}, + const Rectangle& bounds = {{}, {}, {}, {}}, + const Region& destination = {}); + + struct TranslateRegionRequest { + Region region{}; + int16_t dx{}; + int16_t dy{}; + }; + + using TranslateRegionResponse = Response; + + Future TranslateRegion(const TranslateRegionRequest& request); + + Future TranslateRegion(const Region& region = {}, + const int16_t& dx = {}, + const int16_t& dy = {}); + + struct RegionExtentsRequest { + Region source{}; + Region destination{}; + }; + + using RegionExtentsResponse = Response; + + Future RegionExtents(const RegionExtentsRequest& request); + + Future RegionExtents(const Region& source = {}, + const Region& destination = {}); + + struct FetchRegionRequest { + Region region{}; + }; + + struct FetchRegionReply { + uint16_t sequence{}; + Rectangle extents{}; + std::vector rectangles{}; + }; + + using FetchRegionResponse = Response; + + Future FetchRegion(const FetchRegionRequest& request); + + Future FetchRegion(const Region& region = {}); + + struct SetGCClipRegionRequest { + GraphicsContext gc{}; + Region region{}; + int16_t x_origin{}; + int16_t y_origin{}; + }; + + using SetGCClipRegionResponse = Response; + + Future SetGCClipRegion(const SetGCClipRegionRequest& request); + + Future SetGCClipRegion(const GraphicsContext& gc = {}, + const Region& region = {}, + const int16_t& x_origin = {}, + const int16_t& y_origin = {}); + + struct SetWindowShapeRegionRequest { + Window dest{}; + Shape::Sk dest_kind{}; + int16_t x_offset{}; + int16_t y_offset{}; + Region region{}; + }; + + using SetWindowShapeRegionResponse = Response; + + Future SetWindowShapeRegion(const SetWindowShapeRegionRequest& request); + + Future SetWindowShapeRegion(const Window& dest = {}, + const Shape::Sk& dest_kind = {}, + const int16_t& x_offset = {}, + const int16_t& y_offset = {}, + const Region& region = {}); + + struct SetPictureClipRegionRequest { + Render::Picture picture{}; + Region region{}; + int16_t x_origin{}; + int16_t y_origin{}; + }; + + using SetPictureClipRegionResponse = Response; + + Future SetPictureClipRegion(const SetPictureClipRegionRequest& request); + + Future SetPictureClipRegion(const Render::Picture& picture = {}, + const Region& region = {}, + const int16_t& x_origin = {}, + const int16_t& y_origin = {}); + + struct SetCursorNameRequest { + Cursor cursor{}; + std::string name{}; + }; + + using SetCursorNameResponse = Response; + + Future SetCursorName(const SetCursorNameRequest& request); + + Future SetCursorName(const Cursor& cursor = {}, + const std::string& name = {}); + + struct GetCursorNameRequest { + Cursor cursor{}; + }; + + struct GetCursorNameReply { + uint16_t sequence{}; + Atom atom{}; + std::string name{}; + }; + + using GetCursorNameResponse = Response; + + Future GetCursorName(const GetCursorNameRequest& request); + + Future GetCursorName(const Cursor& cursor = {}); + + struct GetCursorImageAndNameRequest {}; + + struct GetCursorImageAndNameReply { + uint16_t sequence{}; + int16_t x{}; + int16_t y{}; + uint16_t width{}; + uint16_t height{}; + uint16_t xhot{}; + uint16_t yhot{}; + uint32_t cursor_serial{}; + Atom cursor_atom{}; + std::vector cursor_image{}; + std::string name{}; + }; + + using GetCursorImageAndNameResponse = Response; + + Future GetCursorImageAndName( + const GetCursorImageAndNameRequest& request); + + Future GetCursorImageAndName(); + + struct ChangeCursorRequest { + Cursor source{}; + Cursor destination{}; + }; + + using ChangeCursorResponse = Response; + + Future ChangeCursor(const ChangeCursorRequest& request); + + Future ChangeCursor(const Cursor& source = {}, + const Cursor& destination = {}); + + struct ChangeCursorByNameRequest { + Cursor src{}; + std::string name{}; + }; + + using ChangeCursorByNameResponse = Response; + + Future ChangeCursorByName(const ChangeCursorByNameRequest& request); + + Future ChangeCursorByName(const Cursor& src = {}, + const std::string& name = {}); + + struct ExpandRegionRequest { + Region source{}; + Region destination{}; + uint16_t left{}; + uint16_t right{}; + uint16_t top{}; + uint16_t bottom{}; + }; + + using ExpandRegionResponse = Response; + + Future ExpandRegion(const ExpandRegionRequest& request); + + Future ExpandRegion(const Region& source = {}, + const Region& destination = {}, + const uint16_t& left = {}, + const uint16_t& right = {}, + const uint16_t& top = {}, + const uint16_t& bottom = {}); + + struct HideCursorRequest { + Window window{}; + }; + + using HideCursorResponse = Response; + + Future HideCursor(const HideCursorRequest& request); + + Future HideCursor(const Window& window = {}); + + struct ShowCursorRequest { + Window window{}; + }; + + using ShowCursorResponse = Response; + + Future ShowCursor(const ShowCursorRequest& request); + + Future ShowCursor(const Window& window = {}); + + struct CreatePointerBarrierRequest { + Barrier barrier{}; + Window window{}; + uint16_t x1{}; + uint16_t y1{}; + uint16_t x2{}; + uint16_t y2{}; + BarrierDirections directions{}; + std::vector devices{}; + }; + + using CreatePointerBarrierResponse = Response; + + Future CreatePointerBarrier(const CreatePointerBarrierRequest& request); + + Future CreatePointerBarrier(const Barrier& barrier = {}, + const Window& window = {}, + const uint16_t& x1 = {}, + const uint16_t& y1 = {}, + const uint16_t& x2 = {}, + const uint16_t& y2 = {}, + const BarrierDirections& directions = {}, + const std::vector& devices = {}); + + struct DeletePointerBarrierRequest { + Barrier barrier{}; + }; + + using DeletePointerBarrierResponse = Response; + + Future DeletePointerBarrier(const DeletePointerBarrierRequest& request); + + Future DeletePointerBarrier(const Barrier& barrier = {}); + + private: + Connection* const connection_; + x11::QueryExtensionReply info_{}; +}; + +} // namespace x11 + +inline constexpr x11::XFixes::SaveSetMode operator|( + x11::XFixes::SaveSetMode l, + x11::XFixes::SaveSetMode r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::XFixes::SaveSetMode operator&( + x11::XFixes::SaveSetMode l, + x11::XFixes::SaveSetMode r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +inline constexpr x11::XFixes::SaveSetTarget operator|( + x11::XFixes::SaveSetTarget l, + x11::XFixes::SaveSetTarget r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::XFixes::SaveSetTarget operator&( + x11::XFixes::SaveSetTarget l, + x11::XFixes::SaveSetTarget r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +inline constexpr x11::XFixes::SaveSetMapping operator|( + x11::XFixes::SaveSetMapping l, + x11::XFixes::SaveSetMapping r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::XFixes::SaveSetMapping operator&( + x11::XFixes::SaveSetMapping l, + x11::XFixes::SaveSetMapping r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +inline constexpr x11::XFixes::SelectionEvent operator|( + x11::XFixes::SelectionEvent l, + x11::XFixes::SelectionEvent r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::XFixes::SelectionEvent operator&( + x11::XFixes::SelectionEvent l, + x11::XFixes::SelectionEvent r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +inline constexpr x11::XFixes::SelectionEventMask operator|( + x11::XFixes::SelectionEventMask l, + x11::XFixes::SelectionEventMask r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::XFixes::SelectionEventMask operator&( + x11::XFixes::SelectionEventMask l, + x11::XFixes::SelectionEventMask r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +inline constexpr x11::XFixes::CursorNotify operator|( + x11::XFixes::CursorNotify l, + x11::XFixes::CursorNotify r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::XFixes::CursorNotify operator&( + x11::XFixes::CursorNotify l, + x11::XFixes::CursorNotify r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +inline constexpr x11::XFixes::CursorNotifyMask operator|( + x11::XFixes::CursorNotifyMask l, + x11::XFixes::CursorNotifyMask r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::XFixes::CursorNotifyMask operator&( + x11::XFixes::CursorNotifyMask l, + x11::XFixes::CursorNotifyMask r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +inline constexpr x11::XFixes::Region operator|(x11::XFixes::Region l, + x11::XFixes::Region r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::XFixes::Region operator&(x11::XFixes::Region l, + x11::XFixes::Region r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +inline constexpr x11::XFixes::BarrierDirections operator|( + x11::XFixes::BarrierDirections l, + x11::XFixes::BarrierDirections r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) | + static_cast(r)); +} + +inline constexpr x11::XFixes::BarrierDirections operator&( + x11::XFixes::BarrierDirections l, + x11::XFixes::BarrierDirections r) { + using T = std::underlying_type_t; + return static_cast(static_cast(l) & + static_cast(r)); +} + +#endif // UI_GFX_X_GENERATED_PROTOS_XFIXES_H_ diff --git a/x/generated_protos/xinerama.cc b/x/generated_protos/xinerama.cc new file mode 100644 index 000000000000..e18471407bc1 --- /dev/null +++ b/x/generated_protos/xinerama.cc @@ -0,0 +1,508 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#include "xinerama.h" + +#include +#include + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "ui/gfx/x/xproto_internal.h" + +namespace x11 { + +Xinerama::Xinerama(Connection* connection, const x11::QueryExtensionReply& info) + : connection_(connection), info_(info) {} + +Future Xinerama::QueryVersion( + const Xinerama::QueryVersionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& major = request.major; + auto& minor = request.minor; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 0; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // major + buf.Write(&major); + + // minor + buf.Write(&minor); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Xinerama::QueryVersion", false); +} + +Future Xinerama::QueryVersion( + const uint8_t& major, + const uint8_t& minor) { + return Xinerama::QueryVersion(Xinerama::QueryVersionRequest{major, minor}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Xinerama::QueryVersionReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& major = (*reply).major; + auto& minor = (*reply).minor; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // major + Read(&major, &buf); + + // minor + Read(&minor, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Xinerama::GetState( + const Xinerama::GetStateRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 1; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Xinerama::GetState", false); +} + +Future Xinerama::GetState(const Window& window) { + return Xinerama::GetState(Xinerama::GetStateRequest{window}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Xinerama::GetStateReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& state = (*reply).state; + auto& sequence = (*reply).sequence; + auto& window = (*reply).window; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // state + Read(&state, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // window + Read(&window, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Xinerama::GetScreenCount( + const Xinerama::GetScreenCountRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 2; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Xinerama::GetScreenCount", false); +} + +Future Xinerama::GetScreenCount( + const Window& window) { + return Xinerama::GetScreenCount(Xinerama::GetScreenCountRequest{window}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Xinerama::GetScreenCountReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& screen_count = (*reply).screen_count; + auto& sequence = (*reply).sequence; + auto& window = (*reply).window; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // screen_count + Read(&screen_count, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // window + Read(&window, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Xinerama::GetScreenSize( + const Xinerama::GetScreenSizeRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + auto& screen = request.screen; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 3; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + // screen + buf.Write(&screen); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Xinerama::GetScreenSize", false); +} + +Future Xinerama::GetScreenSize( + const Window& window, + const uint32_t& screen) { + return Xinerama::GetScreenSize( + Xinerama::GetScreenSizeRequest{window, screen}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Xinerama::GetScreenSizeReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& width = (*reply).width; + auto& height = (*reply).height; + auto& window = (*reply).window; + auto& screen = (*reply).screen; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // width + Read(&width, &buf); + + // height + Read(&height, &buf); + + // window + Read(&window, &buf); + + // screen + Read(&screen, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Xinerama::IsActive( + const Xinerama::IsActiveRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 4; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Xinerama::IsActive", false); +} + +Future Xinerama::IsActive() { + return Xinerama::IsActive(Xinerama::IsActiveRequest{}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Xinerama::IsActiveReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& state = (*reply).state; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // state + Read(&state, &buf); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Xinerama::QueryScreens( + const Xinerama::QueryScreensRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 5; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Xinerama::QueryScreens", false); +} + +Future Xinerama::QueryScreens() { + return Xinerama::QueryScreens(Xinerama::QueryScreensRequest{}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Xinerama::QueryScreensReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint32_t number{}; + auto& screen_info = (*reply).screen_info; + size_t screen_info_len = screen_info.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // number + Read(&number, &buf); + + // pad1 + Pad(&buf, 20); + + // screen_info + screen_info.resize(number); + for (auto& screen_info_elem : screen_info) { + // screen_info_elem + { + auto& x_org = screen_info_elem.x_org; + auto& y_org = screen_info_elem.y_org; + auto& width = screen_info_elem.width; + auto& height = screen_info_elem.height; + + // x_org + Read(&x_org, &buf); + + // y_org + Read(&y_org, &buf); + + // width + Read(&width, &buf); + + // height + Read(&height, &buf); + } + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +} // namespace x11 diff --git a/x/generated_protos/xinerama.h b/x/generated_protos/xinerama.h new file mode 100644 index 000000000000..367d70c5e545 --- /dev/null +++ b/x/generated_protos/xinerama.h @@ -0,0 +1,194 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#ifndef UI_GFX_X_GENERATED_PROTOS_XINERAMA_H_ +#define UI_GFX_X_GENERATED_PROTOS_XINERAMA_H_ + +#include +#include +#include +#include +#include + +#include "base/component_export.h" +#include "base/files/scoped_file.h" +#include "base/memory/ref_counted_memory.h" +#include "base/memory/scoped_refptr.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/gfx/x/error.h" +#include "ui/gfx/x/ref_counted_fd.h" +#include "xproto.h" + +namespace x11 { + +class Connection; + +template +struct Response; + +template +class Future; + +class COMPONENT_EXPORT(X11) Xinerama { + public: + static constexpr unsigned major_version = 1; + static constexpr unsigned minor_version = 1; + + Xinerama(Connection* connection, const x11::QueryExtensionReply& info); + + uint8_t present() const { return info_.present; } + uint8_t major_opcode() const { return info_.major_opcode; } + uint8_t first_event() const { return info_.first_event; } + uint8_t first_error() const { return info_.first_error; } + + Connection* connection() const { return connection_; } + + struct ScreenInfo { + int16_t x_org{}; + int16_t y_org{}; + uint16_t width{}; + uint16_t height{}; + }; + + struct QueryVersionRequest { + uint8_t major{}; + uint8_t minor{}; + }; + + struct QueryVersionReply { + uint16_t sequence{}; + uint16_t major{}; + uint16_t minor{}; + }; + + using QueryVersionResponse = Response; + + Future QueryVersion(const QueryVersionRequest& request); + + Future QueryVersion(const uint8_t& major = {}, + const uint8_t& minor = {}); + + struct GetStateRequest { + Window window{}; + }; + + struct GetStateReply { + uint8_t state{}; + uint16_t sequence{}; + Window window{}; + }; + + using GetStateResponse = Response; + + Future GetState(const GetStateRequest& request); + + Future GetState(const Window& window = {}); + + struct GetScreenCountRequest { + Window window{}; + }; + + struct GetScreenCountReply { + uint8_t screen_count{}; + uint16_t sequence{}; + Window window{}; + }; + + using GetScreenCountResponse = Response; + + Future GetScreenCount( + const GetScreenCountRequest& request); + + Future GetScreenCount(const Window& window = {}); + + struct GetScreenSizeRequest { + Window window{}; + uint32_t screen{}; + }; + + struct GetScreenSizeReply { + uint16_t sequence{}; + uint32_t width{}; + uint32_t height{}; + Window window{}; + uint32_t screen{}; + }; + + using GetScreenSizeResponse = Response; + + Future GetScreenSize(const GetScreenSizeRequest& request); + + Future GetScreenSize(const Window& window = {}, + const uint32_t& screen = {}); + + struct IsActiveRequest {}; + + struct IsActiveReply { + uint16_t sequence{}; + uint32_t state{}; + }; + + using IsActiveResponse = Response; + + Future IsActive(const IsActiveRequest& request); + + Future IsActive(); + + struct QueryScreensRequest {}; + + struct QueryScreensReply { + uint16_t sequence{}; + std::vector screen_info{}; + }; + + using QueryScreensResponse = Response; + + Future QueryScreens(const QueryScreensRequest& request); + + Future QueryScreens(); + + private: + Connection* const connection_; + x11::QueryExtensionReply info_{}; +}; + +} // namespace x11 + +#endif // UI_GFX_X_GENERATED_PROTOS_XINERAMA_H_ diff --git a/x/generated_protos/xinput.cc b/x/generated_protos/xinput.cc new file mode 100644 index 000000000000..f98f5b23b84c --- /dev/null +++ b/x/generated_protos/xinput.cc @@ -0,0 +1,7683 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#include "xinput.h" + +#include +#include + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "ui/gfx/x/xproto_internal.h" + +namespace x11 { + +Input::Input(Connection* connection, const x11::QueryExtensionReply& info) + : connection_(connection), info_(info) {} + +template <> +COMPONENT_EXPORT(X11) +void ReadEvent(Input::DeviceValuatorEvent* event_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& device_id = (*event_).device_id; + auto& sequence = (*event_).sequence; + auto& device_state = (*event_).device_state; + auto& num_valuators = (*event_).num_valuators; + auto& first_valuator = (*event_).first_valuator; + auto& valuators = (*event_).valuators; + size_t valuators_len = valuators.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // device_id + Read(&device_id, &buf); + + // sequence + Read(&sequence, &buf); + + // device_state + Read(&device_state, &buf); + + // num_valuators + Read(&num_valuators, &buf); + + // first_valuator + Read(&first_valuator, &buf); + + // valuators + for (auto& valuators_elem : valuators) { + // valuators_elem + Read(&valuators_elem, &buf); + } + + DCHECK_LE(buf.offset, 32ul); +} + +template <> +COMPONENT_EXPORT(X11) +void ReadEvent(Input::LegacyDeviceEvent* event_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& detail = (*event_).detail; + auto& sequence = (*event_).sequence; + auto& time = (*event_).time; + auto& root = (*event_).root; + auto& event = (*event_).event; + auto& child = (*event_).child; + auto& root_x = (*event_).root_x; + auto& root_y = (*event_).root_y; + auto& event_x = (*event_).event_x; + auto& event_y = (*event_).event_y; + auto& state = (*event_).state; + auto& same_screen = (*event_).same_screen; + auto& device_id = (*event_).device_id; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // detail + Read(&detail, &buf); + + // sequence + Read(&sequence, &buf); + + // time + Read(&time, &buf); + + // root + Read(&root, &buf); + + // event + Read(&event, &buf); + + // child + Read(&child, &buf); + + // root_x + Read(&root_x, &buf); + + // root_y + Read(&root_y, &buf); + + // event_x + Read(&event_x, &buf); + + // event_y + Read(&event_y, &buf); + + // state + uint16_t tmp0; + Read(&tmp0, &buf); + state = static_cast(tmp0); + + // same_screen + Read(&same_screen, &buf); + + // device_id + Read(&device_id, &buf); + + DCHECK_LE(buf.offset, 32ul); +} + +template <> +COMPONENT_EXPORT(X11) +void ReadEvent(Input::DeviceFocusEvent* event_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& detail = (*event_).detail; + auto& sequence = (*event_).sequence; + auto& time = (*event_).time; + auto& window = (*event_).window; + auto& mode = (*event_).mode; + auto& device_id = (*event_).device_id; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // detail + uint8_t tmp1; + Read(&tmp1, &buf); + detail = static_cast(tmp1); + + // sequence + Read(&sequence, &buf); + + // time + Read(&time, &buf); + + // window + Read(&window, &buf); + + // mode + uint8_t tmp2; + Read(&tmp2, &buf); + mode = static_cast(tmp2); + + // device_id + Read(&device_id, &buf); + + // pad0 + Pad(&buf, 18); + + DCHECK_LE(buf.offset, 32ul); +} + +template <> +COMPONENT_EXPORT(X11) +void ReadEvent( + Input::DeviceStateNotifyEvent* event_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& device_id = (*event_).device_id; + auto& sequence = (*event_).sequence; + auto& time = (*event_).time; + auto& num_keys = (*event_).num_keys; + auto& num_buttons = (*event_).num_buttons; + auto& num_valuators = (*event_).num_valuators; + auto& classes_reported = (*event_).classes_reported; + auto& buttons = (*event_).buttons; + size_t buttons_len = buttons.size(); + auto& keys = (*event_).keys; + size_t keys_len = keys.size(); + auto& valuators = (*event_).valuators; + size_t valuators_len = valuators.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // device_id + Read(&device_id, &buf); + + // sequence + Read(&sequence, &buf); + + // time + Read(&time, &buf); + + // num_keys + Read(&num_keys, &buf); + + // num_buttons + Read(&num_buttons, &buf); + + // num_valuators + Read(&num_valuators, &buf); + + // classes_reported + uint8_t tmp3; + Read(&tmp3, &buf); + classes_reported = static_cast(tmp3); + + // buttons + for (auto& buttons_elem : buttons) { + // buttons_elem + Read(&buttons_elem, &buf); + } + + // keys + for (auto& keys_elem : keys) { + // keys_elem + Read(&keys_elem, &buf); + } + + // valuators + for (auto& valuators_elem : valuators) { + // valuators_elem + Read(&valuators_elem, &buf); + } + + DCHECK_LE(buf.offset, 32ul); +} + +template <> +COMPONENT_EXPORT(X11) +void ReadEvent( + Input::DeviceMappingNotifyEvent* event_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& device_id = (*event_).device_id; + auto& sequence = (*event_).sequence; + auto& request = (*event_).request; + auto& first_keycode = (*event_).first_keycode; + auto& count = (*event_).count; + auto& time = (*event_).time; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // device_id + Read(&device_id, &buf); + + // sequence + Read(&sequence, &buf); + + // request + uint8_t tmp4; + Read(&tmp4, &buf); + request = static_cast(tmp4); + + // first_keycode + Read(&first_keycode, &buf); + + // count + Read(&count, &buf); + + // pad0 + Pad(&buf, 1); + + // time + Read(&time, &buf); + + // pad1 + Pad(&buf, 20); + + DCHECK_LE(buf.offset, 32ul); +} + +template <> +COMPONENT_EXPORT(X11) +void ReadEvent( + Input::ChangeDeviceNotifyEvent* event_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& device_id = (*event_).device_id; + auto& sequence = (*event_).sequence; + auto& time = (*event_).time; + auto& request = (*event_).request; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // device_id + Read(&device_id, &buf); + + // sequence + Read(&sequence, &buf); + + // time + Read(&time, &buf); + + // request + uint8_t tmp5; + Read(&tmp5, &buf); + request = static_cast(tmp5); + + // pad0 + Pad(&buf, 23); + + DCHECK_LE(buf.offset, 32ul); +} + +template <> +COMPONENT_EXPORT(X11) +void ReadEvent( + Input::DeviceKeyStateNotifyEvent* event_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& device_id = (*event_).device_id; + auto& sequence = (*event_).sequence; + auto& keys = (*event_).keys; + size_t keys_len = keys.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // device_id + Read(&device_id, &buf); + + // sequence + Read(&sequence, &buf); + + // keys + for (auto& keys_elem : keys) { + // keys_elem + Read(&keys_elem, &buf); + } + + DCHECK_LE(buf.offset, 32ul); +} + +template <> +COMPONENT_EXPORT(X11) +void ReadEvent( + Input::DeviceButtonStateNotifyEvent* event_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& device_id = (*event_).device_id; + auto& sequence = (*event_).sequence; + auto& buttons = (*event_).buttons; + size_t buttons_len = buttons.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // device_id + Read(&device_id, &buf); + + // sequence + Read(&sequence, &buf); + + // buttons + for (auto& buttons_elem : buttons) { + // buttons_elem + Read(&buttons_elem, &buf); + } + + DCHECK_LE(buf.offset, 32ul); +} + +template <> +COMPONENT_EXPORT(X11) +void ReadEvent( + Input::DevicePresenceNotifyEvent* event_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*event_).sequence; + auto& time = (*event_).time; + auto& devchange = (*event_).devchange; + auto& device_id = (*event_).device_id; + auto& control = (*event_).control; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // time + Read(&time, &buf); + + // devchange + uint8_t tmp6; + Read(&tmp6, &buf); + devchange = static_cast(tmp6); + + // device_id + Read(&device_id, &buf); + + // control + Read(&control, &buf); + + // pad1 + Pad(&buf, 20); + + DCHECK_LE(buf.offset, 32ul); +} + +template <> +COMPONENT_EXPORT(X11) +void ReadEvent( + Input::DevicePropertyNotifyEvent* event_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& state = (*event_).state; + auto& sequence = (*event_).sequence; + auto& time = (*event_).time; + auto& property = (*event_).property; + auto& device_id = (*event_).device_id; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // state + uint8_t tmp7; + Read(&tmp7, &buf); + state = static_cast(tmp7); + + // sequence + Read(&sequence, &buf); + + // time + Read(&time, &buf); + + // property + Read(&property, &buf); + + // pad0 + Pad(&buf, 19); + + // device_id + Read(&device_id, &buf); + + DCHECK_LE(buf.offset, 32ul); +} + +template <> +COMPONENT_EXPORT(X11) +void ReadEvent(Input::DeviceChangedEvent* event_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*event_).sequence; + auto& deviceid = (*event_).deviceid; + auto& time = (*event_).time; + uint16_t num_classes{}; + auto& sourceid = (*event_).sourceid; + auto& reason = (*event_).reason; + auto& classes = (*event_).classes; + size_t classes_len = classes.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // extension + uint8_t extension; + Read(&extension, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // event_type + uint16_t event_type; + Read(&event_type, &buf); + + // deviceid + Read(&deviceid, &buf); + + // time + Read(&time, &buf); + + // num_classes + Read(&num_classes, &buf); + + // sourceid + Read(&sourceid, &buf); + + // reason + uint8_t tmp8; + Read(&tmp8, &buf); + reason = static_cast(tmp8); + + // pad0 + Pad(&buf, 11); + + // classes + classes.resize(num_classes); + for (auto& classes_elem : classes) { + // classes_elem + { + Input::DeviceClassType type{}; + auto& len = classes_elem.len; + auto& sourceid = classes_elem.sourceid; + auto& data = classes_elem; + + // type + uint16_t tmp9; + Read(&tmp9, &buf); + type = static_cast(tmp9); + + // len + Read(&len, &buf); + + // sourceid + Read(&sourceid, &buf); + + // data + auto data_expr = type; + if (CaseEq(data_expr, Input::DeviceClassType::Key)) { + data.key.emplace(); + uint16_t num_keys{}; + auto& keys = (*data.key).keys; + size_t keys_len = keys.size(); + + // num_keys + Read(&num_keys, &buf); + + // keys + keys.resize(num_keys); + for (auto& keys_elem : keys) { + // keys_elem + Read(&keys_elem, &buf); + } + } + if (CaseEq(data_expr, Input::DeviceClassType::Button)) { + data.button.emplace(); + uint16_t num_buttons{}; + auto& state = (*data.button).state; + size_t state_len = state.size(); + auto& labels = (*data.button).labels; + size_t labels_len = labels.size(); + + // num_buttons + Read(&num_buttons, &buf); + + // state + state.resize(((num_buttons) + (31)) / (32)); + for (auto& state_elem : state) { + // state_elem + Read(&state_elem, &buf); + } + + // labels + labels.resize(num_buttons); + for (auto& labels_elem : labels) { + // labels_elem + Read(&labels_elem, &buf); + } + } + if (CaseEq(data_expr, Input::DeviceClassType::Valuator)) { + data.valuator.emplace(); + auto& number = (*data.valuator).number; + auto& label = (*data.valuator).label; + auto& min = (*data.valuator).min; + auto& max = (*data.valuator).max; + auto& value = (*data.valuator).value; + auto& resolution = (*data.valuator).resolution; + auto& mode = (*data.valuator).mode; + + // number + Read(&number, &buf); + + // label + Read(&label, &buf); + + // min + { + auto& integral = min.integral; + auto& frac = min.frac; + + // integral + Read(&integral, &buf); + + // frac + Read(&frac, &buf); + } + + // max + { + auto& integral = max.integral; + auto& frac = max.frac; + + // integral + Read(&integral, &buf); + + // frac + Read(&frac, &buf); + } + + // value + { + auto& integral = value.integral; + auto& frac = value.frac; + + // integral + Read(&integral, &buf); + + // frac + Read(&frac, &buf); + } + + // resolution + Read(&resolution, &buf); + + // mode + uint8_t tmp10; + Read(&tmp10, &buf); + mode = static_cast(tmp10); + + // pad0 + Pad(&buf, 3); + } + if (CaseEq(data_expr, Input::DeviceClassType::Scroll)) { + data.scroll.emplace(); + auto& number = (*data.scroll).number; + auto& scroll_type = (*data.scroll).scroll_type; + auto& flags = (*data.scroll).flags; + auto& increment = (*data.scroll).increment; + + // number + Read(&number, &buf); + + // scroll_type + uint16_t tmp11; + Read(&tmp11, &buf); + scroll_type = static_cast(tmp11); + + // pad1 + Pad(&buf, 2); + + // flags + uint32_t tmp12; + Read(&tmp12, &buf); + flags = static_cast(tmp12); + + // increment + { + auto& integral = increment.integral; + auto& frac = increment.frac; + + // integral + Read(&integral, &buf); + + // frac + Read(&frac, &buf); + } + } + if (CaseEq(data_expr, Input::DeviceClassType::Touch)) { + data.touch.emplace(); + auto& mode = (*data.touch).mode; + auto& num_touches = (*data.touch).num_touches; + + // mode + uint8_t tmp13; + Read(&tmp13, &buf); + mode = static_cast(tmp13); + + // num_touches + Read(&num_touches, &buf); + } + } + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset, 32 + 4 * length); +} + +template <> +COMPONENT_EXPORT(X11) +void ReadEvent(Input::DeviceEvent* event_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*event_).sequence; + auto& deviceid = (*event_).deviceid; + auto& time = (*event_).time; + auto& detail = (*event_).detail; + auto& root = (*event_).root; + auto& event = (*event_).event; + auto& child = (*event_).child; + auto& root_x = (*event_).root_x; + auto& root_y = (*event_).root_y; + auto& event_x = (*event_).event_x; + auto& event_y = (*event_).event_y; + uint16_t buttons_len{}; + uint16_t valuators_len{}; + auto& sourceid = (*event_).sourceid; + auto& flags = (*event_).flags; + auto& mods = (*event_).mods; + auto& group = (*event_).group; + auto& button_mask = (*event_).button_mask; + size_t button_mask_len = button_mask.size(); + auto& valuator_mask = (*event_).valuator_mask; + size_t valuator_mask_len = valuator_mask.size(); + auto& axisvalues = (*event_).axisvalues; + size_t axisvalues_len = axisvalues.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // extension + uint8_t extension; + Read(&extension, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // event_type + uint16_t event_type; + Read(&event_type, &buf); + + // deviceid + Read(&deviceid, &buf); + + // time + Read(&time, &buf); + + // detail + Read(&detail, &buf); + + // root + Read(&root, &buf); + + // event + Read(&event, &buf); + + // child + Read(&child, &buf); + + // root_x + Read(&root_x, &buf); + + // root_y + Read(&root_y, &buf); + + // event_x + Read(&event_x, &buf); + + // event_y + Read(&event_y, &buf); + + // buttons_len + Read(&buttons_len, &buf); + + // valuators_len + Read(&valuators_len, &buf); + + // sourceid + Read(&sourceid, &buf); + + // pad0 + Pad(&buf, 2); + + // flags + uint32_t tmp14; + Read(&tmp14, &buf); + flags = static_cast(tmp14); + + // mods + { + auto& base = mods.base; + auto& latched = mods.latched; + auto& locked = mods.locked; + auto& effective = mods.effective; + + // base + Read(&base, &buf); + + // latched + Read(&latched, &buf); + + // locked + Read(&locked, &buf); + + // effective + Read(&effective, &buf); + } + + // group + { + auto& base = group.base; + auto& latched = group.latched; + auto& locked = group.locked; + auto& effective = group.effective; + + // base + Read(&base, &buf); + + // latched + Read(&latched, &buf); + + // locked + Read(&locked, &buf); + + // effective + Read(&effective, &buf); + } + + // button_mask + button_mask.resize(buttons_len); + for (auto& button_mask_elem : button_mask) { + // button_mask_elem + Read(&button_mask_elem, &buf); + } + + // valuator_mask + valuator_mask.resize(valuators_len); + for (auto& valuator_mask_elem : valuator_mask) { + // valuator_mask_elem + Read(&valuator_mask_elem, &buf); + } + + // axisvalues + auto sum15_ = SumOf([](auto& listelem_ref) { return PopCount(listelem_ref); }, + valuator_mask); + axisvalues.resize(sum15_); + for (auto& axisvalues_elem : axisvalues) { + // axisvalues_elem + { + auto& integral = axisvalues_elem.integral; + auto& frac = axisvalues_elem.frac; + + // integral + Read(&integral, &buf); + + // frac + Read(&frac, &buf); + } + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset, 32 + 4 * length); +} + +template <> +COMPONENT_EXPORT(X11) +void ReadEvent(Input::CrossingEvent* event_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*event_).sequence; + auto& deviceid = (*event_).deviceid; + auto& time = (*event_).time; + auto& sourceid = (*event_).sourceid; + auto& mode = (*event_).mode; + auto& detail = (*event_).detail; + auto& root = (*event_).root; + auto& event = (*event_).event; + auto& child = (*event_).child; + auto& root_x = (*event_).root_x; + auto& root_y = (*event_).root_y; + auto& event_x = (*event_).event_x; + auto& event_y = (*event_).event_y; + auto& same_screen = (*event_).same_screen; + auto& focus = (*event_).focus; + uint16_t buttons_len{}; + auto& mods = (*event_).mods; + auto& group = (*event_).group; + auto& buttons = (*event_).buttons; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // extension + uint8_t extension; + Read(&extension, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // event_type + uint16_t event_type; + Read(&event_type, &buf); + + // deviceid + Read(&deviceid, &buf); + + // time + Read(&time, &buf); + + // sourceid + Read(&sourceid, &buf); + + // mode + uint8_t tmp16; + Read(&tmp16, &buf); + mode = static_cast(tmp16); + + // detail + uint8_t tmp17; + Read(&tmp17, &buf); + detail = static_cast(tmp17); + + // root + Read(&root, &buf); + + // event + Read(&event, &buf); + + // child + Read(&child, &buf); + + // root_x + Read(&root_x, &buf); + + // root_y + Read(&root_y, &buf); + + // event_x + Read(&event_x, &buf); + + // event_y + Read(&event_y, &buf); + + // same_screen + Read(&same_screen, &buf); + + // focus + Read(&focus, &buf); + + // buttons_len + Read(&buttons_len, &buf); + + // mods + { + auto& base = mods.base; + auto& latched = mods.latched; + auto& locked = mods.locked; + auto& effective = mods.effective; + + // base + Read(&base, &buf); + + // latched + Read(&latched, &buf); + + // locked + Read(&locked, &buf); + + // effective + Read(&effective, &buf); + } + + // group + { + auto& base = group.base; + auto& latched = group.latched; + auto& locked = group.locked; + auto& effective = group.effective; + + // base + Read(&base, &buf); + + // latched + Read(&latched, &buf); + + // locked + Read(&locked, &buf); + + // effective + Read(&effective, &buf); + } + + // buttons + buttons.resize(buttons_len); + for (auto& buttons_elem : buttons) { + // buttons_elem + Read(&buttons_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset, 32 + 4 * length); +} + +template <> +COMPONENT_EXPORT(X11) +void ReadEvent(Input::HierarchyEvent* event_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*event_).sequence; + auto& deviceid = (*event_).deviceid; + auto& time = (*event_).time; + auto& flags = (*event_).flags; + uint16_t num_infos{}; + auto& infos = (*event_).infos; + size_t infos_len = infos.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // extension + uint8_t extension; + Read(&extension, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // event_type + uint16_t event_type; + Read(&event_type, &buf); + + // deviceid + Read(&deviceid, &buf); + + // time + Read(&time, &buf); + + // flags + uint32_t tmp18; + Read(&tmp18, &buf); + flags = static_cast(tmp18); + + // num_infos + Read(&num_infos, &buf); + + // pad0 + Pad(&buf, 10); + + // infos + infos.resize(num_infos); + for (auto& infos_elem : infos) { + // infos_elem + { + auto& deviceid = infos_elem.deviceid; + auto& attachment = infos_elem.attachment; + auto& type = infos_elem.type; + auto& enabled = infos_elem.enabled; + auto& flags = infos_elem.flags; + + // deviceid + Read(&deviceid, &buf); + + // attachment + Read(&attachment, &buf); + + // type + uint8_t tmp19; + Read(&tmp19, &buf); + type = static_cast(tmp19); + + // enabled + Read(&enabled, &buf); + + // pad0 + Pad(&buf, 2); + + // flags + uint32_t tmp20; + Read(&tmp20, &buf); + flags = static_cast(tmp20); + } + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset, 32 + 4 * length); +} + +template <> +COMPONENT_EXPORT(X11) +void ReadEvent(Input::PropertyEvent* event_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*event_).sequence; + auto& deviceid = (*event_).deviceid; + auto& time = (*event_).time; + auto& property = (*event_).property; + auto& what = (*event_).what; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // extension + uint8_t extension; + Read(&extension, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // event_type + uint16_t event_type; + Read(&event_type, &buf); + + // deviceid + Read(&deviceid, &buf); + + // time + Read(&time, &buf); + + // property + Read(&property, &buf); + + // what + uint8_t tmp21; + Read(&tmp21, &buf); + what = static_cast(tmp21); + + // pad0 + Pad(&buf, 11); + + Align(&buf, 4); + DCHECK_EQ(buf.offset, 32 + 4 * length); +} + +template <> +COMPONENT_EXPORT(X11) +void ReadEvent(Input::RawDeviceEvent* event_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*event_).sequence; + auto& deviceid = (*event_).deviceid; + auto& time = (*event_).time; + auto& detail = (*event_).detail; + auto& sourceid = (*event_).sourceid; + uint16_t valuators_len{}; + auto& flags = (*event_).flags; + auto& valuator_mask = (*event_).valuator_mask; + size_t valuator_mask_len = valuator_mask.size(); + auto& axisvalues = (*event_).axisvalues; + size_t axisvalues_len = axisvalues.size(); + auto& axisvalues_raw = (*event_).axisvalues_raw; + size_t axisvalues_raw_len = axisvalues_raw.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // extension + uint8_t extension; + Read(&extension, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // event_type + uint16_t event_type; + Read(&event_type, &buf); + + // deviceid + Read(&deviceid, &buf); + + // time + Read(&time, &buf); + + // detail + Read(&detail, &buf); + + // sourceid + Read(&sourceid, &buf); + + // valuators_len + Read(&valuators_len, &buf); + + // flags + uint32_t tmp22; + Read(&tmp22, &buf); + flags = static_cast(tmp22); + + // pad0 + Pad(&buf, 4); + + // valuator_mask + valuator_mask.resize(valuators_len); + for (auto& valuator_mask_elem : valuator_mask) { + // valuator_mask_elem + Read(&valuator_mask_elem, &buf); + } + + // axisvalues + auto sum23_ = SumOf([](auto& listelem_ref) { return PopCount(listelem_ref); }, + valuator_mask); + axisvalues.resize(sum23_); + for (auto& axisvalues_elem : axisvalues) { + // axisvalues_elem + { + auto& integral = axisvalues_elem.integral; + auto& frac = axisvalues_elem.frac; + + // integral + Read(&integral, &buf); + + // frac + Read(&frac, &buf); + } + } + + // axisvalues_raw + auto sum24_ = SumOf([](auto& listelem_ref) { return PopCount(listelem_ref); }, + valuator_mask); + axisvalues_raw.resize(sum24_); + for (auto& axisvalues_raw_elem : axisvalues_raw) { + // axisvalues_raw_elem + { + auto& integral = axisvalues_raw_elem.integral; + auto& frac = axisvalues_raw_elem.frac; + + // integral + Read(&integral, &buf); + + // frac + Read(&frac, &buf); + } + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset, 32 + 4 * length); +} + +template <> +COMPONENT_EXPORT(X11) +void ReadEvent(Input::TouchOwnershipEvent* event_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*event_).sequence; + auto& deviceid = (*event_).deviceid; + auto& time = (*event_).time; + auto& touchid = (*event_).touchid; + auto& root = (*event_).root; + auto& event = (*event_).event; + auto& child = (*event_).child; + auto& sourceid = (*event_).sourceid; + auto& flags = (*event_).flags; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // extension + uint8_t extension; + Read(&extension, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // event_type + uint16_t event_type; + Read(&event_type, &buf); + + // deviceid + Read(&deviceid, &buf); + + // time + Read(&time, &buf); + + // touchid + Read(&touchid, &buf); + + // root + Read(&root, &buf); + + // event + Read(&event, &buf); + + // child + Read(&child, &buf); + + // sourceid + Read(&sourceid, &buf); + + // pad0 + Pad(&buf, 2); + + // flags + uint32_t tmp25; + Read(&tmp25, &buf); + flags = static_cast(tmp25); + + // pad1 + Pad(&buf, 8); + + Align(&buf, 4); + DCHECK_EQ(buf.offset, 32 + 4 * length); +} + +template <> +COMPONENT_EXPORT(X11) +void ReadEvent(Input::BarrierEvent* event_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*event_).sequence; + auto& deviceid = (*event_).deviceid; + auto& time = (*event_).time; + auto& eventid = (*event_).eventid; + auto& root = (*event_).root; + auto& event = (*event_).event; + auto& barrier = (*event_).barrier; + auto& dtime = (*event_).dtime; + auto& flags = (*event_).flags; + auto& sourceid = (*event_).sourceid; + auto& root_x = (*event_).root_x; + auto& root_y = (*event_).root_y; + auto& dx = (*event_).dx; + auto& dy = (*event_).dy; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // extension + uint8_t extension; + Read(&extension, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // event_type + uint16_t event_type; + Read(&event_type, &buf); + + // deviceid + Read(&deviceid, &buf); + + // time + Read(&time, &buf); + + // eventid + Read(&eventid, &buf); + + // root + Read(&root, &buf); + + // event + Read(&event, &buf); + + // barrier + Read(&barrier, &buf); + + // dtime + Read(&dtime, &buf); + + // flags + uint32_t tmp26; + Read(&tmp26, &buf); + flags = static_cast(tmp26); + + // sourceid + Read(&sourceid, &buf); + + // pad0 + Pad(&buf, 2); + + // root_x + Read(&root_x, &buf); + + // root_y + Read(&root_y, &buf); + + // dx + { + auto& integral = dx.integral; + auto& frac = dx.frac; + + // integral + Read(&integral, &buf); + + // frac + Read(&frac, &buf); + } + + // dy + { + auto& integral = dy.integral; + auto& frac = dy.frac; + + // integral + Read(&integral, &buf); + + // frac + Read(&frac, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset, 32 + 4 * length); +} + +std::string Input::DeviceError::ToString() const { + std::stringstream ss_; + ss_ << "Input::DeviceError{"; + ss_ << ".sequence = " << static_cast(sequence); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError(Input::DeviceError* error_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + DCHECK_LE(buf.offset, 32ul); +} +std::string Input::EventError::ToString() const { + std::stringstream ss_; + ss_ << "Input::EventError{"; + ss_ << ".sequence = " << static_cast(sequence); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError(Input::EventError* error_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + DCHECK_LE(buf.offset, 32ul); +} +std::string Input::ModeError::ToString() const { + std::stringstream ss_; + ss_ << "Input::ModeError{"; + ss_ << ".sequence = " << static_cast(sequence); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError(Input::ModeError* error_, ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + DCHECK_LE(buf.offset, 32ul); +} +std::string Input::DeviceBusyError::ToString() const { + std::stringstream ss_; + ss_ << "Input::DeviceBusyError{"; + ss_ << ".sequence = " << static_cast(sequence); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError(Input::DeviceBusyError* error_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + DCHECK_LE(buf.offset, 32ul); +} +std::string Input::ClassError::ToString() const { + std::stringstream ss_; + ss_ << "Input::ClassError{"; + ss_ << ".sequence = " << static_cast(sequence); + ss_ << "}"; + return ss_.str(); +} + +template <> +void ReadError(Input::ClassError* error_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*error_).sequence; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // error_code + uint8_t error_code; + Read(&error_code, &buf); + + // sequence + Read(&sequence, &buf); + + DCHECK_LE(buf.offset, 32ul); +} +Future Input::GetExtensionVersion( + const Input::GetExtensionVersionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + uint16_t name_len{}; + auto& name = request.name; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 1; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // name_len + name_len = name.size(); + buf.Write(&name_len); + + // pad0 + Pad(&buf, 2); + + // name + DCHECK_EQ(static_cast(name_len), name.size()); + for (auto& name_elem : name) { + // name_elem + buf.Write(&name_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Input::GetExtensionVersion", false); +} + +Future Input::GetExtensionVersion( + const std::string& name) { + return Input::GetExtensionVersion(Input::GetExtensionVersionRequest{name}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Input::GetExtensionVersionReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& xi_reply_type = (*reply).xi_reply_type; + auto& sequence = (*reply).sequence; + auto& server_major = (*reply).server_major; + auto& server_minor = (*reply).server_minor; + auto& present = (*reply).present; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // xi_reply_type + Read(&xi_reply_type, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // server_major + Read(&server_major, &buf); + + // server_minor + Read(&server_minor, &buf); + + // present + Read(&present, &buf); + + // pad0 + Pad(&buf, 19); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Input::ListInputDevices( + const Input::ListInputDevicesRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 2; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Input::ListInputDevices", false); +} + +Future Input::ListInputDevices() { + return Input::ListInputDevices(Input::ListInputDevicesRequest{}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Input::ListInputDevicesReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& xi_reply_type = (*reply).xi_reply_type; + auto& sequence = (*reply).sequence; + uint8_t devices_len{}; + auto& devices = (*reply).devices; + auto& infos = (*reply).infos; + size_t infos_len = infos.size(); + auto& names = (*reply).names; + size_t names_len = names.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // xi_reply_type + Read(&xi_reply_type, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // devices_len + Read(&devices_len, &buf); + + // pad0 + Pad(&buf, 23); + + // devices + devices.resize(devices_len); + for (auto& devices_elem : devices) { + // devices_elem + { + auto& device_type = devices_elem.device_type; + auto& device_id = devices_elem.device_id; + auto& num_class_info = devices_elem.num_class_info; + auto& device_use = devices_elem.device_use; + + // device_type + Read(&device_type, &buf); + + // device_id + Read(&device_id, &buf); + + // num_class_info + Read(&num_class_info, &buf); + + // device_use + uint8_t tmp27; + Read(&tmp27, &buf); + device_use = static_cast(tmp27); + + // pad0 + Pad(&buf, 1); + } + } + + // infos + auto sum28_ = SumOf( + [](auto& listelem_ref) { + auto& device_type = listelem_ref.device_type; + auto& device_id = listelem_ref.device_id; + auto& num_class_info = listelem_ref.num_class_info; + auto& device_use = listelem_ref.device_use; + + return num_class_info; + }, + devices); + infos.resize(sum28_); + for (auto& infos_elem : infos) { + // infos_elem + { + Input::InputClass class_id{}; + auto& len = infos_elem.len; + auto& info = infos_elem; + + // class_id + uint8_t tmp29; + Read(&tmp29, &buf); + class_id = static_cast(tmp29); + + // len + Read(&len, &buf); + + // info + auto info_expr = class_id; + if (CaseEq(info_expr, Input::InputClass::Key)) { + info.key.emplace(); + auto& min_keycode = (*info.key).min_keycode; + auto& max_keycode = (*info.key).max_keycode; + auto& num_keys = (*info.key).num_keys; + + // min_keycode + Read(&min_keycode, &buf); + + // max_keycode + Read(&max_keycode, &buf); + + // num_keys + Read(&num_keys, &buf); + + // pad0 + Pad(&buf, 2); + } + if (CaseEq(info_expr, Input::InputClass::Button)) { + info.button.emplace(); + auto& num_buttons = (*info.button).num_buttons; + + // num_buttons + Read(&num_buttons, &buf); + } + if (CaseEq(info_expr, Input::InputClass::Valuator)) { + info.valuator.emplace(); + uint8_t axes_len{}; + auto& mode = (*info.valuator).mode; + auto& motion_size = (*info.valuator).motion_size; + auto& axes = (*info.valuator).axes; + + // axes_len + Read(&axes_len, &buf); + + // mode + uint8_t tmp30; + Read(&tmp30, &buf); + mode = static_cast(tmp30); + + // motion_size + Read(&motion_size, &buf); + + // axes + axes.resize(axes_len); + for (auto& axes_elem : axes) { + // axes_elem + { + auto& resolution = axes_elem.resolution; + auto& minimum = axes_elem.minimum; + auto& maximum = axes_elem.maximum; + + // resolution + Read(&resolution, &buf); + + // minimum + Read(&minimum, &buf); + + // maximum + Read(&maximum, &buf); + } + } + } + } + } + + // names + names.resize(devices_len); + for (auto& names_elem : names) { + // names_elem + { + uint8_t name_len{}; + auto& name = names_elem.name; + + // name_len + Read(&name_len, &buf); + + // name + name.resize(name_len); + for (auto& name_elem : name) { + // name_elem + Read(&name_elem, &buf); + } + } + } + + // pad1 + Pad(&buf, 1); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Input::OpenDevice( + const Input::OpenDeviceRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& device_id = request.device_id; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 3; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // device_id + buf.Write(&device_id); + + // pad0 + Pad(&buf, 3); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Input::OpenDevice", false); +} + +Future Input::OpenDevice(const uint8_t& device_id) { + return Input::OpenDevice(Input::OpenDeviceRequest{device_id}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Input::OpenDeviceReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& xi_reply_type = (*reply).xi_reply_type; + auto& sequence = (*reply).sequence; + uint8_t num_classes{}; + auto& class_info = (*reply).class_info; + size_t class_info_len = class_info.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // xi_reply_type + Read(&xi_reply_type, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // num_classes + Read(&num_classes, &buf); + + // pad0 + Pad(&buf, 23); + + // class_info + class_info.resize(num_classes); + for (auto& class_info_elem : class_info) { + // class_info_elem + { + auto& class_id = class_info_elem.class_id; + auto& event_type_base = class_info_elem.event_type_base; + + // class_id + uint8_t tmp31; + Read(&tmp31, &buf); + class_id = static_cast(tmp31); + + // event_type_base + Read(&event_type_base, &buf); + } + } + + // pad1 + Align(&buf, 4); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Input::CloseDevice(const Input::CloseDeviceRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& device_id = request.device_id; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 4; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // device_id + buf.Write(&device_id); + + // pad0 + Pad(&buf, 3); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Input::CloseDevice", false); +} + +Future Input::CloseDevice(const uint8_t& device_id) { + return Input::CloseDevice(Input::CloseDeviceRequest{device_id}); +} + +Future Input::SetDeviceMode( + const Input::SetDeviceModeRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& device_id = request.device_id; + auto& mode = request.mode; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 5; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // device_id + buf.Write(&device_id); + + // mode + uint8_t tmp32; + tmp32 = static_cast(mode); + buf.Write(&tmp32); + + // pad0 + Pad(&buf, 2); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Input::SetDeviceMode", false); +} + +Future Input::SetDeviceMode( + const uint8_t& device_id, + const ValuatorMode& mode) { + return Input::SetDeviceMode(Input::SetDeviceModeRequest{device_id, mode}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Input::SetDeviceModeReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& xi_reply_type = (*reply).xi_reply_type; + auto& sequence = (*reply).sequence; + auto& status = (*reply).status; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // xi_reply_type + Read(&xi_reply_type, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // status + uint8_t tmp33; + Read(&tmp33, &buf); + status = static_cast(tmp33); + + // pad0 + Pad(&buf, 23); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Input::SelectExtensionEvent( + const Input::SelectExtensionEventRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + uint16_t num_classes{}; + auto& classes = request.classes; + size_t classes_len = classes.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 6; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + // num_classes + num_classes = classes.size(); + buf.Write(&num_classes); + + // pad0 + Pad(&buf, 2); + + // classes + DCHECK_EQ(static_cast(num_classes), classes.size()); + for (auto& classes_elem : classes) { + // classes_elem + buf.Write(&classes_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Input::SelectExtensionEvent", + false); +} + +Future Input::SelectExtensionEvent( + const Window& window, + const std::vector& classes) { + return Input::SelectExtensionEvent( + Input::SelectExtensionEventRequest{window, classes}); +} + +Future +Input::GetSelectedExtensionEvents( + const Input::GetSelectedExtensionEventsRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 7; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Input::GetSelectedExtensionEvents", false); +} + +Future +Input::GetSelectedExtensionEvents(const Window& window) { + return Input::GetSelectedExtensionEvents( + Input::GetSelectedExtensionEventsRequest{window}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Input::GetSelectedExtensionEventsReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& xi_reply_type = (*reply).xi_reply_type; + auto& sequence = (*reply).sequence; + uint16_t num_this_classes{}; + uint16_t num_all_classes{}; + auto& this_classes = (*reply).this_classes; + size_t this_classes_len = this_classes.size(); + auto& all_classes = (*reply).all_classes; + size_t all_classes_len = all_classes.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // xi_reply_type + Read(&xi_reply_type, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // num_this_classes + Read(&num_this_classes, &buf); + + // num_all_classes + Read(&num_all_classes, &buf); + + // pad0 + Pad(&buf, 20); + + // this_classes + this_classes.resize(num_this_classes); + for (auto& this_classes_elem : this_classes) { + // this_classes_elem + Read(&this_classes_elem, &buf); + } + + // all_classes + all_classes.resize(num_all_classes); + for (auto& all_classes_elem : all_classes) { + // all_classes_elem + Read(&all_classes_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Input::ChangeDeviceDontPropagateList( + const Input::ChangeDeviceDontPropagateListRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + uint16_t num_classes{}; + auto& mode = request.mode; + auto& classes = request.classes; + size_t classes_len = classes.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 8; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + // num_classes + num_classes = classes.size(); + buf.Write(&num_classes); + + // mode + uint8_t tmp34; + tmp34 = static_cast(mode); + buf.Write(&tmp34); + + // pad0 + Pad(&buf, 1); + + // classes + DCHECK_EQ(static_cast(num_classes), classes.size()); + for (auto& classes_elem : classes) { + // classes_elem + buf.Write(&classes_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Input::ChangeDeviceDontPropagateList", false); +} + +Future Input::ChangeDeviceDontPropagateList( + const Window& window, + const PropagateMode& mode, + const std::vector& classes) { + return Input::ChangeDeviceDontPropagateList( + Input::ChangeDeviceDontPropagateListRequest{window, mode, classes}); +} + +Future +Input::GetDeviceDontPropagateList( + const Input::GetDeviceDontPropagateListRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 9; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Input::GetDeviceDontPropagateList", false); +} + +Future +Input::GetDeviceDontPropagateList(const Window& window) { + return Input::GetDeviceDontPropagateList( + Input::GetDeviceDontPropagateListRequest{window}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Input::GetDeviceDontPropagateListReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& xi_reply_type = (*reply).xi_reply_type; + auto& sequence = (*reply).sequence; + uint16_t num_classes{}; + auto& classes = (*reply).classes; + size_t classes_len = classes.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // xi_reply_type + Read(&xi_reply_type, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // num_classes + Read(&num_classes, &buf); + + // pad0 + Pad(&buf, 22); + + // classes + classes.resize(num_classes); + for (auto& classes_elem : classes) { + // classes_elem + Read(&classes_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Input::GetDeviceMotionEvents( + const Input::GetDeviceMotionEventsRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& start = request.start; + auto& stop = request.stop; + auto& device_id = request.device_id; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 10; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // start + buf.Write(&start); + + // stop + buf.Write(&stop); + + // device_id + buf.Write(&device_id); + + // pad0 + Pad(&buf, 3); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Input::GetDeviceMotionEvents", false); +} + +Future Input::GetDeviceMotionEvents( + const Time& start, + const Time& stop, + const uint8_t& device_id) { + return Input::GetDeviceMotionEvents( + Input::GetDeviceMotionEventsRequest{start, stop, device_id}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Input::GetDeviceMotionEventsReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& xi_reply_type = (*reply).xi_reply_type; + auto& sequence = (*reply).sequence; + uint32_t num_events{}; + auto& num_axes = (*reply).num_axes; + auto& device_mode = (*reply).device_mode; + auto& events = (*reply).events; + size_t events_len = events.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // xi_reply_type + Read(&xi_reply_type, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // num_events + Read(&num_events, &buf); + + // num_axes + Read(&num_axes, &buf); + + // device_mode + uint8_t tmp35; + Read(&tmp35, &buf); + device_mode = static_cast(tmp35); + + // pad0 + Pad(&buf, 18); + + // events + events.resize(num_events); + for (auto& events_elem : events) { + // events_elem + { + auto& time = events_elem.time; + auto& axisvalues = events_elem.axisvalues; + size_t axisvalues_len = axisvalues.size(); + + // time + Read(&time, &buf); + + // axisvalues + axisvalues.resize(num_axes); + for (auto& axisvalues_elem : axisvalues) { + // axisvalues_elem + Read(&axisvalues_elem, &buf); + } + } + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Input::ChangeKeyboardDevice( + const Input::ChangeKeyboardDeviceRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& device_id = request.device_id; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 11; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // device_id + buf.Write(&device_id); + + // pad0 + Pad(&buf, 3); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Input::ChangeKeyboardDevice", false); +} + +Future Input::ChangeKeyboardDevice( + const uint8_t& device_id) { + return Input::ChangeKeyboardDevice( + Input::ChangeKeyboardDeviceRequest{device_id}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Input::ChangeKeyboardDeviceReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& xi_reply_type = (*reply).xi_reply_type; + auto& sequence = (*reply).sequence; + auto& status = (*reply).status; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // xi_reply_type + Read(&xi_reply_type, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // status + uint8_t tmp36; + Read(&tmp36, &buf); + status = static_cast(tmp36); + + // pad0 + Pad(&buf, 23); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Input::ChangePointerDevice( + const Input::ChangePointerDeviceRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& x_axis = request.x_axis; + auto& y_axis = request.y_axis; + auto& device_id = request.device_id; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 12; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // x_axis + buf.Write(&x_axis); + + // y_axis + buf.Write(&y_axis); + + // device_id + buf.Write(&device_id); + + // pad0 + Pad(&buf, 1); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Input::ChangePointerDevice", false); +} + +Future Input::ChangePointerDevice( + const uint8_t& x_axis, + const uint8_t& y_axis, + const uint8_t& device_id) { + return Input::ChangePointerDevice( + Input::ChangePointerDeviceRequest{x_axis, y_axis, device_id}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Input::ChangePointerDeviceReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& xi_reply_type = (*reply).xi_reply_type; + auto& sequence = (*reply).sequence; + auto& status = (*reply).status; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // xi_reply_type + Read(&xi_reply_type, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // status + uint8_t tmp37; + Read(&tmp37, &buf); + status = static_cast(tmp37); + + // pad0 + Pad(&buf, 23); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Input::GrabDevice( + const Input::GrabDeviceRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& grab_window = request.grab_window; + auto& time = request.time; + uint16_t num_classes{}; + auto& this_device_mode = request.this_device_mode; + auto& other_device_mode = request.other_device_mode; + auto& owner_events = request.owner_events; + auto& device_id = request.device_id; + auto& classes = request.classes; + size_t classes_len = classes.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 13; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // grab_window + buf.Write(&grab_window); + + // time + buf.Write(&time); + + // num_classes + num_classes = classes.size(); + buf.Write(&num_classes); + + // this_device_mode + uint8_t tmp38; + tmp38 = static_cast(this_device_mode); + buf.Write(&tmp38); + + // other_device_mode + uint8_t tmp39; + tmp39 = static_cast(other_device_mode); + buf.Write(&tmp39); + + // owner_events + buf.Write(&owner_events); + + // device_id + buf.Write(&device_id); + + // pad0 + Pad(&buf, 2); + + // classes + DCHECK_EQ(static_cast(num_classes), classes.size()); + for (auto& classes_elem : classes) { + // classes_elem + buf.Write(&classes_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Input::GrabDevice", false); +} + +Future Input::GrabDevice( + const Window& grab_window, + const Time& time, + const GrabMode& this_device_mode, + const GrabMode& other_device_mode, + const uint8_t& owner_events, + const uint8_t& device_id, + const std::vector& classes) { + return Input::GrabDevice(Input::GrabDeviceRequest{ + grab_window, time, this_device_mode, other_device_mode, owner_events, + device_id, classes}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Input::GrabDeviceReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& xi_reply_type = (*reply).xi_reply_type; + auto& sequence = (*reply).sequence; + auto& status = (*reply).status; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // xi_reply_type + Read(&xi_reply_type, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // status + uint8_t tmp40; + Read(&tmp40, &buf); + status = static_cast(tmp40); + + // pad0 + Pad(&buf, 23); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Input::UngrabDevice(const Input::UngrabDeviceRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& time = request.time; + auto& device_id = request.device_id; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 14; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // time + buf.Write(&time); + + // device_id + buf.Write(&device_id); + + // pad0 + Pad(&buf, 3); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Input::UngrabDevice", false); +} + +Future Input::UngrabDevice(const Time& time, const uint8_t& device_id) { + return Input::UngrabDevice(Input::UngrabDeviceRequest{time, device_id}); +} + +Future Input::GrabDeviceKey(const Input::GrabDeviceKeyRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& grab_window = request.grab_window; + uint16_t num_classes{}; + auto& modifiers = request.modifiers; + auto& modifier_device = request.modifier_device; + auto& grabbed_device = request.grabbed_device; + auto& key = request.key; + auto& this_device_mode = request.this_device_mode; + auto& other_device_mode = request.other_device_mode; + auto& owner_events = request.owner_events; + auto& classes = request.classes; + size_t classes_len = classes.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 15; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // grab_window + buf.Write(&grab_window); + + // num_classes + num_classes = classes.size(); + buf.Write(&num_classes); + + // modifiers + uint16_t tmp41; + tmp41 = static_cast(modifiers); + buf.Write(&tmp41); + + // modifier_device + buf.Write(&modifier_device); + + // grabbed_device + buf.Write(&grabbed_device); + + // key + buf.Write(&key); + + // this_device_mode + uint8_t tmp42; + tmp42 = static_cast(this_device_mode); + buf.Write(&tmp42); + + // other_device_mode + uint8_t tmp43; + tmp43 = static_cast(other_device_mode); + buf.Write(&tmp43); + + // owner_events + buf.Write(&owner_events); + + // pad0 + Pad(&buf, 2); + + // classes + DCHECK_EQ(static_cast(num_classes), classes.size()); + for (auto& classes_elem : classes) { + // classes_elem + buf.Write(&classes_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Input::GrabDeviceKey", false); +} + +Future Input::GrabDeviceKey(const Window& grab_window, + const ModMask& modifiers, + const uint8_t& modifier_device, + const uint8_t& grabbed_device, + const uint8_t& key, + const GrabMode& this_device_mode, + const GrabMode& other_device_mode, + const uint8_t& owner_events, + const std::vector& classes) { + return Input::GrabDeviceKey(Input::GrabDeviceKeyRequest{ + grab_window, modifiers, modifier_device, grabbed_device, key, + this_device_mode, other_device_mode, owner_events, classes}); +} + +Future Input::UngrabDeviceKey( + const Input::UngrabDeviceKeyRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& grabWindow = request.grabWindow; + auto& modifiers = request.modifiers; + auto& modifier_device = request.modifier_device; + auto& key = request.key; + auto& grabbed_device = request.grabbed_device; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 16; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // grabWindow + buf.Write(&grabWindow); + + // modifiers + uint16_t tmp44; + tmp44 = static_cast(modifiers); + buf.Write(&tmp44); + + // modifier_device + buf.Write(&modifier_device); + + // key + buf.Write(&key); + + // grabbed_device + buf.Write(&grabbed_device); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Input::UngrabDeviceKey", false); +} + +Future Input::UngrabDeviceKey(const Window& grabWindow, + const ModMask& modifiers, + const uint8_t& modifier_device, + const uint8_t& key, + const uint8_t& grabbed_device) { + return Input::UngrabDeviceKey(Input::UngrabDeviceKeyRequest{ + grabWindow, modifiers, modifier_device, key, grabbed_device}); +} + +Future Input::GrabDeviceButton( + const Input::GrabDeviceButtonRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& grab_window = request.grab_window; + auto& grabbed_device = request.grabbed_device; + auto& modifier_device = request.modifier_device; + uint16_t num_classes{}; + auto& modifiers = request.modifiers; + auto& this_device_mode = request.this_device_mode; + auto& other_device_mode = request.other_device_mode; + auto& button = request.button; + auto& owner_events = request.owner_events; + auto& classes = request.classes; + size_t classes_len = classes.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 17; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // grab_window + buf.Write(&grab_window); + + // grabbed_device + buf.Write(&grabbed_device); + + // modifier_device + buf.Write(&modifier_device); + + // num_classes + num_classes = classes.size(); + buf.Write(&num_classes); + + // modifiers + uint16_t tmp45; + tmp45 = static_cast(modifiers); + buf.Write(&tmp45); + + // this_device_mode + uint8_t tmp46; + tmp46 = static_cast(this_device_mode); + buf.Write(&tmp46); + + // other_device_mode + uint8_t tmp47; + tmp47 = static_cast(other_device_mode); + buf.Write(&tmp47); + + // button + buf.Write(&button); + + // owner_events + buf.Write(&owner_events); + + // pad0 + Pad(&buf, 2); + + // classes + DCHECK_EQ(static_cast(num_classes), classes.size()); + for (auto& classes_elem : classes) { + // classes_elem + buf.Write(&classes_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Input::GrabDeviceButton", false); +} + +Future Input::GrabDeviceButton(const Window& grab_window, + const uint8_t& grabbed_device, + const uint8_t& modifier_device, + const ModMask& modifiers, + const GrabMode& this_device_mode, + const GrabMode& other_device_mode, + const uint8_t& button, + const uint8_t& owner_events, + const std::vector& classes) { + return Input::GrabDeviceButton(Input::GrabDeviceButtonRequest{ + grab_window, grabbed_device, modifier_device, modifiers, this_device_mode, + other_device_mode, button, owner_events, classes}); +} + +Future Input::UngrabDeviceButton( + const Input::UngrabDeviceButtonRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& grab_window = request.grab_window; + auto& modifiers = request.modifiers; + auto& modifier_device = request.modifier_device; + auto& button = request.button; + auto& grabbed_device = request.grabbed_device; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 18; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // grab_window + buf.Write(&grab_window); + + // modifiers + uint16_t tmp48; + tmp48 = static_cast(modifiers); + buf.Write(&tmp48); + + // modifier_device + buf.Write(&modifier_device); + + // button + buf.Write(&button); + + // grabbed_device + buf.Write(&grabbed_device); + + // pad0 + Pad(&buf, 3); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Input::UngrabDeviceButton", + false); +} + +Future Input::UngrabDeviceButton(const Window& grab_window, + const ModMask& modifiers, + const uint8_t& modifier_device, + const uint8_t& button, + const uint8_t& grabbed_device) { + return Input::UngrabDeviceButton(Input::UngrabDeviceButtonRequest{ + grab_window, modifiers, modifier_device, button, grabbed_device}); +} + +Future Input::AllowDeviceEvents( + const Input::AllowDeviceEventsRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& time = request.time; + auto& mode = request.mode; + auto& device_id = request.device_id; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 19; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // time + buf.Write(&time); + + // mode + uint8_t tmp49; + tmp49 = static_cast(mode); + buf.Write(&tmp49); + + // device_id + buf.Write(&device_id); + + // pad0 + Pad(&buf, 2); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Input::AllowDeviceEvents", + false); +} + +Future Input::AllowDeviceEvents(const Time& time, + const DeviceInputMode& mode, + const uint8_t& device_id) { + return Input::AllowDeviceEvents( + Input::AllowDeviceEventsRequest{time, mode, device_id}); +} + +Future Input::GetDeviceFocus( + const Input::GetDeviceFocusRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& device_id = request.device_id; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 20; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // device_id + buf.Write(&device_id); + + // pad0 + Pad(&buf, 3); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Input::GetDeviceFocus", false); +} + +Future Input::GetDeviceFocus( + const uint8_t& device_id) { + return Input::GetDeviceFocus(Input::GetDeviceFocusRequest{device_id}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Input::GetDeviceFocusReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& xi_reply_type = (*reply).xi_reply_type; + auto& sequence = (*reply).sequence; + auto& focus = (*reply).focus; + auto& time = (*reply).time; + auto& revert_to = (*reply).revert_to; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // xi_reply_type + Read(&xi_reply_type, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // focus + Read(&focus, &buf); + + // time + Read(&time, &buf); + + // revert_to + uint8_t tmp50; + Read(&tmp50, &buf); + revert_to = static_cast(tmp50); + + // pad0 + Pad(&buf, 15); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Input::SetDeviceFocus( + const Input::SetDeviceFocusRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& focus = request.focus; + auto& time = request.time; + auto& revert_to = request.revert_to; + auto& device_id = request.device_id; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 21; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // focus + buf.Write(&focus); + + // time + buf.Write(&time); + + // revert_to + uint8_t tmp51; + tmp51 = static_cast(revert_to); + buf.Write(&tmp51); + + // device_id + buf.Write(&device_id); + + // pad0 + Pad(&buf, 2); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Input::SetDeviceFocus", false); +} + +Future Input::SetDeviceFocus(const Window& focus, + const Time& time, + const InputFocus& revert_to, + const uint8_t& device_id) { + return Input::SetDeviceFocus( + Input::SetDeviceFocusRequest{focus, time, revert_to, device_id}); +} + +Future Input::GetFeedbackControl( + const Input::GetFeedbackControlRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& device_id = request.device_id; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 22; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // device_id + buf.Write(&device_id); + + // pad0 + Pad(&buf, 3); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Input::GetFeedbackControl", false); +} + +Future Input::GetFeedbackControl( + const uint8_t& device_id) { + return Input::GetFeedbackControl(Input::GetFeedbackControlRequest{device_id}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Input::GetFeedbackControlReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& xi_reply_type = (*reply).xi_reply_type; + auto& sequence = (*reply).sequence; + uint16_t num_feedbacks{}; + auto& feedbacks = (*reply).feedbacks; + size_t feedbacks_len = feedbacks.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // xi_reply_type + Read(&xi_reply_type, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // num_feedbacks + Read(&num_feedbacks, &buf); + + // pad0 + Pad(&buf, 22); + + // feedbacks + feedbacks.resize(num_feedbacks); + for (auto& feedbacks_elem : feedbacks) { + // feedbacks_elem + { + Input::FeedbackClass class_id{}; + auto& feedback_id = feedbacks_elem.feedback_id; + auto& len = feedbacks_elem.len; + auto& data = feedbacks_elem; + + // class_id + uint8_t tmp52; + Read(&tmp52, &buf); + class_id = static_cast(tmp52); + + // feedback_id + Read(&feedback_id, &buf); + + // len + Read(&len, &buf); + + // data + auto data_expr = class_id; + if (CaseEq(data_expr, Input::FeedbackClass::Keyboard)) { + data.keyboard.emplace(); + auto& pitch = (*data.keyboard).pitch; + auto& duration = (*data.keyboard).duration; + auto& led_mask = (*data.keyboard).led_mask; + auto& led_values = (*data.keyboard).led_values; + auto& global_auto_repeat = (*data.keyboard).global_auto_repeat; + auto& click = (*data.keyboard).click; + auto& percent = (*data.keyboard).percent; + auto& auto_repeats = (*data.keyboard).auto_repeats; + size_t auto_repeats_len = auto_repeats.size(); + + // pitch + Read(&pitch, &buf); + + // duration + Read(&duration, &buf); + + // led_mask + Read(&led_mask, &buf); + + // led_values + Read(&led_values, &buf); + + // global_auto_repeat + Read(&global_auto_repeat, &buf); + + // click + Read(&click, &buf); + + // percent + Read(&percent, &buf); + + // pad0 + Pad(&buf, 1); + + // auto_repeats + for (auto& auto_repeats_elem : auto_repeats) { + // auto_repeats_elem + Read(&auto_repeats_elem, &buf); + } + } + if (CaseEq(data_expr, Input::FeedbackClass::Pointer)) { + data.pointer.emplace(); + auto& accel_num = (*data.pointer).accel_num; + auto& accel_denom = (*data.pointer).accel_denom; + auto& threshold = (*data.pointer).threshold; + + // pad1 + Pad(&buf, 2); + + // accel_num + Read(&accel_num, &buf); + + // accel_denom + Read(&accel_denom, &buf); + + // threshold + Read(&threshold, &buf); + } + if (CaseEq(data_expr, Input::FeedbackClass::String)) { + data.string.emplace(); + auto& max_symbols = (*data.string).max_symbols; + uint16_t num_keysyms{}; + auto& keysyms = (*data.string).keysyms; + size_t keysyms_len = keysyms.size(); + + // max_symbols + Read(&max_symbols, &buf); + + // num_keysyms + Read(&num_keysyms, &buf); + + // keysyms + keysyms.resize(num_keysyms); + for (auto& keysyms_elem : keysyms) { + // keysyms_elem + Read(&keysyms_elem, &buf); + } + } + if (CaseEq(data_expr, Input::FeedbackClass::Integer)) { + data.integer.emplace(); + auto& resolution = (*data.integer).resolution; + auto& min_value = (*data.integer).min_value; + auto& max_value = (*data.integer).max_value; + + // resolution + Read(&resolution, &buf); + + // min_value + Read(&min_value, &buf); + + // max_value + Read(&max_value, &buf); + } + if (CaseEq(data_expr, Input::FeedbackClass::Led)) { + data.led.emplace(); + auto& led_mask = (*data.led).led_mask; + auto& led_values = (*data.led).led_values; + + // led_mask + Read(&led_mask, &buf); + + // led_values + Read(&led_values, &buf); + } + if (CaseEq(data_expr, Input::FeedbackClass::Bell)) { + data.bell.emplace(); + auto& percent = (*data.bell).percent; + auto& pitch = (*data.bell).pitch; + auto& duration = (*data.bell).duration; + + // percent + Read(&percent, &buf); + + // pad2 + Pad(&buf, 3); + + // pitch + Read(&pitch, &buf); + + // duration + Read(&duration, &buf); + } + } + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Input::ChangeFeedbackControl( + const Input::ChangeFeedbackControlRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& mask = request.mask; + auto& device_id = request.device_id; + auto& feedback_id = request.feedback_id; + auto& feedback = request.feedback; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 23; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // mask + uint32_t tmp53; + tmp53 = static_cast(mask); + buf.Write(&tmp53); + + // device_id + buf.Write(&device_id); + + // feedback_id + buf.Write(&feedback_id); + + // pad0 + Pad(&buf, 2); + + // feedback + { + FeedbackClass class_id{}; + auto& feedback_id = feedback.feedback_id; + auto& len = feedback.len; + auto& data = feedback; + + // class_id + SwitchVar(FeedbackClass::Keyboard, data.keyboard.has_value(), false, + &class_id); + SwitchVar(FeedbackClass::Pointer, data.pointer.has_value(), false, + &class_id); + SwitchVar(FeedbackClass::String, data.string.has_value(), false, &class_id); + SwitchVar(FeedbackClass::Integer, data.integer.has_value(), false, + &class_id); + SwitchVar(FeedbackClass::Led, data.led.has_value(), false, &class_id); + SwitchVar(FeedbackClass::Bell, data.bell.has_value(), false, &class_id); + uint8_t tmp54; + tmp54 = static_cast(class_id); + buf.Write(&tmp54); + + // feedback_id + buf.Write(&feedback_id); + + // len + buf.Write(&len); + + // data + auto data_expr = class_id; + if (CaseEq(data_expr, FeedbackClass::Keyboard)) { + auto& key = (*data.keyboard).key; + auto& auto_repeat_mode = (*data.keyboard).auto_repeat_mode; + auto& key_click_percent = (*data.keyboard).key_click_percent; + auto& bell_percent = (*data.keyboard).bell_percent; + auto& bell_pitch = (*data.keyboard).bell_pitch; + auto& bell_duration = (*data.keyboard).bell_duration; + auto& led_mask = (*data.keyboard).led_mask; + auto& led_values = (*data.keyboard).led_values; + + // key + buf.Write(&key); + + // auto_repeat_mode + buf.Write(&auto_repeat_mode); + + // key_click_percent + buf.Write(&key_click_percent); + + // bell_percent + buf.Write(&bell_percent); + + // bell_pitch + buf.Write(&bell_pitch); + + // bell_duration + buf.Write(&bell_duration); + + // led_mask + buf.Write(&led_mask); + + // led_values + buf.Write(&led_values); + } + if (CaseEq(data_expr, FeedbackClass::Pointer)) { + auto& num = (*data.pointer).num; + auto& denom = (*data.pointer).denom; + auto& threshold = (*data.pointer).threshold; + + // pad0 + Pad(&buf, 2); + + // num + buf.Write(&num); + + // denom + buf.Write(&denom); + + // threshold + buf.Write(&threshold); + } + if (CaseEq(data_expr, FeedbackClass::String)) { + uint16_t num_keysyms{}; + auto& keysyms = (*data.string).keysyms; + size_t keysyms_len = keysyms.size(); + + // pad1 + Pad(&buf, 2); + + // num_keysyms + num_keysyms = keysyms.size(); + buf.Write(&num_keysyms); + + // keysyms + DCHECK_EQ(static_cast(num_keysyms), keysyms.size()); + for (auto& keysyms_elem : keysyms) { + // keysyms_elem + buf.Write(&keysyms_elem); + } + } + if (CaseEq(data_expr, FeedbackClass::Integer)) { + auto& int_to_display = (*data.integer).int_to_display; + + // int_to_display + buf.Write(&int_to_display); + } + if (CaseEq(data_expr, FeedbackClass::Led)) { + auto& led_mask = (*data.led).led_mask; + auto& led_values = (*data.led).led_values; + + // led_mask + buf.Write(&led_mask); + + // led_values + buf.Write(&led_values); + } + if (CaseEq(data_expr, FeedbackClass::Bell)) { + auto& percent = (*data.bell).percent; + auto& pitch = (*data.bell).pitch; + auto& duration = (*data.bell).duration; + + // percent + buf.Write(&percent); + + // pad2 + Pad(&buf, 3); + + // pitch + buf.Write(&pitch); + + // duration + buf.Write(&duration); + } + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Input::ChangeFeedbackControl", + false); +} + +Future Input::ChangeFeedbackControl(const ChangeFeedbackControlMask& mask, + const uint8_t& device_id, + const uint8_t& feedback_id, + const FeedbackCtl& feedback) { + return Input::ChangeFeedbackControl(Input::ChangeFeedbackControlRequest{ + mask, device_id, feedback_id, feedback}); +} + +Future Input::GetDeviceKeyMapping( + const Input::GetDeviceKeyMappingRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& device_id = request.device_id; + auto& first_keycode = request.first_keycode; + auto& count = request.count; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 24; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // device_id + buf.Write(&device_id); + + // first_keycode + buf.Write(&first_keycode); + + // count + buf.Write(&count); + + // pad0 + Pad(&buf, 1); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Input::GetDeviceKeyMapping", false); +} + +Future Input::GetDeviceKeyMapping( + const uint8_t& device_id, + const KeyCode& first_keycode, + const uint8_t& count) { + return Input::GetDeviceKeyMapping( + Input::GetDeviceKeyMappingRequest{device_id, first_keycode, count}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Input::GetDeviceKeyMappingReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& xi_reply_type = (*reply).xi_reply_type; + auto& sequence = (*reply).sequence; + auto& keysyms_per_keycode = (*reply).keysyms_per_keycode; + auto& keysyms = (*reply).keysyms; + size_t keysyms_len = keysyms.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // xi_reply_type + Read(&xi_reply_type, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // keysyms_per_keycode + Read(&keysyms_per_keycode, &buf); + + // pad0 + Pad(&buf, 23); + + // keysyms + keysyms.resize(length); + for (auto& keysyms_elem : keysyms) { + // keysyms_elem + Read(&keysyms_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Input::ChangeDeviceKeyMapping( + const Input::ChangeDeviceKeyMappingRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& device_id = request.device_id; + auto& first_keycode = request.first_keycode; + auto& keysyms_per_keycode = request.keysyms_per_keycode; + auto& keycode_count = request.keycode_count; + auto& keysyms = request.keysyms; + size_t keysyms_len = keysyms.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 25; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // device_id + buf.Write(&device_id); + + // first_keycode + buf.Write(&first_keycode); + + // keysyms_per_keycode + buf.Write(&keysyms_per_keycode); + + // keycode_count + buf.Write(&keycode_count); + + // keysyms + DCHECK_EQ(static_cast((keycode_count) * (keysyms_per_keycode)), + keysyms.size()); + for (auto& keysyms_elem : keysyms) { + // keysyms_elem + buf.Write(&keysyms_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Input::ChangeDeviceKeyMapping", + false); +} + +Future Input::ChangeDeviceKeyMapping(const uint8_t& device_id, + const KeyCode& first_keycode, + const uint8_t& keysyms_per_keycode, + const uint8_t& keycode_count, + const std::vector& keysyms) { + return Input::ChangeDeviceKeyMapping(Input::ChangeDeviceKeyMappingRequest{ + device_id, first_keycode, keysyms_per_keycode, keycode_count, keysyms}); +} + +Future Input::GetDeviceModifierMapping( + const Input::GetDeviceModifierMappingRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& device_id = request.device_id; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 26; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // device_id + buf.Write(&device_id); + + // pad0 + Pad(&buf, 3); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Input::GetDeviceModifierMapping", false); +} + +Future Input::GetDeviceModifierMapping( + const uint8_t& device_id) { + return Input::GetDeviceModifierMapping( + Input::GetDeviceModifierMappingRequest{device_id}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Input::GetDeviceModifierMappingReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& xi_reply_type = (*reply).xi_reply_type; + auto& sequence = (*reply).sequence; + auto& keycodes_per_modifier = (*reply).keycodes_per_modifier; + auto& keymaps = (*reply).keymaps; + size_t keymaps_len = keymaps.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // xi_reply_type + Read(&xi_reply_type, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // keycodes_per_modifier + Read(&keycodes_per_modifier, &buf); + + // pad0 + Pad(&buf, 23); + + // keymaps + keymaps.resize((keycodes_per_modifier) * (8)); + for (auto& keymaps_elem : keymaps) { + // keymaps_elem + Read(&keymaps_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Input::SetDeviceModifierMapping( + const Input::SetDeviceModifierMappingRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& device_id = request.device_id; + auto& keycodes_per_modifier = request.keycodes_per_modifier; + auto& keymaps = request.keymaps; + size_t keymaps_len = keymaps.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 27; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // device_id + buf.Write(&device_id); + + // keycodes_per_modifier + buf.Write(&keycodes_per_modifier); + + // pad0 + Pad(&buf, 2); + + // keymaps + DCHECK_EQ(static_cast((keycodes_per_modifier) * (8)), keymaps.size()); + for (auto& keymaps_elem : keymaps) { + // keymaps_elem + buf.Write(&keymaps_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Input::SetDeviceModifierMapping", false); +} + +Future Input::SetDeviceModifierMapping( + const uint8_t& device_id, + const uint8_t& keycodes_per_modifier, + const std::vector& keymaps) { + return Input::SetDeviceModifierMapping(Input::SetDeviceModifierMappingRequest{ + device_id, keycodes_per_modifier, keymaps}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Input::SetDeviceModifierMappingReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& xi_reply_type = (*reply).xi_reply_type; + auto& sequence = (*reply).sequence; + auto& status = (*reply).status; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // xi_reply_type + Read(&xi_reply_type, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // status + uint8_t tmp55; + Read(&tmp55, &buf); + status = static_cast(tmp55); + + // pad0 + Pad(&buf, 23); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Input::GetDeviceButtonMapping( + const Input::GetDeviceButtonMappingRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& device_id = request.device_id; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 28; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // device_id + buf.Write(&device_id); + + // pad0 + Pad(&buf, 3); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Input::GetDeviceButtonMapping", false); +} + +Future Input::GetDeviceButtonMapping( + const uint8_t& device_id) { + return Input::GetDeviceButtonMapping( + Input::GetDeviceButtonMappingRequest{device_id}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Input::GetDeviceButtonMappingReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& xi_reply_type = (*reply).xi_reply_type; + auto& sequence = (*reply).sequence; + uint8_t map_size{}; + auto& map = (*reply).map; + size_t map_len = map.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // xi_reply_type + Read(&xi_reply_type, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // map_size + Read(&map_size, &buf); + + // pad0 + Pad(&buf, 23); + + // map + map.resize(map_size); + for (auto& map_elem : map) { + // map_elem + Read(&map_elem, &buf); + } + + // pad1 + Align(&buf, 4); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Input::SetDeviceButtonMapping( + const Input::SetDeviceButtonMappingRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& device_id = request.device_id; + uint8_t map_size{}; + auto& map = request.map; + size_t map_len = map.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 29; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // device_id + buf.Write(&device_id); + + // map_size + map_size = map.size(); + buf.Write(&map_size); + + // pad0 + Pad(&buf, 2); + + // map + DCHECK_EQ(static_cast(map_size), map.size()); + for (auto& map_elem : map) { + // map_elem + buf.Write(&map_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Input::SetDeviceButtonMapping", false); +} + +Future Input::SetDeviceButtonMapping( + const uint8_t& device_id, + const std::vector& map) { + return Input::SetDeviceButtonMapping( + Input::SetDeviceButtonMappingRequest{device_id, map}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Input::SetDeviceButtonMappingReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& xi_reply_type = (*reply).xi_reply_type; + auto& sequence = (*reply).sequence; + auto& status = (*reply).status; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // xi_reply_type + Read(&xi_reply_type, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // status + uint8_t tmp56; + Read(&tmp56, &buf); + status = static_cast(tmp56); + + // pad0 + Pad(&buf, 23); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Input::QueryDeviceState( + const Input::QueryDeviceStateRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& device_id = request.device_id; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 30; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // device_id + buf.Write(&device_id); + + // pad0 + Pad(&buf, 3); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Input::QueryDeviceState", false); +} + +Future Input::QueryDeviceState( + const uint8_t& device_id) { + return Input::QueryDeviceState(Input::QueryDeviceStateRequest{device_id}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Input::QueryDeviceStateReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& xi_reply_type = (*reply).xi_reply_type; + auto& sequence = (*reply).sequence; + uint8_t num_classes{}; + auto& classes = (*reply).classes; + size_t classes_len = classes.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // xi_reply_type + Read(&xi_reply_type, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // num_classes + Read(&num_classes, &buf); + + // pad0 + Pad(&buf, 23); + + // classes + classes.resize(num_classes); + for (auto& classes_elem : classes) { + // classes_elem + { + Input::InputClass class_id{}; + auto& len = classes_elem.len; + auto& data = classes_elem; + + // class_id + uint8_t tmp57; + Read(&tmp57, &buf); + class_id = static_cast(tmp57); + + // len + Read(&len, &buf); + + // data + auto data_expr = class_id; + if (CaseEq(data_expr, Input::InputClass::Key)) { + data.key.emplace(); + auto& num_keys = (*data.key).num_keys; + auto& keys = (*data.key).keys; + size_t keys_len = keys.size(); + + // num_keys + Read(&num_keys, &buf); + + // pad0 + Pad(&buf, 1); + + // keys + for (auto& keys_elem : keys) { + // keys_elem + Read(&keys_elem, &buf); + } + } + if (CaseEq(data_expr, Input::InputClass::Button)) { + data.button.emplace(); + auto& num_buttons = (*data.button).num_buttons; + auto& buttons = (*data.button).buttons; + size_t buttons_len = buttons.size(); + + // num_buttons + Read(&num_buttons, &buf); + + // pad1 + Pad(&buf, 1); + + // buttons + for (auto& buttons_elem : buttons) { + // buttons_elem + Read(&buttons_elem, &buf); + } + } + if (CaseEq(data_expr, Input::InputClass::Valuator)) { + data.valuator.emplace(); + uint8_t num_valuators{}; + auto& mode = (*data.valuator).mode; + auto& valuators = (*data.valuator).valuators; + size_t valuators_len = valuators.size(); + + // num_valuators + Read(&num_valuators, &buf); + + // mode + uint8_t tmp58; + Read(&tmp58, &buf); + mode = static_cast(tmp58); + + // valuators + valuators.resize(num_valuators); + for (auto& valuators_elem : valuators) { + // valuators_elem + Read(&valuators_elem, &buf); + } + } + } + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Input::DeviceBell(const Input::DeviceBellRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& device_id = request.device_id; + auto& feedback_id = request.feedback_id; + auto& feedback_class = request.feedback_class; + auto& percent = request.percent; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 32; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // device_id + buf.Write(&device_id); + + // feedback_id + buf.Write(&feedback_id); + + // feedback_class + buf.Write(&feedback_class); + + // percent + buf.Write(&percent); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Input::DeviceBell", false); +} + +Future Input::DeviceBell(const uint8_t& device_id, + const uint8_t& feedback_id, + const uint8_t& feedback_class, + const int8_t& percent) { + return Input::DeviceBell(Input::DeviceBellRequest{device_id, feedback_id, + feedback_class, percent}); +} + +Future Input::SetDeviceValuators( + const Input::SetDeviceValuatorsRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& device_id = request.device_id; + auto& first_valuator = request.first_valuator; + uint8_t num_valuators{}; + auto& valuators = request.valuators; + size_t valuators_len = valuators.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 33; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // device_id + buf.Write(&device_id); + + // first_valuator + buf.Write(&first_valuator); + + // num_valuators + num_valuators = valuators.size(); + buf.Write(&num_valuators); + + // pad0 + Pad(&buf, 1); + + // valuators + DCHECK_EQ(static_cast(num_valuators), valuators.size()); + for (auto& valuators_elem : valuators) { + // valuators_elem + buf.Write(&valuators_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Input::SetDeviceValuators", false); +} + +Future Input::SetDeviceValuators( + const uint8_t& device_id, + const uint8_t& first_valuator, + const std::vector& valuators) { + return Input::SetDeviceValuators( + Input::SetDeviceValuatorsRequest{device_id, first_valuator, valuators}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Input::SetDeviceValuatorsReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& xi_reply_type = (*reply).xi_reply_type; + auto& sequence = (*reply).sequence; + auto& status = (*reply).status; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // xi_reply_type + Read(&xi_reply_type, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // status + uint8_t tmp59; + Read(&tmp59, &buf); + status = static_cast(tmp59); + + // pad0 + Pad(&buf, 23); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Input::GetDeviceControl( + const Input::GetDeviceControlRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& control_id = request.control_id; + auto& device_id = request.device_id; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 34; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // control_id + uint16_t tmp60; + tmp60 = static_cast(control_id); + buf.Write(&tmp60); + + // device_id + buf.Write(&device_id); + + // pad0 + Pad(&buf, 1); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Input::GetDeviceControl", false); +} + +Future Input::GetDeviceControl( + const DeviceControl& control_id, + const uint8_t& device_id) { + return Input::GetDeviceControl( + Input::GetDeviceControlRequest{control_id, device_id}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Input::GetDeviceControlReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& xi_reply_type = (*reply).xi_reply_type; + auto& sequence = (*reply).sequence; + auto& status = (*reply).status; + auto& control = (*reply).control; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // xi_reply_type + Read(&xi_reply_type, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // status + Read(&status, &buf); + + // pad0 + Pad(&buf, 23); + + // control + { + Input::DeviceControl control_id{}; + auto& len = control.len; + auto& data = control; + + // control_id + uint16_t tmp61; + Read(&tmp61, &buf); + control_id = static_cast(tmp61); + + // len + Read(&len, &buf); + + // data + auto data_expr = control_id; + if (CaseEq(data_expr, Input::DeviceControl::resolution)) { + data.resolution.emplace(); + uint32_t num_valuators{}; + auto& resolution_values = (*data.resolution).resolution_values; + size_t resolution_values_len = resolution_values.size(); + auto& resolution_min = (*data.resolution).resolution_min; + size_t resolution_min_len = resolution_min.size(); + auto& resolution_max = (*data.resolution).resolution_max; + size_t resolution_max_len = resolution_max.size(); + + // num_valuators + Read(&num_valuators, &buf); + + // resolution_values + resolution_values.resize(num_valuators); + for (auto& resolution_values_elem : resolution_values) { + // resolution_values_elem + Read(&resolution_values_elem, &buf); + } + + // resolution_min + resolution_min.resize(num_valuators); + for (auto& resolution_min_elem : resolution_min) { + // resolution_min_elem + Read(&resolution_min_elem, &buf); + } + + // resolution_max + resolution_max.resize(num_valuators); + for (auto& resolution_max_elem : resolution_max) { + // resolution_max_elem + Read(&resolution_max_elem, &buf); + } + } + if (CaseEq(data_expr, Input::DeviceControl::abs_calib)) { + data.abs_calib.emplace(); + auto& min_x = (*data.abs_calib).min_x; + auto& max_x = (*data.abs_calib).max_x; + auto& min_y = (*data.abs_calib).min_y; + auto& max_y = (*data.abs_calib).max_y; + auto& flip_x = (*data.abs_calib).flip_x; + auto& flip_y = (*data.abs_calib).flip_y; + auto& rotation = (*data.abs_calib).rotation; + auto& button_threshold = (*data.abs_calib).button_threshold; + + // min_x + Read(&min_x, &buf); + + // max_x + Read(&max_x, &buf); + + // min_y + Read(&min_y, &buf); + + // max_y + Read(&max_y, &buf); + + // flip_x + Read(&flip_x, &buf); + + // flip_y + Read(&flip_y, &buf); + + // rotation + Read(&rotation, &buf); + + // button_threshold + Read(&button_threshold, &buf); + } + if (CaseEq(data_expr, Input::DeviceControl::core)) { + data.core.emplace(); + auto& status = (*data.core).status; + auto& iscore = (*data.core).iscore; + + // status + Read(&status, &buf); + + // iscore + Read(&iscore, &buf); + + // pad0 + Pad(&buf, 2); + } + if (CaseEq(data_expr, Input::DeviceControl::enable)) { + data.enable.emplace(); + auto& enable = (*data.enable).enable; + + // enable + Read(&enable, &buf); + + // pad1 + Pad(&buf, 3); + } + if (CaseEq(data_expr, Input::DeviceControl::abs_area)) { + data.abs_area.emplace(); + auto& offset_x = (*data.abs_area).offset_x; + auto& offset_y = (*data.abs_area).offset_y; + auto& width = (*data.abs_area).width; + auto& height = (*data.abs_area).height; + auto& screen = (*data.abs_area).screen; + auto& following = (*data.abs_area).following; + + // offset_x + Read(&offset_x, &buf); + + // offset_y + Read(&offset_y, &buf); + + // width + Read(&width, &buf); + + // height + Read(&height, &buf); + + // screen + Read(&screen, &buf); + + // following + Read(&following, &buf); + } + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Input::ChangeDeviceControl( + const Input::ChangeDeviceControlRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& control_id = request.control_id; + auto& device_id = request.device_id; + auto& control = request.control; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 35; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // control_id + uint16_t tmp62; + tmp62 = static_cast(control_id); + buf.Write(&tmp62); + + // device_id + buf.Write(&device_id); + + // pad0 + Pad(&buf, 1); + + // control + { + DeviceControl control_id{}; + auto& len = control.len; + auto& data = control; + + // control_id + SwitchVar(DeviceControl::resolution, data.resolution.has_value(), false, + &control_id); + SwitchVar(DeviceControl::abs_calib, data.abs_calib.has_value(), false, + &control_id); + SwitchVar(DeviceControl::core, data.core.has_value(), false, &control_id); + SwitchVar(DeviceControl::enable, data.enable.has_value(), false, + &control_id); + SwitchVar(DeviceControl::abs_area, data.abs_area.has_value(), false, + &control_id); + uint16_t tmp63; + tmp63 = static_cast(control_id); + buf.Write(&tmp63); + + // len + buf.Write(&len); + + // data + auto data_expr = control_id; + if (CaseEq(data_expr, DeviceControl::resolution)) { + auto& first_valuator = (*data.resolution).first_valuator; + uint8_t num_valuators{}; + auto& resolution_values = (*data.resolution).resolution_values; + size_t resolution_values_len = resolution_values.size(); + + // first_valuator + buf.Write(&first_valuator); + + // num_valuators + num_valuators = resolution_values.size(); + buf.Write(&num_valuators); + + // pad0 + Pad(&buf, 2); + + // resolution_values + DCHECK_EQ(static_cast(num_valuators), resolution_values.size()); + for (auto& resolution_values_elem : resolution_values) { + // resolution_values_elem + buf.Write(&resolution_values_elem); + } + } + if (CaseEq(data_expr, DeviceControl::abs_calib)) { + auto& min_x = (*data.abs_calib).min_x; + auto& max_x = (*data.abs_calib).max_x; + auto& min_y = (*data.abs_calib).min_y; + auto& max_y = (*data.abs_calib).max_y; + auto& flip_x = (*data.abs_calib).flip_x; + auto& flip_y = (*data.abs_calib).flip_y; + auto& rotation = (*data.abs_calib).rotation; + auto& button_threshold = (*data.abs_calib).button_threshold; + + // min_x + buf.Write(&min_x); + + // max_x + buf.Write(&max_x); + + // min_y + buf.Write(&min_y); + + // max_y + buf.Write(&max_y); + + // flip_x + buf.Write(&flip_x); + + // flip_y + buf.Write(&flip_y); + + // rotation + buf.Write(&rotation); + + // button_threshold + buf.Write(&button_threshold); + } + if (CaseEq(data_expr, DeviceControl::core)) { + auto& status = (*data.core).status; + + // status + buf.Write(&status); + + // pad1 + Pad(&buf, 3); + } + if (CaseEq(data_expr, DeviceControl::enable)) { + auto& enable = (*data.enable).enable; + + // enable + buf.Write(&enable); + + // pad2 + Pad(&buf, 3); + } + if (CaseEq(data_expr, DeviceControl::abs_area)) { + auto& offset_x = (*data.abs_area).offset_x; + auto& offset_y = (*data.abs_area).offset_y; + auto& width = (*data.abs_area).width; + auto& height = (*data.abs_area).height; + auto& screen = (*data.abs_area).screen; + auto& following = (*data.abs_area).following; + + // offset_x + buf.Write(&offset_x); + + // offset_y + buf.Write(&offset_y); + + // width + buf.Write(&width); + + // height + buf.Write(&height); + + // screen + buf.Write(&screen); + + // following + buf.Write(&following); + } + } + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Input::ChangeDeviceControl", false); +} + +Future Input::ChangeDeviceControl( + const DeviceControl& control_id, + const uint8_t& device_id, + const DeviceCtl& control) { + return Input::ChangeDeviceControl( + Input::ChangeDeviceControlRequest{control_id, device_id, control}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Input::ChangeDeviceControlReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& xi_reply_type = (*reply).xi_reply_type; + auto& sequence = (*reply).sequence; + auto& status = (*reply).status; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // xi_reply_type + Read(&xi_reply_type, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // status + Read(&status, &buf); + + // pad0 + Pad(&buf, 23); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Input::ListDeviceProperties( + const Input::ListDevicePropertiesRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& device_id = request.device_id; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 36; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // device_id + buf.Write(&device_id); + + // pad0 + Pad(&buf, 3); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Input::ListDeviceProperties", false); +} + +Future Input::ListDeviceProperties( + const uint8_t& device_id) { + return Input::ListDeviceProperties( + Input::ListDevicePropertiesRequest{device_id}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Input::ListDevicePropertiesReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& xi_reply_type = (*reply).xi_reply_type; + auto& sequence = (*reply).sequence; + uint16_t num_atoms{}; + auto& atoms = (*reply).atoms; + size_t atoms_len = atoms.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // xi_reply_type + Read(&xi_reply_type, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // num_atoms + Read(&num_atoms, &buf); + + // pad0 + Pad(&buf, 22); + + // atoms + atoms.resize(num_atoms); + for (auto& atoms_elem : atoms) { + // atoms_elem + Read(&atoms_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Input::ChangeDeviceProperty( + const Input::ChangeDevicePropertyRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& property = request.property; + auto& type = request.type; + auto& device_id = request.device_id; + PropertyFormat format{}; + auto& mode = request.mode; + auto& num_items = request.num_items; + auto& items = request; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 37; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // property + buf.Write(&property); + + // type + buf.Write(&type); + + // device_id + buf.Write(&device_id); + + // format + SwitchVar(PropertyFormat::c_8Bits, items.data8.has_value(), false, &format); + SwitchVar(PropertyFormat::c_16Bits, items.data16.has_value(), false, &format); + SwitchVar(PropertyFormat::c_32Bits, items.data32.has_value(), false, &format); + uint8_t tmp64; + tmp64 = static_cast(format); + buf.Write(&tmp64); + + // mode + uint8_t tmp65; + tmp65 = static_cast(mode); + buf.Write(&tmp65); + + // pad0 + Pad(&buf, 1); + + // num_items + buf.Write(&num_items); + + // items + auto items_expr = format; + if (CaseEq(items_expr, PropertyFormat::c_8Bits)) { + auto& data8 = *items.data8; + size_t data8_len = data8.size(); + + // data8 + DCHECK_EQ(static_cast(num_items), data8.size()); + for (auto& data8_elem : data8) { + // data8_elem + buf.Write(&data8_elem); + } + + // pad1 + Align(&buf, 4); + } + if (CaseEq(items_expr, PropertyFormat::c_16Bits)) { + auto& data16 = *items.data16; + size_t data16_len = data16.size(); + + // data16 + DCHECK_EQ(static_cast(num_items), data16.size()); + for (auto& data16_elem : data16) { + // data16_elem + buf.Write(&data16_elem); + } + + // pad2 + Align(&buf, 4); + } + if (CaseEq(items_expr, PropertyFormat::c_32Bits)) { + auto& data32 = *items.data32; + size_t data32_len = data32.size(); + + // data32 + DCHECK_EQ(static_cast(num_items), data32.size()); + for (auto& data32_elem : data32) { + // data32_elem + buf.Write(&data32_elem); + } + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Input::ChangeDeviceProperty", + false); +} + +Future Input::ChangeDeviceProperty( + const Atom& property, + const Atom& type, + const uint8_t& device_id, + const PropMode& mode, + const uint32_t& num_items, + const absl::optional>& data8, + const absl::optional>& data16, + const absl::optional>& data32) { + return Input::ChangeDeviceProperty(Input::ChangeDevicePropertyRequest{ + property, type, device_id, mode, num_items, data8, data16, data32}); +} + +Future Input::DeleteDeviceProperty( + const Input::DeleteDevicePropertyRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& property = request.property; + auto& device_id = request.device_id; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 38; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // property + buf.Write(&property); + + // device_id + buf.Write(&device_id); + + // pad0 + Pad(&buf, 3); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Input::DeleteDeviceProperty", + false); +} + +Future Input::DeleteDeviceProperty(const Atom& property, + const uint8_t& device_id) { + return Input::DeleteDeviceProperty( + Input::DeleteDevicePropertyRequest{property, device_id}); +} + +Future Input::GetDeviceProperty( + const Input::GetDevicePropertyRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& property = request.property; + auto& type = request.type; + auto& offset = request.offset; + auto& len = request.len; + auto& device_id = request.device_id; + auto& c_delete = request.c_delete; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 39; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // property + buf.Write(&property); + + // type + buf.Write(&type); + + // offset + buf.Write(&offset); + + // len + buf.Write(&len); + + // device_id + buf.Write(&device_id); + + // c_delete + buf.Write(&c_delete); + + // pad0 + Pad(&buf, 2); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Input::GetDeviceProperty", false); +} + +Future Input::GetDeviceProperty( + const Atom& property, + const Atom& type, + const uint32_t& offset, + const uint32_t& len, + const uint8_t& device_id, + const uint8_t& c_delete) { + return Input::GetDeviceProperty(Input::GetDevicePropertyRequest{ + property, type, offset, len, device_id, c_delete}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Input::GetDevicePropertyReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& xi_reply_type = (*reply).xi_reply_type; + auto& sequence = (*reply).sequence; + auto& type = (*reply).type; + auto& bytes_after = (*reply).bytes_after; + auto& num_items = (*reply).num_items; + Input::PropertyFormat format{}; + auto& device_id = (*reply).device_id; + auto& items = (*reply); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // xi_reply_type + Read(&xi_reply_type, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // type + Read(&type, &buf); + + // bytes_after + Read(&bytes_after, &buf); + + // num_items + Read(&num_items, &buf); + + // format + uint8_t tmp66; + Read(&tmp66, &buf); + format = static_cast(tmp66); + + // device_id + Read(&device_id, &buf); + + // pad0 + Pad(&buf, 10); + + // items + auto items_expr = format; + if (CaseEq(items_expr, Input::PropertyFormat::c_8Bits)) { + items.data8.emplace(); + auto& data8 = *items.data8; + size_t data8_len = data8.size(); + + // data8 + data8.resize(num_items); + for (auto& data8_elem : data8) { + // data8_elem + Read(&data8_elem, &buf); + } + + // pad1 + Align(&buf, 4); + } + if (CaseEq(items_expr, Input::PropertyFormat::c_16Bits)) { + items.data16.emplace(); + auto& data16 = *items.data16; + size_t data16_len = data16.size(); + + // data16 + data16.resize(num_items); + for (auto& data16_elem : data16) { + // data16_elem + Read(&data16_elem, &buf); + } + + // pad2 + Align(&buf, 4); + } + if (CaseEq(items_expr, Input::PropertyFormat::c_32Bits)) { + items.data32.emplace(); + auto& data32 = *items.data32; + size_t data32_len = data32.size(); + + // data32 + data32.resize(num_items); + for (auto& data32_elem : data32) { + // data32_elem + Read(&data32_elem, &buf); + } + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Input::XIQueryPointer( + const Input::XIQueryPointerRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + auto& deviceid = request.deviceid; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 40; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + // deviceid + buf.Write(&deviceid); + + // pad0 + Pad(&buf, 2); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Input::XIQueryPointer", false); +} + +Future Input::XIQueryPointer( + const Window& window, + const DeviceId& deviceid) { + return Input::XIQueryPointer(Input::XIQueryPointerRequest{window, deviceid}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Input::XIQueryPointerReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& root = (*reply).root; + auto& child = (*reply).child; + auto& root_x = (*reply).root_x; + auto& root_y = (*reply).root_y; + auto& win_x = (*reply).win_x; + auto& win_y = (*reply).win_y; + auto& same_screen = (*reply).same_screen; + uint16_t buttons_len{}; + auto& mods = (*reply).mods; + auto& group = (*reply).group; + auto& buttons = (*reply).buttons; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // root + Read(&root, &buf); + + // child + Read(&child, &buf); + + // root_x + Read(&root_x, &buf); + + // root_y + Read(&root_y, &buf); + + // win_x + Read(&win_x, &buf); + + // win_y + Read(&win_y, &buf); + + // same_screen + Read(&same_screen, &buf); + + // pad1 + Pad(&buf, 1); + + // buttons_len + Read(&buttons_len, &buf); + + // mods + { + auto& base = mods.base; + auto& latched = mods.latched; + auto& locked = mods.locked; + auto& effective = mods.effective; + + // base + Read(&base, &buf); + + // latched + Read(&latched, &buf); + + // locked + Read(&locked, &buf); + + // effective + Read(&effective, &buf); + } + + // group + { + auto& base = group.base; + auto& latched = group.latched; + auto& locked = group.locked; + auto& effective = group.effective; + + // base + Read(&base, &buf); + + // latched + Read(&latched, &buf); + + // locked + Read(&locked, &buf); + + // effective + Read(&effective, &buf); + } + + // buttons + buttons.resize(buttons_len); + for (auto& buttons_elem : buttons) { + // buttons_elem + Read(&buttons_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Input::XIWarpPointer(const Input::XIWarpPointerRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& src_win = request.src_win; + auto& dst_win = request.dst_win; + auto& src_x = request.src_x; + auto& src_y = request.src_y; + auto& src_width = request.src_width; + auto& src_height = request.src_height; + auto& dst_x = request.dst_x; + auto& dst_y = request.dst_y; + auto& deviceid = request.deviceid; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 41; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // src_win + buf.Write(&src_win); + + // dst_win + buf.Write(&dst_win); + + // src_x + buf.Write(&src_x); + + // src_y + buf.Write(&src_y); + + // src_width + buf.Write(&src_width); + + // src_height + buf.Write(&src_height); + + // dst_x + buf.Write(&dst_x); + + // dst_y + buf.Write(&dst_y); + + // deviceid + buf.Write(&deviceid); + + // pad0 + Pad(&buf, 2); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Input::XIWarpPointer", false); +} + +Future Input::XIWarpPointer(const Window& src_win, + const Window& dst_win, + const Fp1616& src_x, + const Fp1616& src_y, + const uint16_t& src_width, + const uint16_t& src_height, + const Fp1616& dst_x, + const Fp1616& dst_y, + const DeviceId& deviceid) { + return Input::XIWarpPointer( + Input::XIWarpPointerRequest{src_win, dst_win, src_x, src_y, src_width, + src_height, dst_x, dst_y, deviceid}); +} + +Future Input::XIChangeCursor( + const Input::XIChangeCursorRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + auto& cursor = request.cursor; + auto& deviceid = request.deviceid; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 42; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + // cursor + buf.Write(&cursor); + + // deviceid + buf.Write(&deviceid); + + // pad0 + Pad(&buf, 2); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Input::XIChangeCursor", false); +} + +Future Input::XIChangeCursor(const Window& window, + const Cursor& cursor, + const DeviceId& deviceid) { + return Input::XIChangeCursor( + Input::XIChangeCursorRequest{window, cursor, deviceid}); +} + +Future Input::XIChangeHierarchy( + const Input::XIChangeHierarchyRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + uint8_t num_changes{}; + auto& changes = request.changes; + size_t changes_len = changes.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 43; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // num_changes + num_changes = changes.size(); + buf.Write(&num_changes); + + // pad0 + Pad(&buf, 3); + + // changes + DCHECK_EQ(static_cast(num_changes), changes.size()); + for (auto& changes_elem : changes) { + // changes_elem + { + HierarchyChangeType type{}; + auto& len = changes_elem.len; + auto& data = changes_elem; + + // type + SwitchVar(HierarchyChangeType::AddMaster, data.add_master.has_value(), + false, &type); + SwitchVar(HierarchyChangeType::RemoveMaster, + data.remove_master.has_value(), false, &type); + SwitchVar(HierarchyChangeType::AttachSlave, data.attach_slave.has_value(), + false, &type); + SwitchVar(HierarchyChangeType::DetachSlave, data.detach_slave.has_value(), + false, &type); + uint16_t tmp67; + tmp67 = static_cast(type); + buf.Write(&tmp67); + + // len + buf.Write(&len); + + // data + auto data_expr = type; + if (CaseEq(data_expr, HierarchyChangeType::AddMaster)) { + uint16_t name_len{}; + auto& send_core = (*data.add_master).send_core; + auto& enable = (*data.add_master).enable; + auto& name = (*data.add_master).name; + + // name_len + name_len = name.size(); + buf.Write(&name_len); + + // send_core + buf.Write(&send_core); + + // enable + buf.Write(&enable); + + // name + DCHECK_EQ(static_cast(name_len), name.size()); + for (auto& name_elem : name) { + // name_elem + buf.Write(&name_elem); + } + + // pad0 + Align(&buf, 4); + } + if (CaseEq(data_expr, HierarchyChangeType::RemoveMaster)) { + auto& deviceid = (*data.remove_master).deviceid; + auto& return_mode = (*data.remove_master).return_mode; + auto& return_pointer = (*data.remove_master).return_pointer; + auto& return_keyboard = (*data.remove_master).return_keyboard; + + // deviceid + buf.Write(&deviceid); + + // return_mode + uint8_t tmp68; + tmp68 = static_cast(return_mode); + buf.Write(&tmp68); + + // pad1 + Pad(&buf, 1); + + // return_pointer + buf.Write(&return_pointer); + + // return_keyboard + buf.Write(&return_keyboard); + } + if (CaseEq(data_expr, HierarchyChangeType::AttachSlave)) { + auto& deviceid = (*data.attach_slave).deviceid; + auto& master = (*data.attach_slave).master; + + // deviceid + buf.Write(&deviceid); + + // master + buf.Write(&master); + } + if (CaseEq(data_expr, HierarchyChangeType::DetachSlave)) { + auto& deviceid = (*data.detach_slave).deviceid; + + // deviceid + buf.Write(&deviceid); + + // pad2 + Pad(&buf, 2); + } + } + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Input::XIChangeHierarchy", + false); +} + +Future Input::XIChangeHierarchy( + const std::vector& changes) { + return Input::XIChangeHierarchy(Input::XIChangeHierarchyRequest{changes}); +} + +Future Input::XISetClientPointer( + const Input::XISetClientPointerRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + auto& deviceid = request.deviceid; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 44; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + // deviceid + buf.Write(&deviceid); + + // pad0 + Pad(&buf, 2); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Input::XISetClientPointer", + false); +} + +Future Input::XISetClientPointer(const Window& window, + const DeviceId& deviceid) { + return Input::XISetClientPointer( + Input::XISetClientPointerRequest{window, deviceid}); +} + +Future Input::XIGetClientPointer( + const Input::XIGetClientPointerRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 45; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Input::XIGetClientPointer", false); +} + +Future Input::XIGetClientPointer( + const Window& window) { + return Input::XIGetClientPointer(Input::XIGetClientPointerRequest{window}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Input::XIGetClientPointerReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& set = (*reply).set; + auto& deviceid = (*reply).deviceid; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // set + Read(&set, &buf); + + // pad1 + Pad(&buf, 1); + + // deviceid + Read(&deviceid, &buf); + + // pad2 + Pad(&buf, 20); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Input::XISelectEvents( + const Input::XISelectEventsRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + uint16_t num_mask{}; + auto& masks = request.masks; + size_t masks_len = masks.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 46; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + // num_mask + num_mask = masks.size(); + buf.Write(&num_mask); + + // pad0 + Pad(&buf, 2); + + // masks + DCHECK_EQ(static_cast(num_mask), masks.size()); + for (auto& masks_elem : masks) { + // masks_elem + { + auto& deviceid = masks_elem.deviceid; + uint16_t mask_len{}; + auto& mask = masks_elem.mask; + + // deviceid + buf.Write(&deviceid); + + // mask_len + mask_len = mask.size(); + buf.Write(&mask_len); + + // mask + DCHECK_EQ(static_cast(mask_len), mask.size()); + for (auto& mask_elem : mask) { + // mask_elem + uint32_t tmp69; + tmp69 = static_cast(mask_elem); + buf.Write(&tmp69); + } + } + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Input::XISelectEvents", false); +} + +Future Input::XISelectEvents(const Window& window, + const std::vector& masks) { + return Input::XISelectEvents(Input::XISelectEventsRequest{window, masks}); +} + +Future Input::XIQueryVersion( + const Input::XIQueryVersionRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& major_version = request.major_version; + auto& minor_version = request.minor_version; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 47; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // major_version + buf.Write(&major_version); + + // minor_version + buf.Write(&minor_version); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Input::XIQueryVersion", false); +} + +Future Input::XIQueryVersion( + const uint16_t& major_version, + const uint16_t& minor_version) { + return Input::XIQueryVersion( + Input::XIQueryVersionRequest{major_version, minor_version}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Input::XIQueryVersionReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& major_version = (*reply).major_version; + auto& minor_version = (*reply).minor_version; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // major_version + Read(&major_version, &buf); + + // minor_version + Read(&minor_version, &buf); + + // pad1 + Pad(&buf, 20); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Input::XIQueryDevice( + const Input::XIQueryDeviceRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& deviceid = request.deviceid; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 48; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // deviceid + buf.Write(&deviceid); + + // pad0 + Pad(&buf, 2); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Input::XIQueryDevice", false); +} + +Future Input::XIQueryDevice( + const DeviceId& deviceid) { + return Input::XIQueryDevice(Input::XIQueryDeviceRequest{deviceid}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Input::XIQueryDeviceReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint16_t num_infos{}; + auto& infos = (*reply).infos; + size_t infos_len = infos.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // num_infos + Read(&num_infos, &buf); + + // pad1 + Pad(&buf, 22); + + // infos + infos.resize(num_infos); + for (auto& infos_elem : infos) { + // infos_elem + { + auto& deviceid = infos_elem.deviceid; + auto& type = infos_elem.type; + auto& attachment = infos_elem.attachment; + uint16_t num_classes{}; + uint16_t name_len{}; + auto& enabled = infos_elem.enabled; + auto& name = infos_elem.name; + auto& classes = infos_elem.classes; + size_t classes_len = classes.size(); + + // deviceid + Read(&deviceid, &buf); + + // type + uint16_t tmp70; + Read(&tmp70, &buf); + type = static_cast(tmp70); + + // attachment + Read(&attachment, &buf); + + // num_classes + Read(&num_classes, &buf); + + // name_len + Read(&name_len, &buf); + + // enabled + Read(&enabled, &buf); + + // pad0 + Pad(&buf, 1); + + // name + name.resize(name_len); + for (auto& name_elem : name) { + // name_elem + Read(&name_elem, &buf); + } + + // pad1 + Align(&buf, 4); + + // classes + classes.resize(num_classes); + for (auto& classes_elem : classes) { + // classes_elem + { + Input::DeviceClassType type{}; + auto& len = classes_elem.len; + auto& sourceid = classes_elem.sourceid; + auto& data = classes_elem; + + // type + uint16_t tmp71; + Read(&tmp71, &buf); + type = static_cast(tmp71); + + // len + Read(&len, &buf); + + // sourceid + Read(&sourceid, &buf); + + // data + auto data_expr = type; + if (CaseEq(data_expr, Input::DeviceClassType::Key)) { + data.key.emplace(); + uint16_t num_keys{}; + auto& keys = (*data.key).keys; + size_t keys_len = keys.size(); + + // num_keys + Read(&num_keys, &buf); + + // keys + keys.resize(num_keys); + for (auto& keys_elem : keys) { + // keys_elem + Read(&keys_elem, &buf); + } + } + if (CaseEq(data_expr, Input::DeviceClassType::Button)) { + data.button.emplace(); + uint16_t num_buttons{}; + auto& state = (*data.button).state; + size_t state_len = state.size(); + auto& labels = (*data.button).labels; + size_t labels_len = labels.size(); + + // num_buttons + Read(&num_buttons, &buf); + + // state + state.resize(((num_buttons) + (31)) / (32)); + for (auto& state_elem : state) { + // state_elem + Read(&state_elem, &buf); + } + + // labels + labels.resize(num_buttons); + for (auto& labels_elem : labels) { + // labels_elem + Read(&labels_elem, &buf); + } + } + if (CaseEq(data_expr, Input::DeviceClassType::Valuator)) { + data.valuator.emplace(); + auto& number = (*data.valuator).number; + auto& label = (*data.valuator).label; + auto& min = (*data.valuator).min; + auto& max = (*data.valuator).max; + auto& value = (*data.valuator).value; + auto& resolution = (*data.valuator).resolution; + auto& mode = (*data.valuator).mode; + + // number + Read(&number, &buf); + + // label + Read(&label, &buf); + + // min + { + auto& integral = min.integral; + auto& frac = min.frac; + + // integral + Read(&integral, &buf); + + // frac + Read(&frac, &buf); + } + + // max + { + auto& integral = max.integral; + auto& frac = max.frac; + + // integral + Read(&integral, &buf); + + // frac + Read(&frac, &buf); + } + + // value + { + auto& integral = value.integral; + auto& frac = value.frac; + + // integral + Read(&integral, &buf); + + // frac + Read(&frac, &buf); + } + + // resolution + Read(&resolution, &buf); + + // mode + uint8_t tmp72; + Read(&tmp72, &buf); + mode = static_cast(tmp72); + + // pad0 + Pad(&buf, 3); + } + if (CaseEq(data_expr, Input::DeviceClassType::Scroll)) { + data.scroll.emplace(); + auto& number = (*data.scroll).number; + auto& scroll_type = (*data.scroll).scroll_type; + auto& flags = (*data.scroll).flags; + auto& increment = (*data.scroll).increment; + + // number + Read(&number, &buf); + + // scroll_type + uint16_t tmp73; + Read(&tmp73, &buf); + scroll_type = static_cast(tmp73); + + // pad1 + Pad(&buf, 2); + + // flags + uint32_t tmp74; + Read(&tmp74, &buf); + flags = static_cast(tmp74); + + // increment + { + auto& integral = increment.integral; + auto& frac = increment.frac; + + // integral + Read(&integral, &buf); + + // frac + Read(&frac, &buf); + } + } + if (CaseEq(data_expr, Input::DeviceClassType::Touch)) { + data.touch.emplace(); + auto& mode = (*data.touch).mode; + auto& num_touches = (*data.touch).num_touches; + + // mode + uint8_t tmp75; + Read(&tmp75, &buf); + mode = static_cast(tmp75); + + // num_touches + Read(&num_touches, &buf); + } + } + } + } + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Input::XISetFocus(const Input::XISetFocusRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + auto& time = request.time; + auto& deviceid = request.deviceid; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 49; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + // time + buf.Write(&time); + + // deviceid + buf.Write(&deviceid); + + // pad0 + Pad(&buf, 2); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Input::XISetFocus", false); +} + +Future Input::XISetFocus(const Window& window, + const Time& time, + const DeviceId& deviceid) { + return Input::XISetFocus(Input::XISetFocusRequest{window, time, deviceid}); +} + +Future Input::XIGetFocus( + const Input::XIGetFocusRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& deviceid = request.deviceid; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 50; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // deviceid + buf.Write(&deviceid); + + // pad0 + Pad(&buf, 2); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Input::XIGetFocus", false); +} + +Future Input::XIGetFocus(const DeviceId& deviceid) { + return Input::XIGetFocus(Input::XIGetFocusRequest{deviceid}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Input::XIGetFocusReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& focus = (*reply).focus; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // focus + Read(&focus, &buf); + + // pad1 + Pad(&buf, 20); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Input::XIGrabDevice( + const Input::XIGrabDeviceRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + auto& time = request.time; + auto& cursor = request.cursor; + auto& deviceid = request.deviceid; + auto& mode = request.mode; + auto& paired_device_mode = request.paired_device_mode; + auto& owner_events = request.owner_events; + uint16_t mask_len{}; + auto& mask = request.mask; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 51; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + // time + buf.Write(&time); + + // cursor + buf.Write(&cursor); + + // deviceid + buf.Write(&deviceid); + + // mode + uint8_t tmp76; + tmp76 = static_cast(mode); + buf.Write(&tmp76); + + // paired_device_mode + uint8_t tmp77; + tmp77 = static_cast(paired_device_mode); + buf.Write(&tmp77); + + // owner_events + uint8_t tmp78; + tmp78 = static_cast(owner_events); + buf.Write(&tmp78); + + // pad0 + Pad(&buf, 1); + + // mask_len + mask_len = mask.size(); + buf.Write(&mask_len); + + // mask + DCHECK_EQ(static_cast(mask_len), mask.size()); + for (auto& mask_elem : mask) { + // mask_elem + buf.Write(&mask_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Input::XIGrabDevice", false); +} + +Future Input::XIGrabDevice( + const Window& window, + const Time& time, + const Cursor& cursor, + const DeviceId& deviceid, + const GrabMode& mode, + const GrabMode& paired_device_mode, + const GrabOwner& owner_events, + const std::vector& mask) { + return Input::XIGrabDevice( + Input::XIGrabDeviceRequest{window, time, cursor, deviceid, mode, + paired_device_mode, owner_events, mask}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Input::XIGrabDeviceReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& status = (*reply).status; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // status + uint8_t tmp79; + Read(&tmp79, &buf); + status = static_cast(tmp79); + + // pad1 + Pad(&buf, 23); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Input::XIUngrabDevice( + const Input::XIUngrabDeviceRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& time = request.time; + auto& deviceid = request.deviceid; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 52; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // time + buf.Write(&time); + + // deviceid + buf.Write(&deviceid); + + // pad0 + Pad(&buf, 2); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Input::XIUngrabDevice", false); +} + +Future Input::XIUngrabDevice(const Time& time, const DeviceId& deviceid) { + return Input::XIUngrabDevice(Input::XIUngrabDeviceRequest{time, deviceid}); +} + +Future Input::XIAllowEvents(const Input::XIAllowEventsRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& time = request.time; + auto& deviceid = request.deviceid; + auto& event_mode = request.event_mode; + auto& touchid = request.touchid; + auto& grab_window = request.grab_window; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 53; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // time + buf.Write(&time); + + // deviceid + buf.Write(&deviceid); + + // event_mode + uint8_t tmp80; + tmp80 = static_cast(event_mode); + buf.Write(&tmp80); + + // pad0 + Pad(&buf, 1); + + // touchid + buf.Write(&touchid); + + // grab_window + buf.Write(&grab_window); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Input::XIAllowEvents", false); +} + +Future Input::XIAllowEvents(const Time& time, + const DeviceId& deviceid, + const EventMode& event_mode, + const uint32_t& touchid, + const Window& grab_window) { + return Input::XIAllowEvents(Input::XIAllowEventsRequest{ + time, deviceid, event_mode, touchid, grab_window}); +} + +Future Input::XIPassiveGrabDevice( + const Input::XIPassiveGrabDeviceRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& time = request.time; + auto& grab_window = request.grab_window; + auto& cursor = request.cursor; + auto& detail = request.detail; + auto& deviceid = request.deviceid; + uint16_t num_modifiers{}; + uint16_t mask_len{}; + auto& grab_type = request.grab_type; + auto& grab_mode = request.grab_mode; + auto& paired_device_mode = request.paired_device_mode; + auto& owner_events = request.owner_events; + auto& mask = request.mask; + auto& modifiers = request.modifiers; + size_t modifiers_len = modifiers.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 54; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // time + buf.Write(&time); + + // grab_window + buf.Write(&grab_window); + + // cursor + buf.Write(&cursor); + + // detail + buf.Write(&detail); + + // deviceid + buf.Write(&deviceid); + + // num_modifiers + num_modifiers = modifiers.size(); + buf.Write(&num_modifiers); + + // mask_len + mask_len = mask.size(); + buf.Write(&mask_len); + + // grab_type + uint8_t tmp81; + tmp81 = static_cast(grab_type); + buf.Write(&tmp81); + + // grab_mode + uint8_t tmp82; + tmp82 = static_cast(grab_mode); + buf.Write(&tmp82); + + // paired_device_mode + uint8_t tmp83; + tmp83 = static_cast(paired_device_mode); + buf.Write(&tmp83); + + // owner_events + uint8_t tmp84; + tmp84 = static_cast(owner_events); + buf.Write(&tmp84); + + // pad0 + Pad(&buf, 2); + + // mask + DCHECK_EQ(static_cast(mask_len), mask.size()); + for (auto& mask_elem : mask) { + // mask_elem + buf.Write(&mask_elem); + } + + // modifiers + DCHECK_EQ(static_cast(num_modifiers), modifiers.size()); + for (auto& modifiers_elem : modifiers) { + // modifiers_elem + buf.Write(&modifiers_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Input::XIPassiveGrabDevice", false); +} + +Future Input::XIPassiveGrabDevice( + const Time& time, + const Window& grab_window, + const Cursor& cursor, + const uint32_t& detail, + const DeviceId& deviceid, + const GrabType& grab_type, + const GrabMode22& grab_mode, + const GrabMode& paired_device_mode, + const GrabOwner& owner_events, + const std::vector& mask, + const std::vector& modifiers) { + return Input::XIPassiveGrabDevice(Input::XIPassiveGrabDeviceRequest{ + time, grab_window, cursor, detail, deviceid, grab_type, grab_mode, + paired_device_mode, owner_events, mask, modifiers}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Input::XIPassiveGrabDeviceReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint16_t num_modifiers{}; + auto& modifiers = (*reply).modifiers; + size_t modifiers_len = modifiers.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // num_modifiers + Read(&num_modifiers, &buf); + + // pad1 + Pad(&buf, 22); + + // modifiers + modifiers.resize(num_modifiers); + for (auto& modifiers_elem : modifiers) { + // modifiers_elem + { + auto& modifiers = modifiers_elem.modifiers; + auto& status = modifiers_elem.status; + + // modifiers + Read(&modifiers, &buf); + + // status + uint8_t tmp85; + Read(&tmp85, &buf); + status = static_cast(tmp85); + + // pad0 + Pad(&buf, 3); + } + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Input::XIPassiveUngrabDevice( + const Input::XIPassiveUngrabDeviceRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& grab_window = request.grab_window; + auto& detail = request.detail; + auto& deviceid = request.deviceid; + uint16_t num_modifiers{}; + auto& grab_type = request.grab_type; + auto& modifiers = request.modifiers; + size_t modifiers_len = modifiers.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 55; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // grab_window + buf.Write(&grab_window); + + // detail + buf.Write(&detail); + + // deviceid + buf.Write(&deviceid); + + // num_modifiers + num_modifiers = modifiers.size(); + buf.Write(&num_modifiers); + + // grab_type + uint8_t tmp86; + tmp86 = static_cast(grab_type); + buf.Write(&tmp86); + + // pad0 + Pad(&buf, 3); + + // modifiers + DCHECK_EQ(static_cast(num_modifiers), modifiers.size()); + for (auto& modifiers_elem : modifiers) { + // modifiers_elem + buf.Write(&modifiers_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Input::XIPassiveUngrabDevice", + false); +} + +Future Input::XIPassiveUngrabDevice( + const Window& grab_window, + const uint32_t& detail, + const DeviceId& deviceid, + const GrabType& grab_type, + const std::vector& modifiers) { + return Input::XIPassiveUngrabDevice(Input::XIPassiveUngrabDeviceRequest{ + grab_window, detail, deviceid, grab_type, modifiers}); +} + +Future Input::XIListProperties( + const Input::XIListPropertiesRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& deviceid = request.deviceid; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 56; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // deviceid + buf.Write(&deviceid); + + // pad0 + Pad(&buf, 2); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Input::XIListProperties", false); +} + +Future Input::XIListProperties( + const DeviceId& deviceid) { + return Input::XIListProperties(Input::XIListPropertiesRequest{deviceid}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Input::XIListPropertiesReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint16_t num_properties{}; + auto& properties = (*reply).properties; + size_t properties_len = properties.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // num_properties + Read(&num_properties, &buf); + + // pad1 + Pad(&buf, 22); + + // properties + properties.resize(num_properties); + for (auto& properties_elem : properties) { + // properties_elem + Read(&properties_elem, &buf); + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Input::XIChangeProperty( + const Input::XIChangePropertyRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& deviceid = request.deviceid; + auto& mode = request.mode; + PropertyFormat format{}; + auto& property = request.property; + auto& type = request.type; + auto& num_items = request.num_items; + auto& items = request; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 57; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // deviceid + buf.Write(&deviceid); + + // mode + uint8_t tmp87; + tmp87 = static_cast(mode); + buf.Write(&tmp87); + + // format + SwitchVar(PropertyFormat::c_8Bits, items.data8.has_value(), false, &format); + SwitchVar(PropertyFormat::c_16Bits, items.data16.has_value(), false, &format); + SwitchVar(PropertyFormat::c_32Bits, items.data32.has_value(), false, &format); + uint8_t tmp88; + tmp88 = static_cast(format); + buf.Write(&tmp88); + + // property + buf.Write(&property); + + // type + buf.Write(&type); + + // num_items + buf.Write(&num_items); + + // items + auto items_expr = format; + if (CaseEq(items_expr, PropertyFormat::c_8Bits)) { + auto& data8 = *items.data8; + size_t data8_len = data8.size(); + + // data8 + DCHECK_EQ(static_cast(num_items), data8.size()); + for (auto& data8_elem : data8) { + // data8_elem + buf.Write(&data8_elem); + } + + // pad0 + Align(&buf, 4); + } + if (CaseEq(items_expr, PropertyFormat::c_16Bits)) { + auto& data16 = *items.data16; + size_t data16_len = data16.size(); + + // data16 + DCHECK_EQ(static_cast(num_items), data16.size()); + for (auto& data16_elem : data16) { + // data16_elem + buf.Write(&data16_elem); + } + + // pad1 + Align(&buf, 4); + } + if (CaseEq(items_expr, PropertyFormat::c_32Bits)) { + auto& data32 = *items.data32; + size_t data32_len = data32.size(); + + // data32 + DCHECK_EQ(static_cast(num_items), data32.size()); + for (auto& data32_elem : data32) { + // data32_elem + buf.Write(&data32_elem); + } + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Input::XIChangeProperty", false); +} + +Future Input::XIChangeProperty( + const DeviceId& deviceid, + const PropMode& mode, + const Atom& property, + const Atom& type, + const uint32_t& num_items, + const absl::optional>& data8, + const absl::optional>& data16, + const absl::optional>& data32) { + return Input::XIChangeProperty(Input::XIChangePropertyRequest{ + deviceid, mode, property, type, num_items, data8, data16, data32}); +} + +Future Input::XIDeleteProperty( + const Input::XIDeletePropertyRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& deviceid = request.deviceid; + auto& property = request.property; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 58; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // deviceid + buf.Write(&deviceid); + + // pad0 + Pad(&buf, 2); + + // property + buf.Write(&property); + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Input::XIDeleteProperty", false); +} + +Future Input::XIDeleteProperty(const DeviceId& deviceid, + const Atom& property) { + return Input::XIDeleteProperty( + Input::XIDeletePropertyRequest{deviceid, property}); +} + +Future Input::XIGetProperty( + const Input::XIGetPropertyRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& deviceid = request.deviceid; + auto& c_delete = request.c_delete; + auto& property = request.property; + auto& type = request.type; + auto& offset = request.offset; + auto& len = request.len; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 59; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // deviceid + buf.Write(&deviceid); + + // c_delete + buf.Write(&c_delete); + + // pad0 + Pad(&buf, 1); + + // property + buf.Write(&property); + + // type + buf.Write(&type); + + // offset + buf.Write(&offset); + + // len + buf.Write(&len); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Input::XIGetProperty", false); +} + +Future Input::XIGetProperty(const DeviceId& deviceid, + const uint8_t& c_delete, + const Atom& property, + const Atom& type, + const uint32_t& offset, + const uint32_t& len) { + return Input::XIGetProperty(Input::XIGetPropertyRequest{ + deviceid, c_delete, property, type, offset, len}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Input::XIGetPropertyReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + auto& type = (*reply).type; + auto& bytes_after = (*reply).bytes_after; + auto& num_items = (*reply).num_items; + Input::PropertyFormat format{}; + auto& items = (*reply); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // type + Read(&type, &buf); + + // bytes_after + Read(&bytes_after, &buf); + + // num_items + Read(&num_items, &buf); + + // format + uint8_t tmp89; + Read(&tmp89, &buf); + format = static_cast(tmp89); + + // pad1 + Pad(&buf, 11); + + // items + auto items_expr = format; + if (CaseEq(items_expr, Input::PropertyFormat::c_8Bits)) { + items.data8.emplace(); + auto& data8 = *items.data8; + size_t data8_len = data8.size(); + + // data8 + data8.resize(num_items); + for (auto& data8_elem : data8) { + // data8_elem + Read(&data8_elem, &buf); + } + + // pad2 + Align(&buf, 4); + } + if (CaseEq(items_expr, Input::PropertyFormat::c_16Bits)) { + items.data16.emplace(); + auto& data16 = *items.data16; + size_t data16_len = data16.size(); + + // data16 + data16.resize(num_items); + for (auto& data16_elem : data16) { + // data16_elem + Read(&data16_elem, &buf); + } + + // pad3 + Align(&buf, 4); + } + if (CaseEq(items_expr, Input::PropertyFormat::c_32Bits)) { + items.data32.emplace(); + auto& data32 = *items.data32; + size_t data32_len = data32.size(); + + // data32 + data32.resize(num_items); + for (auto& data32_elem : data32) { + // data32_elem + Read(&data32_elem, &buf); + } + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Input::XIGetSelectedEvents( + const Input::XIGetSelectedEventsRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& window = request.window; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 60; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // window + buf.Write(&window); + + Align(&buf, 4); + + return connection_->SendRequest( + &buf, "Input::XIGetSelectedEvents", false); +} + +Future Input::XIGetSelectedEvents( + const Window& window) { + return Input::XIGetSelectedEvents(Input::XIGetSelectedEventsRequest{window}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr detail::ReadReply< + Input::XIGetSelectedEventsReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique(); + + auto& sequence = (*reply).sequence; + uint16_t num_masks{}; + auto& masks = (*reply).masks; + size_t masks_len = masks.size(); + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // num_masks + Read(&num_masks, &buf); + + // pad1 + Pad(&buf, 22); + + // masks + masks.resize(num_masks); + for (auto& masks_elem : masks) { + // masks_elem + { + auto& deviceid = masks_elem.deviceid; + uint16_t mask_len{}; + auto& mask = masks_elem.mask; + + // deviceid + Read(&deviceid, &buf); + + // mask_len + Read(&mask_len, &buf); + + // mask + mask.resize(mask_len); + for (auto& mask_elem : mask) { + // mask_elem + uint32_t tmp90; + Read(&tmp90, &buf); + mask_elem = static_cast(tmp90); + } + } + } + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + +Future Input::XIBarrierReleasePointer( + const Input::XIBarrierReleasePointerRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + uint32_t num_barriers{}; + auto& barriers = request.barriers; + size_t barriers_len = barriers.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 61; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // num_barriers + num_barriers = barriers.size(); + buf.Write(&num_barriers); + + // barriers + DCHECK_EQ(static_cast(num_barriers), barriers.size()); + for (auto& barriers_elem : barriers) { + // barriers_elem + { + auto& deviceid = barriers_elem.deviceid; + auto& barrier = barriers_elem.barrier; + auto& eventid = barriers_elem.eventid; + + // deviceid + buf.Write(&deviceid); + + // pad0 + Pad(&buf, 2); + + // barrier + buf.Write(&barrier); + + // eventid + buf.Write(&eventid); + } + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Input::XIBarrierReleasePointer", + false); +} + +Future Input::XIBarrierReleasePointer( + const std::vector& barriers) { + return Input::XIBarrierReleasePointer( + Input::XIBarrierReleasePointerRequest{barriers}); +} + +Future Input::SendExtensionEvent( + const Input::SendExtensionEventRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& destination = request.destination; + auto& device_id = request.device_id; + auto& propagate = request.propagate; + uint16_t num_classes{}; + uint8_t num_events{}; + auto& events = request.events; + size_t events_len = events.size(); + auto& classes = request.classes; + size_t classes_len = classes.size(); + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 31; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // destination + buf.Write(&destination); + + // device_id + buf.Write(&device_id); + + // propagate + buf.Write(&propagate); + + // num_classes + num_classes = classes.size(); + buf.Write(&num_classes); + + // num_events + num_events = events.size(); + buf.Write(&num_events); + + // pad0 + Pad(&buf, 3); + + // events + DCHECK_EQ(static_cast(num_events), events.size()); + for (auto& events_elem : events) { + // events_elem + buf.Write(&events_elem); + } + + // classes + DCHECK_EQ(static_cast(num_classes), classes.size()); + for (auto& classes_elem : classes) { + // classes_elem + buf.Write(&classes_elem); + } + + Align(&buf, 4); + + return connection_->SendRequest(&buf, "Input::SendExtensionEvent", + false); +} + +Future Input::SendExtensionEvent(const Window& destination, + const uint8_t& device_id, + const uint8_t& propagate, + const std::vector& events, + const std::vector& classes) { + return Input::SendExtensionEvent(Input::SendExtensionEventRequest{ + destination, device_id, propagate, events, classes}); +} + +} // namespace x11 diff --git a/x/generated_protos/xinput.h b/x/generated_protos/xinput.h new file mode 100644 index 000000000000..e522384c04c0 --- /dev/null +++ b/x/generated_protos/xinput.h @@ -0,0 +1,3234 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file was automatically generated with: +// ../../ui/gfx/x/gen_xproto.py \ +// ../../third_party/xcbproto/src \ +// gen/ui/gfx/x \ +// bigreq \ +// composite \ +// damage \ +// dpms \ +// dri2 \ +// dri3 \ +// ge \ +// glx \ +// present \ +// randr \ +// record \ +// render \ +// res \ +// screensaver \ +// shape \ +// shm \ +// sync \ +// xc_misc \ +// xevie \ +// xf86dri \ +// xf86vidmode \ +// xfixes \ +// xinerama \ +// xinput \ +// xkb \ +// xprint \ +// xproto \ +// xselinux \ +// xtest \ +// xv \ +// xvmc + +#ifndef UI_GFX_X_GENERATED_PROTOS_XINPUT_H_ +#define UI_GFX_X_GENERATED_PROTOS_XINPUT_H_ + +#include +#include +#include +#include +#include + +#include "base/component_export.h" +#include "base/files/scoped_file.h" +#include "base/memory/ref_counted_memory.h" +#include "base/memory/scoped_refptr.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/gfx/x/error.h" +#include "ui/gfx/x/ref_counted_fd.h" +#include "xfixes.h" +#include "xproto.h" + +namespace x11 { + +class Connection; + +template +struct Response; + +template +class Future; + +class COMPONENT_EXPORT(X11) Input { + public: + static constexpr unsigned major_version = 2; + static constexpr unsigned minor_version = 3; + + Input(Connection* connection, const x11::QueryExtensionReply& info); + + uint8_t present() const { return info_.present; } + uint8_t major_opcode() const { return info_.major_opcode; } + uint8_t first_event() const { return info_.first_event; } + uint8_t first_error() const { return info_.first_error; } + + Connection* connection() const { return connection_; } + + enum class EventClass : uint32_t {}; + + enum class KeyCode : uint8_t {}; + + enum class Fp1616 : int32_t {}; + + enum class DeviceUse : int { + IsXPointer = 0, + IsXKeyboard = 1, + IsXExtensionDevice = 2, + IsXExtensionKeyboard = 3, + IsXExtensionPointer = 4, + }; + + enum class InputClass : int { + Key = 0, + Button = 1, + Valuator = 2, + Feedback = 3, + Proximity = 4, + Focus = 5, + Other = 6, + }; + + enum class ValuatorMode : int { + Relative = 0, + Absolute = 1, + }; + + enum class EventTypeBase : uint8_t {}; + + enum class PropagateMode : int { + AddToList = 0, + DeleteFromList = 1, + }; + + enum class ModifierDevice : int { + UseXKeyboard = 255, + }; + + enum class DeviceInputMode : int { + AsyncThisDevice = 0, + SyncThisDevice = 1, + ReplayThisDevice = 2, + AsyncOtherDevices = 3, + AsyncAll = 4, + SyncAll = 5, + }; + + enum class FeedbackClass : int { + Keyboard = 0, + Pointer = 1, + String = 2, + Integer = 3, + Led = 4, + Bell = 5, + }; + + enum class ChangeFeedbackControlMask : int { + KeyClickPercent = 1 << 0, + Percent = 1 << 1, + Pitch = 1 << 2, + Duration = 1 << 3, + Led = 1 << 4, + LedMode = 1 << 5, + Key = 1 << 6, + AutoRepeatMode = 1 << 7, + String = 1 << 0, + Integer = 1 << 0, + AccelNum = 1 << 0, + AccelDenom = 1 << 1, + Threshold = 1 << 2, + }; + + enum class ValuatorStateModeMask : int { + DeviceModeAbsolute = 1 << 0, + OutOfProximity = 1 << 1, + }; + + enum class DeviceControl : int { + resolution = 1, + abs_calib = 2, + core = 3, + enable = 4, + abs_area = 5, + }; + + enum class PropertyFormat : int { + c_8Bits = 8, + c_16Bits = 16, + c_32Bits = 32, + }; + + enum class DeviceId : uint16_t { + All = 0, + AllMaster = 1, + }; + + enum class HierarchyChangeType : int { + AddMaster = 1, + RemoveMaster = 2, + AttachSlave = 3, + DetachSlave = 4, + }; + + enum class ChangeMode : int { + Attach = 1, + Float = 2, + }; + + enum class XIEventMask : int { + DeviceChanged = 1 << 1, + KeyPress = 1 << 2, + KeyRelease = 1 << 3, + ButtonPress = 1 << 4, + ButtonRelease = 1 << 5, + Motion = 1 << 6, + Enter = 1 << 7, + Leave = 1 << 8, + FocusIn = 1 << 9, + FocusOut = 1 << 10, + Hierarchy = 1 << 11, + Property = 1 << 12, + RawKeyPress = 1 << 13, + RawKeyRelease = 1 << 14, + RawButtonPress = 1 << 15, + RawButtonRelease = 1 << 16, + RawMotion = 1 << 17, + TouchBegin = 1 << 18, + TouchUpdate = 1 << 19, + TouchEnd = 1 << 20, + TouchOwnership = 1 << 21, + RawTouchBegin = 1 << 22, + RawTouchUpdate = 1 << 23, + RawTouchEnd = 1 << 24, + BarrierHit = 1 << 25, + BarrierLeave = 1 << 26, + }; + + enum class DeviceClassType : int { + Key = 0, + Button = 1, + Valuator = 2, + Scroll = 3, + Touch = 8, + }; + + enum class DeviceType : int { + MasterPointer = 1, + MasterKeyboard = 2, + SlavePointer = 3, + SlaveKeyboard = 4, + FloatingSlave = 5, + }; + + enum class ScrollFlags : int { + NoEmulation = 1 << 0, + Preferred = 1 << 1, + }; + + enum class ScrollType : int { + Vertical = 1, + Horizontal = 2, + }; + + enum class TouchMode : int { + Direct = 1, + Dependent = 2, + }; + + enum class GrabOwner : int { + NoOwner = 0, + Owner = 1, + }; + + enum class EventMode : int { + AsyncDevice = 0, + SyncDevice = 1, + ReplayDevice = 2, + AsyncPairedDevice = 3, + AsyncPair = 4, + SyncPair = 5, + AcceptTouch = 6, + RejectTouch = 7, + }; + + enum class GrabMode22 : int { + Sync = 0, + Async = 1, + Touch = 2, + }; + + enum class GrabType : int { + Button = 0, + Keycode = 1, + Enter = 2, + FocusIn = 3, + TouchBegin = 4, + }; + + enum class ModifierMask : int { + Any = 1 << 31, + }; + + enum class MoreEventsMask : int { + MoreEvents = 1 << 7, + }; + + enum class ClassesReportedMask : int { + OutOfProximity = 1 << 7, + DeviceModeAbsolute = 1 << 6, + ReportingValuators = 1 << 2, + ReportingButtons = 1 << 1, + ReportingKeys = 1 << 0, + }; + + enum class ChangeDevice : int { + NewPointer = 0, + NewKeyboard = 1, + }; + + enum class DeviceChange : int { + Added = 0, + Removed = 1, + Enabled = 2, + Disabled = 3, + Unrecoverable = 4, + ControlChanged = 5, + }; + + enum class ChangeReason : int { + SlaveSwitch = 1, + DeviceChange = 2, + }; + + enum class KeyEventFlags : int { + KeyRepeat = 1 << 16, + }; + + enum class PointerEventFlags : int { + PointerEmulated = 1 << 16, + }; + + enum class NotifyMode : int { + Normal = 0, + Grab = 1, + Ungrab = 2, + WhileGrabbed = 3, + PassiveGrab = 4, + PassiveUngrab = 5, + }; + + enum class NotifyDetail : int { + Ancestor = 0, + Virtual = 1, + Inferior = 2, + Nonlinear = 3, + NonlinearVirtual = 4, + Pointer = 5, + PointerRoot = 6, + None = 7, + }; + + enum class HierarchyMask : int { + MasterAdded = 1 << 0, + MasterRemoved = 1 << 1, + SlaveAdded = 1 << 2, + SlaveRemoved = 1 << 3, + SlaveAttached = 1 << 4, + SlaveDetached = 1 << 5, + DeviceEnabled = 1 << 6, + DeviceDisabled = 1 << 7, + }; + + enum class PropertyFlag : int { + Deleted = 0, + Created = 1, + Modified = 2, + }; + + enum class TouchEventFlags : int { + TouchPendingEnd = 1 << 16, + TouchEmulatingPointer = 1 << 17, + }; + + enum class TouchOwnershipFlags : int { + None = 0, + }; + + enum class BarrierFlags : int { + PointerReleased = 1 << 0, + DeviceIsGrabbed = 1 << 1, + }; + + struct Fp3232 { + int32_t integral{}; + uint32_t frac{}; + }; + + struct DeviceInfo { + Atom device_type{}; + uint8_t device_id{}; + uint8_t num_class_info{}; + DeviceUse device_use{}; + }; + + struct KeyInfo { + InputClass class_id{}; + uint8_t len{}; + KeyCode min_keycode{}; + KeyCode max_keycode{}; + uint16_t num_keys{}; + }; + + struct ButtonInfo { + InputClass class_id{}; + uint8_t len{}; + uint16_t num_buttons{}; + }; + + struct AxisInfo { + uint32_t resolution{}; + int32_t minimum{}; + int32_t maximum{}; + }; + + struct ValuatorInfo { + InputClass class_id{}; + uint8_t len{}; + ValuatorMode mode{}; + uint32_t motion_size{}; + std::vector axes{}; + }; + + struct InputInfo { + uint8_t len{}; + struct Key { + KeyCode min_keycode{}; + KeyCode max_keycode{}; + uint16_t num_keys{}; + }; + struct Button { + uint16_t num_buttons{}; + }; + struct Valuator { + ValuatorMode mode{}; + uint32_t motion_size{}; + std::vector axes{}; + }; + absl::optional key{}; + absl::optional