Skip to content

Commit

Permalink
Introduced copy text from image feature on macOS
Browse files Browse the repository at this point in the history
fix brave/brave-browser#27513

"Copy Text From Image" entry is added to render view's context menu.
It gets bitmap data from renderer via LocalFrame::GetImage() mojom interface,
and then passed it to TextRecognitionDialog to extract and show text from that
image.
  • Loading branch information
simonhong committed Jan 6, 2023
1 parent 2777d01 commit 1f145cb
Show file tree
Hide file tree
Showing 25 changed files with 627 additions and 6 deletions.
2 changes: 2 additions & 0 deletions app/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import("//brave/browser/shell_integrations/buildflags/buildflags.gni")
import("//brave/components/brave_vpn/common/buildflags/buildflags.gni")
import("//brave/components/speedreader/common/buildflags/buildflags.gni")
import("//brave/components/text_recognition/common/buildflags/buildflags.gni")
import("//brave/resources/brave_grit.gni")
import("//build/config/features.gni")
import("//build/config/locales.gni")
Expand All @@ -19,6 +20,7 @@ brave_grit("brave_generated_resources_grit") {
"enable_speedreader=$enable_speedreader",
"enable_brave_vpn=$enable_brave_vpn",
"enable_pin_shortcut=$enable_pin_shortcut",
"enable_text_recognition=$enable_text_recognition",
]
source = "brave_generated_resources.grd"
output_dir = "$root_gen_dir/brave"
Expand Down
1 change: 1 addition & 0 deletions app/brave_command_ids.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
#define IDC_COPY_CLEAN_LINK 56040
#define IDC_TOGGLE_TAB_MUTE 56041
#define IDC_SIDEBAR_TOGGLE_POSITION 56042
#define IDC_CONTENT_CONTEXT_COPY_TEXT_FROM_IMAGE 56043

#define IDC_CONTENT_CONTEXT_IMPORT_IPNS_KEYS_START 56100
#define IDC_CONTENT_CONTEXT_IMPORT_IPNS_KEYS_END 56199
Expand Down
19 changes: 19 additions & 0 deletions app/brave_generated_resources.grd
Original file line number Diff line number Diff line change
Expand Up @@ -891,6 +891,25 @@ Or change later at <ph name="SETTINGS_EXTENIONS_LINK">$2<ex>brave://settings/ext
</message>
</if>

<!-- Text recognition dialog-->
<if expr="enable_text_recognition">
<message name="IDS_CONTENT_CONTEXT_COPY_TEXT_FROM_IMAGE" desc="The name of the context menu item to get text from image">
Copy Text From Image
</message>
<message name="IDS_TEXT_RECOG_DIALOG_CLOSE_BUTTON" desc="The text for the close button of text recognition dialog">
Close
</message>
<message name="IDS_TEXT_RECOG_DIALOG_HEADER_IN_PROGRESS" desc="The text for the header of text recognition dialog">
Copying text from image...
</message>
<message name="IDS_TEXT_RECOG_DIALOG_HEADER_COMPLETE" desc="The text for the header of text recognition dialog">
Text copied from image
</message>
<message name="IDS_TEXT_RECOG_DIALOG_HEADER_FAILED" desc="The text for the header of text recognition dialog">
Text copy failed
</message>
</if>

<!-- Brave VPN -->
<if expr="enable_brave_vpn">
<message name="IDS_SETTINGS_SHOW_VPN_BUTTON" desc="The label for showing vpn button in the toolbar">
Expand Down
10 changes: 10 additions & 0 deletions browser/ui/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import("//brave/components/ipfs/buildflags/buildflags.gni")
import("//brave/components/ntp_background_images/buildflags/buildflags.gni")
import("//brave/components/playlist/buildflags/buildflags.gni")
import("//brave/components/speedreader/common/buildflags/buildflags.gni")
import("//brave/components/text_recognition/common/buildflags/buildflags.gni")
import("//brave/components/tor/buildflags/buildflags.gni")
import("//build/config/features.gni")
import("//chrome/common/features.gni")
Expand Down Expand Up @@ -430,6 +431,15 @@ source_set("ui") {
]
}

