paperback 0.0.3 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/tests.yml +46 -0
  3. data/.rubocop-disables.yml +26 -12
  4. data/CHANGELOG.md +21 -0
  5. data/README.md +27 -6
  6. data/bin/paperback +4 -0
  7. data/lib/paperback/cli.rb +38 -2
  8. data/lib/paperback/document.rb +134 -17
  9. data/lib/paperback/preparer.rb +107 -29
  10. data/lib/paperback/version.rb +2 -1
  11. data/lib/paperback.rb +12 -0
  12. data/paperback.gemspec +10 -7
  13. data/sorbet/config +3 -0
  14. data/sorbet/rbi/annotations/rainbow.rbi +269 -0
  15. data/sorbet/rbi/gems/ast@2.4.2.rbi +584 -0
  16. data/sorbet/rbi/gems/chunky_png@1.4.0.rbi +4498 -0
  17. data/sorbet/rbi/gems/coderay@1.1.3.rbi +3426 -0
  18. data/sorbet/rbi/gems/diff-lcs@1.5.0.rbi +1083 -0
  19. data/sorbet/rbi/gems/method_source@1.0.0.rbi +272 -0
  20. data/sorbet/rbi/gems/netrc@0.11.0.rbi +158 -0
  21. data/sorbet/rbi/gems/parallel@1.22.1.rbi +277 -0
  22. data/sorbet/rbi/gems/parser@3.2.0.0.rbi +6963 -0
  23. data/sorbet/rbi/gems/pdf-core@0.4.0.rbi +1682 -0
  24. data/sorbet/rbi/gems/prawn@1.3.0.rbi +5567 -0
  25. data/sorbet/rbi/gems/pry@0.14.1.rbi +9990 -0
  26. data/sorbet/rbi/gems/rainbow@3.1.1.rbi +408 -0
  27. data/sorbet/rbi/gems/rake@13.0.6.rbi +3023 -0
  28. data/sorbet/rbi/gems/rbi@0.0.16.rbi +3008 -0
  29. data/sorbet/rbi/gems/regexp_parser@2.6.1.rbi +3481 -0
  30. data/sorbet/rbi/gems/rexml@3.2.5.rbi +4717 -0
  31. data/sorbet/rbi/gems/rqrcode@0.10.1.rbi +617 -0
  32. data/sorbet/rbi/gems/rspec-core@3.12.0.rbi +10791 -0
  33. data/sorbet/rbi/gems/rspec-expectations@3.12.1.rbi +8106 -0
  34. data/sorbet/rbi/gems/rspec-mocks@3.12.1.rbi +5305 -0
  35. data/sorbet/rbi/gems/rspec-support@3.12.0.rbi +1617 -0
  36. data/sorbet/rbi/gems/rspec@3.12.0.rbi +88 -0
  37. data/sorbet/rbi/gems/rubocop-ast@1.24.1.rbi +6617 -0
  38. data/sorbet/rbi/gems/rubocop@0.93.1.rbi +40848 -0
  39. data/sorbet/rbi/gems/ruby-progressbar@1.11.0.rbi +1234 -0
  40. data/sorbet/rbi/gems/sixword@0.4.0.rbi +536 -0
  41. data/sorbet/rbi/gems/spoom@1.1.15.rbi +2383 -0
  42. data/sorbet/rbi/gems/subprocess@1.5.6.rbi +391 -0
  43. data/sorbet/rbi/gems/tapioca@0.10.5.rbi +3207 -0
  44. data/sorbet/rbi/gems/thor@1.2.1.rbi +3956 -0
  45. data/sorbet/rbi/gems/ttfunk@1.4.0.rbi +1951 -0
  46. data/sorbet/rbi/gems/unicode-display_width@1.8.0.rbi +40 -0
  47. data/sorbet/rbi/gems/unparser@0.6.7.rbi +4524 -0
  48. data/sorbet/rbi/gems/webrick@1.7.0.rbi +2555 -0
  49. data/sorbet/rbi/gems/yard-sorbet@0.8.0.rbi +441 -0
  50. data/sorbet/rbi/gems/yard@0.9.28.rbi +17816 -0
  51. data/sorbet/tapioca/config.yml +13 -0
  52. data/sorbet/tapioca/require.rb +4 -0
  53. data/spec/functional/paperback/cli_spec.rb +195 -0
  54. data/spec/spec_helper.rb +1 -0
  55. data/spec/unit/paperback_spec.rb +1 -0
  56. metadata +91 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 2e11bb71dec04963fa615cc0b522af8ab818a671
