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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c5f6da89731f271f230b6784f82f01bb4f1dfe4060d4caae6d956fa73d8fc13e
4
- data.tar.gz: c3d2e9dc8baf1975acbca704b8b76dab210c0f959ce78b8655d1c3c295585d10
3
+ metadata.gz: b5e34005af4a00f153fe6597426be27d03e047c9fce46af4e669c22138e0b726
4
+ data.tar.gz: 95fb970cb38489fd18b2d8aee4e0f48e843f9b14f45a4806ea7b5b1e6f79045c
5
5
  SHA512:
6
- metadata.gz: c850b48c57e89e431b3d7340b1cd4819f28887402cdc03af7e108533249a4e8742351732c22217b0fed011e4799b9b00268219ec2b1ea17d6576ef50125ffbbb
7
- data.tar.gz: 60f89a46753d7caa4d70a3832c2dc68bd01470528c5c6c6939df10920608f3e451ed7c11d42874fd6944df1a06bdd4847020e1289fb1b07c100fc502cc48e5ce
6
+ metadata.gz: 6f1e65790f45ab85cb633883c4f7d8597d8271e15aedf6ef670e58491f0658a0bf53370a232abdda370088bc13594445e292d7e73fda1b34e9fa460a15ae0eef
7
+ data.tar.gz: be16ec8f1fde0eb5c022a39b29588429319d6aa553fdc368ee90f6a409659a0d7d0a30eba212683e4f5640eb961c127bef46d2f7f4dded9879ab58195c0df67e
data/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
- ## [Unreleased]
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
- // Use empty password owner & user pwd same.
14
- qpdf->processFile(filename.c_str());
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
- try {
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
- try {
77
- std::vector<unsigned char> copy(buf, buf + len); // simple ownership
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
- try {
91
- handle->write(out_filename);
92
- return 0;
93
- } catch (const std::exception&) {
94
- errno = EIO;
95
- return -1;
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
@@ -1,6 +1,8 @@
1
1
  // x_object_finder.hpp
2
2
  #pragma once
3
3
 
4
+ #define POINTERHOLDER_TRANSITION 1
5
+
4
6
  #include <qpdf/QPDF.hh>
5
7
  #include <qpdf/QPDFPageDocumentHelper.hh>
6
8
  #include <qpdf/QPDFPageObjectHelper.hh>
@@ -2,6 +2,8 @@
2
2
 
3
3
  #pragma once
4
4
 
5
+ #define POINTERHOLDER_TRANSITION 1
6
+
5
7
  #include <qpdf/QPDF.hh>
6
8
  #include <qpdf/QPDFWriter.hh>
7
9
  #include <qpdf/QPDFPageObjectHelper.hh>
@@ -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 self, VALUE filename) {
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
- DocumentHandle* h = qpdf_ruby::qpdf_ruby_open(StringValueCStr(filename));
150
- if (!h) rb_sys_fail("qpdf_ruby_open");
151
- DATA_PTR(self) = h;
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 = h->write_to_memory();
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
- DocumentHandle* h = qpdf_ruby_open_memory("ruby-memory", reinterpret_cast<unsigned char const*>(RSTRING_PTR(str)),
175
- RSTRING_LEN(str), StringValueCStr(password));
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
  }
@@ -6,5 +6,6 @@
6
6
  VALUE rb_qpdf_mark_paths_as_artifacts(VALUE self);
7
7
  VALUE rb_qpdf_ensure_bboxs(VALUE self);
8
8
  VALUE rb_qpdf_get_structure_string(VALUE self);
9
+ VALUE rb_qpdf_doc_set_encryption(int argc, VALUE* argv, VALUE self);
9
10
 
10
11
  #endif /* QPDF_RUBY_H */
@@ -1,5 +1,7 @@
1
1
  #pragma once
2
2
 
3
+ #define POINTERHOLDER_TRANSITION 1
4
+
3
5
  #include <qpdf/QPDF.hh>
4
6
  #include <qpdf/QPDFWriter.hh>
5
7
  #include <qpdf/QPDFPageObjectHelper.hh>
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module QpdfRuby
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
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.0
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