pfa 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,551 @@
1
+ #
2
+ # Module inclus dans la classe RelativePFA::Node
3
+ #
4
+ # @note
5
+ #
6
+ # On le met en module pour avoir toutes les méthodes séparées
7
+ # du module principal.
8
+ #
9
+ module MagickPFA
10
+
11
+
12
+ #
13
+ # Largeur totale du graphique
14
+ # (noter que les dimensions sont les mêmes pour l'image JPG et
15
+ # l'image HTML)
16
+ #
17
+ # @note
18
+ # Les tailles sont calculées pour un livre de
19
+ # 5.25 po (1334 mm) x 8 po (2032 mm)
20
+ # avec des marges de 20 mm
21
+ # Donc une surface utilisable de :
22
+ # 1334 - 2 * 20 = 1334 - 40 = 1294
23
+ # x 2032 - 2 * 20 = 2032 - 40 = 1992
24
+ #
25
+ # Comme l'image sera mise "de travers" dans la page, sa largeur
26
+ # sera de 1992 et sa hauteur 1294. On triple pour obtenir une
27
+ # bonne taille pour l'imprimerie.
28
+ # Donc :
29
+ # 3 x 1992 = 5976 px en largeur (PFA_WIDTH)
30
+ # 3 x 1294 = 3882 px en hauteur (PFA_HEIGHT)
31
+ #
32
+ # Soit un rapport de 5976 / 3882 = 1,5394
33
+ #
34
+ # Pour les tests, on utilise les valeurs
35
+ # 4000 px x 2598 px (4000 / 1,5394)
36
+ #
37
+ # @note
38
+ #
39
+ # En fait, :pfa_height ne définit pas vraiment la hauteur des
40
+ # PFA (et donc de l'image) mais permet de calculer la hauteur
41
+ # de ligne LINE_HEIGHT. L'image, elle, fera 12 * LINE_HEIGHT de
42
+ # hauteur.
43
+ #
44
+ DIMS_CONSTANTS_PER_THING = {
45
+ real_book: {
46
+ pfa_width: 5976, pfa_height: 3882, pfa_left_margin: 0, pfa_right_margin:0,
47
+ font_size: 15
48
+ },
49
+ test: {
50
+ pfa_width: 4000, pfa_height: 2598, pfa_left_margin: 20, pfa_right_margin:20,
51
+ font_size: 15
52
+ },
53
+ default: {
54
+ pfa_width: 4000, pfa_height: 2598, pfa_left_margin: 20, pfa_right_margin:20,
55
+ font_size: 15
56
+ },
57
+ }
58
+
59
+ FONTHEIGHT_MAIN_TITLE = 25
60
+
61
+ def self.rem_const_if_exists(const_name)
62
+ if MagickPFA.const_defined?(const_name)
63
+ # dbg "Je dois détruire la constante #{key.to_s.upcase}".orange
64
+ MagickPFA.send(:remove_const, const_name)
65
+ end
66
+ if MagickPFA.const_defined?(const_name)
67
+ MagickPFA.send(:remove_const, const_name)
68
+ end
69
+ end
70
+ # Définition des dimensions
71
+ #
72
+ # @param params [Symbol|Hash]
73
+ # Soit la clé dans la table DIMS_CONSTANTS_PER_THING
74
+ # Soit une table définissant :pfa_width, :pfa_height,
75
+ # :pfa_left_margin, pfa_right_margin
76
+ #
77
+ def self.define_dims_constants(key_thing)
78
+
79
+ params = nil
80
+ case key_thing
81
+ when Symbol then params = DIMS_CONSTANTS_PER_THING[key_thing]
82
+ when Hash
83
+ #
84
+ # Table fournie
85
+ # (dans ce cas, il faut vérifier les valeurs et mettre les
86
+ # valeurs par défaut des valeurs manquantes)
87
+ #
88
+ params = key_thing
89
+ DIMS_CONSTANTS_PER_THING[:default].each do |key, value|
90
+ params.key?(key) || params.merge!(key => value)
91
+ end
92
+ else raise PFAFatalError.new(101)
93
+ end
94
+
95
+ params.each do |key, value|
96
+ const_name = key.to_s.upcase
97
+ rem_const_if_exists(const_name)
98
+ # Object.const_set(const_name, value)
99
+ # self.class.const_set(const_name, value)
100
+ MagickPFA.const_set(const_name, value)
101
+ end
102
+
103
+ #
104
+ # Définition de la hauteur de ligne
105
+ #
106
+ rem_const_if_exists('LINE_HEIGHT') # tests
107
+ MagickPFA.const_set('LINE_HEIGHT', (MagickPFA::PFA_HEIGHT.to_f / 15).to_i)
108
+ # MagickPFA.const_set('LINE_HEIGHT', (MagickPFA::PFA_HEIGHT.to_f / 10).to_i)
109
+
110
+ rem_const_if_exists('QUART_LINE_HEIGHT') # tests
111
+ MagickPFA.const_set('QUART_LINE_HEIGHT', LINE_HEIGHT / 4)
112
+
113
+ #
114
+ # Différence en hauteur du paradigme réel par rapport au paradigme
115
+ # idéal
116
+ #
117
+ rem_const_if_exists('VOFFSET_REL_PFA') # tests
118
+ MagickPFA.const_set('VOFFSET_REL_PFA', 6 * LINE_HEIGHT)
119
+
120
+ rem_const_if_exists('TOP_MARGIN')
121
+ MagickPFA.const_set('TOP_MARGIN', 4 * FONTHEIGHT_MAIN_TITLE)
122
+
123
+ #
124
+ # Position verticale des éléments en fonction de leur nature
125
+ #
126
+ rem_const_if_exists('ABS_TOPS') # tests
127
+ MagickPFA.const_set('ABS_TOPS', {
128
+ part: TOP_MARGIN,
129
+ sequence: TOP_MARGIN + 3 * LINE_HEIGHT,
130
+ noeud: TOP_MARGIN + 3 * LINE_HEIGHT,
131
+ })
132
+ rem_const_if_exists('ABS_BOTTOMS') # tests
133
+ MagickPFA.const_set('ABS_BOTTOMS', {
134
+ part: ABS_TOPS[:part] + VOFFSET_REL_PFA - LINE_HEIGHT - 100,
135
+ sequence: ABS_TOPS[:sequence] + VOFFSET_REL_PFA,
136
+ noeud: ABS_TOPS[:noeud] + VOFFSET_REL_PFA,
137
+ })
138
+ rem_const_if_exists('TOPS') # tests
139
+ MagickPFA.const_set('TOPS', {
140
+ part: ABS_BOTTOMS[:part] + LINE_HEIGHT,
141
+ sequence: ABS_TOPS[:sequence] + VOFFSET_REL_PFA,
142
+ noeud: ABS_TOPS[:noeud] + VOFFSET_REL_PFA,
143
+ })
144
+ rem_const_if_exists('BOTTOMS') # tests
145
+ MagickPFA.const_set('BOTTOMS', {
146
+ part: ABS_BOTTOMS[:part] + VOFFSET_REL_PFA,
147
+ sequence: ABS_BOTTOMS[:sequence] + VOFFSET_REL_PFA,
148
+ noeud: ABS_BOTTOMS[:noeud] + VOFFSET_REL_PFA,
149
+ })
150
+ #
151
+ # Hauteur en fonction du type des éléments
152
+ #
153
+ rem_const_if_exists('HEIGHTS') # tests
154
+ MagickPFA.const_set('HEIGHTS', {
155
+ part: PFA_HEIGHT / 1.4,
156
+ sequence: 50, # avant : PFA::LINE_HEIGHT (dans fichier relatif)
157
+ noeud: 50 # idem
158
+ })
159
+
160
+ #
161
+ # Taille de fonte de base. Elle correspond à la grosseur
162
+ # des séquences
163
+ #
164
+ # @todo
165
+ # Plus tard, on pourra modifier aussi les proportions de
166
+ # chaque taille de partie
167
+ #
168
+ rem_const_if_exists('BASE_FONTSIZE') # tests
169
+ MagickPFA.const_set('BASE_FONTSIZE', params[:font_size])
170
+
171
+ rem_const_if_exists('FONTSIZES') # tests
172
+ MagickPFA.const_set('FONTSIZES', {
173
+ part: 1.6 * BASE_FONTSIZE,
174
+ sequence: 1.0 * BASE_FONTSIZE,
175
+ noeud: 1.0 * BASE_FONTSIZE,
176
+ })
177
+
178
+ rem_const_if_exists('Label_Width') # tests
179
+ MagickPFA.const_set('Label_Width', {
180
+ part: 400,
181
+ sequence: 400,
182
+ noeud: 400,
183
+ })
184
+ rem_const_if_exists('Label_Height') # tests
185
+ MagickPFA.const_set('Label_Height', {
186
+ part: 200,
187
+ sequence: 200,
188
+ noeud: 200,
189
+ })
190
+
191
+ # Taille de fonte des horloges par partie
192
+ rem_const_if_exists('FontSize_Horloges') # tests
193
+ MagickPFA.const_set('FontSize_Horloges', {
194
+ part: 1.2 * BASE_FONTSIZE,
195
+ sequence: 1.2 * BASE_FONTSIZE,
196
+ noeud: 1.2 * BASE_FONTSIZE,
197
+ })
198
+ rem_const_if_exists('Horloge_Width') # tests
199
+ MagickPFA.const_set('Horloge_Width', {
200
+ part: 300,
201
+ sequence: 300,
202
+ noeud: 300
203
+ })
204
+ # Hauteur de l'horloge (pour glisser l'offset en dessous)
205
+ rem_const_if_exists('Horloge_Height') # tests
206
+ MagickPFA.const_set('Horloge_Height', {
207
+ part: 112,
208
+ sequence: 112,
209
+ noeud: 112
210
+ })
211
+
212
+ # Décalage vertical pour que les horloges des actes et de fin
213
+ # soient bien collées au bord haut
214
+ rem_const_if_exists('VOFFSET_MAIN_HORLOGES') # tests
215
+ MagickPFA.const_set('VOFFSET_MAIN_HORLOGES', Horloge_Height[:part] - 60)
216
+
217
+ end # define_dims_constants
218
+
219
+
220
+ # -- Commande ImageMagick --
221
+
222
+ CONVERT_CMD = 'convert' # /usr/local/bin/convert si pas d'alias
223
+
224
+ # Le début du code de la commande convert
225
+ def self.code_for_intro
226
+ <<~CMD.strip
227
+ #{CONVERT_CMD} -size #{PFA_WIDTH}x#{LINE_HEIGHT * 12} xc:white
228
+ -units PixelsPerInch
229
+ -density 300
230
+ -background transparent
231
+ -set colorspace sRGB
232
+ CMD
233
+ end
234
+
235
+ #
236
+ # Pour écrire le titre du paradigme
237
+ CODE_TITRE_PARADIGME = <<~CMD.strip.freeze
238
+ -pointsize %{lh}
239
+ -font Arial
240
+ -draw "text %{lf},%{tp} '%{titre}'"
241
+ CMD
242
+
243
+ CODE_BOITE_ACTE = <<~CMD.strip.freeze
244
+ -stroke black
245
+ -strokewidth 3
246
+ -background transparent
247
+ -fill transparent
248
+ -draw "rectangle %{lf},%{tp} %{rg},%{bt}"
249
+ CMD
250
+
251
+ CODE_PART_NAME = <<~CMD.strip.freeze
252
+ \\( -extent %{w}x%{h} -stroke black -strokewidth 1 -background transparent
253
+ -pointsize %{fs} -font Arial -gravity Center label:"%{n}" \\)
254
+ -geometry +%{gh}+%{gv} -gravity NorthWest -composite
255
+ CMD
256
+ # {
257
+ # w: width, h: font_height + 20, n: mark, fs: font_size,
258
+ # gh: left, # pour la géométrie, décalage par rapport au Nord-Ouest
259
+ # gv: top + LINE_HEIGHT, # pour la géométrie, décalage vertical
260
+ # }
261
+
262
+ # --- HORLOGES ---
263
+
264
+ # Code ImageMagick de l'horloge pour les parties
265
+ #
266
+ CODE_HORLOGE = <<~CMD.strip.freeze
267
+ \\( -extent %{w}x%{h} -background %{bg} -stroke %{fg} -strokewidth 1 -fill %{fg}
268
+ -pointsize %{fs} -font Georgia -gravity Center label:"%{mk}" \\)
269
+ -geometry +%{l}+%{t} -gravity %{g} -composite
270
+ CMD
271
+ # -gravity NorthWest
272
+
273
+ # Code ImageMagick pour indiquer le décalage entre le temps réel
274
+ # et le temps absolu.
275
+ CODE_OFFSET = <<~CMD.strip.freeze
276
+ \\( -extent %{wo}x%{ho} -pointsize %{fso} -font Georgia -background %{bgo}
277
+ -fill %{fgo} -stroke %{fgo} -strokewidth 1 -gravity Center
278
+ label:"%{mko}" \\) -geometry +%{lo}+%{to} -gravity NorthWest -composite
279
+ CMD
280
+
281
+ # @return [BashString] Le code Image Magick de l'horloge pour tous
282
+ # les éléments
283
+ #
284
+ def horloge_code(real_pfa, current_left)
285
+ start_ntime = real_pfa ? start_at : abs_start
286
+ #
287
+ # Si c'est le noeud réel, il faut calculer son décalage avec le
288
+ # noeud absolu
289
+ #
290
+ start_at.calc_offset(abs_start) if real_pfa
291
+ #
292
+ # Le left dépend de la nature de l'élément
293
+ # @TODO Il serait possible de mettre ça en propriété de l'éménet
294
+ #
295
+ theleft = if part?
296
+ (real_pfa ? left : abs_left) + 2
297
+ else
298
+ (real_pfa ? left : abs_left) - Horloge_Width[type] / 2
299
+ end
300
+
301
+ # Problème d'overlay
302
+ if current_left && theleft < current_left
303
+ theleft = current_left + 10
304
+ end
305
+
306
+ #
307
+ # Le top dépend de la nature de l'élément
308
+ #
309
+ thetop = if part?
310
+ (real_pfa ? top : abs_top) + VOFFSET_MAIN_HORLOGES
311
+ else
312
+ (real_pfa ? top : abs_top) + 50
313
+ end
314
+
315
+ background = part? ? 'black' : 'gray90'
316
+ foreground = part? ? 'gray90' : 'black'
317
+
318
+ #
319
+ # Les données template en fonction du pfa absolu ou réel
320
+ #
321
+ data_template = {
322
+ w: Horloge_Width[type], # largeur (sauf pour actes)
323
+ h: Horloge_Height[type], # hauteur de l'horloge (surtout pour décalage)
324
+ l: theleft, # left de l'horloge
325
+ t: thetop, # top de l'horloge (et du décalage)
326
+ fs: FontSize_Horloges[type], # Font size
327
+ bg: background,
328
+ fg: foreground,
329
+ mk: start_ntime.to_horloge, # Horloge
330
+ g: 'NorthWest',
331
+ }
332
+ #
333
+ # On ajoute les valeurs pour le pfa réel
334
+ #
335
+ data_template.merge!({
336
+ wo: Horloge_Width[type],
337
+ ho: Horloge_Height[type],
338
+ lo: theleft, # left de la marque de décalage
339
+ to: thetop + Horloge_Height[type], # top de l'offset (sous l'horloge)
340
+ fso: FontSize_Horloges[type] - 4, # Font size pour le décalage
341
+ mko: start_ntime.offset_as_horloge(abs_start), # marque du décalage
342
+ # @note
343
+ # Il faut impérativement appeler offset_as_horloge avant
344
+ # le reste ci-dessous,
345
+ bgo: start_ntime.background_per_offset, # background du décalage
346
+ fgo: start_ntime.foreground_per_offset, # couleur du décalage
347
+ }) if real_pfa
348
+
349
+ code = CODE_HORLOGE
350
+ code += ' ' + CODE_OFFSET if real_pfa
351
+ code % data_template
352
+ end
353
+
354
+ def horloge_fin(real_pfa)
355
+ CODE_HORLOGE % {
356
+ w: Horloge_Width[type],
357
+ h: Horloge_Height[type],
358
+ l: 0,
359
+ t: (real_pfa ? top : abs_top) + VOFFSET_MAIN_HORLOGES,
360
+ fs: FontSize_Horloges[type], # Font size
361
+ mk: pfa.duration.as_horloge,
362
+ bg: 'black',
363
+ fg: 'white',
364
+ g: 'NorthEast'
365
+ }
366
+ end
367
+
368
+ # [BashCode] Code ImageMagick pour le titre des paradigmes
369
+ def titre_pfa(for_real)
370
+ CODE_TITRE_PARADIGME % {
371
+ lf: 20,
372
+ lh: 25, # line-height (aka font-size)
373
+ tp: (for_real ? top : abs_top),
374
+ titre: for_real ? "PARADIGME RÉEL DU FILM" : "PARADIGME IDÉAL DU FILM"
375
+ }
376
+ end
377
+
378
+ # [BashString] Code pour dessiner le noeud, quand c'est un acte,
379
+ # dans l'image.
380
+ def act_box_code(real_pfa)
381
+ CODE_BOITE_ACTE % {
382
+ lf: real_pfa ? left : abs_left,
383
+ tp: (real_pfa ? top : abs_top) + 50, # +50 pour ne pas coller au main title
384
+ rg: real_pfa ? right : abs_right,
385
+ bt: real_pfa ? bottom : abs_bottom
386
+ }
387
+ end
388
+
389
+ # [BashString] Code pour dessiner les noms des parties (actes)
390
+ def act_name_code(for_pfa)
391
+ CODE_PART_NAME % {
392
+ w: for_pfa ? width : abs_width, h: FONTSIZES[:part] + 100, n: mark, fs: FONTSIZES[:part],
393
+ gh: for_pfa ? left : abs_left, # pour la géométrie, décalage par rapport au Nord-Ouest
394
+ gv: (for_pfa ? top : abs_top) + LINE_HEIGHT, # pour la géométrie, décalage vertical
395
+ }
396
+ end
397
+
398
+
399
+ # @return [BashString] Le code pour dessiner l'élément
400
+ #
401
+ # @param real_pfa [Bool]
402
+ # Pour savoir si on doit retourner le code pour le pfa
403
+ # réel ou pour le pfa absolu.
404
+ #
405
+ # @param current_left [Integer|Float]
406
+ # Le left sur lequel on se trouve, pour savoir si ça déborde
407
+ #
408
+ def image_magick_code(real_pfa, current_left)
409
+ code = if type == :sequence
410
+ CODE_IMAGEMAGICK_SEQUENCE
411
+ else
412
+ CODE_IMAGEMAGICK_NOEUD
413
+ end
414
+ # Données template (en fonction du paradigem)
415
+ # dbg "Node : #{id}".orange
416
+ # dbg "Node type : #{type.inspect}".orange
417
+ data_temp = {
418
+ l: real_pfa ? left : abs_left, # alignement gauche
419
+ t: real_pfa ? top : abs_top, # top
420
+ w: Label_Width[type], # largeur pour la marque (pour le moment, arbitraire)
421
+ h: Label_Height[type], # hauteur pour la marque (sur deux lignes)
422
+ fs: FONTSIZES[type],
423
+ a: 'Center', # modifié par l'overlay
424
+ m: mark.gsub(/ /,"\\n"),
425
+ }
426
+
427
+ data_temp.merge!({
428
+ # Noter que ci-dessous on n'utilise pas le @width de l'élément
429
+ # pour avoir des ronds uniformes
430
+ r: data_temp[:l] + 20, # right (pour la boite du nom)
431
+ b: data_temp[:t] + 20,
432
+ lm: data_temp[:l] - data_temp[:w] / 2, # le left de la marque du nom
433
+ tm: data_temp[:t] - 250, # le top de la marque
434
+ lh: data_temp[:l] - data_temp[:w] / 2, # le left de l'horloge
435
+ th: data_temp[:t] + 150, # le top de l'horloge
436
+ })
437
+
438
+ if current_left && data_temp[:lm] < current_left
439
+ # dbg "PROBLÈME D'OVERLAY avec #{id} (:l)".rouge
440
+ data_temp[:lm] = current_left
441
+ data_temp[:a] = 'West'
442
+ end
443
+
444
+ code % data_temp
445
+ end
446
+
447
+ # @return [BashString] Code Image Magick pour un noeud du Paradigme
448
+ # de Field Augmenté
449
+ #
450
+ CODE_IMAGEMAGICK_NOEUD = <<~CMD.strip.freeze
451
+ -stroke black
452
+ -fill gray40
453
+ -draw "circle %{l},%{t} %{r},%{b}"
454
+ \\( -extent %{w}x%{h} -pointsize %{fs} -stroke black -fill black -background transparent -gravity %{a}
455
+ label:"%{m}" \\) -geometry +%{lm}+%{tm} -gravity NorthWest -composite
456
+ CMD
457
+
458
+ # TODO : À FAIRE
459
+ CODE_IMAGEMAGICK_SEQUENCE = <<~CMD.strip.freeze
460
+ -stroke black
461
+ -fill black
462
+ -draw "circle %{l},%{t} %{r},%{b}"
463
+ CMD
464
+
465
+
466
+
467
+ # # La marque pour une séquence (un crochet allongé)
468
+ # #
469
+ # def img_lines_for_real_sequence
470
+ # <<~CMD
471
+ # -strokewidth #{AnyBuilder::BORDERS[:seq]}
472
+ # -stroke #{AnyBuilder::COLORS[:seq]}
473
+ # -fill white
474
+ # -draw "polyline #{left+4},#{top+demiheight} #{left+4},#{bottom} #{right-4},#{bottom} #{right-4},#{top+demiheight}"
475
+ # #{mark_horloge}
476
+ # CMD
477
+ # end
478
+
479
+ # # La marque pour un nœud (un rond/point)
480
+ # def img_lines_for_real_noeud
481
+ # <<~CMD
482
+ # -strokewidth #{AnyBuilder::BORDERS[:noeud]}
483
+ # -stroke #{AnyBuilder::COLORS[:noeud]}
484
+ # -fill white
485
+ # -draw "roundrectangle #{left},#{top} #{right},#{bottom} 10,10"
486
+ # #{mark_horloge}
487
+ # CMD
488
+ # end
489
+
490
+ RECTIFS = {
491
+ part: 50,
492
+ sequence: 0,
493
+ noeud: 0
494
+ }
495
+
496
+ #
497
+ # Graisse de la police en fonction du type de l'élément
498
+ #
499
+ ABS_FONTWEIGHTS = {
500
+ part: 3,
501
+ sequence: 2,
502
+ noeud: 1
503
+ }
504
+ FONTWEIGHTS = {
505
+ part: 1,
506
+ sequence: 1,
507
+ noeud: 1
508
+ }
509
+
510
+ #
511
+ # Couleur en fonction du type de l'élément
512
+ #
513
+ COLORS = {
514
+ part: 'gray75',
515
+ sequence: 'gray55',
516
+ noeud: 'gray55'
517
+ }
518
+
519
+ #
520
+ # Couleur plus sombre en fonction de l'élément
521
+ #
522
+ DARKERS = {
523
+ part: 'gray50',
524
+ sequence: 'gray45',
525
+ noeud: 'gray45'
526
+ }
527
+
528
+ #
529
+ # Gravité en fonction du type de l'élément
530
+ #
531
+ GRAVITIES = {
532
+ part: 'Center',
533
+ sequence: 'Center',
534
+ noeud: 'Center'
535
+ }
536
+
537
+ #
538
+ # Largeur des bords en fonction du type de l'élément
539
+ #
540
+ ABS_BORDERS = {
541
+ part: 3,
542
+ sequence: 2,
543
+ noeud: 1
544
+ }
545
+ BORDERS = {
546
+ part: 1,
547
+ sequence: 1,
548
+ noeud: 1
549
+ }
550
+
551
+ end