mcc2 1.5.0

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.
Files changed (120) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data/.autotest +23 -0
  4. data/.gitignore +15 -0
  5. data/.rubocop.yml +3 -0
  6. data/Gemfile +13 -0
  7. data/History.txt +6 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +51 -0
  10. data/Rakefile +12 -0
  11. data/config/i18n-tasks.yml +193 -0
  12. data/exe/mcc2 +18 -0
  13. data/exe/mcc2-generate-preview +17 -0
  14. data/exe/mcc2print +6 -0
  15. data/lib/libguib16.rb +1620 -0
  16. data/lib/mcc2/advanced_data_edit_dlg.rb +139 -0
  17. data/lib/mcc2/cc_dialogbox.rb +52 -0
  18. data/lib/mcc2/constants.rb +154 -0
  19. data/lib/mcc2/dataman/base.rb +61 -0
  20. data/lib/mcc2/dataman/csv.rb +49 -0
  21. data/lib/mcc2/dataman/database.rb +8 -0
  22. data/lib/mcc2/dataman/dbf.rb +45 -0
  23. data/lib/mcc2/dataman/none.rb +25 -0
  24. data/lib/mcc2/dataview.rb +348 -0
  25. data/lib/mcc2/dataview_dlg.rb +232 -0
  26. data/lib/mcc2/debug_tools.rb +20 -0
  27. data/lib/mcc2/fx_colored_item.rb +101 -0
  28. data/lib/mcc2/generate_preview.rb +68 -0
  29. data/lib/mcc2/gui/advanced_data_edit_dlg.rbin +0 -0
  30. data/lib/mcc2/gui/dataview_dlg.pp +522 -0
  31. data/lib/mcc2/gui/layoutview_dlg.pp +5115 -0
  32. data/lib/mcc2/gui/mcc2_mainwin.pp +838 -0
  33. data/lib/mcc2/gui/picture_data_edit_dlg.rbin +0 -0
  34. data/lib/mcc2/gui/printerview_dlg.pp +3311 -0
  35. data/lib/mcc2/images/Image-Arrow-Left.gif +0 -0
  36. data/lib/mcc2/images/Image-Arrow-Right.gif +0 -0
  37. data/lib/mcc2/images/Image-FastArrowLeft.gif +0 -0
  38. data/lib/mcc2/images/Image-FastArrowRight.bmp +0 -0
  39. data/lib/mcc2/images/Image-FastArrowRight.gif +0 -0
  40. data/lib/mcc2/images/application-exit.png +0 -0
  41. data/lib/mcc2/images/copy.png +0 -0
  42. data/lib/mcc2/images/country_de.png +0 -0
  43. data/lib/mcc2/images/country_gb.png +0 -0
  44. data/lib/mcc2/images/country_rs.png +0 -0
  45. data/lib/mcc2/images/cut.png +0 -0
  46. data/lib/mcc2/images/distribute-horizontal-baseline.png +0 -0
  47. data/lib/mcc2/images/distribute-vertical-baseline.png +0 -0
  48. data/lib/mcc2/images/document-close.png +0 -0
  49. data/lib/mcc2/images/document-edit.png +0 -0
  50. data/lib/mcc2/images/document-new.png +0 -0
  51. data/lib/mcc2/images/document-open.png +0 -0
  52. data/lib/mcc2/images/document-print-frame.png +0 -0
  53. data/lib/mcc2/images/document-print.png +0 -0
  54. data/lib/mcc2/images/document-save-as.png +0 -0
  55. data/lib/mcc2/images/document-save.png +0 -0
  56. data/lib/mcc2/images/documentation.png +0 -0
  57. data/lib/mcc2/images/draw-rectangle.png +0 -0
  58. data/lib/mcc2/images/dummy.gif +0 -0
  59. data/lib/mcc2/images/exchange-positions.png +0 -0
  60. data/lib/mcc2/images/insert-image.png +0 -0
  61. data/lib/mcc2/images/insert-text.png +0 -0
  62. data/lib/mcc2/images/libreoffice-oasis-master-document.png +0 -0
  63. data/lib/mcc2/images/measure.png +0 -0
  64. data/lib/mcc2/images/messagebox_warning.png +0 -0
  65. data/lib/mcc2/images/paste.png +0 -0
  66. data/lib/mcc2/images/process-stop.png +0 -0
  67. data/lib/mcc2/images/zoom-in.png +0 -0
  68. data/lib/mcc2/layout.rb +323 -0
  69. data/lib/mcc2/layout_file.rb +794 -0
  70. data/lib/mcc2/layout_object/barcode.rb +33 -0
  71. data/lib/mcc2/layout_object/box.rb +18 -0
  72. data/lib/mcc2/layout_object/chip.rb +54 -0
  73. data/lib/mcc2/layout_object/embed.rb +16 -0
  74. data/lib/mcc2/layout_object/go_attribut.rb +16 -0
  75. data/lib/mcc2/layout_object/graphic.rb +59 -0
  76. data/lib/mcc2/layout_object/magstripe.rb +13 -0
  77. data/lib/mcc2/layout_object/picture.rb +22 -0
  78. data/lib/mcc2/layout_object/text.rb +29 -0
  79. data/lib/mcc2/layout_win.rb +2052 -0
  80. data/lib/mcc2/layoutview.rb +845 -0
  81. data/lib/mcc2/layoutview_dlg.rb +2058 -0
  82. data/lib/mcc2/locales.rb +27 -0
  83. data/lib/mcc2/mcc2_mainwin.rb +453 -0
  84. data/lib/mcc2/mcc2gui.rb +295 -0
  85. data/lib/mcc2/mcc2print.rb +54 -0
  86. data/lib/mcc2/picture_data_edit_dlg.rb +139 -0
  87. data/lib/mcc2/print_dlg.rb +520 -0
  88. data/lib/mcc2/printer/base.rb +148 -0
  89. data/lib/mcc2/printer/card_printer.rb +45 -0
  90. data/lib/mcc2/printer/file_printer.rb +135 -0
  91. data/lib/mcc2/printer/gdi_card_printer.rb +14 -0
  92. data/lib/mcc2/printer/gdi_printer.rb +197 -0
  93. data/lib/mcc2/printer/linux_cups_printer.rb +92 -0
  94. data/lib/mcc2/printer/preview.rb +277 -0
  95. data/lib/mcc2/printer/printer_cab.rb +118 -0
  96. data/lib/mcc2/printer/printer_cx320_direct.rb +286 -0
  97. data/lib/mcc2/printer/printer_epl.rb +118 -0
  98. data/lib/mcc2/printer/printer_ttp34x.rb +124 -0
  99. data/lib/mcc2/printer/printer_zebra.rb +324 -0
  100. data/lib/mcc2/printer/svg_drawer.rb +689 -0
  101. data/lib/mcc2/printerview.rb +604 -0
  102. data/lib/mcc2/printerview_dlg.rb +1248 -0
  103. data/lib/mcc2/version.rb +3 -0
  104. data/lib/mcc2.rb +11 -0
  105. data/locales/de.yml +6 -0
  106. data/locales/en.yml +502 -0
  107. data/locales/rs.yml +502 -0
  108. data/mcc2.gemspec +41 -0
  109. data/test/dummy.rb +19 -0
  110. data/test/layouts/bart_simpson.png +0 -0
  111. data/test/layouts/color_test.xml +151 -0
  112. data/test/layouts/ms.xml +67 -0
  113. data/test/ruby_print.rb +380 -0
  114. data/test/test.csv +4 -0
  115. data/test/test.dbf +0 -0
  116. data/test/test.str +4 -0
  117. data/test/test_mcc2.rb +8 -0
  118. data.tar.gz.sig +6 -0
  119. metadata +421 -0
  120. metadata.gz.sig +0 -0