if (enable_text_recognition) {
sources += [
"views/text_recognition_dialog_view.cc",
"views/text_recognition_dialog_view.h",
]

deps += [ "//brave/components/text_recognition/browser" ]
}

# This is no longer compiled into Chromium on Android, but we still
# need it
if (is_android) {
Expand Down
9 changes: 9 additions & 0 deletions browser/ui/browser_dialogs.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,21 @@
#define BRAVE_BROWSER_UI_BROWSER_DIALOGS_H_

class Browser;
class SkBitmap;

namespace content {
class WebContents;
} // namespace content

namespace brave {

// Tab restore dialog will be launched after ask dialog is closed.
void ShowCrashReportPermissionAskDialog(Browser* browser);

// Show web modal dialog for showing text that recognized from |image|.
void ShowTextRecognitionDialog(content::WebContents* web_contents,
const SkBitmap& image);

} // namespace brave

#endif // BRAVE_BROWSER_UI_BROWSER_DIALOGS_H_
126 changes: 126 additions & 0 deletions browser/ui/views/text_recognition_dialog_view.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/* Copyright (c) 2023 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/. */

#include "brave/browser/ui/views/text_recognition_dialog_view.h"

#include <memory>

#include "base/bind.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "brave/components/l10n/common/localization_util.h"
#include "brave/components/text_recognition/browser/text_recognition.h"
#include "brave/grit/brave_generated_resources.h"
#include "components/constrained_window/constrained_window_views.h"
#include "ui/base/clipboard/scoped_clipboard_writer.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/scroll_view.h"
#include "ui/views/layout/flex_layout.h"
#include "ui/views/widget/widget.h"
#include "ui/views/window/dialog_client_view.h"

namespace brave {

void ShowTextRecognitionDialog(content::WebContents* web_contents,
const SkBitmap& image) {
constrained_window::ShowWebModalDialogViews(
new TextRecognitionDialogView(image), web_contents)
->Show();
}

} // namespace brave

TextRecognitionDialogView::TextRecognitionDialogView(const SkBitmap& image) {
SetModalType(ui::MODAL_TYPE_CHILD);
SetButtons(ui::DIALOG_BUTTON_OK);
SetButtonLabel(ui::DIALOG_BUTTON_OK,
brave_l10n::GetLocalizedResourceUTF16String(
IDS_TEXT_RECOG_DIALOG_CLOSE_BUTTON));
SetShowCloseButton(false);

SetLayoutManager(std::make_unique<views::FlexLayout>())
->SetOrientation(views::LayoutOrientation::kVertical)
.SetMainAxisAlignment(views::LayoutAlignment::kStart)
.SetInteriorMargin(gfx::Insets::TLBR(24, 26, 0, 26));

header_label_ = AddChildView(std::make_unique<views::Label>());
const int size_diff = 14 - views::Label::GetDefaultFontList().GetFontSize();
header_label_->SetFontList(
views::Label::GetDefaultFontList()
.DeriveWithSizeDelta(size_diff)
.DeriveWithWeight(gfx::Font::Weight::SEMIBOLD));
header_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
header_label_->SetProperty(views::kMarginsKey,
gfx::Insets::TLBR(0, 0, 10, 0));

if (image.empty()) {
UpdateContents({});
return;
}

StartExtractingText(image);
}

TextRecognitionDialogView::~TextRecognitionDialogView() = default;

void TextRecognitionDialogView::StartExtractingText(const SkBitmap& image) {
header_label_->SetText(brave_l10n::GetLocalizedResourceUTF16String(
IDS_TEXT_RECOG_DIALOG_HEADER_IN_PROGRESS));

base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
base::BindOnce(&GetTextFromImage, image),
base::BindOnce(&TextRecognitionDialogView::OnGetTextFromImage,
weak_factory_.GetWeakPtr()));
}

void TextRecognitionDialogView::OnGetTextFromImage(
const std::vector<std::string>& text) {
UpdateContents(text);
AdjustWidgetSize();
}

