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,277 @@
1
+ require "mcc2/printer/base" # die Basisklasse für die Printer
2
+ require "mcc2/printer/card_printer"
3
+ require "mcc2/printer/svg_drawer"
4
+ require 'nokogiri'
5
+
6
+ module Mcc2
7
+ module Printer
8
+ class Preview < Base
9
+ include SvgDrawer
10
+ include CardPrinting
11
+ include Fox
12
+
13
+ def initialize(parent, layout)
14
+ super()
15
+ @parent = parent
16
+ @layout = layout
17
+ @selected_object_ids = []
18
+ @render_queue = nil
19
+ @render_thread = nil
20
+ @render_running = false
21
+
22
+ FXHorizontalFrame.new(parent, opts: LAYOUT_FILL_X) do |frame1|
23
+ @label_zoom = FXLabel.new(frame1, "") do |label_zoom|
24
+ label_zoom.img = "mcc2/images/zoom-in.png"
25
+ label_zoom.tipText = I18n.t(:preview_label_zoom_tip, default: "Hier kann die Vorschau vergrößert oder verkleinert werden. Wird die\ntatsächliche Auflösung des angeschlossenen Bildschirms eingestellt,\nerscheint die Vorschau in Originalgröße.")
26
+ end
27
+ @dial_zoom = FXDial.new(frame1, opts: DIAL_HORIZONTAL | LAYOUT_FIX_WIDTH, width: 100) do |dial_zoom|
28
+ dial_zoom.tipText = @label_zoom.tipText
29
+ dial_zoom.connect(SEL_CHANGED) { set_zoom }
30
+ dial_zoom.connect(SEL_LEFTBUTTONRELEASE) { draw_preview(true, @row) }
31
+ dial_zoom.value = @parent.app.reg.readRealEntry("settings", "preview_dpi", 90)
32
+ dial_zoom.range = 1..1000
33
+ end
34
+ FXLabel.new(frame1, I18n.t(:preview_optimize_barcodes, default: "Barcodes optimieren auf"))
35
+ @textfield_barcode_dpi = FXTextField.new(frame1, 4) do |tf|
36
+ tf.tipText = I18n.t(:preview_optimize_barcodes_tip, default: "DPI des Druckers")
37
+ tf.connect(SEL_COMMAND) do
38
+ @layout_file.set_attr_value(S_BARCODE_DPI, @textfield_barcode_dpi.text.to_f)
39
+ @layout.changed_attributes
40
+ draw_preview(true, @row)
41
+ end
42
+ end
43
+ FXLabel.new(frame1, "DPI")
44
+ @checkbutton_continous_update = FXCheckButton.new(frame1, I18n.t(:preview_keep_updated, default: "Vorschau aktuell halten")) do |checkbutton|
45
+ checkbutton.tipText = I18n.t(:preview_keep_updated_tip, default: "Bei Fehlern in den Layout-Einstellungen kann die Vorschau\nabgeschaltet werden, um mehrfache Hinweise zu vermeiden.")
46
+ checkbutton.connect(SEL_COMMAND) do
47
+ draw_preview(true, @row) if checkbutton.checked?
48
+ @parent.app.reg.writeBoolEntry("settings", "preview_continous_update", checkbutton.checked?)
49
+ end
50
+ checkbutton.checkState = @parent.app.reg.readBoolEntry("settings", "preview_continous_update", true)
51
+ end
52
+ end
53
+
54
+ @image_view = FXImageView.new(parent, opts: LAYOUT_FILL)
55
+ @image_view.connect(SEL_LEFTBUTTONPRESS, &method(:image_view_clicked))
56
+ @image_view.connect(SEL_KEYPRESS, &method(:image_view_keypress))
57
+
58
+ @error_view = FXText.new(parent, opts: LAYOUT_FILL|TEXT_WORDWRAP)
59
+ @error_view.editable = false
60
+ enable_error_view(false)
61
+ set_zoom
62
+ end
63
+
64
+ def enable_error_view(text = nil, error = nil)
65
+ if text
66
+ @image_view.visible = false
67
+ @error_view.visible = true
68
+ @error_view.text = "#{text}\n\n#{error}"
69
+ else
70
+ @image_view.visible = true
71
+ @error_view.visible = false
72
+ @error_view.text = ""
73
+ end
74
+ end
75
+
76
+ def layout_file=(value)
77
+ super
78
+ @textfield_barcode_dpi.text = @layout_file.get_attr_value(S_BARCODE_DPI) || "203.0"
79
+ end
80
+
81
+ def selected_object_ids=(ids)
82
+ @selected_object_ids = ids
83
+ draw_preview false, @row
84
+ end
85
+
86
+ # DPI zuweisen ohne Exception wegen Unstimmigkeit
87
+ def dpi=(v)
88
+ v = v.to_f
89
+ @dpi = v
90
+ end
91
+
92
+ def set_focus(to)
93
+ @image_view.setFocus
94
+ end
95
+
96
+ def image_view_clicked(sender, sel, event)
97
+ # There is no accessor for the content position in FXImageView.
98
+ # So the calculation is a bit awkward.
99
+ content_x = if @image_view.viewportWidth >= @image_view.contentWidth
100
+ (@image_view.viewportWidth - @image_view.contentWidth) / 2
101
+ else
102
+ @image_view.xPosition
103
+ end
104
+ content_y = if @image_view.viewportHeight >= @image_view.contentHeight
105
+ (@image_view.viewportHeight - @image_view.contentHeight) / 2
106
+ else
107
+ @image_view.yPosition
108
+ end
109
+ point = Vector2d.new(
110
+ event.win_x - content_x,
111
+ event.win_y - content_y
112
+ )
113
+
114
+ if @rect_list
115
+ rect_ids = [@rect_list.point_inside(point).last&.obj_id].compact
116
+ obj_ids = (event.state & Fox::CONTROLMASK != 0) ? @selected_object_ids : []
117
+ @layout.selected_object_ids = obj_ids + rect_ids - (obj_ids & rect_ids)
118
+ end
119
+
120
+ @layout.set_focus(:layout)
121
+ end
122
+
123
+ def image_view_keypress(sender, sel, event)
124
+ if event.type == 1 # Keypress
125
+ case event.code
126
+ when Fox::KEY_Up
127
+ @layout.move_selected(0, -1, event.state)
128
+ when Fox::KEY_Down
129
+ @layout.move_selected(0, 1, event.state)
130
+ when Fox::KEY_Left
131
+ @layout.move_selected(-1, 0, event.state)
132
+ when Fox::KEY_Right
133
+ @layout.move_selected(1, 0, event.state)
134
+ else
135
+ 0
136
+ end
137
+ end
138
+ end
139
+
140
+ # verändert die Zoomstufe, wenn am Rädchen gedreht wird
141
+ def set_zoom
142
+ self.dpi = @dial_zoom.value
143
+ @label_zoom.text = "#{dpi} DPI"
144
+ @parent.app.reg.writeRealEntry("settings", "preview_dpi", dpi)
145
+ end
146
+
147
+ # zeichnet die Vorschau im Layout-Modus
148
+ def draw_preview(immediately = false, row = nil)
149
+ @row = row
150
+ return unless @checkbutton_continous_update.checked?
151
+
152
+ @draw_preview_at = Time.now + (immediately ? 0 : 0.1)
153
+ start_preview_timer
154
+ end
155
+
156
+ def start_preview_timer
157
+ return unless @draw_preview_at
158
+
159
+ @render_queue ||= Queue.new
160
+ @render_thread ||= Thread.new do
161
+ loop do
162
+ @render_queue.pop
163
+
164
+ # require 'ruby-prof'
165
+ # result = RubyProf::Profile.profile do
166
+ draw_preview_now(@row)
167
+ # end
168
+ # printer = RubyProf::GraphPrinter.new(result)
169
+ # printer.print(STDOUT)
170
+
171
+ @render_running = false
172
+ # requeue preview generation, when something was changed
173
+ # while the thread was running.
174
+ @parent.runOnUiThread do
175
+ start_preview_timer
176
+ end
177
+ end
178
+ end
179
+
180
+ delay = @draw_preview_at - Time.now
181
+ @parent.app.addTimeout((delay > 0) ? delay * 1000 : 0) do
182
+ if @draw_preview_at && !@render_running
183
+ if Time.now >= @draw_preview_at
184
+ @draw_preview_at = nil
185
+ @render_running = true
186
+ @render_queue << true
187
+ else
188
+ # addTimeout didn't wait as long as expected per Time.now -> re-schedule
189
+ start_preview_timer
190
+ end
191
+ end
192
+ end
193
+ end
194
+
195
+ attr_reader :font_map
196
+
197
+ def draw_preview_now(row = nil)
198
+ require 'rsvg2'
199
+ require 'cairo'
200
+ require 'pango'
201
+ require 'ttfunk'
202
+
203
+ @font_map = {}
204
+
205
+ # allgemeine Einstellungen des Image vornehmen
206
+ layout_width_mm = @layout_file.get_attr_value(S_WIDTH).to_f
207
+ layout_height_mm = @layout_file.get_attr_value(S_HEIGHT).to_f
208
+ duplex = @layout_file.get_attr_value(S_DUPLEX) == S_TRUE
209
+
210
+ preview = true
211
+
212
+ card_offset = 0
213
+
214
+ image_width_mm = width_with_margins(layout_width_mm)
215
+ image_height_mm = (duplex ? 2 : 1) * height_with_margins(layout_height_mm) + card_offset
216
+ image_width = mm(image_width_mm).round
217
+ image_height = mm(image_height_mm).round
218
+
219
+ rect_list = nil
220
+
221
+ svg_data = svg_builder(viewBox: "0.5 0.5 #{image_width} #{image_height}") do |svg|
222
+ svg.g(transform: "scale(-1 -1) translate(-#{image_width} -#{image_height})") do
223
+
224
+ # Front
225
+ draw_pos_x = 0
226
+ draw_pos_y = 0
227
+ tra = Transformation.new
228
+ rect_list = draw(svg, tra.translate(mm(draw_pos_x), mm(draw_pos_y)), @layout_file, preview, @selected_object_ids, row, 0, layers: :all)
229
+
230
+ # Rear
231
+ if duplex
232
+ draw_pos_y = height_with_margins(layout_height_mm) + card_offset
233
+ rect_list.add draw(svg, tra.translate(mm(draw_pos_x), mm(draw_pos_y)), @layout_file, preview, @selected_object_ids, row, 1, layers: :all)
234
+ end
235
+ end
236
+ end
237
+ # puts svg_data
238
+ svg_handle = RSVG::Handle.new_from_data(svg_data)
239
+
240
+ surface = Cairo::ImageSurface.new(Cairo::FORMAT_RGB24, image_width, image_height)
241
+ context = Cairo::Context.new(surface)
242
+ context.render_rsvg_handle(svg_handle)
243
+
244
+ fx_image = FXImage.new(@parent.app, nil, 0, image_width, image_height)
245
+ argb = surface.data
246
+ # Cairo uses BGRA pixel data memory layout, but FXRuby uses RGBA.
247
+ argb.reverse! # make ARGB, works together with "rotate(180) translate(..)" above
248
+ argb.delete_prefix!(argb[0]) # drop first byte to make RGB
249
+ argb << "\0" # add the missing but ignored alpha byte to make RGBA
250
+ fx_image.pixels = argb
251
+
252
+ @parent.runOnUiThread do
253
+ enable_error_view(false)
254
+ @rect_list = rect_list
255
+ fx_image.create
256
+ begin
257
+ @image_view.image = fx_image
258
+ @image_view.show
259
+ @image_view.recalc
260
+ rescue RuntimeError => e
261
+ raise unless /already released/.match?(e.to_s)
262
+ end
263
+ end
264
+ rescue Printer::Base::PrintError => e
265
+ @parent.runOnUiThread do
266
+ enable_error_view(*I18n.t(:preview_error_printing_object, default: [["Fehler beim Erstellen eines Grafikobjekts", "%{e}"]], e: e.to_s))
267
+ end
268
+ @printing = false
269
+ rescue => e
270
+ DebugTools.write_exception("draw_preview_thread", e)
271
+ @parent.runOnUiThread do
272
+ enable_error_view(*I18n.t(:preview_error_printing_exception, default: [["Fehler beim Erstellen der Layoutvorschau", "%{e}"]], e: DebugTools.unkown_error_message(e)))
273
+ end
274
+ end
275
+ end
276
+ end
277
+ end
@@ -0,0 +1,118 @@
1
+ require "mcc2/printer/card_printer"
2
+ require "mcc2/printer/svg_drawer"
3
+ require "socket"
4
+ require "rmagick"
5
+
6
+ module Mcc2
7
+ module Printer
8
+ class PrinterCab < CardPrinter
9
+ include SvgDrawer
10
+
11
+ # die Ränder außerhalb der Karte, auf den maximal gedruckt werden darf (für vollflächige Bilder notwendig)
12
+ def card_margin_left
13
+ 0
14
+ end
15
+
16
+ def card_margin_right
17
+ 0
18
+ end
19
+
20
+ def card_margin_top
21
+ 0
22
+ end
23
+
24
+ def card_margin_bottom
25
+ 0
26
+ end
27
+
28
+ #--------------------------------------------------------------------------------------------------------------
29
+
30
+ def print_card(layout_file, row)
31
+ self.dpi = layout_file.get_attr_value(S_CABPRINTER_DPI).to_f
32
+
33
+ page_commands = layout_file.get_attr_value(S_CABPRINTER_PAGE_COMMANDS)
34
+ page_width = layout_file.get_attr_value(S_WIDTH).to_f
35
+ page_height = layout_file.get_attr_value(S_HEIGHT).to_f
36
+ draw_pos_x = layout_file.get_attr_value(S_X_OFFSET).to_f
37
+ draw_pos_y = layout_file.get_attr_value(S_Y_OFFSET).to_f
38
+ image_width = mm(page_width).round
39
+ image_height = mm(page_height).round
40
+
41
+ surface = Cairo::ImageSurface.new(Cairo::FORMAT_RGB24, image_width, image_height)
42
+ surface.set_fallback_resolution(dpi, dpi)
43
+ Cairo::Context.new(surface) do |context|
44
+ context.set_source_rgb(1.0, 1.0, 1.0) # make white background
45
+ context.paint
46
+
47
+ svg_data = svg_builder(viewBox: "0 0 #{image_width} #{image_height}") do |svg|
48
+ tra = Transformation.new
49
+ draw(svg, tra, layout_file, false, nil, row, 0)
50
+ end
51
+ svg_handle = RSVG::Handle.new_from_data(svg_data)
52
+ context.render_rsvg_handle(svg_handle)
53
+ end
54
+
55
+ # Convert to ImageMagick image
56
+ # "BGRA" matches the byte ordering of Cairo's FORMAT_ARGB32
57
+ pic = Magick::Image.from_blob(surface.data) do |options|
58
+ options.format = 'BGRA'
59
+ options.size = "#{image_width}x#{image_height}"
60
+ options.depth = 8 # Bits per pixel channel
61
+ end.first
62
+
63
+ page_commands.gsub!("\\e", "\e")
64
+ page_commands.gsub!("\n", "\r\n")
65
+ page_commands.gsub!("%PAGE_WIDTH%", page_width.to_s)
66
+ page_commands.gsub!("%PAGE_HEIGHT%", page_height.to_s)
67
+ page_commands.gsub!("%PAGE_OFFSET_X%", draw_pos_x.to_s)
68
+ page_commands.gsub!("%PAGE_OFFSET_Y%", draw_pos_y.to_s)
69
+
70
+ page_commands.gsub!("%IMAGE_DATA_PNG%") do
71
+ pic = pic.quantize(2, Magick::RGBColorspace, false)
72
+ data = pic.to_blob do |image|
73
+ image.format = "png8"
74
+ end
75
+ data.gsub("\e", "\e\e")
76
+ end
77
+
78
+ page_commands.gsub!("%IMAGE_DATA_PCX%") do
79
+ pic = pic.quantize(2, Magick::RGBColorspace, false)
80
+ data = pic.to_blob do |image|
81
+ image.format = "pcx"
82
+ end
83
+ data.gsub("\e", "\e\e")
84
+ end
85
+
86
+ open_socket(layout_file) do |sock|
87
+ sock.write page_commands
88
+ end
89
+
90
+ true
91
+ end
92
+
93
+ #--------------------------------------------------------------------------------------------------------------
94
+
95
+ def open_socket(layout_file)
96
+ host = layout_file.get_attr_value(S_CABPRINTER_HOST)
97
+ port = layout_file.get_attr_value(S_CABPRINTER_PORT).to_i
98
+ sock = TCPSocket.open(host, port)
99
+ if block_given?
100
+ begin
101
+ return yield(sock)
102
+ ensure
103
+ sock.close
104
+ end
105
+ end
106
+ sock
107
+ end
108
+ private :open_socket
109
+
110
+ def send_command(layout_file, data, &block)
111
+ open_socket(layout_file) do |sock|
112
+ sock.write data
113
+ yield sock if block
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,286 @@
1
+ require "json"
2
+ require "libcx320"
3
+ require "mcc2/printer/card_printer"
4
+ require "mcc2/printer/svg_drawer"
5
+ require "rmagick"
6
+
7
+ module Mcc2
8
+ module Printer
9
+ class PrinterCx320Direct < CardPrinter
10
+ include SvgDrawer
11
+
12
+ # die Ränder außerhalb der Karte, auf den maximal gedruckt werden darf (für vollflächige Bilder notwendig)
13
+ def card_margin_left
14
+ 0.8
15
+ end
16
+
17
+ def card_margin_right
18
+ 0.8
19
+ end
20
+
21
+ def card_margin_top
22
+ 0.8
23
+ end
24
+
25
+ def card_margin_bottom
26
+ 0.8
27
+ end
28
+
29
+ #--------------------------------------------------------------------------------------------------------------
30
+
31
+ PrintLayers = {
32
+ 0 => :YMC,
33
+ 1 => :K,
34
+ 2 => :UV,
35
+ 3 => :PeelOff
36
+ }
37
+
38
+ def initialize(*args)
39
+ super
40
+ @first_card = true
41
+ end
42
+
43
+ def print_card(layout_file, row)
44
+ chip = layout_file.build_chip_instance
45
+
46
+ self.dpi = 300
47
+ duplex = layout_file.get_attr_value(S_DUPLEX) == S_TRUE
48
+ x_offset = layout_file.get_attr_value(S_X_OFFSET).to_f
49
+ y_offset = layout_file.get_attr_value(S_Y_OFFSET).to_f
50
+ parallel = case layout_file.get_attr_value(S_CX320_PARALLEL_PRINT)
51
+ when "off" then false
52
+ when "on" then true
53
+ when "second_card" then !@first_card
54
+ else raise("invalid value for attr #{S_CX320_PARALLEL_PRINT}")
55
+ end
56
+
57
+ config = JSON.parse(layout_file.get_attr_value(S_CX320_CONFIG))
58
+
59
+ front_layers = layout_file.get_layers_of_page(0)
60
+ front_planes = generate_image_planes(layout_file, row, 0, front_layers, x_offset, y_offset)
61
+ front_layer_syms = front_layers.map { |l| PrintLayers[l] }.compact.uniq
62
+
63
+ if duplex
64
+ back_layers = layout_file.get_layers_of_page(1)
65
+ back_planes = generate_image_planes(layout_file, row, 1, back_layers, x_offset, y_offset)
66
+ back_layer_syms = back_layers.map { |l| PrintLayers[l] }.compact.uniq
67
+ end
68
+
69
+ open_printer(layout_file) do |dev|
70
+ write_config(config, dev)
71
+
72
+ while_busy do
73
+ dev.load_card(chip ? :contactless : :retransfer, false, false, parallel)
74
+ end
75
+
76
+ begin
77
+ front_planes.each do |plane, data|
78
+ dev.image_out(plane, data, 0) if data
79
+ end
80
+
81
+ if parallel
82
+ # run printing in parallel to load_card and chip
83
+ while_busy do
84
+ dev.print(front_layer_syms, 0, parallel)
85
+ end
86
+ end
87
+
88
+ if chip
89
+ wait_for_card_position(dev, :contactless)
90
+ begin
91
+ # Kartenpersonalisierung
92
+ chip.pers_chip(row)
93
+ rescue Exception
94
+ ignore_all_errors do
95
+ while_busy do
96
+ dev.move_card(:eject, false, false, true)
97
+ end
98
+ end
99
+ raise
100
+ end
101
+
102
+ while_busy do
103
+ dev.move_card(:retransfer, false)
104
+ end
105
+ end
106
+
107
+ if !parallel
108
+ dev.print(front_layer_syms, 0)
109
+ end
110
+
111
+ if duplex
112
+ while_busy do
113
+ dev.retransfer_and_turn(parallel)
114
+ end
115
+
116
+ back_planes.each do |plane, data|
117
+ dev.image_out(plane, data, 1) if data
118
+ end
119
+
120
+ while_busy do
121
+ dev.print(back_layer_syms, 1, parallel)
122
+ end
123
+ end
124
+ while_busy do
125
+ dev.retransfer_and_eject(parallel)
126
+ end
127
+ rescue
128
+ ignore_all_errors do
129
+ while_busy do
130
+ dev.move_card(:eject, false, false, true)
131
+ end
132
+ end
133
+ raise
134
+ end
135
+ end
136
+
137
+ @first_card = false
138
+ true
139
+ end
140
+
141
+ def draw_rvg_image(layout_file, row, page, layers, x_offset, y_offset)
142
+ image_width, image_height = 1036, 664
143
+ Cairo::ImageSurface.new(Cairo::FORMAT_RGB24, image_width, image_height) do |surface|
144
+ surface.set_fallback_resolution(dpi, dpi)
145
+ Cairo::Context.new(surface) do |context|
146
+ context.set_source_rgb(1.0, 1.0, 1.0) # make white background
147
+ context.paint
148
+
149
+ svg_data = svg_builder(viewBox: "0 0 #{image_width} #{image_height}") do |svg|
150
+ tra = Transformation.new
151
+ draw(svg, tra.translate(mm(x_offset).round, mm(y_offset).round), layout_file, false, nil, row, page, layers: layers)
152
+ end
153
+ svg_handle = RSVG::Handle.new_from_data(svg_data)
154
+ context.render_rsvg_handle(svg_handle)
155
+ end
156
+
157
+ # Convert to ImageMagick image
158
+ # "BGRA" matches the byte ordering of Cairo's FORMAT_ARGB32
159
+ image = Magick::Image.from_blob(surface.data) do |options|
160
+ options.format = 'BGRA'
161
+ options.size = "#{image_width}x#{image_height}"
162
+ options.depth = 8 # Bits per pixel channel
163
+ end.first
164
+
165
+ return image
166
+ end
167
+ end
168
+
169
+ def generate_image_planes(layout_file, row, page, layers, x_offset, y_offset)
170
+ if layers.include?(0)
171
+ pic = draw_rvg_image(layout_file, row, page, [0], x_offset, y_offset)
172
+
173
+ # TODO: Convert this code to use mcc2_c_helper.gem instead of rmagick
174
+
175
+ # Convert RGB to CMY image by inversion of color values
176
+ gradient_pic = Magick::Image.new(256, 1)
177
+ gradient = 0.upto(255).map do |i|
178
+ a = (255 - i) / 255.0 * Magick::QuantumRange
179
+ [a, a, a]
180
+ end
181
+ gradient_pic.import_pixels(0, 0, 256, 1, "RGB", gradient.flatten)
182
+ pic = pic.clut_channel(gradient_pic)
183
+
184
+ data_c, data_m, data_y = ["R", "G", "B"].map do |color|
185
+ pic.export_pixels_to_str(0, 0, pic.columns, pic.rows, color, Magick::CharPixel)
186
+ end
187
+ end
188
+
189
+ if layers.include?(1)
190
+ pic = draw_rvg_image(layout_file, row, page, [1], x_offset, y_offset).negate
191
+ data_k = pic.export_pixels_to_str(0, 0, pic.columns, pic.rows, "R", Magick::CharPixel)
192
+ end
193
+
194
+ if layers.include?(2)
195
+ pic = draw_rvg_image(layout_file, row, page, [2], x_offset, y_offset).negate
196
+ data_uv = pic.export_pixels_to_str(0, 0, pic.columns, pic.rows, "R", Magick::CharPixel)
197
+ end
198
+
199
+ if layers.include?(3)
200
+ pic = draw_rvg_image(layout_file, row, page, [3], x_offset, y_offset).negate
201
+ data_peeloff = pic.export_pixels_to_str(0, 0, pic.columns, pic.rows, "R", Magick::CharPixel)
202
+ end
203
+
204
+ [data_c, data_m, data_y, data_k, data_uv, data_peeloff].each do |data|
205
+ raise "internal error unexpected size #{data.bytesize}" if data && data.bytesize != 1036 * 664
206
+ end
207
+
208
+ {
209
+ C: data_c,
210
+ M: data_m,
211
+ Y: data_y,
212
+ K: data_k,
213
+ UV: data_uv,
214
+ PeelOff: data_peeloff
215
+ }
216
+ end
217
+
218
+ def while_busy
219
+ yield
220
+ rescue LibCX320::ErrorHandling::BusyError
221
+ puts "Printer busy"
222
+ sleep 0.1
223
+ retry
224
+ end
225
+
226
+ def ignore_all_errors
227
+ yield
228
+ rescue => e
229
+ puts "Ignore error: #{e.class} #{e}"
230
+ end
231
+
232
+ def wait_for_card_position(dev, pos)
233
+ while while_busy { dev.get_current_card_position }[:position] != pos
234
+ puts "Wait for card position #{pos}"
235
+ sleep 0.1
236
+ end
237
+ end
238
+
239
+ def open_printer(layout_file)
240
+ printer_klass = LibCX320.const_get(layout_file.get_attr_value(S_CX320_DRIVER))
241
+ printer_nr = layout_file.get_attr_value(S_CX320_PRINTER_NR).to_i
242
+ dev = printer_klass.devices[printer_nr]
243
+ raise "Printer #{printer_nr} not found" unless dev
244
+
245
+ begin
246
+ while_busy do
247
+ dev.ready?
248
+ end
249
+ yield dev
250
+ ensure
251
+ dev.close
252
+ end
253
+ end
254
+
255
+ def run_command(&)
256
+ open_printer(layout_file, &)
257
+ end
258
+
259
+ def write_config(config, dev)
260
+ # Aktuelle Konfig auslesen und mit gespeicherter Konfig vergleichen
261
+ attrs = dev.mode_sense(:print) + dev.mode_sense(:ink) + dev.mode_sense(:mag) + dev.log_sense(:log)
262
+ config_changed = attrs.find do |attr|
263
+ value = config[attr.name.to_s]
264
+ value && value != attr.value
265
+ end
266
+ if config_changed
267
+ # Wenn kein Konfig-Wert gespeichert, dann auf ausgelesenen Wert zurück greifen
268
+ config = attrs.select(&:writeable).map do |attr|
269
+ value = config[attr.name.to_s]
270
+ [attr.name.to_s, value || attr.value]
271
+ end
272
+
273
+ # neue Konfig in Drucker speichern
274
+ dev.mode_select(:print, config.select { |k, v| k.to_s =~ /^print_/ })
275
+ dev.mode_select(:mag, config.select { |k, v| k.to_s =~ /^mag_/ })
276
+
277
+ dev.rezero_unit
278
+ begin
279
+ dev.ready?
280
+ rescue LibCX320::ErrorHandling::MediumChanged
281
+ end
282
+ end
283
+ end
284
+ end
285
+ end
286
+ end