Class: Clef::Renderer::PdfRenderer

Inherits:
Base
  • Object
show all
Includes:
NotationHelpers
Defined in:
lib/clef/renderer/pdf_renderer.rb

Constant Summary collapse

LEFT_PADDING =
80
TOP_PADDING =
56
STAFF_START_X =
160

Constants included from NotationHelpers

NotationHelpers::ACCIDENTAL_FALLBACK, NotationHelpers::ACCIDENTAL_GLYPH_KEYS, NotationHelpers::FLAT_ORDER, NotationHelpers::REST_LABELS, NotationHelpers::SHARP_ORDER

Instance Attribute Summary

Attributes inherited from Base

#font_manager, #glyph_table, #style

Instance Method Summary collapse

Methods inherited from Base

#initialize

Constructor Details

This class inherits a constructor from Clef::Renderer::Base

Instance Method Details

#draw_accidental(pdf, pitch, x, y, key_signature, state) ⇒ Object

Parameters:



287
288
289
290
291
292
293
294
# File 'lib/clef/renderer/pdf_renderer.rb', line 287

def draw_accidental(pdf, pitch, x, y, key_signature, state)
  alteration = accidental_for_pitch(pitch, key_signature, state)
  return if alteration.nil?

  key = accidental_glyph_key(alteration)
  glyph = smufl_enabled? ? glyph_table[key] : accidental_text(alteration)
  pdf.text_box(glyph, at: [x - 12, y + 5], size: 9)
end

#draw_articulations(pdf, articulations, x, y) ⇒ Object

Parameters:

  • pdf (Prawn::Document)
  • articulations (Array<Symbol>)
  • x (Float)
  • y (Float)


313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
# File 'lib/clef/renderer/pdf_renderer.rb', line 313

def draw_articulations(pdf, articulations, x, y)
  articulations.each do |articulation|
    case articulation
    when :staccato
      pdf.circle([x, y + 12], 1.5)
      pdf.fill
    when :tenuto
      pdf.stroke_line [x - 4, y + 12], [x + 4, y + 12]
    when :accent
      pdf.text_box(">", at: [x - 4, y + 14], size: 9)
    when :marcato
      pdf.stroke_line [x - 4, y + 10], [x, y + 16]
      pdf.stroke_line [x, y + 16], [x + 4, y + 10]
    when :fermata
      draw_fermata(pdf, x, y)
    else
      pdf.text_box(articulation.to_s, at: [x - 4, y + 12], size: 6)
    end
  end
end

#draw_barline(pdf, x, baseline) ⇒ Object

Parameters:

  • pdf (Prawn::Document)
  • x (Float)
  • baseline (Float)


349
350
351
352
353
# File 'lib/clef/renderer/pdf_renderer.rb', line 349

def draw_barline(pdf, x, baseline)
  top = baseline + (style.staff_space * 0.5)
  bottom = baseline - (style.staff_space * 4.5)
  pdf.stroke_line [x, top], [x, bottom]
end

#draw_chord(pdf, chord, x, baseline, clef, accidental_state:, key_signature:) ⇒ Object

Parameters:



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/clef/renderer/pdf_renderer.rb', line 203

def draw_chord(pdf, chord, x, baseline, clef, accidental_state:, key_signature:)
  notes = chord_notes(chord)
  offsets = chord_note_offsets(notes)
  ys = []
  notes.each_with_index do |note, index|
    y = pitch_to_y(note.pitch, baseline, clef)
    ys << y
    note_x = x + offsets[index]
    draw_ledger_lines(pdf, note_x, y, baseline)
    draw_notehead(pdf, note_x, y, duration: chord.duration)
    draw_accidental(pdf, note.pitch, note_x - accidental_offset(index), y, key_signature, accidental_state)
  end
  draw_chord_stem(pdf, notes, x, ys, clef) if stem_required?(chord.duration)
  ys.each { |y| draw_dot(pdf, chord.duration, x, y) }
end

#draw_chord_stem(pdf, notes, x, ys, clef) ⇒ Object

Parameters:



270
271
272
273
274
275
276
277
278
279
# File 'lib/clef/renderer/pdf_renderer.rb', line 270

def draw_chord_stem(pdf, notes, x, ys, clef)
  direction = Clef::Layout::Stem.direction(notes, clef)
  anchor_note = chord_stem_anchor_note(notes, direction)
  anchor_index = notes.index(anchor_note)
  anchor_y = ys[anchor_index]
  stem_len = style.staff_space * chord_stem_length(notes, clef, direction)
  y2 = (direction == :up) ? anchor_y + stem_len : anchor_y - stem_len
  stem_x = (direction == :up) ? x + 3 : x - 3
  pdf.stroke_line [stem_x, anchor_y], [stem_x, y2]
end

#draw_clef(pdf, clef, x, baseline) ⇒ Object

