Class: Okab::Page

Inherits:
Object
  • Object
show all
Defined in:
lib/okab/page.rb,
sig/okab.rbs

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(document, width:, height:) ⇒ Page

Returns a new instance of Page.

Parameters:

  • document (Document)
  • width: (Numeric)
  • height: (Numeric)

Raises:

  • (ArgumentError)


43
44
45
46
47
# File 'lib/okab/page.rb', line 43

def initialize(document, width:, height:)
  @document, @width, @height = document, Float(width), Float(height)
  raise ArgumentError, "page dimensions must be finite and positive" unless [@width, @height].all? { |value| value.finite? && value.positive? }
  @operations, @annotations, @open_clips = [], [], 0
end

Instance Attribute Details

#annotations ⇒ Array[Array[untyped]] (readonly)

Returns the value of attribute annotations.

Returns:

  • (Array[Array[untyped]])


41
42
43
# File 'lib/okab/page.rb', line 41

def annotations
  @annotations
end

#document ⇒ Document (readonly)

Returns the value of attribute document.

Returns:



41
42
43
# File 'lib/okab/page.rb', line 41

def document
  @document
end

#height ⇒ Float (readonly)

Returns the value of attribute height.

Returns:

  • (Float)


41
42
43
# File 'lib/okab/page.rb', line 41

def height
  @height
end

#operations ⇒ Array[Array[untyped]] (readonly)

Returns the value of attribute operations.

Returns:

  • (Array[Array[untyped]])


41
42
43
# File 'lib/okab/page.rb', line 41

def operations
  @operations
end

#width ⇒ Float (readonly)

Returns the value of attribute width.

Returns:

  • (Float)


41
42
43
# File 'lib/okab/page.rb', line 41

def width
  @width
end

Instance Method Details

#clip {|path| ... } ⇒ self

Yields:

Yield Parameters:

Yield Returns:

  • (void)

Returns:

  • (self)

Raises:

  • (ArgumentError)


126
127
128
129
130
131
132
133
134
# File 'lib/okab/page.rb', line 126

def clip
  raise ArgumentError, "clip requires a block" unless block_given?
  path = Path.new
  yield path
  raise ArgumentError, "clip path must not be empty" if path.commands.empty?
  @operations << [:raw, "q\n#{path.commands.join("\n")}\nW n"]
  @open_clips += 1
  self
end

#close ⇒ self

Returns:

  • (self)


94
# File 'lib/okab/page.rb', line 94

def close = raw("h")

#content(fonts:) ⇒ String

Parameters:

  • fonts: (Hash[Integer, String])

Returns:

  • (String)


208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
# File 'lib/okab/page.rb', line 208

def content(fonts:)
  output = +"".b
  @operations.each do |operation|
    case operation[0]
    when :raw then output << operation[1].b << "\n".b
    when :text
      _, x, y, font, encoded, size, color, tracking, bold, italic = operation
      output << "#{color.join(' ')} rg\n"
      if bold
        output << "#{color.join(' ')} RG\n#{PDF::Encoding.number((size * 0.035).round(4))} w\n"
      end
      matrix = italic ? "1 0 0.2 1 #{PDF::Encoding.number(x)} #{PDF::Encoding.number(y)} Tm" : "#{PDF::Encoding.number(x)} #{PDF::Encoding.number(y)} Td"
      output << "BT /#{fonts.fetch(font.object_id)} #{PDF::Encoding.number(size)} Tf #{PDF::Encoding.number(tracking)} Tc #{bold ? 2 : 0} Tr #{matrix} #{PDF::Encoding.hex(encoded)} Tj ET\n".b
    when :image
      _, name, x, y, width, height = operation
      output << "q\n#{PDF::Encoding.number(width)} 0 0 #{PDF::Encoding.number(height)} #{PDF::Encoding.number(x)} #{PDF::Encoding.number(y)} cm\n/#{name} Do\nQ\n".b
    end
  end
  @open_clips.times { output << "Q\n".b }
  output
end

#curve_to(x1, y1, x2, y2, x3, y3) ⇒ self

Parameters:

  • x1 (Numeric)
  • y1 (Numeric)
  • x2 (Numeric)
  • y2 (Numeric)
  • x3 (Numeric)
  • y3 (Numeric)

Returns:

  • (self)


93
# File 'lib/okab/page.rb', line 93

