mork 0.17.0 → 0.18.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: a0c88481311d1eeaf37c2df7f308dbde8e72968a23cdd8a649012d1591898412
4
- data.tar.gz: 90ff649618ec8c71c75e2c1b6fa09a36d0bd9016a1bc529628ab5f4027b4bf97
3
+ metadata.gz: 9c871eb798a14173c833892d6cb6030e266158239058f9ef0fbb2ff359c1073f
4
+ data.tar.gz: 8e2ac94ee3e806455d617f959459eebe6964521c9711064e5f0c86facf9fda8f
5
5
  SHA512:
6
- metadata.gz: 16bbe20136a27742f2247b7ac00856e3dd1bb68349149c65ba94ab5bb06656ecf92e2400a509953251af5074bfbc1bbbd7db63b74a299e9c785db751750e4d10
7
- data.tar.gz: 30b783689b3bac8dc7785fcaeac7681eb3da5f1d9f6a679a7a08be267094b918e71fc103c3ab7d69d4ac90a4de77a164e39aa4717fa047d42fa46eddc6b3c8d4
6
+ metadata.gz: 464bb64cfe62e30bba0037b154d527a00d165b1bd71b4ffa4534bc28cace42fd86aa8455d927d6d660c218d25be92cca4292f60aa66b787af8ce9ed7bea82ba9
7
+ data.tar.gz: 37ab32c16c165c6c120a8addca74a6bafbe44a02d494ab80968a499dd5d44bad1b6402031c3ff8703a94b8ffbcdd8fc6afd093369b9535883aa49aa2ff2dd0e3
data/README.md CHANGED
@@ -64,6 +64,14 @@ Response sheets are created through the `Mork::SheetPDF` class. Two pieces of in
64
64
  s = SheetPDF.new content, layout
