stationery 0.2.0 → 0.3.0

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +21 -1
  3. data/README.md +47 -10
  4. data/lib/stationery/builder.rb +2 -1
  5. data/lib/stationery/canvas/text.rb +5 -4
  6. data/lib/stationery/canvas.rb +13 -0
  7. data/lib/stationery/document.rb +9 -2
  8. data/lib/stationery/elements.rb +1 -1
  9. data/lib/stationery/fonts/font.rb +36 -20
  10. data/lib/stationery/fonts/font_book.rb +3 -0
  11. data/lib/stationery/fonts/glyph_run.rb +4 -3
  12. data/lib/stationery/fonts/gpos.rb +11 -9
  13. data/lib/stationery/fonts/gsub/ligature_subst.rb +44 -0
  14. data/lib/stationery/fonts/gsub.rb +65 -0
  15. data/lib/stationery/fonts/ligatures.rb +16 -0
  16. data/lib/stationery/fonts/registry.rb +18 -9
  17. data/lib/stationery/fonts/to_unicode.rb +1 -1
  18. data/lib/stationery/fonts/true_type.rb +36 -12
  19. data/lib/stationery/fonts/woff.rb +52 -0
  20. data/lib/stationery/layout/box.rb +23 -6
  21. data/lib/stationery/layout/row.rb +1 -1
  22. data/lib/stationery/layout/svg.rb +5 -2
  23. data/lib/stationery/layout/text.rb +2 -1
  24. data/lib/stationery/pdf/assembler.rb +3 -2
  25. data/lib/stationery/pdf/encryption/aes.rb +37 -0
  26. data/lib/stationery/pdf/encryption/rc4.rb +33 -0
  27. data/lib/stationery/pdf/encryption/revision4.rb +48 -0
  28. data/lib/stationery/pdf/encryption/revision6.rb +56 -0
  29. data/lib/stationery/pdf/encryption/standard_security.rb +78 -0
  30. data/lib/stationery/pdf/serializer.rb +17 -9
  31. data/lib/stationery/pdf/writer.rb +24 -5
  32. data/lib/stationery/resources.rb +8 -2
  33. data/lib/stationery/svg/bounds.rb +76 -0
  34. data/lib/stationery/svg/document.rb +48 -18
  35. data/lib/stationery/svg/gradient.rb +115 -0
  36. data/lib/stationery/svg/painter.rb +70 -0
  37. data/lib/stationery/svg/parser.rb +24 -4
  38. data/lib/stationery/svg/selector.rb +37 -0
  39. data/lib/stationery/svg/shading.rb +34 -0
  40. data/lib/stationery/svg/style.rb +37 -20
  41. data/lib/stationery/svg/stylesheet.rb +47 -0
  42. data/lib/stationery/svg/text.rb +122 -0
  43. data/lib/stationery/svg/transform.rb +11 -0
  44. data/lib/stationery/text/paragraph.rb +1 -0
  45. data/lib/stationery/text/style.rb +4 -2
  46. data/lib/stationery/text/wrapper.rb +2 -1
  47. data/lib/stationery/version.rb +1 -1
  48. data/lib/stationery.rb +16 -0
  49. metadata +17 -1
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Stationery
4
+ module SVG
5
+ # Lays out a <text> element: its strings and tspans become pieces along
6
+ # the baseline, a new chunk starting wherever x or y is set, and each
7
+ # chunk is shifted by its text-anchor. Glyphs are drawn upright at the
8
+ # transformed origin, sized by the transform's uniform scale, so rotated,
9
+ # skewed or unevenly scaled text is approximated.
10
+ class Text
11
+ ANCHORS = { "middle" => 0.5, "end" => 1.0 }.freeze
12
+ DEFAULT_SIZE = 16
13
+
14
+ Piece = Data.define(:runs, :style, :x, :y, :chunk, :advance)
15
+
16
+ def initialize(canvas, painter, book, family)
17
+ @canvas = canvas
18
+ @painter = painter
19
+ @book = book
20
+ @family = family
21
+ end
22
+
23
+ def draw(element, style)
24
+ @pieces = []
25
+ @x = @y = @chunk = 0
26
+ @space = true
27
+ place(element, style)
28
+ trim
29
+ @pieces.group_by(&:chunk).each_value { |chunk| draw_chunk(chunk) }
30
+ end
31
+
32
+ private
33
+
34
+ def place(element, style)
35
+ move(element.attributes)
36
+ element.children.each do |child|
37
+ if child.is_a?(String) then add(child, style)
38
+ elsif child.name == "tspan" then place(child, style.child(child))
39
+ end
40
+ end
41
+ end
42
+
43
+ def move(attributes)
44
+ x, y, dx, dy = attributes.values_at("x", "y", "dx", "dy").map { |value| first(value) }
45
+ @chunk += 1 if x || y
46
+ @x = (x || @x) + (dx || 0)
47
+ @y = (y || @y) + (dy || 0)
48
+ end
49
+
50
+ def first(value) = value.to_s.split(/[\s,]+/).find { |part| !part.empty? }&.to_f
51
+
52
+ # Collapses the space between pieces and at the start of the text.
53
+ def add(string, style)
54
+ string = string.lstrip if @space
55
+ return if string.empty?
56
+
57
+ @space = string.end_with?(" ")
58
+ @pieces << piece(string, style, @x, @y)
59
+ @x += @pieces.last.advance
60
+ end
61
+
62
+ def piece(string, style, x, y, chunk = @chunk)
63
+ italic = style.values["font-style"].to_s.match?(/italic|oblique/)
64
+ text_style = Stationery::Text::Style.new(family: family(style), size: size(style), weight: weight(style),
65
+ style: italic ? :italic : :normal)
66
+ runs = @book.fallback([Stationery::Text::Run.new(string, text_style)])
67
+ advance = runs.sum { |run| font(run).width_of(run.text, text_style.size) }
68
+ Piece.new(runs, style, x, y, chunk, advance)
69
+ end
70
+
71
+ def trim
72
+ last = @pieces.last or return
73
+ text = last.runs.map(&:text).join
74
+ return unless text.end_with?(" ")
75
+
76
+ @pieces[-1] = piece(text.rstrip, last.style, last.x, last.y, last.chunk)
77
+ end
78
+
79
+ def draw_chunk(pieces)
80
+ width = pieces.last.x + pieces.last.advance - pieces.first.x
81
+ shift = -width * ANCHORS.fetch(pieces.first.style.values["text-anchor"].to_s, 0)
82
+ pieces.each { |piece| draw_piece(piece, shift) }
83
+ end
84
+
85
+ def draw_piece(piece, shift)
86
+ color = @painter.color(piece.style.fill, piece.style)
87
+ return unless color
88
+
89
+ x = piece.x + shift
90
+ scale = Transform.scale(piece.style.matrix)
91
+ piece.runs.each do |run|
92
+ font, face = @book.resolve(run.style)
93
+ page_x, page_y = Transform.apply(piece.style.matrix, x, piece.y)
94
+ @canvas.text(run.text, x: page_x, y: page_y, font:, size: run.style.size * scale, color:,
95
+ opacity: Painter.translucent(piece.style.opacity),
96
+ synthetic_bold: face.synthetic_bold, synthetic_oblique: face.synthetic_oblique)
97
+ x += font.width_of(run.text, run.style.size)
98
+ end
99
+ end
100
+
101
+ def font(run) = @book.resolve(run.style).first
102
+
103
+ # The first family of the list the book knows, else the document's.
104
+ def family(style)
105
+ names = style.values["font-family"].to_s.split(",").map { |name| name.strip.delete("'\"") }
106
+ names.find { |name| !name.empty? && @book.known?(name) } || @family
107
+ end
108
+
109
+ def size(style)
110
+ size = style.values["font-size"].to_f
111
+ size.positive? ? size : DEFAULT_SIZE
112
+ end
113
+
114
+ def weight(style)
115
+ value = style.values["font-weight"].to_s.strip
116
+ return value.to_i if value.match?(/\A\d+\z/)
117
+
118
+ %w[bold bolder].include?(value) ? :bold : :regular
119
+ end
120
+ end
121
+ end
122
+ end
@@ -31,6 +31,17 @@ module Stationery
31
31
  multiply(multiply([1, 0, 0, 1, cx, cy], rotation), [1, 0, 0, 1, -cx, -cy])