def curve_to(x1, y1, x2, y2, x3, y3) = raw(Path.new.curve_to(x1, y1, x2, y2, x3, y3).commands.last)

#ellipse(x, y, width, height) ⇒ self

Parameters:

  • x (Numeric)
  • y (Numeric)
  • width (Numeric)
  • height (Numeric)

Returns:

  • (self)


97
98
99
100
# File 'lib/okab/page.rb', line 97

def ellipse(x, y, width, height)
  path = Path.new.ellipse(*[x, y, width, height].map { |value| finite(value, "ellipse bounds") })
  raw(path.commands.join("\n"))
end

#fill(color, rule: :nonzero) ⇒ self

Parameters:

  • color (Array[Numeric])
  • rule: (:nonzero, :evenodd) (defaults to: :nonzero)

Returns:

  • (self)

Raises:

  • (ArgumentError)


107
108
109
110
# File 'lib/okab/page.rb', line 107

def fill(color, rule: :nonzero)
  raise ArgumentError, "fill rule must be nonzero or evenodd" unless %i[nonzero evenodd].include?(rule)
  raw("#{rgb(color).join(' ')} rg\n#{rule == :evenodd ? 'f*' : 'f'}")
end

#fill_and_stroke(color, width: 1) ⇒ self

Parameters:

  • color (Array[Numeric])
  • width: (Numeric) (defaults to: 1)

Returns:

  • (self)


124
# File 'lib/okab/page.rb', line 124

def fill_and_stroke(color, width: 1) = raw("#{rgb(color).join(' ')} rg\n#{rgb(color).join(' ')} RG\n#{positive(width, 'line width')} w\nB")

#glyph(glyph_id, x:, y:, font:, size:, unicode:, color: [0, 0, 0]) ⇒ self

Parameters:

  • glyph_id (Integer)
  • x: (Numeric)
  • y: (Numeric)
  • font: (Font, Alhena::Font)
  • size: (Numeric)
  • unicode: (String)
  • color: (Array[Numeric]) (defaults to: [0, 0, 0])

Returns:

  • (self)

Raises:

  • (ArgumentError)


60
61
62
63
64
65
66
67
# File 'lib/okab/page.rb', line 60

def glyph(glyph_id, x:, y:, font:, size:, unicode:, color: [0, 0, 0])
  size = finite(size, "font size")
  raise ArgumentError, "font size must be positive" unless size.positive?
  embedded = @document.embed_font(font)
  encoded = embedded.encode_glyph(glyph_id, unicode: unicode)
  @operations << [:text, finite(x, "x"), finite(y, "y"), embedded, encoded, size, rgb(color), 0.0, false, false]
  self
end

#image(data, x:, y:, width:, height:, format: :auto) ⇒ self

Parameters:

  • data (String, Image)
  • x: (Numeric)
  • y: (Numeric)
  • width: (Numeric)
  • height: (Numeric)
  • format: (Symbol) (defaults to: :auto)

Returns:

  • (self)


182
183
184
185
186
187
# File 'lib/okab/page.rb', line 182

def image(data, x:, y:, width:, height:, format: :auto)
  image = data.is_a?(Image) ? data : Image.decode(data, format: format)
  name = @document.register_image(image)
  @operations << [:image, name, finite(x, "x"), finite(y, "y"), finite(width, "width"), finite(height, "height")]
  self
end

#line_to(x, y) ⇒ self

Parameters:

  • x (Numeric)
  • y (Numeric)

Returns:

  • (self)


92
# File 'lib/okab/page.rb', line 92

def line_to(x, y) = raw(Path.new.line_to(x, y).commands.last)

Parameters:

  • bounds (Array[Numeric])
  • uri: (String) (defaults to: nil)
  • page: (Page) (defaults to: nil)

Returns:

  • (self)


189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/okab/page.rb', line 189

def link(bounds, uri: nil, page: nil)
  raise ArgumentError, "provide either uri or page" if (!!uri == !!page)
  x, y, width, height = normalize_bounds(bounds)
  action = if uri
    parsed = URI.parse(uri.to_s)
    valid = (parsed.is_a?(URI::HTTP) && parsed.host && %w[http https].include?(parsed.scheme)) ||
      (parsed.is_a?(URI::MailTo) && !parsed.to.empty?)
    raise ArgumentError, "link URI must use http, https, or mailto" unless valid
    [:uri, uri.to_s.freeze]
  else
    raise ArgumentError, "destination page belongs to another document" unless page.is_a?(Page) && @document.owns_page?(page)
    [:page, page]
  end
  @annotations << [x, y, width, height, *action]
  self
