paperback 0.0.3 → 0.0.5

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.
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