qpdf_ruby 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +24 -1
- data/cliff.toml +138 -0
- data/ext/qpdf_ruby/document_handle.cpp +76 -24
- data/ext/qpdf_ruby/document_handle.hpp +32 -2
- data/ext/qpdf_ruby/pdf_image_mapper.hpp +2 -0
- data/ext/qpdf_ruby/pdf_struct_walker.hpp +2 -0
- data/ext/qpdf_ruby/qpdf_ruby.cpp +100 -9
- data/ext/qpdf_ruby/qpdf_ruby.hpp +1 -0
- data/ext/qpdf_ruby/struct_node.hpp +2 -0
- data/lib/qpdf_ruby/version.rb +1 -1
- data/lib/qpdf_ruby.rb +4 -0
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b5e34005af4a00f153fe6597426be27d03e047c9fce46af4e669c22138e0b726
|
4
|
+
data.tar.gz: 95fb970cb38489fd18b2d8aee4e0f48e843f9b14f45a4806ea7b5b1e6f79045c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6f1e65790f45ab85cb633883c4f7d8597d8271e15aedf6ef670e58491f0658a0bf53370a232abdda370088bc13594445e292d7e73fda1b34e9fa460a15ae0eef
|
7
|
+
data.tar.gz: be16ec8f1fde0eb5c022a39b29588429319d6aa553fdc368ee90f6a409659a0d7d0a30eba212683e4f5640eb961c127bef46d2f7f4dded9879ab58195c0df67e
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,28 @@
|
|
1
|
-
|
1
|
+
<!-- generated by git-cliff start -->
|
2
|
+
# Changelog
|
3
|
+
|
4
|
+
All notable changes to this project will be documented in this file.
|
5
|
+
|
6
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
7
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
8
|
+
|
9
|
+
[unreleased]: https://github.com///compare/v0.1.1..HEAD
|
10
|
+
|
11
|
+
<!-- generated by git-cliff -->
|
12
|
+
|
13
|
+
## [0.1.1] - 2025-06-30
|
14
|
+
|
15
|
+
### 🚀 Features
|
16
|
+
|
17
|
+
- Add PDF encryption support
|
18
|
+
|
19
|
+
|
2
20
|
|
3
21
|
## [0.1.0] - 2025-06-11
|
4
22
|
|
5
23
|
- Initial release
|
24
|
+
|
25
|
+
### 🔄 Released
|
26
|
+
|
27
|
+
- [unreleased](https://github.com/dieter-medium/bidi2pdf/compare/v0.1.1..HEAD)
|
28
|
+
- [0.1.1](https://github.com/dieter-medium/bidi2pdf/compare/v0.1.0..v0.1.1)
|
data/cliff.toml
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
# git-cliff ~ configuration file
|
2
|
+
# https://git-cliff.org/docs/configuration
|
3
|
+
|
4
|
+
[changelog]
|
5
|
+
# template for the changelog header
|
6
|
+
header = """
|
7
|
+
<!-- generated by git-cliff start -->
|
8
|
+
# Changelog\n
|
9
|
+
All notable changes to this project will be documented in this file.
|
10
|
+
|
11
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
12
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n
|
13
|
+
"""
|
14
|
+
# template for the changelog body
|
15
|
+
# https://keats.github.io/tera/docs/#introduction
|
16
|
+
body = """
|
17
|
+
{%- macro remote_url() -%}
|
18
|
+
https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}
|
19
|
+
{%- endmacro -%}
|
20
|
+
|
21
|
+
{% if version -%}
|
22
|
+
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
|
23
|
+
{% else -%}
|
24
|
+
## [Unreleased]
|
25
|
+
{% endif -%}
|
26
|
+
|
27
|
+
{% for group, commits in commits | group_by(attribute="group") %}
|
28
|
+
### {{ group | upper_first }}
|
29
|
+
{%- for commit in commits %}
|
30
|
+
- {{ commit.message | split(pat="\n") | first | upper_first | trim }}\
|
31
|
+
{% if commit.remote.username %} by @{{ commit.remote.username }}{%- endif -%}
|
32
|
+
{% if commit.remote.pr_number %} in \
|
33
|
+
[#{{ commit.remote.pr_number }}]({{ self::remote_url() }}/pull/{{ commit.remote.pr_number }}) \
|
34
|
+
{%- endif -%}
|
35
|
+
{% endfor %}
|
36
|
+
{% endfor %}
|
37
|
+
|
38
|
+
{%- if github.contributors | filter(attribute="is_first_time", value=true) | length != 0 %}
|
39
|
+
## New Contributors
|
40
|
+
{%- endif -%}
|
41
|
+
|
42
|
+
{% for contributor in github.contributors | filter(attribute="is_first_time", value=true) %}
|
43
|
+
* @{{ contributor.username }} made their first contribution
|
44
|
+
{%- if contributor.pr_number %} in \
|
45
|
+
[#{{ contributor.pr_number }}]({{ self::remote_url() }}/pull/{{ contributor.pr_number }}) \
|
46
|
+
{%- endif %}
|
47
|
+
{%- endfor %}\n
|
48
|
+
"""
|
49
|
+
# template for the changelog footer
|
50
|
+
footer = """
|
51
|
+
{%- macro remote_url() -%}
|
52
|
+
https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}
|
53
|
+
{%- endmacro -%}
|
54
|
+
|
55
|
+
{% for release in releases -%}
|
56
|
+
{% if release.version -%}
|
57
|
+
{% if release.previous.version -%}
|
58
|
+
[{{ release.version | trim_start_matches(pat="v") }}]: \
|
59
|
+
{{ self::remote_url() }}/compare/{{ release.previous.version }}..{{ release.version }}
|
60
|
+
{% endif -%}
|
61
|
+
{% else -%}
|
62
|
+
[unreleased]: {{ self::remote_url() }}/compare/{{ release.previous.version }}..HEAD
|
63
|
+
{% endif -%}
|
64
|
+
{% endfor %}
|
65
|
+
<!-- generated by git-cliff end -->
|
66
|
+
"""
|
67
|
+
# remove the leading and trailing whitespace from the templates
|
68
|
+
trim = true
|
69
|
+
|
70
|
+
[git]
|
71
|
+
# parse the commits based on https://www.conventionalcommits.org
|
72
|
+
conventional_commits = true
|
73
|
+
# filter out the commits that are not conventional
|
74
|
+
filter_unconventional = false
|
75
|
+
# regex for preprocessing the commit messages
|
76
|
+
commit_preprocessors = [
|
77
|
+
# remove issue numbers from commits
|
78
|
+
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "" },
|
79
|
+
]
|
80
|
+
# regex for parsing and grouping commits
|
81
|
+
commit_parsers = [
|
82
|
+
# ✅ Features (additions)
|
83
|
+
{ message = "^feat(?:\\([^)]+\\))?!?:", group = "🚀 Added" },
|
84
|
+
{ message = "^[aA]dd", group = "🚀 Added" },
|
85
|
+
{ message = "^[sS]upport", group = "🚀 Added" },
|
86
|
+
|
87
|
+
# ❌ Removals
|
88
|
+
{ message = "^[rR]emove", group = "🗑️ Removed" },
|
89
|
+
{ message = "^[dD]elete", group = "🗑️ Removed" },
|
90
|
+
|
91
|
+
# 🐛 Fixes
|
92
|
+
{ message = "^fix(?:\\([^)]+\\))?!?:", group = "🐛 Fixed" },
|
93
|
+
{ message = "^[tT]est", group = "🐛 Fixed" },
|
94
|
+
{ message = "^[fF]ix", group = "🐛 Fixed" },
|
95
|
+
|
96
|
+
# 🎨 Refactors
|
97
|
+
{ message = "^refactor(?:\\([^)]+\\))?!?:", group = "🎨 Refactored" },
|
98
|
+
|
99
|
+
# ⚡️ Performance
|
100
|
+
{ message = "^perf(?:\\([^)]+\\))?!?:", group = "⚡️ Performance" },
|
101
|
+
|
102
|
+
# 📝 Docs
|
103
|
+
{ message = "^docs(?:\\([^)]+\\))?!?:", group = "📝 Docs" },
|
104
|
+
|
105
|
+
# 💄 Style (formatting, whitespace, etc.)
|
106
|
+
{ message = "^style(?:\\([^)]+\\))?!?:", group = "💄 Style" },
|
107
|
+
|
108
|
+
# 🧪 Tests
|
109
|
+
{ message = "^test(?:\\([^)]+\\))?!?:", group = "🧪 Tests" },
|
110
|
+
|
111
|
+
# 🔧 Build
|
112
|
+
{ message = "^build(?:\\([^)]+\\))?!?:", group = "🔧 Build" },
|
113
|
+
|
114
|
+
# 🛠️ CI
|
115
|
+
{ message = "^ci(?:\\([^)]+\\))?!?:", skip = true },
|
116
|
+
|
117
|
+
# 🧹 Chores (skip)
|
118
|
+
{ message = "^chore\\(release\\): prepare for", skip = true },
|
119
|
+
{ message = "^chore\\(deps.*\\)", skip = true },
|
120
|
+
{ message = "^chore\\(pr\\)", skip = true },
|
121
|
+
{ message = "^chore\\(pull\\)", skip = true },
|
122
|
+
{ message = "^chore(?:\\([^)]+\\))?!?:", skip = true },
|
123
|
+
{ message = "^\\s*chore", skip = true },
|
124
|
+
|
125
|
+
# ⏪ Reverts
|
126
|
+
{ message = "^revert(?:\\([^)]+\\))?!?:", group = "⏪ Reverted" },
|
127
|
+
|
128
|
+
# 🌀 Catch-all (only if nothing else matched)
|
129
|
+
{ message = "^.*", group = "🔄 Changed" }
|
130
|
+
]
|
131
|
+
|
132
|
+
|
133
|
+
# filter out the commits that are not matched by commit parsers
|
134
|
+
filter_commits = false
|
135
|
+
# sort the tags topologically
|
136
|
+
topo_order = false
|
137
|
+
# sort the commits inside sections by oldest/newest order
|
138
|
+
sort_commits = "newest"
|
@@ -1,17 +1,22 @@
|
|
1
|
+
#define POINTERHOLDER_TRANSITION 1
|
2
|
+
|
1
3
|
#include "document_handle.hpp"
|
2
4
|
|
3
5
|
#include <qpdf/QPDFWriter.hh>
|
6
|
+
#include <qpdf/QPDF.hh>
|
4
7
|
#include <stdexcept>
|
5
8
|
#include <system_error>
|
6
9
|
#include <cerrno>
|
7
10
|
|
8
11
|
using namespace qpdf_ruby;
|
9
12
|
|
10
|
-
std::unique_ptr<DocumentHandle> DocumentHandle::open(const std::string& filename) {
|
13
|
+
std::unique_ptr<DocumentHandle> DocumentHandle::open(const std::string& filename, std::string const& pwd) {
|
11
14
|
auto qpdf = std::make_shared<QPDF>();
|
12
15
|
try {
|
13
|
-
|
14
|
-
|
16
|
+
qpdf->processFile(filename.c_str(), pwd.empty() ? nullptr : pwd.c_str());
|
17
|
+
} catch (const QPDFExc& qex) {
|
18
|
+
throw std::runtime_error(std::string("qpdf_ruby: failed to open \"") + filename + "\": " + qex.what() +
|
19
|
+
" (code: " + std::to_string(qex.getErrorCode()) + ")");
|
15
20
|
} catch (const std::exception& ex) {
|
16
21
|
throw std::runtime_error(std::string("qpdf_ruby: failed to open “") + filename + "”: " + ex.what());
|
17
22
|
}
|
@@ -40,6 +45,7 @@ void DocumentHandle::write(const std::string& out_filename) {
|
|
40
45
|
// honour original file’s extension-level features (linearized? encrypted? …)
|
41
46
|
QPDFWriter w(*m_qpdf, out_filename.c_str());
|
42
47
|
w.setStaticID(true); // deterministic IDs – helps tests
|
48
|
+
setup_encryption(w);
|
43
49
|
w.write();
|
44
50
|
} catch (const std::exception& ex) {
|
45
51
|
throw std::runtime_error(std::string("qpdf_ruby: failed to write “") + out_filename + "”: " + ex.what());
|
@@ -50,6 +56,9 @@ std::string DocumentHandle::write_to_memory() {
|
|
50
56
|
try {
|
51
57
|
QPDFWriter w(*m_qpdf, nullptr);
|
52
58
|
w.setStaticID(true);
|
59
|
+
|
60
|
+
setup_encryption(w);
|
61
|
+
|
53
62
|
w.setOutputMemory();
|
54
63
|
w.write();
|
55
64
|
|
@@ -60,26 +69,62 @@ std::string DocumentHandle::write_to_memory() {
|
|
60
69
|
}
|
61
70
|
}
|
62
71
|
|
72
|
+
void DocumentHandle::setup_encryption(QPDFWriter& w) const {
|
73
|
+
if (!m_encryption_requested) return;
|
74
|
+
|
75
|
+
switch (m_encrypt_R) {
|
76
|
+
case 4:
|
77
|
+
w.setR4EncryptionParametersInsecure(m_user_pw.c_str(), m_owner_pw.c_str(), m_encrypt_accessibility,
|
78
|
+
m_encrypt_allow_extract, m_encrypt_assemble, m_encrypt_annotate_and_form,
|
79
|
+
m_encrypt_form_filling, m_encrypt_allow_modify, m_encrypt_allow_print,
|
80
|
+
m_encrypt_encrypt_metadata, m_encrypt_use_aes);
|
81
|
+
break;
|
82
|
+
case 5:
|
83
|
+
w.setR5EncryptionParameters(m_user_pw.c_str(), m_owner_pw.c_str(), m_encrypt_accessibility,
|
84
|
+
m_encrypt_allow_extract, m_encrypt_assemble, m_encrypt_annotate_and_form,
|
85
|
+
m_encrypt_form_filling, m_encrypt_allow_modify, m_encrypt_allow_print,
|
86
|
+
m_encrypt_encrypt_metadata);
|
87
|
+
break;
|
88
|
+
case 6:
|
89
|
+
w.setR6EncryptionParameters(m_user_pw.c_str(), m_owner_pw.c_str(), m_encrypt_accessibility,
|
90
|
+
m_encrypt_allow_extract, m_encrypt_assemble, m_encrypt_annotate_and_form,
|
91
|
+
m_encrypt_form_filling, m_encrypt_allow_modify, m_encrypt_allow_print,
|
92
|
+
m_encrypt_encrypt_metadata);
|
93
|
+
break;
|
94
|
+
default:
|
95
|
+
throw std::runtime_error("Unsupported encryption revision (R): Only 4, 5, 6 are supported.");
|
96
|
+
}
|
97
|
+
}
|
98
|
+
|
99
|
+
void DocumentHandle::set_encryption(const std::string& user_pw, const std::string& owner_pw, int R,
|
100
|
+
qpdf_r3_print_e allow_print, bool allow_modify, bool allow_extract,
|
101
|
+
bool accessibility, bool assemble, bool annotate_and_form, bool form_filling,
|
102
|
+
bool encrypt_metadata, bool use_aes) {
|
103
|
+
m_user_pw = user_pw;
|
104
|
+
m_owner_pw = owner_pw;
|
105
|
+
m_encrypt_R = R;
|
106
|
+
m_encrypt_allow_print = allow_print;
|
107
|
+
m_encrypt_allow_modify = allow_modify;
|
108
|
+
m_encrypt_allow_extract = allow_extract;
|
109
|
+
m_encrypt_accessibility = accessibility;
|
110
|
+
m_encrypt_assemble = assemble;
|
111
|
+
m_encrypt_annotate_and_form = annotate_and_form;
|
112
|
+
m_encrypt_form_filling = form_filling;
|
113
|
+
m_encrypt_encrypt_metadata = encrypt_metadata;
|
114
|
+
m_encrypt_use_aes = use_aes;
|
115
|
+
m_encryption_requested = true;
|
116
|
+
}
|
117
|
+
|
63
118
|
// ------------------------- C bridge impl --------------------------------
|
64
119
|
|
65
120
|
extern "C" {
|
66
|
-
DocumentHandle* qpdf_ruby_open(const char* filename) {
|
67
|
-
|
68
|
-
return DocumentHandle::open(filename).release();
|
69
|
-
} catch (const std::exception&) {
|
70
|
-
errno = EIO;
|
71
|
-
return nullptr;
|
72
|
-
}
|
121
|
+
DocumentHandle* qpdf_ruby_open(const char* filename, char const* pwd) {
|
122
|
+
return DocumentHandle::open(filename, pwd ? pwd : "").release();
|
73
123
|
}
|
74
124
|
|
75
125
|
DocumentHandle* qpdf_ruby_open_memory(char const* desc, unsigned char const* buf, size_t len, char const* pwd) {
|
76
|
-
|
77
|
-
|
78
|
-
return DocumentHandle::open_memory(desc, std::move(copy), pwd ? pwd : "").release();
|
79
|
-
} catch (...) {
|
80
|
-
errno = EIO;
|
81
|
-
return nullptr;
|
82
|
-
}
|
126
|
+
std::vector<unsigned char> copy(buf, buf + len); // simple ownership
|
127
|
+
return DocumentHandle::open_memory(desc, std::move(copy), pwd ? pwd : "").release();
|
83
128
|
}
|
84
129
|
|
85
130
|
int qpdf_ruby_write(DocumentHandle* handle, const char* out_filename) {
|
@@ -87,13 +132,20 @@ int qpdf_ruby_write(DocumentHandle* handle, const char* out_filename) {
|
|
87
132
|
errno = EBADF;
|
88
133
|
return -1;
|
89
134
|
}
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
135
|
+
|
136
|
+
handle->write(out_filename);
|
137
|
+
return 0;
|
138
|
+
}
|
139
|
+
|
140
|
+
void qpdf_ruby_set_encryption(DocumentHandle* handle, const char* user_pw, const char* owner_pw, int R,
|
141
|
+
qpdf_r3_print_e allow_print, int allow_modify, int allow_extract, int accessibility,
|
142
|
+
int assemble, int annotate_and_form, int form_filling, int encrypt_metadata,
|
143
|
+
int use_aes) {
|
144
|
+
if (!handle) return;
|
145
|
+
|
146
|
+
handle->set_encryption(std::string(user_pw ? user_pw : ""), std::string(owner_pw ? owner_pw : ""), R, allow_print,
|
147
|
+
allow_modify != 0, allow_extract != 0, accessibility != 0, assemble != 0,
|
148
|
+
annotate_and_form != 0, form_filling != 0, encrypt_metadata != 0, use_aes != 0);
|
97
149
|
}
|
98
150
|
|
99
151
|
void qpdf_ruby_close(DocumentHandle* handle) {
|
@@ -1,8 +1,11 @@
|
|
1
1
|
#pragma once
|
2
2
|
|
3
|
+
#define POINTERHOLDER_TRANSITION 1
|
4
|
+
|
3
5
|
#include <memory>
|
4
6
|
#include <string>
|
5
7
|
#include <qpdf/QPDF.hh>
|
8
|
+
#include <qpdf/QPDFWriter.hh>
|
6
9
|
|
7
10
|
namespace qpdf_ruby {
|
8
11
|
|
@@ -16,7 +19,7 @@ namespace qpdf_ruby {
|
|
16
19
|
class DocumentHandle final {
|
17
20
|
public:
|
18
21
|
// ---- factory ----------------------------------------------------------
|
19
|
-
static std::unique_ptr<DocumentHandle> open(const std::string& filename);
|
22
|
+
static std::unique_ptr<DocumentHandle> open(const std::string& filename, std::string const& pwd);
|
20
23
|
static std::unique_ptr<DocumentHandle> open_memory(std::string const& description, std::vector<unsigned char> data,
|
21
24
|
std::string const& password = "");
|
22
25
|
|
@@ -26,6 +29,12 @@ class DocumentHandle final {
|
|
26
29
|
|
27
30
|
std::string write_to_memory();
|
28
31
|
|
32
|
+
void set_encryption(const std::string& user_pw, const std::string& owner_pw, int R, qpdf_r3_print_e allow_print,
|
33
|
+
bool allow_modify, bool allow_extract, bool accessibility = true, bool assemble = true,
|
34
|
+
bool annotate_and_form = true, bool form_filling = true, bool encrypt_metadata = true,
|
35
|
+
bool use_aes = true);
|
36
|
+
void setup_encryption(QPDFWriter& w) const;
|
37
|
+
|
29
38
|
/** Direct access for C++ helpers that need the raw QPDF. */
|
30
39
|
QPDF& qpdf() { return *m_qpdf; }
|
31
40
|
const QPDF& qpdf() const { return *m_qpdf; }
|
@@ -42,11 +51,26 @@ class DocumentHandle final {
|
|
42
51
|
|
43
52
|
std::shared_ptr<QPDF> m_qpdf;
|
44
53
|
std::vector<unsigned char> m_owned_buf;
|
54
|
+
|
55
|
+
// --- New encryption settings ---
|
56
|
+
bool m_encryption_requested = false;
|
57
|
+
std::string m_user_pw;
|
58
|
+
std::string m_owner_pw;
|
59
|
+
int m_encrypt_R = 4;
|
60
|
+
qpdf_r3_print_e m_encrypt_allow_print{qpdf_r3p_full};
|
61
|
+
bool m_encrypt_allow_modify = true;
|
62
|
+
bool m_encrypt_allow_extract = true;
|
63
|
+
bool m_encrypt_accessibility = true;
|
64
|
+
bool m_encrypt_assemble = true;
|
65
|
+
bool m_encrypt_annotate_and_form = true;
|
66
|
+
bool m_encrypt_form_filling = true;
|
67
|
+
bool m_encrypt_encrypt_metadata = true;
|
68
|
+
bool m_encrypt_use_aes = true;
|
45
69
|
};
|
46
70
|
|
47
71
|
extern "C" {
|
48
72
|
/** Returns a freshly allocated handle or nullptr on error (see errno). */
|
49
|
-
DocumentHandle* qpdf_ruby_open(const char* filename);
|
73
|
+
DocumentHandle* qpdf_ruby_open(const char* filename, char const* pwd);
|
50
74
|
|
51
75
|
DocumentHandle* qpdf_ruby_open_memory(char const* desc, unsigned char const* buf, size_t len, char const* pwd);
|
52
76
|
|
@@ -57,4 +81,10 @@ int qpdf_ruby_write(DocumentHandle* handle, const char* out_filename);
|
|
57
81
|
void qpdf_ruby_close(DocumentHandle* handle);
|
58
82
|
}
|
59
83
|
|
84
|
+
void qpdf_ruby_set_encryption(struct DocumentHandle* handle, const char* user_pw, const char* owner_pw, int R,
|
85
|
+
qpdf_r3_print_e allow_print,
|
86
|
+
int allow_modify, // bool as int (0/1)
|
87
|
+
int allow_extract, int accessibility, int assemble, int annotate_and_form,
|
88
|
+
int form_filling, int encrypt_metadata, int use_aes);
|
89
|
+
|
60
90
|
} // namespace qpdf_ruby
|
data/ext/qpdf_ruby/qpdf_ruby.cpp
CHANGED
@@ -17,6 +17,7 @@
|
|
17
17
|
|
18
18
|
VALUE rb_mQpdfRuby;
|
19
19
|
VALUE rb_cDocument;
|
20
|
+
VALUE rb_eQpdfRubyError;
|
20
21
|
|
21
22
|
using namespace qpdf_ruby;
|
22
23
|
|
@@ -144,16 +145,35 @@ static void doc_free(void* ptr) { qpdf_ruby::qpdf_ruby_close(static_cast<qpdf_ru
|
|
144
145
|
|
145
146
|
static VALUE doc_alloc(VALUE klass) { return Data_Wrap_Struct(klass, /* mark */ 0, doc_free, nullptr); }
|
146
147
|
|
147
|
-
static VALUE doc_initialize(VALUE
|
148
|
+
static VALUE doc_initialize(int argc, VALUE* argv, VALUE self) {
|
149
|
+
VALUE filename, password;
|
150
|
+
|
151
|
+
filename = Qnil;
|
152
|
+
password = Qnil;
|
153
|
+
|
154
|
+
rb_scan_args(argc, argv, "11", &filename, &password); // 1 required, 1 optional
|
155
|
+
|
148
156
|
Check_Type(filename, T_STRING);
|
149
|
-
|
150
|
-
|
151
|
-
|
157
|
+
|
158
|
+
const char* pw = "";
|
159
|
+
if (!NIL_P(password)) {
|
160
|
+
Check_Type(password, T_STRING);
|
161
|
+
pw = StringValueCStr(password);
|
162
|
+
}
|
163
|
+
|
164
|
+
try {
|
165
|
+
DocumentHandle* h = qpdf_ruby::qpdf_ruby_open(StringValueCStr(filename), pw);
|
166
|
+
if (!h) rb_sys_fail("qpdf_ruby_open");
|
167
|
+
DATA_PTR(self) = h;
|
168
|
+
} catch (const std::exception& e) {
|
169
|
+
rb_raise(rb_eQpdfRubyError, "%s", e.what());
|
170
|
+
}
|
152
171
|
|
153
172
|
return self;
|
154
173
|
}
|
155
174
|
|
156
175
|
static VALUE doc_write(VALUE self, VALUE out_filename) {
|
176
|
+
Check_Type(out_filename, T_STRING);
|
157
177
|
DocumentHandle* h;
|
158
178
|
Data_Get_Struct(self, DocumentHandle, h);
|
159
179
|
if (qpdf_ruby::qpdf_ruby_write(h, StringValueCStr(out_filename)) == -1) rb_sys_fail("qpdf_ruby_write");
|
@@ -163,16 +183,26 @@ static VALUE doc_write(VALUE self, VALUE out_filename) {
|
|
163
183
|
|
164
184
|
VALUE qpdf_ruby_write_memory(DocumentHandle* h) {
|
165
185
|
if (!h) rb_sys_fail("Bad handle");
|
166
|
-
std::string bytes
|
186
|
+
std::string bytes;
|
187
|
+
|
188
|
+
try {
|
189
|
+
bytes = h->write_to_memory();
|
190
|
+
} catch (const std::exception& e) {
|
191
|
+
rb_raise(rb_eQpdfRubyError, "%s", e.what());
|
192
|
+
}
|
167
193
|
return rb_str_new(bytes.data(), bytes.size());
|
168
194
|
}
|
169
195
|
|
170
196
|
static VALUE doc_from_memory(VALUE klass, VALUE str, VALUE password) {
|
171
197
|
Check_Type(str, T_STRING);
|
172
198
|
Check_Type(password, T_STRING);
|
173
|
-
|
174
|
-
|
175
|
-
|
199
|
+
DocumentHandle* h;
|
200
|
+
try {
|
201
|
+
h = qpdf_ruby_open_memory("ruby-memory", reinterpret_cast<unsigned char const*>(RSTRING_PTR(str)), RSTRING_LEN(str),
|
202
|
+
StringValueCStr(password));
|
203
|
+
} catch (const std::exception& e) {
|
204
|
+
rb_raise(rb_eQpdfRubyError, "%s", e.what());
|
205
|
+
}
|
176
206
|
|
177
207
|
if (!h) rb_sys_fail("qpdf_ruby_open_memory");
|
178
208
|
|
@@ -186,13 +216,69 @@ static VALUE doc_to_memory(VALUE self) {
|
|
186
216
|
return qpdf_ruby_write_memory(h); // returns a Ruby ::String
|
187
217
|
}
|
188
218
|
|
219
|
+
VALUE rb_qpdf_doc_set_encryption(int argc, VALUE* argv, VALUE self) {
|
220
|
+
VALUE kwargs;
|
221
|
+
ID keys[12];
|
222
|
+
VALUE values[12] = {
|
223
|
+
rb_str_new_cstr(""), // user_pw
|
224
|
+
rb_str_new_cstr(""), // owner_pw
|
225
|
+
INT2NUM(4), // encryption_revision
|
226
|
+
INT2NUM(1), // allow_print
|
227
|
+
Qfalse, // allow_modify
|
228
|
+
Qfalse, // allow_extract
|
229
|
+
Qtrue, // accessibility
|
230
|
+
Qfalse, // assemble
|
231
|
+
Qfalse, // annotate_and_form
|
232
|
+
Qfalse, // form_filling
|
233
|
+
Qtrue, // encrypt_metadata
|
234
|
+
Qtrue // use_aes
|
235
|
+
};
|
236
|
+
|
237
|
+
keys[0] = rb_intern("user_pw");
|
238
|
+
keys[1] = rb_intern("owner_pw");
|
239
|
+
keys[2] = rb_intern("encryption_revision");
|
240
|
+
keys[3] = rb_intern("allow_print");
|
241
|
+
keys[4] = rb_intern("allow_modify");
|
242
|
+
keys[5] = rb_intern("allow_extract");
|
243
|
+
keys[6] = rb_intern("accessibility");
|
244
|
+
keys[7] = rb_intern("assemble");
|
245
|
+
keys[8] = rb_intern("annotate_and_form");
|
246
|
+
keys[9] = rb_intern("form_filling");
|
247
|
+
keys[10] = rb_intern("encrypt_metadata");
|
248
|
+
keys[11] = rb_intern("use_aes");
|
249
|
+
|
250
|
+
rb_scan_args(argc, argv, ":", &kwargs);
|
251
|
+
|
252
|
+
// Get values for each keyword, or Qnil if missing
|
253
|
+
rb_get_kwargs(kwargs, keys, 0, 12, values);
|
254
|
+
|
255
|
+
DocumentHandle* h;
|
256
|
+
Data_Get_Struct(self, DocumentHandle, h);
|
257
|
+
|
258
|
+
h->set_encryption(StringValueCStr(values[0]), // user_pw
|
259
|
+
StringValueCStr(values[1]), // owner_pw
|
260
|
+
NUM2INT(values[2]), // r
|
261
|
+
static_cast<qpdf_r3_print_e>(NUM2INT(values[3])), // allow_print
|
262
|
+
RTEST(values[4]), // allow_modify
|
263
|
+
RTEST(values[5]), // allow_extract
|
264
|
+
RTEST(values[6]), // accessibility
|
265
|
+
RTEST(values[7]), // assemble
|
266
|
+
RTEST(values[8]), // annotate_and_form
|
267
|
+
RTEST(values[9]), // form_filling
|
268
|
+
RTEST(values[10]), // encrypt_metadata
|
269
|
+
RTEST(values[11]) // use_aes
|
270
|
+
);
|
271
|
+
return Qnil;
|
272
|
+
}
|
273
|
+
|
189
274
|
RUBY_FUNC_EXPORTED "C" void Init_qpdf_ruby(void) {
|
190
275
|
rb_mQpdfRuby = rb_define_module("QpdfRuby");
|
191
276
|
rb_cDocument = rb_define_class_under(rb_mQpdfRuby, "Document", rb_cObject);
|
277
|
+
rb_eQpdfRubyError = rb_define_class_under(rb_mQpdfRuby, "Error", rb_eStandardError);
|
192
278
|
|
193
279
|
rb_define_alloc_func(rb_cDocument, doc_alloc);
|
194
280
|
|
195
|
-
rb_define_method(rb_cDocument, "initialize", RUBY_METHOD_FUNC(doc_initialize), 1);
|
281
|
+
rb_define_method(rb_cDocument, "initialize", RUBY_METHOD_FUNC(doc_initialize), -1);
|
196
282
|
rb_define_singleton_method(rb_cDocument, "from_memory", RUBY_METHOD_FUNC(doc_from_memory), 2);
|
197
283
|
|
198
284
|
rb_define_method(rb_cDocument, "write", RUBY_METHOD_FUNC(doc_write), 1);
|
@@ -201,4 +287,9 @@ RUBY_FUNC_EXPORTED "C" void Init_qpdf_ruby(void) {
|
|
201
287
|
rb_define_method(rb_cDocument, "mark_paths_as_artifacts", RUBY_METHOD_FUNC(rb_qpdf_mark_paths_as_artifacts), 0);
|
202
288
|
rb_define_method(rb_cDocument, "ensure_bbox", RUBY_METHOD_FUNC(rb_qpdf_ensure_bboxs), 0);
|
203
289
|
rb_define_method(rb_cDocument, "show_structure", RUBY_METHOD_FUNC(rb_qpdf_get_structure_string), 0);
|
290
|
+
rb_define_method(rb_cDocument, "encrypt", RUBY_METHOD_FUNC(rb_qpdf_doc_set_encryption), -1);
|
291
|
+
|
292
|
+
rb_define_const(rb_mQpdfRuby, "PRINT_FULL", INT2NUM(qpdf_r3p_full));
|
293
|
+
rb_define_const(rb_mQpdfRuby, "PRINT_LOW", INT2NUM(qpdf_r3p_low));
|
294
|
+
rb_define_const(rb_mQpdfRuby, "PRINT_NONE", INT2NUM(qpdf_r3p_none));
|
204
295
|
}
|
data/ext/qpdf_ruby/qpdf_ruby.hpp
CHANGED
data/lib/qpdf_ruby/version.rb
CHANGED
data/lib/qpdf_ruby.rb
CHANGED
@@ -4,6 +4,10 @@ require_relative "qpdf_ruby/version"
|
|
4
4
|
require_relative "qpdf_ruby/qpdf_ruby"
|
5
5
|
|
6
6
|
module QpdfRuby
|
7
|
+
ENCRYPTION_REVISION_AES_128 = 4 # Acrobat 6.x, 128-bit AES
|
8
|
+
ENCRYPTION_REVISION_AES_256 = 5 # Acrobat 9.x, 256-bit AES
|
9
|
+
ENCRYPTION_REVISION_AES_256U = 6 # Acrobat X, 256-bit AES (PDF 2.0+ update)
|
10
|
+
|
7
11
|
class Error < StandardError; end
|
8
12
|
# Your code goes here...
|
9
13
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: qpdf_ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dieter S.
|
@@ -139,6 +139,7 @@ files:
|
|
139
139
|
- LICENSE.txt
|
140
140
|
- README.md
|
141
141
|
- Rakefile
|
142
|
+
- cliff.toml
|
142
143
|
- docker/Dockerfile
|
143
144
|
- exe/qpdf_ruby
|
144
145
|
- ext/qpdf_ruby/array_node.cpp
|