pfa 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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