32
32
  end
33
33
 
34
+ def apply(matrix, x, y)
35
+ a, b, c, d, e, f = matrix
36
+ [(a * x) + (c * y) + e, (b * x) + (d * y) + f]
37
+ end
38
+
39
+ # The uniform scale a matrix applies to areas, as a length factor.
40
+ def scale(matrix)
41
+ a, b, c, d, = matrix
42
+ Math.sqrt(((a * d) - (b * c)).abs)
43
+ end
44
+
34
45
  # The transform that applies `inner` first, then `outer`.
35
46
  def multiply(outer, inner)
36
47
  a, b, c, d, e, f = outer
@@ -102,6 +102,7 @@ module Stationery
102
102
  color: style.color, letter_spacing: style.letter_spacing, rise: style.rise,
103
103
  opacity: style.opacity, underline: style.underline,
104
104
  strikethrough: style.strikethrough, kerning: style.kerning,
105
+ ligatures: style.ligatures,
105
106
  synthetic_bold: fragment.face.synthetic_bold,
106
107
  synthetic_oblique: fragment.face.synthetic_oblique, word_spacing:)
107
108
  canvas.link(x, top, width, line.height, style.link) if style.link
@@ -6,9 +6,11 @@ module Stationery
6
6
 
7
7
  # How a run of text looks. Immutable; derive variants with #with.