void TextRecognitionDialogView::UpdateContents(
const std::vector<std::string>& text) {
if (text.empty()) {
header_label_->SetText(brave_l10n::GetLocalizedResourceUTF16String(
IDS_TEXT_RECOG_DIALOG_HEADER_FAILED));
return;
}

header_label_->SetText(brave_l10n::GetLocalizedResourceUTF16String(
IDS_TEXT_RECOG_DIALOG_HEADER_COMPLETE));

// Treat each string in |text| as a separated line string.
std::string unified_string;
for (const auto& t : text) {
unified_string += t;
unified_string += '\n';
}

ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste)
.WriteText(base::UTF8ToUTF16(unified_string));

auto* scroll_view = AddChildView(std::make_unique<views::ScrollView>());
scroll_view->SetProperty(views::kMarginsKey, gfx::Insets::VH(0, 10));
scroll_view->ClipHeightTo(0, 350);
auto* label = scroll_view->SetContents(
std::make_unique<views::Label>(base::UTF8ToUTF16(unified_string)));

label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
label->SetSelectable(true);
label->SetMultiLine(true);
}

void TextRecognitionDialogView::AdjustWidgetSize() {
GetWidget()->SetSize(GetDialogClientView()->GetPreferredSize());
}

BEGIN_METADATA(TextRecognitionDialogView, views::DialogDelegateView)
END_METADATA
45 changes: 45 additions & 0 deletions browser/ui/views/text_recognition_dialog_view.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/* Copyright (c) 2023 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/. */

#ifndef BRAVE_BROWSER_UI_VIEWS_TEXT_RECOGNITION_DIALOG_VIEW_H_
#define BRAVE_BROWSER_UI_VIEWS_TEXT_RECOGNITION_DIALOG_VIEW_H_

#include <string>
#include <vector>

#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/views/window/dialog_delegate.h"

class SkBitmap;

namespace views {
class Label;
} // namespace views

class TextRecognitionDialogView : public views::DialogDelegateView {
public:
METADATA_HEADER(TextRecognitionDialogView);

explicit TextRecognitionDialogView(const SkBitmap& image);
TextRecognitionDialogView(const TextRecognitionDialogView&) = delete;
TextRecognitionDialogView& operator=(const TextRecognitionDialogView&) =
delete;
~TextRecognitionDialogView() override;

private:
void StartExtractingText(const SkBitmap& image);
void OnGetTextFromImage(const std::vector<std::string>& text);

// Show |text| in this dialog and copy it to clipboard.
void UpdateContents(const std::vector<std::string>& text);
void AdjustWidgetSize();

raw_ptr<views::Label> header_label_ = nullptr;
base::WeakPtrFactory<TextRecognitionDialogView> weak_factory_{this};
};