4
- data.tar.gz: 7d5c5b5a1e45bf30b62303415e5697681867f57d
2
+ SHA256:
3
+ metadata.gz: fe9b3003bc8749cbd4c5bd312b2b1d0b7546f9268e0f1c41e94c71f12a62df2c
4
+ data.tar.gz: d9630db98c97621804f79a951a140d2d9faa5080bc853c7e2492920e58dec07f
5
5
  SHA512:
6
- metadata.gz: 03c36ef11c1a27ff57254d004f1ddb697920f726b7e29e72845d4f72e62f259bc987889d6db65a00af662cc2aaefc0916feadc69c4d44195431c7679020d0bd2
7
- data.tar.gz: 3d7c478f33e894a6303d0fd1546a8e4e21c8f4df5509900081b04ca6a229fa4858f8357e220b26101727724bb960df2aa19b06c5278e3feae357ef4e34889075
6
+ metadata.gz: 15f5f07874c345f8ed9c0565a7704bf46be836c9d398ba8ec0f39fac6501cd4152762ed257cba92ea3e2f0e1abe223a9aec2a3318e13bc620507ec809033ba30
7
+ data.tar.gz: e6bbb5beb3ea669e663031ffe8d25088d2657f264fdd7569048f7f3d90264c0954f838025ae4c020c9b28c5971a8ca53669377edf5a731cfff8bcacfcb8fd4eb
@@ -0,0 +1,46 @@
1
+ # This workflow uses actions that are not certified by GitHub.
2
+ # They are provided by a third-party and are governed by
3
+ # separate terms of service, privacy policy, and support
4
+ # documentation.
5
+ # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
6
+ # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
7
+
8
+ name: Ruby
9
+
10
+ on:
11
+ - push
12
+ - pull_request
13
+
14
+ permissions:
15
+ contents: read
16
+
17
+ jobs:
18
+ test:
19
+
20
+ runs-on: ubuntu-latest
21
+ strategy:
22
+ matrix:
23
+ ruby-version: ['2.7', '3.0', '3.1', '3.2']
24
+
25
+ steps:
26
+ - uses: actions/checkout@v3
27
+
28
+ - name: Install apt dependencies for tests
29
+ run: |
30
+ sudo apt-get update -y
31
+ sudo apt-get install poppler-utils
32
+
33
+ - name: Set up Ruby
34
+ # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
35
+ # change this to (see https://github.com/ruby/setup-ruby#versioning):
36
+ # uses: ruby/setup-ruby@v1
37
+ uses: ruby/setup-ruby@319066216501fbd5e2d568f14b7d68c19fb67a5d # v1.33.1 / 2023-01-06
38
+ with:
39
+ ruby-version: ${{ matrix.ruby-version }}
40
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
41
+
42
+ - name: Run tests
43
+ run: bundle exec rake test
44
+
45
+ - name: Sorbet Typecheck
46
+ run: bundle exec srb tc
@@ -1,7 +1,4 @@
1
1
  # Configuration parameters: EnforcedStyle, SupportedStyles.
2
- Style/StringLiterals:
3
- Enabled: false
4
-
5
2
  Metrics/AbcSize:
6
3
  Max: 50
7
4
 