65
65
  ```
66
66
 
67
+ Mork uses its bundled Open Sans Regular font for all text in generated PDFs.
68
+ Prawn embeds only the glyphs used by each document, so no font needs to be
69
+ installed on the host that creates or prints the PDF. Open Sans covers extended
70
+ Latin, Greek, Cyrillic, and Hebrew, but it does not cover every Unicode script;
71
+ Arabic, Indic scripts, CJK, and emoji are not supported by Mork's default font.
72
+ The bundled font is distributed under the SIL Open Font License in
73
+ `lib/mork/fonts/OFL.txt`.
74
+
67
75
  Let’s look at each argument in turn.
68
76
 
69
77
  ### content
@@ -0,0 +1,88 @@
1
+ Copyright 2020 The Open Sans Project Authors (https://github.com/googlefonts/opensans)
2
+
3
+ -----------------------------------------------------------
4
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
5
+ -----------------------------------------------------------
6
+
7
+ PREAMBLE
8
+ The goals of the Open Font License (OFL) are to stimulate worldwide
9
+ development of collaborative font projects, to support the font
10
+ creation efforts of academic and linguistic communities, and to
11
+ provide a free and open framework in which fonts may be shared and
12
+ improved in partnership with others.
13
+
14
+ The OFL allows the licensed fonts to be used, studied, modified and
15
+ redistributed freely as long as they are not sold by themselves. The
16
+ fonts, including any derivative works, can be bundled, embedded,
17
+ redistributed and/or sold with any software provided that any reserved
18
+ names are not used by derivative works. The fonts and derivatives,
19
+ however, cannot be released under any other type of license. The
20
+ requirement for fonts to remain under this license does not apply to
21
+ any document created using the fonts or their derivatives.
22
+
23
+ DEFINITIONS
24
+ "Font Software" refers to the set of files released by the Copyright
25
+ Holder(s) under this license and clearly marked as such. This may
26
+ include source files, build scripts and documentation.
27
+
28
+ "Reserved Font Name" refers to any names specified as such after the
29
+ copyright statement(s).
30
+
31
+ "Original Version" refers to the collection of Font Software
32
+ components as distributed by the Copyright Holder(s).
33
+
34
+ "Modified Version" refers to any derivative made by adding to,
35
+ deleting, or substituting -- in part or in whole -- any of the
36
+ components of the Original Version, by changing formats or by porting
37
+ the Font Software to a new environment.
38
+
39
+ "Author" refers to any designer, engineer, programmer, technical
40
+ writer or other person who contributed to the Font Software.
41
+
42
+ PERMISSION & CONDITIONS
43
+ Permission is hereby granted, free of charge, to any person obtaining
44
+ a copy of the Font Software, to use, study, copy, merge, embed,
45
+ modify, redistribute, and sell modified and unmodified copies of the
46
+ Font Software, subject to the following conditions:
47
+
48
+ 1) Neither the Font Software nor any of its individual components, in
49
+ Original or Modified Versions, may be sold by itself.
50
+
51
+ 2) Original or Modified Versions of the Font Software may be bundled,
52
+ redistributed and/or sold with any software, provided that each copy
53
+ contains the above copyright notice and this license. These can be
54
+ included either as stand-alone text files, human-readable headers or
55
+ in the appropriate machine-readable metadata fields within text or
56
+ binary files as long as those fields can be easily viewed by the user.
57
+
58
+ 3) No Modified Version of the Font Software may use the Reserved Font
59
+ Name(s) unless explicit written permission is granted by the
60
+ corresponding Copyright Holder. This restriction only applies to the
61
+ primary font name as presented to the users.
62
+
63
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
64
+ Software shall not be used to promote, endorse or advertise any
65
+ Modified Version, except to acknowledge the contribution(s) of the
66
+ Copyright Holder(s) and the Author(s) or with their explicit written
67
+ permission.
68
+
69
+ 5) The Font Software, modified or unmodified, in part or in whole,
70
+ must be distributed entirely under this license, and must not be
71
+ distributed under any other license. The requirement for fonts to
72
+ remain under this license does not apply to any document created using
73
+ the Font Software.
74
+
75
+ TERMINATION
76
+ This license becomes null and void if any of the above conditions are
77
+ not met.
78
+
79
+ DISCLAIMER
80
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
81
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
82
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
83
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
84
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
85
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
86
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
87
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
88
+ OTHER DEALINGS IN THE FONT SOFTWARE.
Binary file
@@ -6,6 +6,11 @@ module Mork
6
6
  # Generating response sheets as PDF files.
7
7
  # See the README file for usage
8
8
  class SheetPDF < Prawn::Document
9
+ DEFAULT_FONT_FAMILY = 'Open Sans'
10
+ DEFAULT_FONT_PATH = File.expand_path('fonts/OpenSans-Regular.ttf', __dir__)
11
+ ITEM_TEXT_RISE_EM = 0.177
12
+ private_constant :DEFAULT_FONT_FAMILY, :DEFAULT_FONT_PATH, :ITEM_TEXT_RISE_EM
13
+
9
14
  include Extensions
10
15
  def initialize(content, layout=nil, duplex=false)
11
16
  @content =
@@ -24,6 +29,7 @@ module Mork
24
29
  else raise ArgumentError, 'Invalid initialization parameter'
25
30
  end
26
31
  super my_page_params
32
+ use_default_font
27
33
  @duplex = duplex
28
34
  process
29
35
  end
@@ -44,6 +50,15 @@ module Mork
44
50
  private
45
51
  ###########################################################################
46
52
 
53
+ def use_default_font
54
+ font_families.update(
55
+ DEFAULT_FONT_FAMILY => {
56
+ normal: { file: DEFAULT_FONT_PATH, subset: true }
57
+ }
58
+ )
59
+ font DEFAULT_FONT_FAMILY
60
+ end
61
+
47
62
  def my_page_params
48
63
  {
49
64
  page_size: @grip.page_size,
@@ -63,9 +78,7 @@ module Mork
63
78
  start_new_page if i>0
64
79
  barcode(content[:barcode] || 0)
65
80
  header(content[:header] || [])
66
- unless equal_choice_number?
67
- questions_and_choices ch_len[i]
68
- end
81
+ choice_cells(ch_len[i]) unless equal_choice_number?
69
82
  start_new_page if @duplex
70
83
  end
71
84
  end
@@ -73,11 +86,9 @@ module Mork
73
86
  def make_repeaters
74
87
  pages = @duplex ? :odd : :all
75
88
 
76
- if equal_choice_number?
77
- repeat(pages) do
78
- questions_and_choices ch_len.first
79
- end
80
- end
89
+ repeat(pages) { question_numbers }
90
+
91
+ repeat(pages) { choice_cells(ch_len.first) } if equal_choice_number?
81
92
 
82
93
  repeat(pages) do
83
94
  calibration_cell_repeater
@@ -129,14 +140,19 @@ module Mork
129
140
  end
130
141
  end
131
142
 
132
- def questions_and_choices(n_ch)
133
- n_ch.each_with_index do |n, i|
143
+ def question_numbers
144
+ ch_len.first.length.times do |i|
134
145
  text_box "#{i+1}",
135
- at: @grip.qnum_xy(i),
146
+ at: item_text_at(@grip.qnum_xy(i)),
136
147
  width: @grip.qnum_width,
137
148
  height: @grip.height_of_cell,
138
149
  align: :right,
139
150
  valign: :center
151
+ end
152
+ end
153
+
154
+ def choice_cells(n_ch)
155
+ n_ch.each_with_index do |n, i|
140
156
  stamp_at "s#{n}", @grip.item_xy(i)
141
157
  end
142
158
  end
@@ -161,7 +177,7 @@ module Mork
161
177
  @grip.width_of_uid,
162
178
  @grip.height_of_uid,
163
179
  @grip.uround
164
- text_box i, at: [offx, 0],
180
+ text_box i, at: item_text_at([offx, 0]),
165
181
  width: @grip.width_of_uid,
166
182
  height: @grip.height_of_uid,
167
183
  align: :center,
@@ -186,19 +202,31 @@ module Mork
186
202
  @grip.height_of_cell,
187
203
  @grip.cround
188
204
  text_box l,
189
- at: [x,0],
205
+ at: item_text_at([x, 0]),
190
206
  width: @grip.width_of_cell,
191
207
  height: @grip.height_of_cell,
192
208
  align: :center,
193
209
  valign: :center
194
210
  end
195
211
 
212
+ # Prawn vertically centers the font's full ascender box. Open Sans has a
213
+ # tall ascender relative to its visible capitals and digits, so raise only
214
+ # item text while leaving the OMR geometry unchanged.
215
+ def item_text_at(position)
216
+ [position[0], position[1] + @grip.item_font_size * ITEM_TEXT_RISE_EM]
217
+ end
218
+
196
219
  def equal_choice_number?
197
- return false unless ch_len.all? { |c| c.length == ch_len[0].length }
198
- ch_len[0].each_with_index do |c, i|
199
- return false unless ch_len.all? { |x| c == x[i] }
220
+ return @equal_choice_number unless @equal_choice_number.nil?
221
+
222
+ first_choices = ch_len.first
223
+ return @equal_choice_number = false unless ch_len.all? { |c| c.length == first_choices.length }
224
+
225
+ first_choices.each_with_index do |c, i|
226
+ return @equal_choice_number = false unless ch_len.all? { |x| c == x[i] }
200
227
  end
201
- true
228
+
229
+ @equal_choice_number = true
202
230
  end
203
231
 
204
232
  def ensure_presence_of_choices
data/lib/mork/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Mork
2
- VERSION = '0.17.0'
2
+ VERSION = '0.18.1'
3
3
  end
@@ -0,0 +1,40 @@
1
+ require 'benchmark'
2
+ require 'mork'
3
+
4
+ def build_content(pages:, unequal:)
5
+ Array.new(pages) do |i|
6
+ choices =
7
+ if unequal
8
+ Array.new(120) { |j| 1 + ((i + j) % 5) }
9
+ else
10
+ [5] * 120
11
+ end
12
+
13
+ {
14
+ barcode: i,
15
+ choices: choices,
16
+ header: { title: "Test #{i + 1}" }
17
+ }
18
+ end
19
+ end
20
+
21
+ def measure(label)
22
+ started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
23
+ result = yield
24
+ elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at
25
+ [label, elapsed, result]
26
+ end
27
+
28
+ puts "ruby=#{RUBY_VERSION} prawn=#{Prawn::VERSION}"
29
+ puts "pages\tshape\tbuild_s\trender_s\tbytes"
30
+
31
+ [1, 20, 100].each do |pages|
32
+ [[false, 'equal'], [true, 'unequal']].each do |unequal, shape|
33
+ content = build_content(pages: pages, unequal: unequal)
34
+
35
+ _, build_s, pdf = measure('build') { Mork::SheetPDF.new(content) }
36
+ _, render_s, bytes = measure('render') { pdf.to_pdf.bytesize }
37
+
38
+ puts [pages, shape, format('%.6f', build_s), format('%.6f', render_s), bytes].join("\t")
39
+ end
40
+ end
@@ -48,6 +48,38 @@ module Mork
48
48
  expect(sp.instance_variable_get('@content').first).to be_a Hash
49
49
  end
50
50
 
51
+ it 'uses the bundled Open Sans font with subsetting' do
52
+ pdf = SheetPDF.new({})
53
+
54
+ expect(pdf.font.family).to eq 'Open Sans'
55
+ expect(pdf.font.options[:subset]).to be true
56
+ expect(pdf.font.options[:file]).to end_with 'lib/mork/fonts/OpenSans-Regular.ttf'
57
+ end
58
+
59
+ it 'embeds Open Sans and renders supported Unicode across pages' do
60
+ content = [
61
+ { header: { title: 'Extended Latin: Ž; Greek: Ω' } },
62
+ { header: { title: 'Cyrillic: Ж; Hebrew: א' } }
63
+ ]
64
+
65
+ bytes = SheetPDF.new(content).to_pdf
66
+
67
+ expect(bytes).to include '/FontFile2'
68
+ expect(bytes).to include 'OpenSans-Regular'
69
+ end
70
+
71
+ it 'raises item labels without changing their OMR coordinates' do
72
+ pdf = SheetPDF.new({})
73
+ grid = pdf.instance_variable_get('@grip')
74
+ cell_position = grid.item_xy(0)
75
+
76
+ text_position = pdf.send(:item_text_at, cell_position)
77
+
78
+ expect(text_position[0]).to eq cell_position[0]
79
+ expect(text_position[1] - cell_position[1]).to be_within(0.001).of(1.593)
80
+ expect(grid.item_xy(0)).to eq cell_position
81
+ end
82
+
51
83
  it 'creates a minimal PDF sheet' do
52
84
  s = SheetPDF.new({})
53
85
  s.save dest 'minimal'
@@ -106,6 +138,23 @@ module Mork
106
138
  SheetPDF.new(c).save dest('uneq')
107
139
  end
108
140
 
141
+ it 'reuses question number repeaters for multi-page unequal batches' do
142
+ choices = Array.new(120) { |i| 1 + (i % 5) }
143
+ c = 3.times.map do |x|
144
+ {
145
+ barcode: x,
146
+ choices: choices.rotate(x),
147
+ header: { title: "Test #{x+1}" }
148
+ }
149
+ end
150
+
151
+ pdf = SheetPDF.new(c)
152
+
153
+ expect(pdf.page_count).to eq 3
154
+ expect(pdf.repeaters.length).to eq 2
155
+ expect { pdf.to_pdf }.not_to raise_error
156
+ end
157
+
109
158
  it 'creates 20 PDF sheets' do
110
159
  c = 20.times.map do |x|
111
160
  content.merge({ header: {title: "Test #{x+1}"}, barcode: x})
@@ -130,4 +179,3 @@ module Mork
130
179
  end
131
180
  end
132
181
  end
133
-
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mork
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.17.0
4
+ version: 0.18.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Giuseppe Bertini
@@ -183,6 +183,8 @@ files:
183
183
  - lib/mork.rb
184
184
  - lib/mork/coord.rb
185
185
  - lib/mork/extensions.rb
186
+ - lib/mork/fonts/OFL.txt
187
+ - lib/mork/fonts/OpenSans-Regular.ttf
186
188
  - lib/mork/grid.rb
187
189
  - lib/mork/grid_const.rb
188
190
  - lib/mork/grid_omr.rb
@@ -195,6 +197,7 @@ files:
195
197
  - lib/mork/version.rb
196
198
  - mork.code-workspace
197
199
  - mork.gemspec
200
+ - script/bench_sheet_pdf.rb
198
201
  - spec/mork/coord_spec.rb
199
202
  - spec/mork/grid_omr_spec.rb
200
203
  - spec/mork/grid_spec.rb