rescue URI::InvalidURIError
  raise ArgumentError, "invalid link URI"
end

#move_to(x, y) ⇒ self

Parameters:

  • x (Numeric)
  • y (Numeric)

Returns:

  • (self)


91
# File 'lib/okab/page.rb', line 91

def move_to(x, y) = raw(Path.new.move_to(x, y).commands.last)

#opacity(value) {|arg0| ... } ⇒ self

Parameters:

  • value (Numeric)

Yields:

Yield Parameters:

Yield Returns:

  • (void)

Returns:

  • (self)

Raises:

  • (ArgumentError)


167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/okab/page.rb', line 167

def opacity(value)
  raise ArgumentError, "opacity requires a block" unless block_given?
  name = @document.opacity_name(value)
  start = @operations.length
  @operations << [:raw, "q\n/#{name} gs"]
  begin
    yield self
  rescue StandardError
    @operations.slice!(start..)
    raise
  end
  @operations << [:raw, "Q"]
  self
end

#path(outline) ⇒ self

Parameters:

Returns:

  • (self)

Raises:

  • (ArgumentError)


102
103
104
105
# File 'lib/okab/page.rb', line 102

def path(outline)
  raise ArgumentError, "path must not be empty" unless outline.is_a?(Path) && !outline.commands.empty?
  raw(outline.commands.join("\n"))
end

#rect(x, y, width, height) ⇒ self

Parameters:

  • x (Numeric)
  • y (Numeric)
  • width (Numeric)
  • height (Numeric)

Returns:

  • (self)


95
# File 'lib/okab/page.rb', line 95

def rect(x, y, width, height) = raw(Path.new.rect(x, y, width, height).commands.last)

#stroke(color, width: 1, cap: :butt, join: :miter, miter: 10, dash: nil) ⇒ self

Parameters:

  • color (Array[Numeric])
  • width: (Numeric) (defaults to: 1)
  • cap: (:butt, :round, :square) (defaults to: :butt)
  • join: (:miter, :round, :bevel) (defaults to: :miter)
  • miter: (Numeric) (defaults to: 10)
  • dash: (Array[Numeric], nil) (defaults to: nil)

Returns:

  • (self)

Raises:

  • (ArgumentError)


112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/okab/page.rb', line 112

def stroke(color, width: 1, cap: :butt, join: :miter, miter: 10, dash: nil)
  raise ArgumentError, "line cap must be butt, round, or square" unless %i[butt round square].include?(cap)
  raise ArgumentError, "line join must be miter, round, or bevel" unless %i[miter round bevel].include?(join)
  style = "#{rgb(color).join(' ')} RG\n#{positive(width, 'line width')} w\n#{ {butt: 0, round: 1, square: 2}.fetch(cap) } J\n#{ {miter: 0, round: 1, bevel: 2}.fetch(join) } j\n#{positive(miter, 'miter limit')} M"
  if dash
    raise ArgumentError, "dash must contain positive lengths" unless dash.is_a?(Array) && !dash.empty? && dash.all? { |value| value.is_a?(Numeric) && value.finite? && value.positive? }
    style << "\n[#{dash.map { |value| PDF::Encoding.number(value) }.join(' ')}] 0 d"
  else
    style << "\n[] 0 d"
  end
  raw("#{style}\nS")
end

#text(string, x:, y:, font:, size:, color: [0, 0, 0], tracking: 0, bold: false, italic: false) ⇒ self

Parameters:

  • string (String)
  • x: (Numeric)
  • y: (Numeric)
  • font: (Font, EmbeddedFont, Alhena::Font)
  • size: (Numeric)
  • color: (Array[Numeric]) (defaults to: [0, 0, 0])
  • tracking: (Numeric) (defaults to: 0)
  • bold: (Boolean) (defaults to: false)
  • italic: (Boolean) (defaults to: false)

Returns:

  • (self)

Raises:

  • (ArgumentError)


49
50
51
52
53
54
55
56
57
58
# File 'lib/okab/page.rb', line 49