Parameters:



75
76
77
78
79
80
81
82
83
# File 'lib/clef/renderer/pdf_renderer.rb', line 75

def draw_clef(pdf, clef, x, baseline)
  glyph = smufl_enabled? ? glyph_table[:"clef_#{clef.type}"] : fallback_clef_text(clef)
  glyph ||= fallback_clef_text(clef)
  size = smufl_enabled? ? 16 : 10
  y = smufl_enabled? ? (baseline - style.staff_space) : (baseline + (style.staff_space * 1.5))

  pdf.text_box(glyph, at: [x, y], size: size)
  x + text_width(pdf, glyph, size: size)
end

#draw_dot(pdf, duration, x, y) ⇒ Object

Parameters:



300
301
302
303
304
305
306
307
# File 'lib/clef/renderer/pdf_renderer.rb', line 300

def draw_dot(pdf, duration, x, y)
  return if duration.dots.zero?

  duration.dots.times do |index|
    pdf.circle([x + 8 + (index * 3), y], 1)
    pdf.fill
  end
end

#draw_element(pdf, element, x, baseline, clef) ⇒ Object

Parameters:

  • pdf (Prawn::Document)
  • element (Object)
  • x (Float)
  • baseline (Float)
  • clef (Clef::Core::Clef)


153
154
155
156
157
158
159
160
161
162
# File 'lib/clef/renderer/pdf_renderer.rb', line 153

def draw_element(pdf, element, x, baseline, clef)
  case element
  when Clef::Core::Note then draw_note(pdf, element, x, baseline, clef, accidental_state: {}, key_signature: nil)
  when Clef::Core::Rest then draw_rest(pdf, element, x, baseline)
  when Clef::Core::Chord then draw_chord(pdf, element, x, baseline, clef, accidental_state: {}, key_signature: nil)
  when Clef::Core::Tuplet then draw_tuplet(pdf, element, x, baseline, clef, accidental_state: {}, key_signature: nil)
  when Clef::Notation::Dynamic then draw_dynamic(pdf, element, x, baseline)
  when Clef::Core::Tempo then draw_tempo_change(pdf, element, x, baseline)
  end
end

#draw_fermata(pdf, x, y) ⇒ Object



334
335
336
337
338
339
340
341
342
343
344
# File 'lib/clef/renderer/pdf_renderer.rb', line 334

def draw_fermata(pdf, x, y)
  if pdf.respond_to?(:stroke_curve)
    pdf.stroke_curve [x - 7, y + 12], [x + 7, y + 12],
      bounds: [[x - 5, y + 18], [x + 5, y + 18]]
  else
    pdf.stroke_line [x - 7, y + 12], [x, y + 17]
    pdf.stroke_line [x, y + 17], [x + 7, y + 12]
  end
  pdf.circle([x, y + 12], 1.2)
  pdf.fill
end

#draw_measure(pdf, measure, staff, start_x, baseline, measure_start = Clef::Ir::Moment.new(0), positions = nil, layout = nil, system: nil) ⇒ Hash

Parameters:

  • pdf (Prawn::Document)
  • measure (Clef::Core::Measure)
  • staff (Clef::Core::Staff)
  • start_x (Float)
  • baseline (Float)
  • measure_start (Clef::Ir::Moment) (defaults to: Clef::Ir::Moment.new(0))
  • positions (Hash, nil) (defaults to: nil)
  • layout (Hash, nil) (defaults to: nil)

Returns:

  • (Hash)


128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/clef/renderer/pdf_renderer.rb', line 128

def draw_measure(pdf, measure, staff, start_x, baseline, measure_start = Clef::Ir::Moment.new(0), positions = nil, layout = nil, system: nil)
  note_points = {}
  voice_elements = {}
  accidental_state = {}
  measure.voices.each_with_index do |(voice_id, voice), index|
    voice_elements[voice_id] = voice.elements
    cursor = Clef::Ir::Moment.new(measure_start.value)
    voice_baseline = baseline + voice_vertical_offset(index)
    voice.elements.each do |element|
      if system.nil? || system.include_moment?(cursor)
        x = x_for_moment(positions, cursor, start_x, position_offset: system&.position_offset)
        draw_element_with_context(pdf, element, x, voice_baseline, staff, measure, accidental_state, note_points)
      end
      cursor += element.length
    end
    draw_beams(pdf, layout, staff, measure, voice_id, note_points)
  end
  [note_points, voice_elements]
end

#draw_measures(pdf, staff, start_x, baseline, positions = nil, layout = nil, system: nil) ⇒ Object

Parameters:

  • pdf (Prawn::Document)
  • staff (Clef::Core::Staff)
  • start_x (Float)
  • baseline (Float)
  • positions (Hash, nil) (defaults to: nil)
  • layout (Hash, nil) (defaults to: nil)