#endif // BRAVE_BROWSER_UI_VIEWS_TEXT_RECOGNITION_DIALOG_VIEW_H_
1 change: 1 addition & 0 deletions chromium_src/chrome/browser/DEPS
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ include_rules = [
"+brave/components/privacy_sandbox",
"+brave/components/sidebar",
"+brave/components/sync",
"+brave/components/text_recognition",
"+brave/components/tor",
"+brave/components/translate",
"+brave/components/url_sanitizer",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright 2018 The Brave Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/* Copyright (c) 2018 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/. */

#include "chrome/browser/renderer_context_menu/render_view_context_menu.h"

Expand All @@ -9,6 +10,7 @@
#include "brave/browser/profiles/profile_util.h"
#include "brave/browser/renderer_context_menu/brave_spelling_options_submenu_observer.h"
#include "brave/browser/ui/browser_commands.h"
#include "brave/browser/ui/browser_dialogs.h"
#include "brave/components/ipfs/buildflags/buildflags.h"
#include "brave/components/tor/buildflags/buildflags.h"
#include "brave/grit/brave_theme_resources.h"
Expand Down Expand Up @@ -137,6 +139,16 @@ void OnTorProfileCreated(const GURL& link_url,

#endif

#if BUILDFLAG(ENABLE_TEXT_RECOGNITION)
void OnGetImage(base::WeakPtr<content::WebContents> web_contents,
const SkBitmap& image) {
if (!web_contents)
return;

brave::ShowTextRecognitionDialog(web_contents.get(), image);
}
#endif

} // namespace

BraveRenderViewContextMenu::BraveRenderViewContextMenu(
Expand All @@ -152,6 +164,10 @@ BraveRenderViewContextMenu::BraveRenderViewContextMenu(

bool BraveRenderViewContextMenu::IsCommandIdEnabled(int id) const {
switch (id) {
#if BUILDFLAG(ENABLE_TEXT_RECOGNITION)
case IDC_CONTENT_CONTEXT_COPY_TEXT_FROM_IMAGE:
return params_.has_image_contents;
#endif
case IDC_COPY_CLEAN_LINK:
return params_.link_url.is_valid();
case IDC_CONTENT_CONTEXT_FORCE_PASTE:
Expand Down Expand Up @@ -249,12 +265,27 @@ void BraveRenderViewContextMenu::ExecuteCommand(int id, int event_flags) {
base::BindRepeating(OnTorProfileCreated, params_.link_url,
HasAlreadyOpenedTorWindow(GetProfile())));
break;
#endif
#if BUILDFLAG(ENABLE_TEXT_RECOGNITION)
case IDC_CONTENT_CONTEXT_COPY_TEXT_FROM_IMAGE:
CopyTextFromImage();
break;
#endif
default:
RenderViewContextMenu_Chromium::ExecuteCommand(id, event_flags);
}
}

#if BUILDFLAG(ENABLE_TEXT_RECOGNITION)
void BraveRenderViewContextMenu::CopyTextFromImage() {
RenderFrameHost* frame_host = GetRenderFrameHost();
if (frame_host)
frame_host->GetImageAt(
params_.x, params_.y,
base::BindOnce(OnGetImage, source_web_contents_->GetWeakPtr()));
}
#endif

void BraveRenderViewContextMenu::AddSpellCheckServiceItem(bool is_checked) {
// Call our implementation, not the one in the base class.
// Assumption:
Expand Down Expand Up @@ -382,6 +413,18 @@ void BraveRenderViewContextMenu::InitMenu() {
IDC_CONTENT_CONTEXT_FORCE_PASTE,
IDS_CONTENT_CONTEXT_FORCE_PASTE);
}
#if BUILDFLAG(ENABLE_TEXT_RECOGNITION)
bool media_image = content_type_->SupportsGroup(
ContextMenuContentType::ITEM_GROUP_MEDIA_IMAGE);
if (media_image) {
index =
menu_model_.GetIndexOfCommandId(IDC_CONTENT_CONTEXT_COPYIMAGELOCATION);
DCHECK(index);
menu_model_.InsertItemWithStringIdAt(
index.value() + 1, IDC_CONTENT_CONTEXT_COPY_TEXT_FROM_IMAGE,
IDS_CONTENT_CONTEXT_COPY_TEXT_FROM_IMAGE);
}
#endif

#if BUILDFLAG(ENABLE_TOR)
// Add Open Link with Tor
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
// Copyright 2018 The Brave Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/* Copyright (c) 2018 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/. */

#ifndef BRAVE_CHROMIUM_SRC_CHROME_BROWSER_RENDERER_CONTEXT_MENU_RENDER_VIEW_CONTEXT_MENU_H_
#define BRAVE_CHROMIUM_SRC_CHROME_BROWSER_RENDERER_CONTEXT_MENU_RENDER_VIEW_CONTEXT_MENU_H_

#include "brave/components/ipfs/buildflags/buildflags.h"
#include "brave/components/text_recognition/common/buildflags/buildflags.h"

#define BRAVE_RENDER_VIEW_CONTEXT_MENU_H_ \
private: \
Expand Down Expand Up @@ -53,6 +55,10 @@ class BraveRenderViewContextMenu : public RenderViewContextMenu_Chromium {

ui::SimpleMenuModel ipfs_submenu_model_;
#endif

#if BUILDFLAG(ENABLE_TEXT_RECOGNITION)
void CopyTextFromImage();
#endif
};

// Use our own subclass as the real RenderViewContextMenu.
Expand Down
Loading

0 comments on commit 1f145cb

Please sign in to comment.