def text(string, x:, y:, font:, size:, color: [0, 0, 0], tracking: 0, bold: false, italic: false)
  validate_text(string)
  size, tracking = finite(size, "font size"), finite(tracking, "tracking")
  raise ArgumentError, "font size must be positive" unless size.positive?
  raise ArgumentError, "bold and italic must be true or false" unless [bold, italic].all? { |value| value == true || value == false }
  embedded = @document.embed_font(font)
  encoded = embedded.encode(string)
  @operations << [:text, finite(x, "x"), finite(y, "y"), embedded, encoded, size, rgb(color), tracking, bold, italic]
  self
end

#text_block(string, x:, y:, width:, font:, size:, line_height:, align: :left, color: [0, 0, 0], tracking: 0) ⇒ self

Parameters:

  • string (String)
  • x: (Numeric)
  • y: (Numeric)
  • width: (Numeric)
  • font: (Font, EmbeddedFont, Alhena::Font)
  • size: (Numeric)
  • line_height: (Numeric)
  • align: (Symbol) (defaults to: :left)
  • color: (Array[Numeric]) (defaults to: [0, 0, 0])
  • tracking: (Numeric) (defaults to: 0)

Returns:

  • (self)

Raises:

  • (ArgumentError)


69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/okab/page.rb', line 69

def text_block(string, x:, y:, width:, font:, size:, line_height:, align: :left, color: [0, 0, 0], tracking: 0)
  validate_text(string)
  x, y = finite(x, "x"), finite(y, "y")
  width, size, line_height = finite(width, "width"), finite(size, "font size"), finite(line_height, "line height")
  raise ArgumentError, "text block dimensions must be positive" unless width.positive? && size.positive? && line_height.positive?
  raise ArgumentError, "align must be left, center, right, or justify" unless %i[left center right justify].include?(align)
  face = font.is_a?(EmbeddedFont) ? font.font : normalize_font(font)
  lines = wrap_lines(string, face, size, width)
  lines.each_with_index do |line, index|
    line_width = face.measure(line, size: size)
    offset = case align
    when :center then (width - line_width) / 2.0
    when :right then width - line_width
    else 0
    end
    extra = align == :justify && index < lines.length - 1 ? (width - line_width) / [line.length, 1].max : 0
    text(line, x: x + offset, y: y - index * line_height, font: face, size: size,
      color: color, tracking: tracking + extra)
  end
  self
end

#transform(a, b, c, d, e, f) {|arg0| ... } ⇒ self

Parameters:

  • a (Numeric)
  • b (Numeric)
  • c (Numeric)
  • d (Numeric)
  • e (Numeric)
  • f (Numeric)

Yields:

Yield Parameters:

Yield Returns:

  • (void)

Returns:

  • (self)

Raises:

  • (ArgumentError)


152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/okab/page.rb', line 152

def transform(a, b, c, d, e, f)
  raise ArgumentError, "transform requires a block" unless block_given?
  values = [a, b, c, d, e, f].map { |value| finite(value, 'transform') }
  start = @operations.length
  @operations << [:raw, "q\n#{values.map { |value| PDF::Encoding.number(value) }.join(' ')} cm"]
  begin
    yield self
  rescue StandardError
    @operations.slice!(start..)
    raise
  end
  @operations << [:raw, "Q"]
  self
end

#with_clip(path, rule: :nonzero) {|arg0| ... } ⇒ self

Parameters:

  • path (Path)
  • rule: (:nonzero, :evenodd) (defaults to: :nonzero)

Yields:

Yield Parameters:

Yield Returns:

  • (void)

Returns:

  • (self)

Raises:

  • (ArgumentError)


136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/okab/page.rb', line 136

def with_clip(path, rule: :nonzero)
  raise ArgumentError, "clip requires a block" unless block_given?
  raise ArgumentError, "clip path must not be empty" unless path.is_a?(Path) && !path.commands.empty?
  raise ArgumentError, "clip rule must be nonzero or evenodd" unless %i[nonzero evenodd].include?(rule)
  start = @operations.length
  @operations << [:raw, "q\n#{path.commands.join("\n")}\n#{rule == :evenodd ? 'W*' : 'W'} n"]
  begin
    yield self
  rescue StandardError
    @operations.slice!(start..)
    raise
  end
  @operations << [:raw, "Q"]
  self
end