@@ -24,6 +21,9 @@ Metrics/MethodLength:
24
21
  Metrics/ModuleLength:
25
22
  Enabled: false
26
23
 
24
+ Metrics/ParameterLists:
25
+ Enabled: false
26
+
27
27
  # Configuration parameters: Exclude.
28
28
  #
29
29
  # We could re-enable this if it understood that the top level module should
@@ -39,6 +39,11 @@ Style/Documentation:
39
39
  # Disagree with these style points
40
40
  # ********************************
41
41
 
42
+ Layout/EmptyLineBetweenDefs:
43
+ Enabled: false
44
+ Layout/EmptyLineAfterGuardClause:
45
+ Enabled: false
46
+
42
47
  Style/DotPosition:
43
48
  Enabled: false
44
49
  Style/DoubleNegation:
@@ -60,12 +65,16 @@ Style/IfUnlessModifier:
60
65
  Style/WhileUntilModifier:
61
66
  Enabled: false
62
67
 
68
+ # Don't favor wacky unless methods
69
+ Style/NegatedIf:
70
+ Enabled: false
71
+ Style/InverseMethods:
72
+ Enabled: false
73
+
63
74
  Style/InfiniteLoop:
64
75
  Enabled: false
65
76
  Style/PercentLiteralDelimiters:
66
- PreferredDelimiters:
67
- '%w': '{}'
68
- '%W': '{}'
77
+ Enabled: false
69
78
  Style/RaiseArgs:
70
79
  EnforcedStyle: compact
71
80
  Style/RedundantReturn:
@@ -87,14 +96,13 @@ Style/SpaceInsideBlockBraces:
87
96
  Style/SpaceInsideHashLiteralBraces:
88
97
  Enabled: false
89
98
 
90
- # Offense count: 27
91
- # Cop supports --auto-correct.
92
99
  # Configuration parameters: EnforcedStyleForMultiline, SupportedStyles.
93
- Style/TrailingCommaInLiteral:
94
- # SupportedStyles:
95
- # - comma
96
- # - no_comma
100
+ Style/TrailingCommaInArrayLiteral:
97
101
  EnforcedStyleForMultiline: comma
102
+ Style/TrailingCommaInHashLiteral:
103
+ EnforcedStyleForMultiline: comma
104
+ Style/TrailingCommaInArguments:
105
+ Enabled: false
98
106
 
99
107
  # Don't favor %w for arrays of words.
100
108
  Style/WordArray:
@@ -104,6 +112,9 @@ Style/WordArray:
104
112
  Style/SymbolArray:
105
113
  Enabled: false
106
114
 
115
+ Style/NumericLiteralPrefix:
116
+ Enabled: false
117
+
107
118
  # Definitely do NOT assign variables within a conditional. Christ.
108
119
  Style/ConditionalAssignment:
109
120
  Enabled: false
@@ -131,3 +142,6 @@ Style/GlobalVars:
131
142
  Style/SpecialGlobalVars:
132
143
  Exclude:
133
144
  - 'paperback.gemspec'
145
+
146
+ Naming/HeredocDelimiterNaming:
147
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -4,4 +4,25 @@ This project adheres to [Semantic Versioning](http://semver.org).
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.0.5] -- 2023-01-17
8
+
9
+ - Upgrade to modern versions of ruby
10
+ - Upgrade a few dependencies, fix type checking
11
+ - Fix tests to work on newer versions of pdftotext
12
+ - Run tests on Github Actions
13
+
14
+ ## [0.0.4] -- 2019-02-26
15
+
16
+ - Add some basic end-to-end tests.
17
+ - Add additional copy using base64 encoding, by default.
18
+ - Add samples and more README notes.
19
+
20
+ ## [0.0.3] -- 2018-08-15
21
+
22
+ - Add option to change sixword font size.
23
+
24
+ ## [0.0.2] -- 2018-08-14
25
+
26
+ - Initial public release
27
+
7
28
  <!-- vim: set tw=79 : -->
