Skip to content

Commit

Permalink
Add format to saveToMemory and expose LOK's saveAs
Browse files Browse the repository at this point in the history
  • Loading branch information
chase committed Oct 25, 2023
1 parent 3d40556 commit 7306a9a
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 47 deletions.
2 changes: 1 addition & 1 deletion src/electron/DEPS
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ vars = {
'0e5d146ba13101a1302d59ea6e6e0b3cace4ae38',

'pyyaml_version': '3.12',
'libreofficekit_version': 'v0.6.1',
'libreofficekit_version': 'v0.6.3',

'chromium_git': 'https://chromium.googlesource.com',
'electron_git': 'https://github.com/electron',
Expand Down
2 changes: 1 addition & 1 deletion src/electron/ELECTRON_VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
20.3.0-rc2
20.3.0-rc3
19 changes: 18 additions & 1 deletion src/electron/npm/libreoffice.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,24 @@ declare namespace LibreOffice {
destRect: string;
};

saveToMemory(): Promise<ArrayBuffer | undefined>;
/**
* saves the document to memory
* @param [format] - the optional format the document saves to, when omitted docx is used
* @returns an array buffer with the bytes of the document or undefiend if saving failed
*/
saveToMemory(format?: string): Promise<ArrayBuffer | undefined>;

/**
* saves the document to memory
* Stores the document's persistent data to a URL and
* continues to be a representation of the old URL.
*
* @param url the location where to store the document
* @param [format] the format to use while exporting, when omitted, then deducted from the URL's file extension
* @param [filter] options for the export filter
* @returns true if the save succeeded, false otherwise
*/
saveAs(url: string, format?: string, filter?: string): boolean;

/**
* show/hide a single row/column header outline for Calc documents
Expand Down
156 changes: 114 additions & 42 deletions src/electron/office/document_client.cc
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,36 @@ struct LokFree {

typedef std::unique_ptr<char[], LokFree> LokStrPtr;

std::unique_ptr<char[]> stringify(const v8::Local<v8::Context>& context,
const v8::Local<v8::Value>& val) {
v8::Isolate* isolate = context->GetIsolate();
v8::Context::Scope context_scope(context);
v8::HandleScope scope(isolate);
v8::TryCatch try_catch(isolate);

v8::Local<v8::String> str;
if (!val->ToString(context).ToLocal(&str))
return {};

uint32_t len = str->Utf8Length(isolate);
char* buf = new (std::nothrow) char[len + 1];
if (!buf)
return {};
std::unique_ptr<char[]> ptr(buf);
str->WriteUtf8(isolate, buf);
buf[len] = '\0';

return ptr;
}

std::unique_ptr<char[]> jsonStringify(const v8::Local<v8::Context>& context,
const v8::Local<v8::Value>& val) {
v8::Local<v8::String> str_object;
if (!v8::JSON::Stringify(context, val).ToLocal(&str_object))
return {};
return stringify(context, str_object);
}

} // namespace

DocumentClient::DocumentClient(base::WeakPtr<OfficeClient> office_client,
Expand Down Expand Up @@ -127,6 +157,7 @@ gin::ObjectTemplateBuilder DocumentClient::GetObjectTemplateBuilder(
.SetMethod("setAuthor", &DocumentClient::SetAuthor)
.SetMethod("gotoOutline", &DocumentClient::GotoOutline)
.SetMethod("saveToMemory", &DocumentClient::SaveToMemoryAsync)
.SetMethod("saveAs", &DocumentClient::SaveAsAsync)
.SetMethod("getTextSelection", &DocumentClient::GetTextSelection)
.SetMethod("setTextSelection", &DocumentClient::SetTextSelection)
.SetMethod("sendDialogEvent", &DocumentClient::SendDialogEvent)
Expand Down Expand Up @@ -394,24 +425,33 @@ v8::Local<v8::Value> DocumentClient::GotoOutline(int idx,
.FromMaybe(v8::Local<v8::Value>());
}

base::span<char> DocumentClient::SaveToMemory(v8::Isolate* isolate) {
base::span<char> DocumentClient::SaveToMemory(v8::Isolate* isolate,
std::unique_ptr<char[]> format) {
char* pOutput = nullptr;
size_t size = document_->saveToMemory(&pOutput, malloc);
size_t size = document_->saveToMemory(&pOutput, malloc,
format ? format.get() : nullptr);
SetView();
if (size < 0) {
return {};
}
return base::span<char>(pOutput, size);
}

v8::Local<v8::Promise> DocumentClient::SaveToMemoryAsync(v8::Isolate* isolate) {
v8::Local<v8::Promise> DocumentClient::SaveToMemoryAsync(v8::Isolate* isolate,
gin::Arguments* args) {
v8::EscapableHandleScope handle_scope(isolate);
v8::Local<v8::Promise::Resolver> promise =
v8::Promise::Resolver::New(isolate->GetCurrentContext()).ToLocalChecked();
v8::Local<v8::Value> arguments;
std::unique_ptr<char[]> format;
if (args->GetNext(&arguments)) {
format = stringify(isolate->GetCurrentContext(), arguments);
}

base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::TaskPriority::USER_VISIBLE, base::MayBlock()},
base::BindOnce(&DocumentClient::SaveToMemory, base::Unretained(this),
isolate),
isolate, std::move(format)),
base::BindOnce(&DocumentClient::SaveToMemoryComplete,
weak_factory_.GetWeakPtr(), isolate,
new ThreadedPromiseResolver(isolate, promise)));
Expand Down Expand Up @@ -439,42 +479,8 @@ void DocumentClient::SaveToMemoryComplete(v8::Isolate* isolate,
resolver->Resolve(isolate, array_buffer);
}

namespace {

std::unique_ptr<char[]> stringify(const v8::Local<v8::Context>& context,
const v8::Local<v8::Value>& val) {
v8::Isolate* isolate = context->GetIsolate();
v8::Context::Scope context_scope(context);
v8::HandleScope scope(isolate);
v8::TryCatch try_catch(isolate);

v8::Local<v8::String> str;
if (!val->ToString(context).ToLocal(&str))
return {};

uint32_t len = str->Utf8Length(isolate);
char* buf = new (std::nothrow) char[len + 1];
if (!buf)
return {};
std::unique_ptr<char[]> ptr(buf);
str->WriteUtf8(isolate, buf);
buf[len] = '\0';

return ptr;
}

std::unique_ptr<char[]> jsonStringify(const v8::Local<v8::Context>& context,
const v8::Local<v8::Value>& val) {
v8::Local<v8::String> str_object;
if (!v8::JSON::Stringify(context, val).ToLocal(&str_object))
return {};
return stringify(context, str_object);
}

} // namespace

void DocumentClient::SetAuthor(const std::string& author,
gin::Arguments* args) {
gin::Arguments* args) {
document_->setAuthor(author.c_str());
}

Expand All @@ -483,16 +489,29 @@ void DocumentClient::PostUnoCommand(const std::string& command,
v8::Local<v8::Value> arguments;
std::unique_ptr<char[]> json_buffer;

bool notifyWhenFinished;
bool notifyWhenFinished = false;
bool nonblocking = false;
if (args->GetNext(&arguments)) {
json_buffer = jsonStringify(args->GetHolderCreationContext(), arguments);
if (!json_buffer)
return;
if (arguments->IsObject()) {
gin::Dictionary options(args->isolate(), arguments.As<v8::Object>());
options.Get("nonblocking", &nonblocking);
}
}

args->GetNext(&notifyWhenFinished);

PostUnoCommandInternal(command, std::move(json_buffer), notifyWhenFinished);
if (nonblocking) {
base::ThreadPool::PostTask(
FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_BLOCKING},
base::BindOnce(&DocumentClient::PostUnoCommandInternal,
base::Unretained(this), command, std::move(json_buffer),
true));
} else {
PostUnoCommandInternal(command, std::move(json_buffer), notifyWhenFinished);
}
}

void DocumentClient::PostUnoCommandInternal(const std::string& command,
Expand Down Expand Up @@ -617,7 +636,8 @@ v8::Local<v8::Value> DocumentClient::GetClipboard(gin::Arguments* args) {

for (size_t i = 0; i < out_count; ++i) {
size_t buffer_size = out_sizes[i];
if (buffer_size <= 0) continue;
if (buffer_size <= 0)
continue;

// allocate a new ArrayBuffer and copy the stream to the backing store
v8::Local<v8::ArrayBuffer> buffer =
Expand Down Expand Up @@ -870,4 +890,56 @@ void DocumentClient::EmitReady(v8::Isolate* isolate,
event_bus_.Emit("ready", ready_value);
}

bool DocumentClient::SaveAs(v8::Isolate* isolate,
std::unique_ptr<char[]> path,
std::unique_ptr<char[]> format,
std::unique_ptr<char[]> options) {
bool success = document_->saveAs(path.get(), format ? format.get() : nullptr,
options ? options.get() : nullptr);
SetView();
return success;
}

v8::Local<v8::Promise> DocumentClient::SaveAsAsync(v8::Isolate* isolate,
gin::Arguments* args) {
v8::EscapableHandleScope handle_scope(isolate);
v8::Local<v8::Promise::Resolver> promise =
v8::Promise::Resolver::New(isolate->GetCurrentContext()).ToLocalChecked();
v8::Local<v8::Value> arguments;
std::unique_ptr<char[]> path;
std::unique_ptr<char[]> format;
std::unique_ptr<char[]> options;
if (!args->GetNext(&arguments)) {
args->ThrowTypeError("missing path");
return {};
}
path = stringify(isolate->GetCurrentContext(), arguments);
if (args->GetNext(&arguments)) {
format = stringify(isolate->GetCurrentContext(), arguments);
}
if (args->GetNext(&arguments)) {
options = stringify(isolate->GetCurrentContext(), arguments);
}

base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::TaskPriority::USER_VISIBLE, base::MayBlock()},
base::BindOnce(&DocumentClient::SaveAs, base::Unretained(this), isolate,
std::move(path), std::move(format), std::move(options)),
base::BindOnce(&DocumentClient::SaveAsComplete,
weak_factory_.GetWeakPtr(), isolate,
new ThreadedPromiseResolver(isolate, promise)));
return handle_scope.Escape(promise->GetPromise());
}

void DocumentClient::SaveAsComplete(v8::Isolate* isolate,
ThreadedPromiseResolver* resolver,
bool success) {
AsyncScope async(isolate);
v8::Local<v8::Context> context = resolver->GetCreationContext(isolate);
v8::Context::Scope context_scope(context);

resolver->Resolve(isolate, v8::Boolean::New(isolate, success));
}


} // namespace electron::office
13 changes: 11 additions & 2 deletions src/electron/office/document_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,10 @@ class DocumentClient : public gin::Wrappable<DocumentClient> {
std::unique_ptr<char[]> json_buffer,
bool notifyWhenFinished);
v8::Local<v8::Value> GotoOutline(int idx, gin::Arguments* args);
v8::Local<v8::Promise> SaveToMemoryAsync(v8::Isolate* isolate);
v8::Local<v8::Promise> SaveToMemoryAsync(v8::Isolate* isolate,
gin::Arguments* args);
v8::Local<v8::Promise> SaveAsAsync(v8::Isolate* isolate,
gin::Arguments* args);
std::vector<std::string> GetTextSelection(const std::string& mime_type,
gin::Arguments* args);
void SetTextSelection(int n_type, int n_x, int n_y);
Expand Down Expand Up @@ -158,10 +161,16 @@ class DocumentClient : public gin::Wrappable<DocumentClient> {
bool IsMounted();

void EmitReady(v8::Isolate* isolate, v8::Global<v8::Context> context);
base::span<char> SaveToMemory(v8::Isolate* isolate);
base::span<char> SaveToMemory(v8::Isolate* isolate,
std::unique_ptr<char[]> format);
void SaveToMemoryComplete(v8::Isolate* isolate,
ThreadedPromiseResolver* resolver,
base::span<char> buffer);
bool SaveAs(v8::Isolate* isolate,
std::unique_ptr<char[]> path,
std::unique_ptr<char[]> format,
std::unique_ptr<char[]> options);
void SaveAsComplete(v8::Isolate* isolate, ThreadedPromiseResolver* resolver, bool success);

// has a
lok::Document* document_ = nullptr;
Expand Down

0 comments on commit 7306a9a

Please sign in to comment.