8
8
  Style = Data.define(:family, :size, :weight, :style, :color, :letter_spacing,
9
- :underline, :strikethrough, :script, :link, :opacity, :kerning) do
9
+ :underline, :strikethrough, :script, :link, :opacity, :kerning,
10
+ :ligatures) do
10
11
  def initialize(family:, size: 10, weight: :regular, style: :normal, color: "#000000", letter_spacing: 0,
11
- underline: false, strikethrough: false, script: nil, link: nil, opacity: nil, kerning: true)
12
+ underline: false, strikethrough: false, script: nil, link: nil, opacity: nil, kerning: true,
13
+ ligatures: true)
12
14
  super
13
15
  end
14
16
 
@@ -125,7 +125,8 @@ module Stationery
125
125
 
126
126
  def measure(text, style)
127
127
  font = @book.resolve(style).first
128
- font.width_of(text, style.render_size, letter_spacing: style.letter_spacing, kerning: style.kerning)
128
+ font.width_of(text, style.render_size, letter_spacing: style.letter_spacing, kerning: style.kerning,
129
+ ligatures: style.ligatures)
129
130
  end
130
131
  end
131
132
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Stationery
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
data/lib/stationery.rb CHANGED
@@ -5,8 +5,14 @@ require_relative "stationery/errors"
5
5
  require_relative "stationery/pdf/types"
6
6
  require_relative "stationery/pdf/serializer"
7
7
  require_relative "stationery/pdf/stream"
8
+ require_relative "stationery/pdf/encryption/rc4"
9
+ require_relative "stationery/pdf/encryption/aes"
10
+ require_relative "stationery/pdf/encryption/revision4"
11
+ require_relative "stationery/pdf/encryption/revision6"
12
+ require_relative "stationery/pdf/encryption/standard_security"
8
13
  require_relative "stationery/pdf/writer"
9
14
  require_relative "stationery/fonts/sfnt_metrics"
15
+ require_relative "stationery/fonts/woff"
10
16
  require_relative "stationery/fonts/true_type"
11
17
  require_relative "stationery/fonts/cff"
12
18
  require_relative "stationery/fonts/cff_subset"
@@ -18,6 +24,9 @@ require_relative "stationery/fonts/gpos/coverage"
18
24
  require_relative "stationery/fonts/gpos/class_def"
19
25
  require_relative "stationery/fonts/gpos/pair_pos"
20
26
  require_relative "stationery/fonts/kerning"
27
+ require_relative "stationery/fonts/gsub"
28
+ require_relative "stationery/fonts/gsub/ligature_subst"
29
+ require_relative "stationery/fonts/ligatures"
21
30
  require_relative "stationery/fonts/subset"
