Skip to content

Commit 9a1e014

Browse files
committed
restore ability to save attachments (fixes #1455)
1 parent aa64772 commit 9a1e014

File tree

5 files changed

+91
-3
lines changed

5 files changed

+91
-3
lines changed

src/EngineAll.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ const char* EngineMupdfGetPassword(EngineBase* engine);
6060
bool EngineMupdfSaveUpdated(EngineBase* engine, const char* path, const ShowErrorCb& showErrorFunc);
6161
Annotation* EngineMupdfGetAnnotationAtPos(EngineBase*, int pageNo, PointF pos, Annotation*);
6262
ByteSlice EngineMupdfLoadAttachment(EngineBase*, int attachmentNo);
63+
ByteSlice EngineMupdfLoadAnnotAttachment(EngineBase*, int objNum);
6364
TempStr EngineMupdfGetPdfInfo(const char* path);
6465
TempStr EngineMupdfGetPdfOutline(const char* path);
6566

src/EngineBase.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ struct PageDestinationFile : IPageDestination {
155155
struct PageDestination : IPageDestination {
156156
char* value = nullptr;
157157
char* name = nullptr;
158+
int embedObjNum = 0; // PDF object number for embedded file attachment annotations
158159

159160
PageDestination() = default;
160161

src/EngineMupdf.cpp

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1355,6 +1355,31 @@ static ByteSlice PdfLoadAttachment(fz_context* ctx, pdf_document* doc, int no) {
13551355
return res;
13561356
}
13571357

1358+
// load embedded file data from a file attachment annotation by PDF object number
1359+
static ByteSlice PdfLoadAnnotationAttachment(fz_context* ctx, pdf_document* doc, int objNum) {
1360+
ByteSlice res;
1361+
fz_try(ctx) {
1362+
pdf_obj* obj = pdf_new_indirect(ctx, doc, objNum, 0);
1363+
pdf_obj* fs = pdf_dict_get(ctx, obj, PDF_NAME(FS));
1364+
if (!fs) {
1365+
pdf_drop_obj(ctx, obj);
1366+
break;
1367+
}
1368+
fz_buffer* buf = pdf_load_embedded_file_contents(ctx, fs);
1369+
if (buf) {
1370+
res.d = (u8*)memdup(buf->data, buf->len);
1371+
res.sz = buf->len;
1372+
fz_drop_buffer(ctx, buf);
1373+
}
1374+
pdf_drop_obj(ctx, obj);
1375+
}
1376+
fz_catch(ctx) {
1377+
fz_report_error(ctx);
1378+
logfa("PdfLoadAnnotationAttachment(objNum=%d) failed\n", objNum);
1379+
}
1380+
return res;
1381+
}
1382+
13581383
// Note: make sure to only call with ctxAccess
13591384
static fz_outline* PdfLoadAttachments(fz_context* ctx, pdf_document* doc, const char* path) {
13601385
fz_outline root{};
@@ -2725,9 +2750,9 @@ static void RebuildCommentsFromAnnotationsInner(fz_context* ctx, pdf_annot* anno
27252750
logf("attachment: %s, num: %d\n", attname, num);
27262751

27272752
auto dest = new PageDestination();
2728-
// TODO: kindDestinationAttachment ?
27292753
dest->kind = kindDestinationLaunchEmbedded;
27302754
dest->value = str::Dup(attname);
2755+
dest->embedObjNum = num;
27312756

27322757
auto el = new PageElementDestination(dest);
27332758
el->pageNo = pageNo;
@@ -3982,6 +4007,15 @@ ByteSlice EngineMupdfLoadAttachment(EngineBase* engine, int attachmentNo) {
39824007
return res;
39834008
}
39844009

4010+
ByteSlice EngineMupdfLoadAnnotAttachment(EngineBase* engine, int objNum) {
4011+
EngineMupdf* epdf = AsEngineMupdf(engine);
4012+
if (!epdf->pdfdoc) {
4013+
return {};
4014+
}
4015+
ScopedCritSec scope(epdf->ctxAccess);
4016+
return PdfLoadAnnotationAttachment(epdf->Ctx(), epdf->pdfdoc, objNum);
4017+
}
4018+
39854019
// if an elements fully obscures another, remove it from the list
39864020
static bool RemoveHeWhoFullyContains(Vec<Annotation*>& els) {
39874021
int n = els.Size();

src/MainWindow.cpp

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -440,8 +440,24 @@ void LinkHandler::GotoLink(IPageDestination* dest) {
440440
return;
441441
}
442442
if (kindDestinationLaunchEmbedded == kind) {
443-
// Not handled here. Must use context menu to trigger launching
444-
// embedded files
443+
PageDestination* pd = (PageDestination*)dest;
444+
if (pd->embedObjNum > 0) {
445+
EngineBase* engine = win->CurrentTab()->AsFixed()->GetEngine();
446+
ByteSlice data = EngineMupdfLoadAnnotAttachment(engine, pd->embedObjNum);
447+
if (!data.empty()) {
448+
const char* fileName = pd->GetValue2();
449+
logf("GotoLink: opening file attachment annotation '%s', objNum: %d, size: %d\n", fileName,
450+
pd->embedObjNum, (int)data.sz);
451+
TempStr tmpDir = GetTempDirTemp();
452+
if (tmpDir) {
453+
TempStr tmpPath = path::JoinTemp(tmpDir, path::GetBaseNameTemp(fileName));
454+
if (file::WriteFile(tmpPath, data)) {
455+
SumatraLaunchBrowser(tmpPath);
456+
}
457+
}
458+
str::Free(data.data());
459+
}
460+
}
445461
return;
446462
}
447463

src/Menu.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -900,6 +900,10 @@ static MenuDef menuDefContext[] = {
900900
_TRN("Copy Co&mment"),
901901
CmdCopyComment,
902902
},
903+
{
904+
_TRN("Save Attachment"),
905+
CmdSaveAttachment,
906+
},
903907
{
904908
_TRN("&Image"),
905909
(UINT_PTR)menuDefContextImage,
@@ -1949,6 +1953,19 @@ void OnWindowContextMenu(MainWindow* win, int x, int y) {
19491953
if (!pageEl || !pageEl->Is(kindPageElementComment) || !value) {
19501954
MenuRemove(popup, CmdCopyComment);
19511955
}
1956+
// show "Save Attachment" only for file attachment annotations
1957+
{
1958+
bool isFileAttachment = false;
1959+
if (pageEl && pageEl->Is(kindPageElementDest)) {
1960+
IPageDestination* elDest = pageEl->AsLink();
1961+
if (elDest && elDest->GetKind() == kindDestinationLaunchEmbedded) {
1962+
isFileAttachment = true;
1963+
}
1964+
}
1965+
if (!isFileAttachment) {
1966+
MenuRemove(popup, CmdSaveAttachment);
1967+
}
1968+
}
19521969
{
19531970
bool onImage = pageEl && pageEl->Is(kindPageElementImage);
19541971
bool isImageEngine = tab && tab->GetEngineType() == kindEngineImage;
@@ -2122,6 +2139,25 @@ void OnWindowContextMenu(MainWindow* win, int x, int y) {
21222139
return;
21232140
}
21242141

2142+
case CmdSaveAttachment: {
2143+
if (pageEl && pageEl->Is(kindPageElementDest)) {
2144+
IPageDestination* elDest = pageEl->AsLink();
2145+
PageDestination* pd = (PageDestination*)elDest;
2146+
if (pd && pd->embedObjNum > 0) {
2147+
ByteSlice data = EngineMupdfLoadAnnotAttachment(engine, pd->embedObjNum);
2148+
if (!data.empty()) {
2149+
const char* fileName = pd->GetValue2();
2150+
TempStr dir = path::GetDirTemp(filePath);
2151+
fileName = path::GetBaseNameTemp(fileName);
2152+
TempStr dstPath = path::JoinTemp(dir, fileName);
2153+
SaveDataToFile(win->hwndFrame, dstPath, data);
2154+
str::Free(data.data());
2155+
}
2156+
}
2157+
}
2158+
return;
2159+
}
2160+
21252161
case CmdCopyImage: {
21262162
if (pageEl) {
21272163
RenderedBitmap* bmp = dm->GetEngine()->GetImageForPageElement(pageEl);

0 commit comments

Comments
 (0)