data/README.md CHANGED
@@ -1,30 +1,51 @@
1
- # Paperback
1
+ # Paperback
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/paperback.svg)](https://rubygems.org/gems/paperback)
4
- [![Build status](https://travis-ci.org/ab/paperback.svg)](https://travis-ci.org/ab/paperback)
5
- [![Code Climate](https://codeclimate.com/github/ab/paperback.svg)](https://codeclimate.com/github/ab/paperback)
6
4
  [![Inline Docs](http://inch-ci.org/github/ab/paperback.svg?branch=master)](http://www.rubydoc.info/github/ab/paperback/master)
5
+ [![Test status](https://github.com/ab/paperback/actions/workflows/tests.yml/badge.svg)](https://github.com/ab/paperback/actions/workflows/tests.yml)
7
6
 
8
7
  *Paperback* is a library that facilitates the creation of paper offline backups
9
8
  of small amounts of important data, such as encryption keys.
10
9
 
11
10
  It is designed to be used for long-term paper storage. Arbitrary data to be
12
- backed up is encoded using QR codes and
13
- [sixword](https://github.com/ab/sixword) English text.
11
+ backed up is encoded using QR codes,
12
+ [sixword](https://github.com/ab/sixword) English text, and Base64.
13
+
14
+ Nothing else approaches the durability and inexpensiveness of paper. This
15
+ library is designed to facilitate the restoration process, which would be
16
+ tedious and error-prone when using human typists or even OCR.
17
+
18
+ The QR code is easily machine readible, the sixword text is easiest to
19
+ transcribe for humans, and the Base64 serves as a fallback for broadest
20
+ compatibility.
14
21
 
15
22
  By default, the backup data is GPG-encrypted with a symmetric passphrase to
16
23
  avoid exposing data to the printer (or scanner, assuming you cover the
17
24
  passphrase when scanning).
18
25
 
26
+ The printed document does contain the SHA256 digest of the original content for
27
+ error correction, which is not a problem for random data like keys. But if you
28
+ are backing up low-entropy secrets and want to preserve the printer-blindness
29
+ property, pad the content with a random salt or encrypt it before using
30
+ paperback.
31
+
19
32
  ## Usage
20
33
 
21
- Typical usage will be through the `paperback` executable.
34
+ Typical usage will be through the `paperback` executable. Use the `--help`
35
+ option for a usage menu.
22
36
 
23
37
  ```sh
24
38
  # Back up the content in data.key
25
39
  paperback data.key out.pdf
26
40
  ```
27
41
 
42
+ ### Sample output
43
+
44
+ See [sample directory](./sample)
45
+
46
+ ![sample page one](./sample/sample.pg1.png)
47
+ ![sample page two](./sample/sample.pg2.png)
48
+
28
49
  ### More complex patterns
29
50
 
30
51
  See the [YARD documentation](http://www.rubydoc.info/github/ab/paperback/master).
data/bin/paperback CHANGED
@@ -53,6 +53,10 @@ Options:
53
53
  options[:encrypt] = val
54
54
  end
55
55
 
56
+ opts.on('--no-base64', "Don't append plain Base64 paragraph") do |val|
57
+ options[:include_base64] = val
58
+ end
59
+
56
60
  opts.on('--passphrase-out FILE', 'Write generated passphrase to FILE',
57
61
  ' ') do |val|
58
62
  options[:passphrase_file] = val
data/lib/paperback/cli.rb CHANGED
@@ -1,12 +1,48 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
1
4
  module Paperback
2
5
  module CLI
6
+ extend T::Sig
7
+
8
+ # Top level CLI interface for Paperback. This is the one stop shop for
9
+ # calling paperback.
10
+ #
11
+ # @param [String] input The input filename
12
+ # @param [String] output The output PDF filename
13
+ # @param [Boolean] encrypt Whether to encrypt input with GPG.
14
+ # @param [Boolean] qr_base64 Whether to base64 the data before encoding it
15
+ # as a QR code
16
+ # @param [Symbol] qr_level Which level of QR code encoding, default `:l`
17
+ # @param [String,nil] comment A comment to add to the printout
18
+ # @param [String] passphrase_file A file to write the generated GPG
19
+ # passphrase to
20
+ # @param [Hash] extra_draw_opts Other options passed to
21
+ # [Paperback::Preparer#render]
22
+ # @param [Boolean] include_base64 Whether to include a Base64 copy of the
23
+ # input
24
+ sig do
25
+ params(
26
+ input: String,
27
+ output: String,
28
+ encrypt: T::Boolean,
29
+ qr_base64: T::Boolean,
30
+ qr_level: Symbol,
31
+ comment: T.nilable(String),
32
+ passphrase_file: T.nilable(String),
33
+ extra_draw_opts: T::Hash[T.untyped, T.untyped],
34
+ include_base64: T::Boolean,
35
+ )
36
+ .void
37
+ end
3
38
  def self.create_backup(input:, output:, encrypt: true, qr_base64: true,
4
39
  qr_level: :l, comment: nil, passphrase_file: nil,
5
- extra_draw_opts: {})
40
+ extra_draw_opts: {}, include_base64: true)
6
41
  prep = Paperback::Preparer.new(filename: input, encrypt: encrypt,
7
42
  qr_base64: qr_base64, qr_level: qr_level,
8
43
  passphrase_file: passphrase_file,
9
- comment: comment)
44
+ comment: comment,
45
+ include_base64: include_base64)
10
46
  prep.render(output_filename: output, extra_draw_opts: extra_draw_opts)
11
47
  end
12
48
  end
@@ -1,24 +1,43 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
1
4
  require 'prawn'
2
5
 
3
6
  # Main class for creating and rendering PDFs
4
7
  module Paperback; class Document
5
- attr_reader :pdf, :debug
8
+ extend T::Sig
9
+
10
+ sig {returns(Prawn::Document)}
11
+ attr_reader :pdf
6
12
 
13
+ sig {returns(T::Boolean)}
14
+ attr_reader :debug
15
+
16
+ sig {params(debug: T::Boolean).void}
7
17
  def initialize(debug: false)
8
18
  log.debug('Document#initialize')
9
- @debug = debug
10
- @pdf = Prawn::Document.new
19
+ @debug = T.let(debug, T::Boolean)
20
+ @pdf = T.let(Prawn::Document.new, Prawn::Document)
21
+ @log = T.let(nil, T.nilable(Logger))
11
22
  end
12
23
 
24
+ sig {returns(Logger)}
13
25
  def log
14
26
  @log ||= Paperback.class_log(self.class)
15
27
  end
16
28
 
29
+ sig do
30
+ params(
31
+ output_file: String,
32
+ draw_opts: T::Hash[Symbol, T.untyped],
33
+ )
34
+ .void
35
+ end
17
36
  def render(output_file:, draw_opts:)
18
37
  log.info('Rendering PDF')
19
38
 
20
39
  # Create all the PDF content
21
- draw_paperback(**draw_opts)
40
+ draw_paperback(**T.unsafe(draw_opts))
22
41
 
23
42
  # Render to output file
24
43
  log.info("Writing PDF to #{output_file.inspect}")
@@ -26,12 +45,39 @@ module Paperback; class Document
26
45
  end
27
46
 
28
47
  # High level method to draw the paperback content on the pdf document
29
- def draw_paperback(qr_code:, sixword_lines:, sixword_bytes:,
30
- labels:, passphrase_sha: nil, passphrase_len: nil,
31
- sixword_font_size: nil)
32
- unless qr_code.is_a?(RQRCode::QRCode)
33
- raise ArgumentError.new('qr_code must be RQRCode::QRCode')
34
- end
48
+ #
49
+ # @param qr_code
50
+ # @param sixword_lines
51
+ # @param sixword_bytes
52
+ # @param labels
53
+ # @param passphrase_sha
54
+ # @param [Integer, nil] passphrase_len Length of the passphrase used to
55
+ # encrypt the original content. If this is not provided, then assume the
56
+ # original content was not encrypted and skip adding gpg -d instructions.
57
+ # @param [Integer] sixword_font_size The font size to use for Sixword text
58
+ # @param [String,nil] base64_content If provided, then append the original
59
+ # content (possibly encrypted) encoded using Base64.
60
+ # @param [Integer, nil] base64_bytes The length of the original content
61
+ # before encoding to base64. This is used for the informational header.
62
+ sig do
63
+ params(
64
+ qr_code: RQRCode::QRCode,
65
+ sixword_lines: T::Array[String],
66
+ sixword_bytes: Integer,
67
+ labels: T::Hash[String, T.untyped],
68
+ passphrase_sha: T.nilable(String),
69
+ passphrase_len: T.nilable(Integer),
70
+ sixword_font_size: T.nilable(Float),
71
+ base64_content: T.nilable(String),
72
+ base64_bytes: T.nilable(Integer),
73
+ )
74
+ .void
75
+ end
76
+ def draw_paperback(qr_code:, sixword_lines:, sixword_bytes:, labels:,
77
+ passphrase_sha: nil, passphrase_len: nil,
78
+ sixword_font_size: nil, base64_content: nil,
79
+ base64_bytes: nil)
80
+ T.assert_type!(qr_code, RQRCode::QRCode)
35
81
 
36
82
  # Header & QR code page
37
83
  pdf.font('Times-Roman')
@@ -53,23 +99,40 @@ module Paperback; class Document
53
99
  pdf.start_new_page
54
100
 
55
101
  draw_sixword(lines: sixword_lines, sixword_bytes: sixword_bytes,
56
- font_size: sixword_font_size)
102
+ font_size: sixword_font_size,
103
+ is_encrypted: !!passphrase_len)
104
+
105
+ if base64_content
106
+ draw_base64(b64_content: base64_content, b64_bytes: T.must(base64_bytes),
107
+ is_encrypted: !!passphrase_len)
108
+ end
57
109
 
58
110
  pdf.number_pages('<page> of <total>', align: :right,
59
111
  at: [pdf.bounds.right - 100, -2])
60
112
  end
61
113
 
62
114
  # If in debug mode, draw axes on the page to assist with layout
115
+ sig {void}
63
116
  def debug_draw_axes
64
117
  return unless debug
65
118
  pdf.float { pdf.stroke_axis }
66
119
  end
67
120
 
68
121
  # Move cursor down by one line
122
+ sig {void}
69
123
  def add_newline
70
124
  pdf.move_down(pdf.font_size)
71
125
  end
72
126
 
127
+ sig do
128
+ params(
129
+ labels: T::Hash[String, T.untyped],
130
+ passphrase_sha: T.nilable(String),
131
+ passphrase_len: T.nilable(Integer),
132
+ repo_url: String,
133
+ )
134
+ .void
135
+ end
73
136
  def draw_header(labels:, passphrase_sha:, passphrase_len:,
74
137
  repo_url: 'https://github.com/ab/paperback')
75
138
 
@@ -80,7 +143,7 @@ module Paperback; class Document
80
143
  pdf.text(intro, inline_format: true)
81
144
  add_newline
82
145
 
83
- label_pad = labels.keys.map(&:length).max + 1
146
+ label_pad = T.must(labels.keys.map(&:length).max) + 1
84
147
 
85
148
  unless passphrase_sha && passphrase_len
86
149
  labels['Encrypted'] = 'no'
@@ -107,7 +170,8 @@ module Paperback; class Document
107
170
 
108
171
  pdf.move_down(8)
109
172
  pdf.indent(72) do
110
- pdf.text('Be sure to cover the passphrase when scanning the QR code!')
173
+ pdf.text('Be sure to cover the passphrase when scanning the QR code!' +
174
+ ' Decrypt with `gpg -d`.')
111
175
  end
112
176
  end
113
177
  end
@@ -116,8 +180,18 @@ module Paperback; class Document
116
180
  # @param [Integer] columns The number of text columns on the page
117
181
  # @param [Integer] hunks_per_row The number of 6-word sentences per line
118
182
  # @param [Integer] sixword_bytes Bytesize of the sixword encoded data
183
+ sig do
184
+ params(
185
+ lines: T::Array[String],
186
+ sixword_bytes: Integer,
187
+ columns: Integer,
188
+ hunks_per_row: Integer,
189
+ font_size: T.nilable(Float),
190
+ is_encrypted: T::Boolean,
191
+ ).void
192
+ end
119
193
  def draw_sixword(lines:, sixword_bytes:, columns: 3, hunks_per_row: 1,
120
- font_size: nil)
194
+ font_size: nil, is_encrypted: true)
121
195
  font_size ||= 11
122
196
 
123
197
  debug_draw_axes
@@ -128,8 +202,9 @@ module Paperback; class Document
128
202
 
129
203
  header = [
130
204
  "This sixword text encodes #{sixword_bytes} bytes in #{lines.length}",
131
- " six-word sentences.",
132
- " Decode with `sixword -d`"
205
+ ' six-word sentences.',
206
+ ' Decode with `sixword -d`',
207
+ (is_encrypted ? ', then `gpg -d`.' : '.')
133
208
  ].join
134
209
 
135
210
  pdf.font('Times-Roman') do
@@ -146,6 +221,11 @@ module Paperback; class Document
146
221
  end
147
222
  end
148
223
 
224
+ sig do
225
+ params(
226
+ qr_modules: T::Array[T::Array[T::Boolean]],
227
+ ).void
228
+ end
149
229
  def draw_qr_code(qr_modules:)
150
230
  qr_height = pdf.cursor # entire rest of page
151
231
  qr_width = pdf.bounds.width # entire page width
@@ -170,10 +250,47 @@ module Paperback; class Document
170
250
  pdf.stroke_color(pixel_val ? '000000' : 'ffffff')
171
251
  pdf.fill_color(pixel_val ? '000000' : 'ffffff')
172
252
  xy = [(col_i + 1) * pixel_width, pdf.cursor]
173
- pdf.fill_and_stroke_rectangle(xy, pixel_width, pixel_height)
253
+ pdf.fill_rectangle(xy, pixel_width, pixel_height)
174
254
  end
175
255
  end
176
256
  end
177
257
  end
178
258
  end
259
+
260
+ # @param [String] b64_content
261
+ sig do
262
+ params(
263
+ b64_content: String,
264
+ b64_bytes: Integer,
265
+ font_size: T.nilable(Float),
266
+ is_encrypted: T::Boolean,
267
+ ).void
268
+ end
269
+ def draw_base64(b64_content:, b64_bytes:, font_size: nil, is_encrypted: true)
270
+ font_size ||= 11
271
+
272
+ debug_draw_axes
273
+
274
+ if is_encrypted
275
+ header = [
276
+ "This PGP text encodes #{b64_bytes} bytes in #{b64_content.length}",
277
+ " characters. Decode with `gpg -d`."
278
+ ].join
279
+ else
280
+ header = [
281
+ "This base64 text encodes #{b64_bytes} bytes in #{b64_content.length}",
282
+ " characters. Decode with `base64 --decode`."
283
+ ].join
284
+ end
285
+
286
+ add_newline
287
+ add_newline
288
+ pdf.text(header)
289
+ add_newline
290
+
291
+ pdf.font('Courier') do
292
+ pdf.text(b64_content)
293
+ end
294
+
295
+ end
179
296
  end; end