22
31
  require_relative "stationery/fonts/registry"
23
32
  require_relative "stationery/fonts/glyph_run"
@@ -77,8 +86,15 @@ require_relative "stationery/svg/path_data"
77
86
  require_relative "stationery/svg/arc"
78
87
  require_relative "stationery/svg/parser"
79
88
  require_relative "stationery/svg/transform"
89
+ require_relative "stationery/svg/selector"
90
+ require_relative "stationery/svg/stylesheet"
80
91
  require_relative "stationery/svg/style"
81
92
  require_relative "stationery/svg/shapes"
93
+ require_relative "stationery/svg/bounds"
94
+ require_relative "stationery/svg/gradient"
95
+ require_relative "stationery/svg/shading"
96
+ require_relative "stationery/svg/painter"
97
+ require_relative "stationery/svg/text"
82
98
  require_relative "stationery/svg/document"
83
99
  require_relative "stationery/layout/svg"
84
100
  require_relative "stationery/layout/wrap"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stationery
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mikael Henriksson
@@ -67,9 +67,12 @@ files:
67
67
  - lib/stationery/fonts/gpos/class_def.rb
68
68
  - lib/stationery/fonts/gpos/coverage.rb
69
69
  - lib/stationery/fonts/gpos/pair_pos.rb
70
+ - lib/stationery/fonts/gsub.rb
71
+ - lib/stationery/fonts/gsub/ligature_subst.rb
70
72
  - lib/stationery/fonts/installer.rb
71
73
  - lib/stationery/fonts/kern_table.rb
72
74
  - lib/stationery/fonts/kerning.rb
75
+ - lib/stationery/fonts/ligatures.rb
73
76
  - lib/stationery/fonts/name_table.rb
74
77
  - lib/stationery/fonts/packs.rb
75
78
  - lib/stationery/fonts/registry.rb
@@ -77,6 +80,7 @@ files:
77
80
  - lib/stationery/fonts/subset.rb
78
81
  - lib/stationery/fonts/to_unicode.rb
79
82
  - lib/stationery/fonts/true_type.rb
83
+ - lib/stationery/fonts/woff.rb
80
84
  - lib/stationery/geometry.rb
81
85
  - lib/stationery/html/document.rb
82
86
  - lib/stationery/html/tokenizer.rb
@@ -124,6 +128,11 @@ files:
124
128
  - lib/stationery/page.rb
125
129
  - lib/stationery/page_templates.rb
126
130
  - lib/stationery/pdf/assembler.rb
131
+ - lib/stationery/pdf/encryption/aes.rb
132
+ - lib/stationery/pdf/encryption/rc4.rb
133
+ - lib/stationery/pdf/encryption/revision4.rb
134
+ - lib/stationery/pdf/encryption/revision6.rb
135
+ - lib/stationery/pdf/encryption/standard_security.rb
127
136
  - lib/stationery/pdf/outline_writer.rb
128
137
  - lib/stationery/pdf/serializer.rb
129
138
  - lib/stationery/pdf/stream.rb
@@ -142,11 +151,18 @@ files:
142
151
  - lib/stationery/rspec.rb
143
152
  - lib/stationery/structure.rb
144
153
  - lib/stationery/svg/arc.rb
154
+ - lib/stationery/svg/bounds.rb
145
155
  - lib/stationery/svg/document.rb
156
+ - lib/stationery/svg/gradient.rb
157
+ - lib/stationery/svg/painter.rb
146
158
  - lib/stationery/svg/parser.rb
147
159
  - lib/stationery/svg/path_data.rb
160
+ - lib/stationery/svg/selector.rb
161
+ - lib/stationery/svg/shading.rb
148
162
  - lib/stationery/svg/shapes.rb
149
163
  - lib/stationery/svg/style.rb
164
+ - lib/stationery/svg/stylesheet.rb
165
+ - lib/stationery/svg/text.rb
150
166
  - lib/stationery/svg/transform.rb
151
167
  - lib/stationery/testing/inspector.rb
152
168
  - lib/stationery/testing/matchers.rb