@@ -0,0 +1,794 @@
1
+ require "nokogiri" # für die Layout-Datei
2
+ require "mcc2/constants"
3
+ require "mcc2/layout_object/go_attribut"
4
+ require "mcc2/layout_object/graphic" # die Basisklasse für die grafischen Objekte
5
+ require "mcc2/layout_object/barcode" # grafische Barcode-Objekte
6
+ require "mcc2/layout_object/picture" # grafische Bild-Objekte
7
+ require "mcc2/layout_object/text" # grafische Text-Objekte
8
+ require "mcc2/layout_object/box"
9
+ require "mcc2/layout_object/magstripe"
10
+ require "mcc2/layout_object/chip"
11
+ require "mcc2/layout_object/embed"
12
+
13
+ module Mcc2
14
+ module Printer
15
+ autoload :GdiPrinter, "mcc2/printer/gdi_printer"
16
+ autoload :FilePrinter, "mcc2/printer/file_printer"
17
+ autoload :LinuxCupsPrinter, "mcc2/printer/linux_cups_printer"
18
+ autoload :PrinterZebra, "mcc2/printer/printer_zebra"
19
+ autoload :PrinterCab, "mcc2/printer/printer_cab"
20
+ autoload :PrinterEpl, "mcc2/printer/printer_epl"
21
+ autoload :PrinterCx320Direct, "mcc2/printer/printer_cx320_direct"
22
+ autoload :PrinterCx320Gdi, "mcc2/printer/printer_cx320_gdi"
23
+ autoload :PrinterTTP34x, "mcc2/printer/printer_ttp34x"
24
+ end
25
+
26
+ module DataMan
27
+ autoload :Database, "mcc2/dataman/database"
28
+ autoload :CSV, "mcc2/dataman/csv"
29
+ autoload :DBF, "mcc2/dataman/dbf"
30
+ autoload :None, "mcc2/dataman/none"
31
+ end
32
+
33
+ class LayoutFile
34
+ class Error < RuntimeError
35
+ end
36
+
37
+ class WrongDataSourceEncoding < Error
38
+ end
39
+
40
+ class WrongDataSourceType < Error
41
+ end
42
+
43
+ class InvalidFileVersion < Error
44
+ end
45
+
46
+ FILE_VERSION_RANGE = 1..4
47
+
48
+ attr_accessor :attributes
49
+ attr_accessor :filename
50
+ attr_reader :warnings
51
+ attr_reader :xpath_changes
52
+
53
+ def self.load_from_xml(*args)
54
+ o = new
55
+ o.load_from_xml(*args)
56
+ o
57
+ end
58
+ #---------------------------------------------------------------------------------------------------------
59
+
60
+ def self.create_xml(*args)
61
+ o = new
62
+ o.create_xml(*args)
63
+ o
64
+ end
65
+ #---------------------------------------------------------------------------------------------------------
66
+
67
+ def initialize
68
+ @changed = true
69
+ @xml_doc = nil
70
+ @attributes = {}
71
+ add_attr(S_DUPLEX, String, false)
72
+ add_attr(S_FRONT_FLIP, String, false)
73
+ add_attr(S_FRONT_FLOP, String, false)
74
+ add_attr(S_BACK_FLIP, String, true)
75
+ add_attr(S_BACK_FLOP, String, true)
76
+ add_attr(S_WIDTH, Integer, LAYOUT_DEFAULT_WIDTH)
77
+ add_attr(S_HEIGHT, Integer, LAYOUT_DEFAULT_HEIGHT)
78
+ add_attr(S_X_OFFSET, Integer, LAYOUT_DEFAULT_X_OFFSET)
79
+ add_attr(S_Y_OFFSET, Integer, LAYOUT_DEFAULT_Y_OFFSET)
80
+ add_attr(S_BARCODE_DPI, Float, 203.2)
81
+ add_attr(S_DATASOURCE, String, "")
82
+ add_attr(S_DATASOURCETYP, String, NONE)
83
+ add_attr(S_DATASOURCEENCODING, String, ENC_UTF8)
84
+ add_attr(S_CONTACTLESS, String, false)
85
+ add_attr(S_CONTACT, String, false)
86
+ add_attr(S_MAGSTRIPE, String, false)
87
+ add_attr(S_PRINTERTYPE, String, "FilePrinter")
88
+ add_attr(S_FILEPRINTER_SAVE_AS, String, "./mcc2_%d_%p.jpeg")
89
+ add_attr(S_FILEPRINTER_DPI, Integer, 300)
90
+ add_attr(S_LINUX_CUPS_PRINTER, String, "")
91
+ add_attr(S_LINUX_CUPS_OPTIONS, String, '{"print-scaling":"none"}')
92
+ add_attr(S_LINUX_CUPS_DPI, Integer, 200)
93
+ add_attr(S_GDIPRINTER_NAME, String, "")
94
+ add_attr(S_GDIPRINTER_INITDATA, String, "")
95
+ add_attr(S_ZEBRAPRINTER_NAME, String, "")
96
+ add_attr(S_ZEBRAPRINTER_INITDATA, String, "")
97
+ add_attr(S_CABPRINTER_HOST, String, "localhost")
98
+ add_attr(S_CABPRINTER_PORT, Integer, 9100)
99
+ add_attr(S_CABPRINTER_DPI, Integer, 300)
100
+ add_attr(S_CABPRINTER_PAGE_COMMANDS, String,
101
+ 'ePNG;*
102
+ dPNG;pngimg
103
+ \e.%IMAGE_DATA_PNG%\e.
104
+ mm
105
+ zO
106
+ J
107
+ H125,+0,T,R0
108
+ P0.00
109
+ O R
110
+ Sl1;0.0,0.00,%PAGE_HEIGHT%,%PAGE_HEIGHT%,%PAGE_WIDTH%
111
+ I:Field1;%PAGE_OFFSET_X%,%PAGE_OFFSET_Y%,0;pngimg
112
+ A1
113
+ ')
114
+ add_attr(S_EPLPRINTER_DEVICE, String, "/dev/usb/lp0")
115
+ add_attr(S_EPLPRINTER_DPI, Integer, 203)
116
+ add_attr(S_EPLPRINTER_PAGE_COMMANDS, String,
117
+ '
118
+ OD
119
+ N
120
+ D8
121
+ S2
122
+ q%PAGE_WIDTH_PX%
123
+ GK"*"
124
+ GM"image1"%IMAGE_LEN_DATA_PCX%
125
+ O
126
+ JF
127
+ WN
128
+ ZB
129
+ GG%PAGE_OFFSET_X_PX%,%PAGE_OFFSET_Y_PX%,"image1"
130
+ P1
131
+ ')
132
+
133
+ add_attr(S_TTP34XPRINTER_DEVICE, String, "/dev/usb/lp0")
134
+ add_attr(S_TTP34XPRINTER_DPI, Integer, 300)
135
+ add_attr(S_TTP34XPRINTER_PAGE_COMMANDS, String,
136
+ 'SPEED 8 REM Wertebereich: 1-8
137
+ DENSITY 12 REM Wertebereich: 0-15
138
+ SIZE %PAGE_WIDTH_MM% mm,%PAGE_HEIGHT_MM% mm
139
+ CLS
140
+ DOWNLOAD "label.pcx",%IMAGE_LEN_DATA_PCX%
141
+ PUTPCX %PAGE_OFFSET_X_PX%,%PAGE_OFFSET_Y_PX%,"label.pcx"
142
+ PRINT 1
143
+ ')
144
+
145
+ add_attr(S_CX320_DRIVER, String, "CX320_DLL")
146
+ add_attr(S_CX320_PRINTER_NR, Integer, 0)
147
+ add_attr(S_CX320_PARALLEL_PRINT, String, "second_card")
148
+ add_attr(S_CX320_CONFIG, String, "{}")
149
+
150
+ @warnings = []
151
+ end
152
+
153
+ private def apply_xpath_changes(xpath_changes)
154
+ xpath_changes.each do |xpath, value|
155
+ if !(m = /^\{(?<filematch>.*)\}(?<xpath>.*)$/.match(xpath)) || (filename && File.fnmatch?(m[:filematch], File.basename(filename)))
156
+ change_first_xpath_value(m&.[](:xpath) || xpath, value)
157
+ end
158
+ end
159
+ @xpath_changes = xpath_changes
160
+ end
161
+
162
+ # erzeugt eine neue XML-Datei und initialisiert die entsprechenden Variablen und Objekte
163
+ def create_xml(filename, xpath_changes = {})
164
+ @filename = filename
165
+ @xml_doc = Nokogiri::XML::Document.new
166
+ @xml_doc.encoding = "UTF-8"
167
+ @xml_doc.root = Nokogiri::XML::Node.new(S_ROOT, @xml_doc)
168
+
169
+ # Layout definieren
170
+ @xml_doc.root << node = Nokogiri::XML::Node.new(S_LAYOUT, @xml_doc)
171
+ attributes.each_pair do |attr_name, attr|
172
+ n = Nokogiri::XML::Node.new(attr.attr_name, @xml_doc)
173
+ n.content = attr.attr_value.to_s
174
+ node << n
175
+ end
176
+
177
+ # Chip definieren
178
+ @xml_doc.root << node = Nokogiri::XML::Node.new(S_CHIP, @xml_doc)
179
+ (1..CHIPPARAM_FIELD_COUNT).each do |i|
180
+ node << Nokogiri::XML::Node.new("#{S_PARAM}_#{i}", @xml_doc)
181
+ end
182
+
183
+ # Magstripe definieren
184
+ ensure_magsripe_attrs
185
+
186
+ # Container für die GraphikObjekte definieren definieren
187
+ @xml_doc.root << node = Nokogiri::XML::Node.new(GRAPHICOBJECTS, @xml_doc)
188
+
189
+ apply_xpath_changes(xpath_changes)
190
+
191
+ changed!
192
+ end
193
+
194
+ # lädt das XML-File und initialisiert die entsprechenden Variablen und Objekte
195
+ # ==== Parameter
196
+ # +filename+:: der NAme des XML-Files in welchem die Layout-Spezifikation gespeichert werden soll
197
+ def load_from_xml(filename, xpath_changes = {})
198
+ return nil if filename == ""
199
+
200
+ @filename = filename
201
+ @changed = false
202
+ @xml_doc = File.open(filename) { |fd| Nokogiri::XML(fd) }
203
+
204
+ apply_xpath_changes(xpath_changes)
205
+
206
+ @xml_doc.root["version"] ||= "1"
207
+ filever = @xml_doc.root["version"].to_i
208
+ unless FILE_VERSION_RANGE.include?(filever)
209
+ raise InvalidFileVersion, I18n.t(:open_layout_version_error, default: "Die Datei %{filename} ist nach Version %{filever} aufgebaut, dieses Programm kann aber nur die Versionen %{supported} verarbeiten", filename: filename.inspect, filever: , supported: FILE_VERSION_RANGE.inspect)
210
+ end
211
+
212
+ if filever == 1 && have_barcode_objects?
213
+ @warnings << I18n.t(:open_layout_old_barby, default: <<~EOT)
214
+ Diese Datei ist noch mit Barby-, statt den neueren Zint-Barcodes aufgebaut.
215
+ Daher müssen alle Barcodes geprüft und ggf. neu justiert werden!
216
+ Diese Warnung entfällt, sobald diese Datei einmal gespeichert wird.
217
+ EOT
218
+ end
219
+
220
+ # ................................................................................................................................
221
+ # die allgemeine Layout-Einstellungen auslesen
222
+
223
+ # kontrollieren, ob alle Nodes des Layouts existieren, ansonsten anlegen und als 'changed' markieren
224
+ lay_attrs = @xml_doc.at_xpath("//#{S_ROOT}/#{S_LAYOUT}")
225
+ @attributes.each_pair do |attr_bez, attr|
226
+ element = lay_attrs.at_xpath(attr.attr_name)
227
+ unless element
228
+ n = Nokogiri::XML::Node.new(attr.attr_name, @xml_doc)
229
+ n.content = attr.attr_value.to_s
230
+ lay_attrs << n
231
+ changed!
232
+ end
233
+ end
234
+
235
+ # kontrollieren, ob die Datenquelle eine gültige Angabe ist, wenn nicht, dann den Benutzer informieren und auf Standard setzen
236
+ check_valid_datasourcetyp
237
+
238
+ # wenn bei Datensourectyp nicht 'none' eingetrage ist, also eine Datenquelle verwendet wird
239
+ # kontrollieren, ob das eingestellte Encoding eine gültige Angabe ist, wenn nicht, dann den Benutzer informieren und auf Standard setzen
240
+ unless get_attr_value(S_DATASOURCETYP) == NONE
241
+ check_valid_encoding
242
+ end
243
+
244
+ # ................................................................................................................................
245
+ # die Chip-Einstellungen auslesen
246
+
247
+ # kontrollieren, ob alle Nodes des Chips existieren, ansonsten als 'changed' markieren, da sie angelegt werden
248
+ chip_attrs = @xml_doc.at_xpath("//#{S_ROOT}/#{S_CHIP}")
249
+ (1..CHIPPARAM_FIELD_COUNT).each do |i|
250
+ attr_name = "#{S_PARAM}_#{i}"
251
+ element = chip_attrs.at_xpath(attr_name)
252
+ unless element
253
+ changed!
254
+ end
255
+
256
+ # Older files store chip params as array.
257
+ # Change them to hash form with the index as the key.
258
+ if filever < 3
259
+ param = element.content
260
+ if i == 8
261
+ if !param.to_s.empty?
262
+ if param =~ /^RUBY;(.*)/
263
+ param = "RubyJobFile=#{$1}"
264
+ set_attr_value(attr_name, param, S_CHIP)
265
+ else
266
+ raise InvalidScriptType, "invalid perso script: #{param.inspect}"
267
+ end
268
+ end
269
+ elsif i == 23
270
+ if !param.to_s.empty?
271
+ param = "OpenReader=#{param}"
272
+ set_attr_value(attr_name, param, S_CHIP)
273
+ end
274
+ elsif !param.to_s.empty?
275
+ param = "#{i}=#{param}"
276
+ set_attr_value(attr_name, param, S_CHIP)
277
+ end
278
+ end
279
+ end
280
+
281
+ # Older files don't respect the bounding box of text objects
282
+ # and object's Y means the text baseline, not the bounding box.
283
+ if filever < 4
284
+ @warnings << I18n.t(:open_layout_old_text_box, default: <<~EOT)
285
+ Diese Datei ist noch mit Textobjekten ohne Beachtung der Rahmengröße gespeichert.
286
+ Die Rahmengröße der Textobjekte wurde nun automatisch auf den des Beispieltextes gesetzt.
287
+ Evtl. muss aber eine Anpassung für die variablen Daten erfolgen.
288
+
289
+ Bitte prüfen Sie, dass alle Texte an der richtigen Position stehen.
290
+ EOT
291
+
292
+ converter = Class.new(Printer::Base) do
293
+ include SvgDrawer
294
+
295
+ def pango_font_of(go)
296
+ font = go[GOA_FONT_TYP]
297
+ if font == "from_file"
298
+ font = go.layout_file.expand_path(go[GOA_FONT_FILE])
299
+ end
300
+ fontsize = go[GOA_FONT_HEIGHT].to_f
301
+ begin
302
+ font_info = get_font_info(font, go)
303
+ rescue Printer::Base::ResourceNotFoundError
304
+ # ignore font error in the migration because
305
+ # verification is requested from the user anyway
306
+ _key, font_info = font_map.first
307
+ end
308
+ get_pango_font(font_info, pt(fontsize))
309
+ end
310
+
311
+ def get_text_sizes(go)
312
+ data = go[GOA_SAMPLE_DATA].to_s
313
+ if !data.empty?
314
+ pango_font = pango_font_of(go)
315
+ context = pango_font_map.create_context
316
+ layout = Pango::Layout.new(context)
317
+ layout.font_description = pango_font
318
+ layout.text = data # set the text to render
319
+
320
+ layout.pixel_size.map{|px| px_to_mm(px) }
321
+ end
322
+ end
323
+
324
+ def get_new_coods(go)
325
+ x = go[GOA_X].to_f
326
+ y = go[GOA_Y].to_f
327
+ rotation = go[GOA_ROTATION].to_f
328
+
329
+ context = pango_font_map.create_context
330
+ pango_font = pango_font_of(go)
331
+ metrics = context.get_metrics(pango_font, Pango::Language.default)
332
+
333
+ descent = metrics.descent.to_f / Pango::SCALE
334
+ line_height = metrics.height.to_f / Pango::SCALE
335
+ rec = Printer::Base::Transformation.new(x, y).rotate(rotation).translate(0, -px_to_mm(line_height - descent))
336
+ return rec.x, rec.y
337
+ end
338
+ end.new
339
+
340
+ converter.add_fonts_from_layout(self)
341
+
342
+ # Remove "-" from font names, which was inserted by rmagick
343
+ sorted_id_list.each do |id|
344
+ go = find_graphic_object_by_id(id)
345
+ font = go[GOA_FONT_TYP]
346
+ if font && font != "from_file"
347
+ font = font.tr("-", " ")
348
+ set_object_by_ids([id], GOA_FONT_TYP, font)
349
+ end
350
+ end
351
+
352
+ # Set a valid size in all text objects to the boinding box of the sample text
353
+ # and adjust X,Y position to the corner of the box.
354
+ sorted_id_list.each do |id|
355
+ go = find_graphic_object_by_id(id)
356
+ typ = go[GOA_TYP]
357
+ if typ == TEXTOBJECT
358
+ just = go[GOA_JUSTIFICATION]
359
+ just = just.sub("Links", "left")
360
+ .sub("Mitte", "mid")
361
+ .sub("Rechts", "right")
362
+
363
+ w, h = converter.get_text_sizes(go)
364
+ # set bounding box and keep a little bit of space for safty
365
+ if just == S_JUST_LEFT
366
+ # Don't change middle or right aligned, they should already be valid
367
+ set_object_by_ids([id], GOA_WIDTH, format("%0.2f", w * 1.1 + 5.0))
368
+ end
369
+ set_object_by_ids([id], GOA_HEIGHT, format("%0.2f", h * 1.1 + 1))
370
+
371
+ x, y = converter.get_new_coods(go)
372
+ # move text up, because old old Y means text baseline
373
+ # and new Y means upper left corner of the bounding box
374
+ set_object_by_ids([id], GOA_X, format("%0.2f", x))
375
+ set_object_by_ids([id], GOA_Y, format("%0.2f", y))
376
+
377
+ set_object_by_ids([id], GOA_JUSTIFICATION, just)
378
+ end
379
+ end
380
+ end
381
+
382
+ ensure_magsripe_attrs
383
+
384
+ # ................................................................................................................................
385
+ # die Einstellungen der graphischen Objekte auslesen
386
+
387
+ # kontrollieren, ob alle Nodes der GraphicObjects existieren, ansonsten anlegen und als 'changed' markieren
388
+ @xml_doc.xpath("//#{S_ROOT}/#{GRAPHICOBJECTS}/#{GRAPHICOBJECT}").each do |go_struct|
389
+ typ = go_struct.at_xpath(GOA_TYP).content
390
+ go = LayoutObject::Graphic.get_object_by_typ(typ)
391
+ go.go_attributes.each_pair do |go_attrname, go_attr|
392
+ element = go_struct.at_xpath(go_attr.attr_name)
393
+ unless element
394
+ n = Nokogiri::XML::Node.new(go_attr.attr_name, @xml_doc)
395
+ n.content = go_attr.attr_value.to_s
396
+ go_struct << n
397
+ changed!
398
+ end
399
+ end
400
+ end
401
+ end
402
+
403
+ def ensure_magsripe_attrs
404
+ # ................................................................................................................................
405
+ # die Magnetstreifen-Einstellungen auslesen
406
+
407
+ # kontrollieren, ob alle Nodes des Magstripes existieren, ansonsten anlegen und als 'changed' markieren
408
+ # todo
409
+ node_lay = @xml_doc.at_xpath("//#{S_ROOT}")
410
+ node_ms = node_lay.at_xpath("//#{S_ROOT}//#{S_MAGSTRIPEPARAMS}")
411
+ unless node_ms
412
+ node_ms = Nokogiri::XML::Node.new(S_MAGSTRIPEPARAMS, @xml_doc)
413
+ node_lay << node_ms
414
+ changed!
415
+ end
416
+ unless node_ms.at_xpath(S_MSSPUR1)
417
+ node_ms << Nokogiri::XML::Node.new(S_MSSPUR1, @xml_doc)
418
+ changed!
419
+ end
420
+ unless node_ms.at_xpath(S_MSSPUR2)
421
+ node_ms << Nokogiri::XML::Node.new(S_MSSPUR2, @xml_doc)
422
+ changed!
423
+ end
424
+ unless node_ms.at_xpath(S_MSSPUR3)
425
+ node_ms << Nokogiri::XML::Node.new(S_MSSPUR3, @xml_doc)
426
+ changed!
427
+ end
428
+ end
429
+
430
+ def have_barcode_objects?
431
+ sorted_id_list.find do |id|
432
+ go = find_graphic_object_by_id(id)
433
+ go[GOA_TYP] == BARCODEOBJECT
434
+ end
435
+ end
436
+
437
+ def find_graphic_object_by_id(id)
438
+ go = get_objectnode_by_id(id)
439
+ typ = go.at_xpath(GOA_TYP).content
440
+
441
+ LayoutObject::Graphic.get_object_by_typ(typ, xml_elem: go, layout_file: self)
442
+ end
443
+
444
+ def magstripe_object
445
+ ms_node = @xml_doc.at_xpath("//#{S_ROOT}//#{S_MAGSTRIPEPARAMS}")
446
+ LayoutObject::Magstripe.new(ms_node)
447
+ end
448
+
449
+ def chip_object
450
+ chip_attrs = @xml_doc.at_xpath("//#{S_ROOT}/#{S_CHIP}")
451
+ LayoutObject::Chip.new chip_attrs
452
+ end
453
+
454
+ # holt den Zweig mit den Einstellungen eines graphischen Objekts aus dem XML-File mit der Layout-Spezifikation
455
+ # ==== Parameter
456
+ # +id+:: die Nummer, unter welcher das Attribut gespeichert ist
457
+ private def get_objectnode_by_id(id)
458
+ @xml_doc.at_xpath("(//#{S_ROOT}/#{GRAPHICOBJECTS}/#{GRAPHICOBJECT})[#{id+1}]")
459
+ end
460
+
461
+ def get_layers_of_page(page_nr)
462
+ @xml_doc.xpath("//#{S_ROOT}/#{GRAPHICOBJECTS}/#{GRAPHICOBJECT}").select do |go_struct|
463
+ go_struct.at_xpath(GOA_PAGE_NR).content.to_i == page_nr
464
+ end.map do |go_struct|
465
+ go_struct.at_xpath(GOA_LAYER).content.to_i
466
+ end.uniq
467
+ end
468
+
469
+ def get_object_by_id(obj_id, attr)
470
+ node = get_objectnode_by_id(obj_id)
471
+ node.at_xpath(attr).content if node
472
+ end
473
+
474
+ # return true - etwas verändert
475
+ # false - nichts verändert
476
+ def set_object_by_ids(obj_ids, attr, value)
477
+ ch = false
478
+ obj_ids.each do |obj_id|
479
+ node = get_objectnode_by_id(obj_id).at_xpath(attr)
480
+
481
+ # When multiple objects are selected -> ignore attributes not available for this object
482
+ next if !node && obj_ids.size > 1
483
+
484
+ value = value.to_s
485
+ if node.content != value
486
+ node.content = value
487
+ ch ||= changed!
488
+ end
489
+ end
490
+ ch
491
+ end
492
+
493
+ # Fügt ein Graphik-Objekt zur Layout-Definition hinzu
494
+ # ==== Parameter
495
+ # +typ+:: Typ (Text, Barcode oder Bild)
496
+ # ==== Return
497
+ # Object [XML-Node]
498
+ def add_object(typ)
499
+ o = LayoutObject::Graphic.get_object_by_typ(typ)
500
+ id = get_new_id
501
+ node = @xml_doc.at_xpath("//#{S_ROOT}/#{GRAPHICOBJECTS}")
502
+ go = Nokogiri::XML::Node.new(GRAPHICOBJECT, @xml_doc)
503
+ node << go
504
+ n = Nokogiri::XML::Node.new(GOA_TYP, @xml_doc)
505
+ n.content = typ
506
+ go << n
507
+ n = Nokogiri::XML::Node.new(GOA_ID, @xml_doc)
508
+ n.content = id.to_s
509
+ go << n
510
+ n = Nokogiri::XML::Node.new(GOA_NAME, @xml_doc)
511
+ n.content = "#{typ}##{id}"
512
+ go << n
513
+ o.go_attributes.each_pair do |goa_name, go_attribute|
514
+ unless [GOA_TYP, GOA_NAME].include?(go_attribute.attr_name)
515
+ n = Nokogiri::XML::Node.new(go_attribute.attr_name, @xml_doc)
516
+ n.content = go_attribute.attr_value.to_s
517
+ go << n
518
+ end
519
+ end
520
+ changed!
521
+
522
+ LayoutObject::Graphic.get_object_by_typ(typ, xml_elem: go, layout_file: self)
523
+ end
524
+
525
+ # ermittelt die nächst höhere ID (beeinflusst die Darstellungsebene)
526
+ def get_new_id
527
+ @xml_doc.xpath("//#{S_ROOT}/#{GRAPHICOBJECTS}/#{GRAPHICOBJECT}").size
528
+ end
529
+
530
+ # liest die ID's der graphischen Objekte aus der Layout-Definition (XML) aus und
531
+ # sortiert sie
532
+ # ==== Return
533
+ # die sortierte Liste mit ID's [Array]
534
+ def sorted_id_list
535
+ @xml_doc.xpath("//#{S_ROOT}/#{GRAPHICOBJECTS}/#{GRAPHICOBJECT}").size.times.to_a
536
+ end
537
+
538
+ # Reindex the objects according to the passed array of obj_ids
539
+ def reorder_id_list(obj_ids)
540
+ # Die ID's im XML entsprechend des ARRAY-Indexes über die alte ID setzen (ist automatisch eine Re-Indizierung zum Entfernen von Löschern.
541
+ # Dabei alle Objekte durchlaufen und im Array die Position der alten ID suchen und in die ID schreiben.
542
+ # Dies muss so getan werden.
543
+ # Wenn es anders herum versucht wird, d.h. das Array durchlaufen und dann das Objekt im XML gesucht, dann werden die ID's schon verändert und die Suche schlägt dann fehl.
544
+ base = @xml_doc.at_xpath("//#{S_ROOT}/#{GRAPHICOBJECTS}")
545
+ elements = base.elements
546
+ obj_ids.each do |id|
547
+ elements[id].parent = base
548
+ end
549
+ changed!
550
+ end
551
+
552
+ # ermittelt den Inhalt eines Attributes des Layout-Abschnittes
553
+ # ==== Return
554
+ # den Inhalt des Attributes [String]
555
+ # ==== Parameter
556
+ # +attr_name+:: der Name des Attributes z.Bsp. 'datasource' Bitte vorwiegend Konstanten verwenden z. Bsp. S_DATASOURCE [String]
557
+ def get_attr_value(attr_name, base = S_LAYOUT)
558
+ lay_attrs = @xml_doc.at_xpath("//#{S_ROOT}/#{base}")
559
+ attr = lay_attrs.at_xpath(attr_name)
560
+ attr&.content
561
+ end
562
+
563
+ # setzt den Inhalt eines Attributes des Layout-Abschnittes
564
+ # ==== Return
565
+ # true wenn erfolgreich geändert
566
+ # false wenn keine Änderung
567
+ # ==== Parameter
568
+ # +attr_name+:: der Name des Attributes z.Bsp. 'datasource' Bitte vorwiegend Konstanten verwenden z. Bsp. S_DATASOURCE [String]
569
+ # +value+:: der Wert der geschrieben werden soll [String]
570
+ def set_attr_value(attr_name, value, base = S_LAYOUT)
571
+ lay_attrs = @xml_doc.at_xpath("//#{S_ROOT}/#{base}")
572
+ attr = lay_attrs.at_xpath(attr_name)
573
+ value = value.to_s
574
+ if attr.content != value
575
+ attr.content = value
576
+ changed!
577
+ else
578
+ false
579
+ end
580
+ end
581
+
582
+ # fügt ein neues Atrtribut zur Liste der Attribute (@attributes) hinzu
583
+ # ==== Parameter
584
+ # +name+:: der Name, den das Attribut erhalten soll
585
+ # +typ+:: der Typ
586
+ # +default+:: der Wert, mit dem das Attribut vorinitialisiert werden soll
587
+ def add_attr(name, typ, default)
588
+ attr = LayoutObject::GO_Attribut.new(name, typ, default)
589
+ @attributes[attr.attr_name] = attr
590
+ end
591
+
592
+ def change_first_xpath_value(xpath, value)
593
+ attr = @xml_doc.at_xpath(xpath)
594
+ raise I18n.t(:set_xpath_no_element_error, default: "Kein Element unter XPath %{xpath} in Datei %{filename} gefunden", xpath: xpath.inspect, filename: @filename) unless attr
595
+ puts I18n.t(:set_xpath_element, default: "Setze Element unter XPath %{xpath} in Datei %{filename} auf %{value}", xpath: xpath.inspect, filename: @filename, value: value.inspect)
596
+ if attr.content != value
597
+ attr.content = value
598
+ changed!
599
+ else
600
+ false
601
+ end
602
+ end
603
+
604
+ def save
605
+ # IDs sequenziell nummerieren für Kompatibilität mit älteren mcc2
606
+ @xml_doc.xpath("//#{S_ROOT}/#{GRAPHICOBJECTS}/#{GRAPHICOBJECT}").each_with_index do |go_struct, idx|
607
+ go_struct.at_xpath(GOA_ID).content = idx
608
+ end
609
+
610
+ @xml_doc.root["version"] = FILE_VERSION_RANGE.last.to_s
611
+ File.open(@filename, "w") do |fd|
612
+ @xml_doc.write_to(fd, indent: 2)
613
+ end
614
+ @changed = false
615
+ end
616
+
617
+ # überprüft, ob der Wert Datasourcetyp in der Layoutdefinition ein gültiger Wert ist.
618
+ # Wenn nicht, wird ein Fehlerdialog angezeigt und der Standardwert gesetzt und das changed-Flag gesetzt
619
+ def check_valid_datasourcetyp
620
+ datasourcetyp = get_attr_value(S_DATASOURCETYP)
621
+ unless DataMan.valid_datasourcetyp?(datasourcetyp)
622
+ default = @attributes[S_DATASOURCETYP].attr_value
623
+ set_attr_value(S_DATASOURCETYP, default)
624
+ raise WrongDataSourceType, I18n.t(:open_layout_invalid_datasource_type, default: <<~EOTXT, datasourcetyp:, valid_types: VALID_DATASOURCES.join("', '"), new: default )
625
+ Der eingestellte Datenquellentyp '%{datasourcetyp}' ist ungültig.
626
+
627
+ Gültige Werte sind '%{valid_types}'.
628
+
629
+ Der Wert wird jetzt auf den Standardwert '%{new}' gesetzt.
630
+
631
+ Bitte stellen Sie in den Layouteigenschaften den korrekten Datenquellentyp
632
+ ein und speichern Sie die Einstellungen.
633
+ EOTXT
634
+ end
635
+ end
636
+
637
+ # überprüft, ob der Wert Datasourcencoding in der Layoutdefinition ein gültiger Wert ist.
638
+ # Wenn nicht, wird ein Fehlerdialog angezeigt und der Standardwert gesetzt und das changed-Flag gesetzt
639
+ def check_valid_encoding
640
+ datasource_encoding = get_attr_value(S_DATASOURCEENCODING)
641
+ unless DataMan.valid_datasource_encoding?(datasource_encoding)
642
+ default = "UTF-8"
643
+ set_attr_value(S_DATASOURCEENCODING, default)
644
+ raise WrongDataSourceEncoding, I18n.t(:open_layout_invalid_datasource_encoding, default: <<-EOTXT, datasource_encoding:, valid_encodings: VALID_ENCODINGS.join("', '"))
645
+ Das eingestellte Encoding der Datenquelle '%{datasource_encoding}' ist ungültig.
646
+
647
+ Gültige Werte sind '%{valid_encodings}'.
648
+
649
+ Bitte stellen Sie in den Layouteigenschaften das korrekte Encoding
650
+ ein und speichern Sie die Einstellungen.
651
+ EOTXT
652
+ end
653
+ end
654
+ #---------------------------------------------------------------------------------------------------------
655
+
656
+ # ersetllt ein neues Graphik-Objekt und kopiert alle Attributte des übergebenen, außer ID, Typ und Name
657
+ # ==== Parameter
658
+ # +node+:: die XML-Node des zu kopierenden Objekts, fals nil übergeben wurde, kehrt die Methode unverrichteter Dinge zurück
659
+ def copy_objects(ids)
660
+ ids.each do |id|
661
+ obj = find_graphic_object_by_id(id)
662
+
663
+ o = add_object(obj[GOA_TYP])
664
+ obj.attributes.each do |attr|
665
+ # alle Attribute, außer Typ und Name kopieren, sofern sie vorhanden, ansonsten den Standard lassen
666
+ unless [GOA_TYP, GOA_NAME].include?(attr)
667
+ o[attr] = obj[attr]
668
+ end
669
+ end
670
+ end
671
+ changed!
672
+ end
673
+
674
+ def clipboard_copy(ids)
675
+ ids.map do |id|
676
+ get_objectnode_by_id(id).to_xml
677
+ end.join("\n")
678
+ end
679
+
680
+ def clipboard_cut(ids)
681
+ xml = clipboard_copy(ids)
682
+ changed = delete_objects(ids)
683
+ return xml, changed
684
+ end
685
+
686
+ def clipboard_paste(xml_data, after_id:)
687
+ frag = Nokogiri::XML.fragment(xml_data)
688
+ num_elements = frag.elements.size
689
+ if after_id
690
+ node = get_objectnode_by_id(after_id)
691
+ if node
692
+ node.add_next_sibling(frag)
693
+ else
694
+ node = @xml_doc.at_xpath("//#{S_ROOT}/#{GRAPHICOBJECTS}")
695
+ node.prepend_child(frag)
696
+ end
697
+ else
698
+ node = @xml_doc.at_xpath("//#{S_ROOT}/#{GRAPHICOBJECTS}")
699
+ after_id = node.elements.size - 1
700
+ node << frag
701
+ end
702
+
703
+ changed! if num_elements > 0
704
+ num_elements.times.map {|t| after_id ? t + after_id + 1 : t }
705
+ end
706
+
707
+ # löscht sie übergebene XML-Node aus dem Layout ohne Rückfrage, diese muss in der aufrufenden Methode durchgeführt werden
708
+ # ==== Parameter
709
+ def delete_objects(ids)
710
+ changed = false
711
+ ids.sort.reverse.each do |id|
712
+ obj = find_graphic_object_by_id(id)
713
+ obj.delete
714
+ changed = true
715
+ end
716
+ changed! if changed
717
+ end
718
+
719
+ def build_printer_instance
720
+ printertype = get_attr_value(S_PRINTERTYPE)
721
+ printerclass = Printer.const_get(printertype)
722
+ printerclass.new
723
+ end
724
+
725
+ def build_dataman_instance
726
+ datamantyp = get_attr_value(S_DATASOURCETYP)
727
+ datasource = get_attr_value(S_DATASOURCE)
728
+ encoding = get_attr_value(S_DATASOURCEENCODING)
729
+ # Ruby-1.9 kennt kein LATIN1
730
+ encoding = "iso-8859-1" if /latin1/i.match?(encoding)
731
+
732
+ if datamantyp == DATABASE
733
+ DataMan::Database.new(datasource)
734
+ elsif datamantyp == CSV
735
+ datasource = expand_path(datasource)
736
+ DataMan::CSV.new(datasource, encoding)
737
+ elsif datamantyp == DBF
738
+ datasource = expand_path(datasource)
739
+ DataMan::DBF.new(datasource, encoding)
740
+ elsif datamantyp == NONE
741
+ DataMan::None.new(datasource)
742
+ else
743
+ raise "invalid type of datasource #{datamantyp.inspect}"
744
+ end
745
+ end
746
+
747
+ def build_chip_instance
748
+ if get_attr_value(S_CONTACTLESS) == S_TRUE || get_attr_value(S_CONTACT) == S_TRUE
749
+ chip_object
750
+ end
751
+ end
752
+
753
+ def get_binary_attr(attr)
754
+ initdatahex = get_attr_value(attr)
755
+ if initdatahex && !initdatahex.empty?
756
+ [initdatahex].pack("H*")
757
+ end
758
+ end
759
+
760
+ def set_binary_attr(attr, bindata)
761
+ set_attr_value(attr, bindata.unpack1("H*"))
762
+ end
763
+
764
+ def expand_path(path)
765
+ path = path.gsub("%D", File.dirname(File.expand_path(@filename))) if @filename
766
+ path = path.gsub("%F", File.basename(@filename, ".*")) if @filename
767
+ pa = File.expand_path(path)
768
+ pa += "/" if path.end_with?("/")
769
+ pa
770
+ end
771
+
772
+ def shrink_path(path)
773
+ path = path.tr("\\", "/")
774
+ if @filename
775
+ path = path.sub(File.dirname(File.expand_path(@filename)), "%D")
776
+ end
777
+ if pwd = Dir.pwd
778
+ path = path.sub(pwd + "/", "")
779
+ end
780
+ if home = Dir.home
781
+ path = path.sub(home + "/", "~/")
782
+ end
783
+ path
784
+ end
785
+
786
+ def changed?
787
+ @changed
788
+ end
789
+
790
+ def changed!
791
+ @changed = true
792
+ end
793
+ end
794
+ end