100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/clef/renderer/pdf_renderer.rb', line 100

def draw_measures(pdf, staff, start_x, baseline, positions = nil, layout = nil, system: nil)
  note_points = {}
  voice_elements = Hash.new { |hash, key| hash[key] = [] }
  measure_start = Clef::Ir::Moment.new(0)
  staff.measures.each do |measure|
    measure_points, measure_voice_elements = draw_measure(pdf, measure, staff, start_x, baseline, measure_start, positions, layout, system: system)
    note_points.merge!(measure_points)
    measure_voice_elements.each { |voice_id, elements| voice_elements[voice_id].concat(elements) }
    bar_moment = measure_start + measure_length_for(measure)
    if system.nil? || system.include_moment?(bar_moment)
      bar_x = x_for_moment(positions, bar_moment, start_x, position_offset: system&.position_offset)
      draw_barline(pdf, bar_x, baseline)
    end
    measure_start += measure_length_for(measure)
  end
  voice_elements.each_value { |elements| draw_note_connections(pdf, elements, note_points) }
  note_points
end

#draw_metadata(pdf, staff, x, baseline) ⇒ Object

Parameters:



89
90
91
92
# File 'lib/clef/renderer/pdf_renderer.rb', line 89

def (pdf, staff, x, baseline)
  cursor = draw_key_signature(pdf, staff.key_signature, x, baseline)
  draw_time_signature(pdf, staff.time_signature, cursor + style.staff_space, baseline)
end

#draw_note(pdf, note, x, baseline, clef, accidental_state:, key_signature:) ⇒ Object

Parameters:



169
170
171
172
173
174
175
176
177
178
# File 'lib/clef/renderer/pdf_renderer.rb', line 169

def draw_note(pdf, note, x, baseline, clef, accidental_state:, key_signature:)
  y = pitch_to_y(note.pitch, baseline, clef)
  draw_ledger_lines(pdf, x, y, baseline)
  draw_notehead(pdf, x, y, duration: note.duration)
  draw_accidental(pdf, note.pitch, x, y, key_signature, accidental_state)
  draw_stem(pdf, note, x, y, clef) if stem_required?(note.duration)
  draw_flag(pdf, note, x, y, clef) if flag_required?(note.duration)
  draw_dot(pdf, note.duration, x, y)
  draw_articulations(pdf, note.articulations, x, y)
end

#draw_notehead(pdf, x, y, duration:) ⇒ Object

Parameters:



239
240
241
242
243
244
245
246
247
248
249
250
# File 'lib/clef/renderer/pdf_renderer.rb', line 239

def draw_notehead(pdf, x, y, duration:)
  pdf.fill_color("000000")
  if filled_notehead?(duration)
    pdf.circle([x, y], style.notehead_width / 2.0)
    pdf.fill
  else
    pdf.fill_color("FFFFFF")
    pdf.ellipse([x, y], style.notehead_width / 2.0, 2.5)
    pdf.fill_and_stroke
    pdf.fill_color("000000")
  end
end

#draw_rest(pdf, rest, x, baseline) ⇒ Object

Parameters:



184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/clef/renderer/pdf_renderer.rb', line 184

def draw_rest(pdf, rest, x, baseline)
  return if rest.kind == :invisible || rest.kind == :spacer

  glyph = if smufl_enabled?
    glyph_table[rest_glyph_key(rest.duration)] || glyph_table[:rest_quarter]
  else
    rest_label(rest.duration)
  end
  pdf.text_box(glyph, at: [x, rest_y(rest, baseline)], size: 14)
  return unless rest.kind == :multi_measure && rest.measures > 1

  pdf.text_box(rest.measures.to_s, at: [x - 2, baseline + style.staff_space], size: 8)
end

#draw_score(pdf, score, positions, layout: nil) ⇒ Object

Parameters:

  • pdf (Prawn::Document)
  • score (Clef::Core::Score)
  • positions (Hash, nil)
  • layout (Hash, nil) (defaults to: nil)


32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/clef/renderer/pdf_renderer.rb', line 32

def draw_score(pdf, score, positions, layout: nil)
  return draw_systems(pdf, score, layout) if layout&.dig(:systems)&.any?

  draw_header(pdf, score)
  staff_index = 0
  score.staff_groups.each do |group|
    group_start = pdf.cursor - TOP_PADDING - (staff_index * style.staff_gap)
    group.staves.each do |staff|
      baseline = pdf.cursor - TOP_PADDING - (staff_index * style.staff_gap)
      draw_staff(pdf, staff, baseline, positions: positions, layout: layout)
      staff_index += 1
    end
    draw_staff_group(pdf, group, group_start, staff_index - 1) if group.staves.length > 1
  end
end

#draw_slurs(pdf, slurs = [], note_points: {}) ⇒ Object

