pfa 1.0.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.
@@ -0,0 +1,198 @@
1
+ #
2
+ # Module de construction de l'image CVG
3
+ #
4
+ require_relative 'imagemagick_module'
5
+ require_relative 'any_builder'
6
+ module PFA
7
+ class RelativePFA
8
+ class ImgBuilder < AnyBuilder
9
+
10
+ attr_reader :code_image_magick
11
+
12
+
13
+ # Initialisation avant la construction
14
+ #
15
+ # On définit notamment toutes les constants PFA_WIDTH etc.
16
+ #
17
+ def init(**params)
18
+ params[:as].is_a?(Symbol) || params[:as].is_a?(Hash) || raise(PFAFatalError.new(101))
19
+ MagickPFA.define_dims_constants(params[:as])
20
+ end
21
+
22
+ def coef_pixels
23
+ @coef_pixels ||= calc_coefficient_pixels
24
+ end
25
+
26
+
27
+ ##########################################
28
+ ### MÉTHODE PRINCIPALE DE CONSTRUCTION ###
29
+ ##########################################
30
+ # Construction de l'image du paradigme
31
+ #
32
+ # @param params [Hash]
33
+ # :as La chose à construire, permettant de définir les
34
+ # dimensions de l'image et les dimensions principales.
35
+ # Mettre :real_book ou :test pour des dimensions
36
+ # différentes et connu.
37
+ # Cf. le fichier any_builder.rb pour voir les valeurs
38
+ # possible.
39
+ #
40
+ def build(**params)
41
+ #
42
+ # Initialisation de la construction. On doit par exemple fournir
43
+ # les dimensions à utiliser à l'aide options[:as]
44
+ #
45
+ init(**params)
46
+
47
+ #
48
+ # Le PFA doit être défini au minimum et ses données doivent être
49
+ # valides.
50
+ #
51
+ pfa.valid?
52
+
53
+ #
54
+ #
55
+ # Détruire l'image si elle existe déjà
56
+ #
57
+ File.delete(image_path) if File.exist?(image_path)
58
+
59
+ #
60
+ # Construction du code pour Image Magick
61
+ # (tous les codes seront injectés dans @code_image_magick)
62
+ #
63
+ @code_image_magick = [MagickPFA.code_for_intro]
64
+
65
+ #
66
+ # Les titres des deux PFA
67
+ # (on peut utiliser n'importe quel noeud pour les afficher)
68
+ #
69
+ @code_image_magick << pfa.exposition.titre_pfa(false)
70
+ @code_image_magick << pfa.exposition.titre_pfa(true)
71
+
72
+ #
73
+ # Le fond, avec les actes
74
+ #
75
+ actes.each do |acte|
76
+ # acte.full_code_image_magick
77
+ # -- Cadre de la partie (acte) --
78
+ @code_image_magick << acte.act_box_code(true)
79
+ @code_image_magick << acte.act_box_code(false)
80
+ # --- Nomde l'acte --
81
+ @code_image_magick << acte.act_name_code(true)
82
+ @code_image_magick << acte.act_name_code(false)
83
+ # --- Horloge de l'acte ---
84
+ @code_image_magick << acte.horloge_code(false, nil)
85
+ @code_image_magick << acte.horloge_code(true, nil)
86
+ end
87
+
88
+ # --- Horloges de fin ---
89
+ #
90
+ # @note
91
+ # Peu importe le noeud qui est appelé
92
+ #
93
+ @code_image_magick << pfa.denouement.horloge_fin(false)
94
+ @code_image_magick << pfa.denouement.horloge_fin(true)
95
+
96
+
97
+ # --- IMPRESSION DES AUTRES NOEUDS/SÉQUENCES ---
98
+ #
99
+ # On passe en revue tous les autres éléments du paradigme réel
100
+ # et s'ils sont imprimable, on les ajoute.
101
+ @last_abs_left = nil
102
+ @last_left = nil
103
+ pfa.data.values.each do |node|
104
+ next if node.part? # déjà traité
105
+ next unless node.drawnable? # pas imprimable
106
+ # -- Mark et nom --
107
+ @code_image_magick << node.image_magick_code(false, @last_abs_left)
108
+ @code_image_magick << node.image_magick_code(true, @last_left)
109
+ # -- Les horloges --
110
+ @code_image_magick << node.horloge_code(false, @last_abs_left)
111
+ @code_image_magick << node.horloge_code(true, @last_left)
112
+ #
113
+ # On garde en mémoire les dimensions pour savoir si ça
114
+ # va manger dessus (overlay)
115
+ #
116
+ @last_abs_left = node.abs_left + [ MagickPFA::Horloge_Width[node.type]/2, MagickPFA::Label_Width[node.type]/2 ].max
117
+ @last_left = node.left + [ MagickPFA::Horloge_Width[node.type]/2, MagickPFA::Label_Width[node.type]/2 ].max
118
+ end
119
+
120
+
121
+ #
122
+ # Chemin d'accès au fichier final
123
+ # (en le protégeant)
124
+ #
125
+ @code_image_magick << "\"#{image_path.gsub(/ /, "\\ ")}\""
126
+
127
+ #
128
+ # On transforme le code ImageMagick en ajoutant des retours
129
+ # de chariot entre chaque section de code
130
+ #
131
+ @code_image_magick = @code_image_magick.join("\n")
132
+
133
+ # STDOUT.write "\n\ncmd à la fin =\n++++\n#{cmd}\n+++++\n".orange
134
+
135
+ #
136
+ # Mise de la commande sur une seule ligne
137
+ # (je ne parviens pas à mettre la commande sur plusieurs lignes,
138
+ # même avec la contre-balance…)
139
+ cmd_finale = @code_image_magick.split("\n").compact.join(" ")
140
+
141
+
142
+ # Pour débugger facilement, on met les numéros des lignes
143
+
144
+ cmd_debug = cmd_finale.split("\n").collect.with_index do |line, idx|
145
+ "#{idx + 1}: #{line.strip}"
146
+ end.join("\n")
147
+ # dbg "\n\nCommande finale\n#{cmd_debug}\n".bleu
148
+
149
+ #
150
+ # *** EXÉCUTION DE LA COMMANDE ***
151
+ #
152
+ res_err = `#{cmd_finale} 2>&1`
153
+
154
+ unless res_err.nil? || res_err.empty?
155
+ raise PFAFatalError.new(5001, **{error: res_err})
156
+ end
157
+
158
+ #
159
+ # L'image doit avoir été créée
160
+ #
161
+ File.exist?(image_path) || raise(PFAFatalError.new(5000, **{path: image_path}))
162
+ puts "Image #{image_path.inspect} produite avec succès."
163
+
164
+ end #/ build
165
+
166
+
167
+
168
+ # @return \Array<Node> Liste des actes du paradigme
169
+ #
170
+ def actes
171
+ @actes ||= begin
172
+ pfa.data.values.select do |node|
173
+ node.part?
174
+ end
175
+ end
176
+ end
177
+
178
+
179
+ # @return \String Chemin d'accès au fichier de l'image finale
180
+ def image_path
181
+ @image_path ||= File.expand_path(File.join('.','pfa.jpg'))
182
+ # @image_path ||= File.expand_path(File.join('.','pfa.img'))
183
+ end
184
+
185
+ # @private
186
+
187
+ # Calcule le coefficiant pixels qui permet de convertir un temps
188
+ # quelconque en pixels en fonction de la durée du film analysé.
189
+ # Produira la constant COEF_PIXELS
190
+ #
191
+ def calc_coefficient_pixels
192
+ (MagickPFA::PFA_WIDTH - MagickPFA::PFA_LEFT_MARGIN - MagickPFA::PFA_RIGHT_MARGIN).to_f / pfa.duration.to_i
193
+ end
194
+
195
+
196
+ end #/class ImgBuilder
197
+ end #/class RelativePFA
198
+ end #/module PFA
@@ -0,0 +1,188 @@
1
+ #
2
+ # Class PFA::NTime
3
+ # -------------------
4
+ # Pour la gestion des temps dans le paradigme de Field
5
+ # On ne passera plus par la class Time, trop compliquée
6
+ #
7
+ module PFA
8
+ class NTime
9
+
10
+ # \Integer Nombre de secondes pour ce temps
11
+ attr_reader :secondes
12
+
13
+ # \Integer Nombre de secondes du zéro
14
+ attr_reader :zero_offset
15
+
16
+ # On peut initialiser un NTime avec :
17
+ # - [Integer] Des secondes
18
+ # - [Time] Un temps
19
+ # - [String] Une horloge
20
+ # - [PFA::NTime] Une instance PFA::NTime (comme celle-ci)
21
+ #
22
+ # @param \Integer zero_offset Décalage du temps avec zéro
23
+ #
24
+ def initialize(foo, zero_offset = 0)
25
+ @zero_offset = zero_offset
26
+ case foo
27
+ when Integer then @secondes = foo
28
+ when Float then @secondes = foo.to_i
29
+ when PFA::NTime then @secondes = foo.to_i
30
+ when Time then @secondes = foo.to_i
31
+ when String then @secondes = PFA.h2s(foo)
32
+ else raise PFAFatalError.new(53, {value: "#{foo}", classe:"#{foo.class}"})
33
+ end
34
+ @secondes -= zero_offset
35
+ end
36
+
37
+
38
+ def as_horloge
39
+ @to_horloge ||= PFA.s2h(secondes)
40
+ end
41
+ alias :to_horloge :as_horloge
42
+
43
+ def as_duree
44
+ @as_duree ||= PFA.s2h(secondes, **{as: :duree})
45
+ end
46
+
47
+ # -- Méthodes de décalage --
48
+
49
+ def calc_offset(abs_time)
50
+ offset_secondes = (abs_time.to_i - self.to_i).abs
51
+ @offset = self.class.new(offset_secondes)
52
+ @hasoffset = offset > 30
53
+ end
54
+
55
+ # @return [String] Une horloge qui indique le décalage avec
56
+ # la valeur idéale (seulement pour les noeuds relatifs) si ce
57
+ # décalage existe.
58
+ #
59
+ # @param abs_time [PFA::NTime] Le temps absolu
60
+ #
61
+ def as_horloge_with_offset(abs_time)
62
+ @as_horloge_with_offset ||= begin
63
+ if offset?
64
+ signe = self > abs_time ? '+' : '-'
65
+ "#{self.as_horloge} (#{signe}#{offset.as_duree})"
66
+ else
67
+ self.as_horloge
68
+ end
69
+ end
70
+ end
71
+
72
+ # @return true s'il y a un décalage trop important avec le
73
+ # temps absolu
74
+ # @noter que cette méthode existe aussi pour le temps absolu
75
+ def offset?
76
+ @hasoffset === true
77
+ end
78
+
79
+ def offset_as_horloge(abs_time)
80
+ @offset_as_horloge ||= begin
81
+ if @hasoffset
82
+ signe = self > abs_time ? '+' : '-'
83
+ " (#{signe}#{offset.as_duree})"
84
+ else
85
+ " "
86
+ end
87
+ end
88
+ end
89
+
90
+ def background_per_offset
91
+ @background_per_offset ||= begin
92
+ if offset > 120
93
+ 'gray40' # offset trop grand
94
+ else
95
+ 'transparent'
96
+ end
97
+ end
98
+ end
99
+ def foreground_per_offset
100
+ @foreground_per_offset ||= begin
101
+ if offset < 60
102
+ 'gray60'
103
+ elsif offset < 120
104
+ 'gray20'
105
+ else
106
+ 'white'
107
+ end
108
+ end
109
+ end
110
+
111
+ # [PFA::NTime] Décalage entre le temps absolu et le temps réel
112
+ def offset
113
+ @offset
114
+ end
115
+
116
+ def to_i
117
+ @secondes
118
+ end
119
+
120
+ # Retourne la valeur en pixels pour le temps courant (ou la durée)
121
+ def to_px(pfa)
122
+ (self.to_i * pfa.img_builder.coef_pixels).to_i
123
+ end
124
+
125
+ # [PFA::NTime] Le temps relatif (à l'écran)
126
+ def relative
127
+ @relative ||= NTime.new(secondes + zero_offset, 0)
128
+ end
129
+
130
+ # --- Helpers Methods ---
131
+
132
+ # @retour [BashString] Le code pour écrire l'horloge dans une
133
+ # image avec ImageMagick pour le noeud +node+
134
+ #
135
+ # @param [PFA::RelativePFA::Node] node Le nœud de l'horloge
136
+ # @param [Hash] options
137
+ # Plus tard, pourra redéfinir :bg_color et :color
138
+ #
139
+ def as_img_horloge_code(node, **options)
140
+ for_real = options[:pfa_type] == :real
141
+ <<~CMD.strip
142
+ \\(
143
+ -background #{options[:bg_color]||'transparent'}
144
+ -stroke #{options[:color]||'gray20'}
145
+ -fill #{options[:color]||'gray20'}
146
+ -strokewidth 1
147
+ -pointsize #{MagickPFA::BASE_FONTSIZE * MagickPFA::HORLOGE_FONT_SIZES[node.type]}
148
+ -size #{surface}
149
+ -gravity #{options[:gravity]||'Center'}
150
+ label:"#{for_real ? self.as_horloge_with_offset(options[:abs_time]) : as_horloge}"
151
+ -extent #{surface}
152
+ \\)
153
+ -gravity northwest
154
+ -geometry +#{node.send(for_real ? :left : :abs_left) + 24}+#{node.send(for_real ? :top : :abs_top) + 8}
155
+ -composite
156
+ CMD
157
+ end
158
+
159
+ def surface
160
+ @surface ||= "#{MagickPFA::PFA_WIDTH/10}x50"
161
+ end
162
+
163
+ # --- Operation Methods ---
164
+
165
+ # Addition
166
+ #
167
+ # @param secondes \PFA::NTime ou \Integer
168
+ #
169
+ def +(secs)
170
+ secs = secs.to_i if secs.is_a?(PFA::NTime)
171
+ return PFA::NTime.new(secs + @secondes)
172
+ end
173
+
174
+ def -(secs)
175
+ secs = secs.to_i if secs.is_a?(PFA::NTime)
176
+ return PFA::NTime.new(@secondes - secs)
177
+ end
178
+
179
+ def <(nodetime)
180
+ @secondes < nodetime.to_i
181
+ end
182
+
183
+ def >(nodetime)
184
+ @secondes > nodetime.to_i
185
+ end
186
+
187
+ end #/class NTime
188
+ end #/module PFA
@@ -0,0 +1,30 @@
1
+ #
2
+ # Pour ajouter des méthodes communes à RelativePFA::Node et
3
+ # RelativePFA::DataTime
4
+ #
5
+ module PFAElementModule
6
+
7
+ # @return true si c'est une partie (acte)
8
+ def part?
9
+ return abs_data[:type] == 'part'
10
+ end
11
+
12
+ # @return true si c'est un élément imprimable (dans le tableau
13
+ # décrivant le PFA, à ne pas confondre avec le graphique)
14
+ def printable?
15
+ return abs_data[:printed] == true
16
+ end
17
+
18
+ # @return true si c'est un élément à dessiner dans le graphique
19
+ # des deux PFA (idéal et réel)
20
+ def drawnable?
21
+ return abs_data[:drawn] == true
22
+ end
23
+
24
+ # Sera redéfini pour RelativePFA::Node, mais pas pour
25
+ #
26
+ def abs_data
27
+ {type: 'data_time', printed: false}
28
+ end
29
+
30
+ end #/module PFAElementModule
@@ -0,0 +1,191 @@
1
+ #
2
+ # PFA Relatif, donc celui étudié
3
+ #
4
+ module PFA
5
+ # Pour pouvoir faire :
6
+ # pfa = PFA.new
7
+ #
8
+ def self.new(data = nil)
9
+ RelativePFA.new(data)
10
+ end
11
+
12
+ # # Pour obtenir le PFA courant (au mépris de toute loi de Démeter…)
13
+ # #
14
+ # def self.current
15
+ # @@current
16
+ # end
17
+ # def self.current=(pfa)
18
+ # @@current = pfa
19
+ # end
20
+
21
+ class RelativePFA
22
+
23
+ # [Hash] Les données du Paradigme de Field Augmenté
24
+ attr_reader :data
25
+
26
+ # Instanciation du paradigme
27
+ #
28
+ # @param input_data [Hash|Nil] Données du paradigme fournies à l'instanciation
29
+ #
30
+ def initialize(input_data = nil)
31
+ @data = {}
32
+ #
33
+ # -- Traitement de toutes les données fournies --
34
+ #
35
+ input_data ||= {}
36
+ input_data.each { |k, v| add(k, v) }
37
+ end
38
+
39
+ def method_missing(method_name, *args, &block)
40
+ if data.key?(method_name)
41
+ data[method_name]
42
+ elsif AbsolutePFA.data[:nodes].key?(method_name)
43
+ data[method_name]
44
+ else
45
+ raise NoMethodError.new(method_name)
46
+ end
47
+ end
48
+
49
+ # Ajout d'un nœud dans le PFA
50
+ #
51
+ # On utilise pour le faire : pfa.add(key, value). Si c'est un
52
+ # nœud, la valeur contient {t: '<horloge>', d: "description"}
53
+ # La clé :t peut-être remplacée par :time ou :start_at
54
+ # La clé :d peut-être remplacée par :description
55
+ #
56
+ def add(key, value)
57
+ # puts "-> add(#{key.inspect}, #{value.inspect}::#{value.class})".orange
58
+ key = key.to_sym
59
+ if AbsolutePFA.data[:nodes].key?(key)
60
+ #
61
+ # Ajout d'un nœud
62
+ #
63
+ # @note
64
+ # Un noeud doit toujours être défini par une table contenant
65
+ # :t (ou :time) et :d (ou :description)
66
+ #
67
+ # La donnée doit être valide. On en profite aussi pour bien
68
+ # définir le temps.
69
+ #
70
+ value = value_valid?(value, key)
71
+ data.merge!(key => RelativePFA::Node.new(self, key, value))
72
+
73
+ elsif AbsolutePFA.data[:times].key?(key)
74
+ #
75
+ # Ajout d'une valeur temporelle
76
+ #
77
+ data.merge!(key => DataTime.new(self, key, value))
78
+ else
79
+ raise PFAFatalError.new(100, **{key: ":#{key}"})
80
+ end
81
+ end
82
+
83
+ # Test la pertinence de la définition de la clé +key+ et produit une
84
+ # erreur éclairante en cas de problème.
85
+ #
86
+ def value_valid?(value, key)
87
+ value.is_a?(Hash) || raise(PFAFatalError.new(219, **{key: key, classe: "#{value.class}"}))
88
+ value.merge!(t: value[:time]) if value.key?(:time)
89
+ value.merge!(d: value[:description]) if value.key?(:description)
90
+ value.key?(:t) || raise(PFAFatalError.new(221, **{noeud: key, classe: "#{value.class}"}))
91
+ value[:t] = PFA.time_from(value[:t])
92
+ value.key?(:d) || raise(PFAFatalError.new(222, **{noeud: key}))
93
+ return value
94
+ end
95
+
96
+ # @return true si les données relative du PFA sont valides
97
+ #
98
+ # Donc principalement qu'elles existent et qu'elles soient
99
+ # conformes aux attentes.
100
+ #
101
+ # Les données minimales pour construire le PFA sont :
102
+ # - le zéro
103
+ # - le temps de fin (end_time)
104
+ # - l'incident déclencheur
105
+ # - le pivot 1
106
+ # - le début du développement (developpement_part1)
107
+ # - le pivot 2
108
+ # - le début du dénouement (denouement)
109
+ # - le climax
110
+ #
111
+ def valid?
112
+ zero || raise(PFAFatalError.new(200))
113
+ end_time || raise(PFAFatalError.new(201))
114
+ exposition || raise(PFAFatalError.new(209))
115
+ incident_declencheur || raise(PFAFatalError.new(202))
116
+ pivot1 || raise(PFAFatalError.new(203))
117
+ developpement_part1 || raise(PFAFatalError.new(204))
118
+ developpement_part2 || raise(PFAFatalError.new(208))
119
+ pivot2 || raise(PFAFatalError.new(205))
120
+ denouement || raise(PFAFatalError.new(206))
121
+ climax || raise(PFAFatalError.new(207))
122
+
123
+ # --- Vérification de la validité des temps ---
124
+ #
125
+ incident_declencheur.start_at > zero || raise_time_error('zero','incident_declencheur')
126
+ pivot1.after?(incident_declencheur) || raise_time_error('incident_declencheur','pivot1')
127
+ developpement_part1.after?(pivot1) || raise_time_error('pivot1', 'developpement_part1')
128
+ if cle_de_voute
129
+ cle_de_voute.after?(developpement_part1) || raise_time_error('developpement_part1','cle_de_voute')
130
+ end
131
+ pivot2.after?(pivot1) || raise_time_error('pivot1', 'pivot2')
132
+ if developpement_part2
133
+ if cle_de_voute
134
+ developpement_part2.after?(cle_de_voute) || raise_time_error('cle_de_voute', 'developpement_part2')
135
+ end
136
+ pivot2.after?(developpement_part2) || raise_time_error('developpement_part2', 'pivot2')
137
+ end
138
+ denouement.after?(pivot2) || raise_time_error('pivot2','denouement')
139
+ climax.after?(denouement) || raise_time_error('denouement','climax')
140
+ data[:end_time].time > climax.start_at || raise_time_error('end_time','climax', end_time, climax)
141
+ end
142
+
143
+ def raise_time_error(key_before, key_after)
144
+ h_before = self.send(key_before.to_sym)
145
+ h_after = self.send(key_after.to_sym)
146
+ h_before = PFA.t2h(h_before) if h_before.is_a?(Time)
147
+ h_after = PFA.t2h(h_after) if h_after.is_a?(Time)
148
+ h_before = h_before.horloge if h_before.is_a?(Node)
149
+ h_after = h_after.horloge if h_after.is_a?(Node)
150
+ raise PFAFatalError.new(220, **{
151
+ key_before: key_before, h_before: h_before,
152
+ key_after: key_after, h_after: h_after
153
+ })
154
+ end
155
+
156
+ # --- Volatile Data ---
157
+
158
+ def zero ; data[:zero].time unless data[:zero].nil? end
159
+ def end_time ; data[:end_time].time unless data[:end_time].nil? end
160
+ def duree
161
+ @duree ||= PFA::NTime.new(end_time, zero.to_i)
162
+ end
163
+ alias :duration :duree
164
+
165
+ # Définitions -raccourcis-
166
+ def zero=(value) ; add(:zero, value) end
167
+ def end_time=(value) ; add(:end_time, value) end
168
+
169
+
170
+ # --- Helper Methods ---
171
+
172
+
173
+
174
+
175
+ # --- Output Methods ---
176
+
177
+ def to_html(**params)
178
+ puts "Je dois apprendre à sortir en HTML".orange
179
+ end
180
+
181
+ def to_img(**params)
182
+ params.key?(:as) || params.merge!(as: :real_book)
183
+ img_builder.build(**params)
184
+ end
185
+
186
+ def img_builder
187
+ @img_builder ||= ImgBuilder.new(self)
188
+ end
189
+
190
+ end #/ class RelativePFA
191
+ end #/ module PFA
@@ -0,0 +1,27 @@
1
+ require_relative 'pfa_element_module'
2
+ module PFA
3
+ class RelativePFA
4
+ class DataTime
5
+
6
+ include PFAElementModule
7
+
8
+ attr_reader :pfa, :key
9
+
10
+ def initialize(pfa, key, value = nil)
11
+ @pfa = pfa
12
+ @key = key
13
+ @raw_value = value
14
+ end
15
+
16
+ def time
17
+ @time ||= PFA::NTime.new(@raw_value, 0)
18
+ end
19
+ alias :start_at :time
20
+
21
+ # Pour la compatibilité quand on boucle sur tous les éléments
22
+ # des données du paradigme relatif
23
+ def type; nil end
24
+
25
+ end #/class DataTime
26
+ end #/class RelativePFA
27
+ end #/module PFA