Parameters:

  • pdf (Prawn::Document)
  • slurs (Array<Clef::Notation::Slur>) (defaults to: [])
  • note_points (Hash) (defaults to: {})


358
359
360
# File 'lib/clef/renderer/pdf_renderer.rb', line 358

def draw_slurs(pdf, slurs = [], note_points: {})
  slurs.each { |slur| draw_notation_connection(pdf, slur, note_points, lift: 14) }
end

#draw_staff(pdf, staff, baseline, positions: nil, layout: nil) ⇒ Object

Parameters:

  • pdf (Prawn::Document)
  • staff (Clef::Core::Staff)
  • baseline (Float)
  • positions (Hash, nil) (defaults to: nil)
  • layout (Hash, nil) (defaults to: nil)


53
54
55
56
57
58
59
60
# File 'lib/clef/renderer/pdf_renderer.rb', line 53

def draw_staff(pdf, staff, baseline, positions: nil, layout: nil)
  draw_staff_lines(pdf, baseline)
  cursor = draw_clef(pdf, staff.clef, LEFT_PADDING - 24, baseline)
  cursor = draw_key_signature(pdf, staff.key_signature, cursor + style.staff_space, baseline)
  cursor = draw_time_signature(pdf, staff.time_signature, cursor + style.staff_space, baseline)
  note_points = draw_measures(pdf, staff, [cursor + style.measure_padding, STAFF_START_X].max, baseline, positions, layout)
  draw_lyrics(pdf, staff, note_points, baseline)
end

#draw_staff_lines(pdf, baseline) ⇒ Object

Parameters:

  • pdf (Prawn::Document)
  • baseline (Float)


64
65
66
67
68
69
# File 'lib/clef/renderer/pdf_renderer.rb', line 64

def draw_staff_lines(pdf, baseline)
  5.times do |index|
    y = baseline - (index * style.staff_space)
    pdf.stroke_line [LEFT_PADDING, y], [staff_right_bound(pdf), y]
  end
end

#draw_stem(pdf, note, x, y, clef) ⇒ Object

Parameters:



257
258
259
260
261
262
263
# File 'lib/clef/renderer/pdf_renderer.rb', line 257

def draw_stem(pdf, note, x, y, clef)
  direction = Clef::Layout::Stem.direction(note, clef)
  stem_len = style.staff_space * Clef::Layout::Stem.length(note, clef, direction)
  y2 = (direction == :up) ? y + stem_len : y - stem_len
  stem_x = (direction == :up) ? x + 3 : x - 3
  pdf.stroke_line [stem_x, y], [stem_x, y2]
end

#draw_ties(pdf, ties = [], note_points: {}) ⇒ Object

Parameters:

  • pdf (Prawn::Document)
  • ties (Array<Clef::Notation::Tie>) (defaults to: [])
  • note_points (Hash) (defaults to: {})


365
366
367
# File 'lib/clef/renderer/pdf_renderer.rb', line 365

def draw_ties(pdf, ties = [], note_points: {})
  ties.each { |tie| draw_notation_connection(pdf, tie, note_points, lift: 8) }
end

#draw_tuplet(pdf, tuplet, x, baseline, clef, accidental_state:, key_signature:, note_points: {}) ⇒ Object

Parameters:



224
225
226
227
228
229
230
231
232
233
# File 'lib/clef/renderer/pdf_renderer.rb', line 224

def draw_tuplet(pdf, tuplet, x, baseline, clef, accidental_state:, key_signature:, note_points: {})
  cursor = x
  staff_context = Struct.new(:clef, :key_signature).new(clef, key_signature)
  measure_context = Struct.new(:key_signature).new(key_signature)
  tuplet.elements.each do |element|
    draw_element_with_context(pdf, element, cursor, baseline, staff_context, measure_context, accidental_state, note_points)
    cursor += duration_spacing(element) * tuplet.ratio
  end
  pdf.text_box(tuplet.actual.to_s, at: [x + ((cursor - x) / 2.0), baseline + style.staff_space], size: 8)
end

#render(score, path, positions: nil, layout: nil, **_options) ⇒ Object

Parameters:

  • score (Clef::Core::Score)
  • path (String)
  • positions (Hash) (defaults to: nil)
  • layout (Hash, nil) (defaults to: nil)


18
19
20
21
22
23
24
25
26
# File 'lib/clef/renderer/pdf_renderer.rb', line 18

def render(score, path, positions: nil, layout: nil, **_options)
  return render_to_io(score, path, positions: positions, layout: layout) if path.respond_to?(:write)

  ensure_parent_directory!(path)
  Prawn::Document.generate(path, page_size: style.page_size, margin: style.margin) do |pdf|
    prepare_canvas(pdf)
    draw_score(pdf, score, positions, layout: layout)
  end
end