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.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data/.autotest +23 -0
- data/.gitignore +15 -0
- data/.rubocop.yml +3 -0
- data/Gemfile +13 -0
- data/History.txt +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +51 -0
- data/Rakefile +12 -0
- data/config/i18n-tasks.yml +193 -0
- data/exe/mcc2 +18 -0
- data/exe/mcc2-generate-preview +17 -0
- data/exe/mcc2print +6 -0
- data/lib/libguib16.rb +1620 -0
- data/lib/mcc2/advanced_data_edit_dlg.rb +139 -0
- data/lib/mcc2/cc_dialogbox.rb +52 -0
- data/lib/mcc2/constants.rb +154 -0
- data/lib/mcc2/dataman/base.rb +61 -0
- data/lib/mcc2/dataman/csv.rb +49 -0
- data/lib/mcc2/dataman/database.rb +8 -0
- data/lib/mcc2/dataman/dbf.rb +45 -0
- data/lib/mcc2/dataman/none.rb +25 -0
- data/lib/mcc2/dataview.rb +348 -0
- data/lib/mcc2/dataview_dlg.rb +232 -0
- data/lib/mcc2/debug_tools.rb +20 -0
- data/lib/mcc2/fx_colored_item.rb +101 -0
- data/lib/mcc2/generate_preview.rb +68 -0
- data/lib/mcc2/gui/advanced_data_edit_dlg.rbin +0 -0
- data/lib/mcc2/gui/dataview_dlg.pp +522 -0
- data/lib/mcc2/gui/layoutview_dlg.pp +5115 -0
- data/lib/mcc2/gui/mcc2_mainwin.pp +838 -0
- data/lib/mcc2/gui/picture_data_edit_dlg.rbin +0 -0
- data/lib/mcc2/gui/printerview_dlg.pp +3311 -0
- data/lib/mcc2/images/Image-Arrow-Left.gif +0 -0
- data/lib/mcc2/images/Image-Arrow-Right.gif +0 -0
- data/lib/mcc2/images/Image-FastArrowLeft.gif +0 -0
- data/lib/mcc2/images/Image-FastArrowRight.bmp +0 -0
- data/lib/mcc2/images/Image-FastArrowRight.gif +0 -0
- data/lib/mcc2/images/application-exit.png +0 -0
- data/lib/mcc2/images/copy.png +0 -0
- data/lib/mcc2/images/country_de.png +0 -0
- data/lib/mcc2/images/country_gb.png +0 -0
- data/lib/mcc2/images/country_rs.png +0 -0
- data/lib/mcc2/images/cut.png +0 -0
- data/lib/mcc2/images/distribute-horizontal-baseline.png +0 -0
- data/lib/mcc2/images/distribute-vertical-baseline.png +0 -0
- data/lib/mcc2/images/document-close.png +0 -0
- data/lib/mcc2/images/document-edit.png +0 -0
- data/lib/mcc2/images/document-new.png +0 -0
- data/lib/mcc2/images/document-open.png +0 -0
- data/lib/mcc2/images/document-print-frame.png +0 -0
- data/lib/mcc2/images/document-print.png +0 -0
- data/lib/mcc2/images/document-save-as.png +0 -0
- data/lib/mcc2/images/document-save.png +0 -0
- data/lib/mcc2/images/documentation.png +0 -0
- data/lib/mcc2/images/draw-rectangle.png +0 -0
- data/lib/mcc2/images/dummy.gif +0 -0
- data/lib/mcc2/images/exchange-positions.png +0 -0
- data/lib/mcc2/images/insert-image.png +0 -0
- data/lib/mcc2/images/insert-text.png +0 -0
- data/lib/mcc2/images/libreoffice-oasis-master-document.png +0 -0
- data/lib/mcc2/images/measure.png +0 -0
- data/lib/mcc2/images/messagebox_warning.png +0 -0
- data/lib/mcc2/images/paste.png +0 -0
- data/lib/mcc2/images/process-stop.png +0 -0
- data/lib/mcc2/images/zoom-in.png +0 -0
- data/lib/mcc2/layout.rb +323 -0
- data/lib/mcc2/layout_file.rb +794 -0
- data/lib/mcc2/layout_object/barcode.rb +33 -0
- data/lib/mcc2/layout_object/box.rb +18 -0
- data/lib/mcc2/layout_object/chip.rb +54 -0
- data/lib/mcc2/layout_object/embed.rb +16 -0
- data/lib/mcc2/layout_object/go_attribut.rb +16 -0
- data/lib/mcc2/layout_object/graphic.rb +59 -0
- data/lib/mcc2/layout_object/magstripe.rb +13 -0
- data/lib/mcc2/layout_object/picture.rb +22 -0
- data/lib/mcc2/layout_object/text.rb +29 -0
- data/lib/mcc2/layout_win.rb +2052 -0
- data/lib/mcc2/layoutview.rb +845 -0
- data/lib/mcc2/layoutview_dlg.rb +2058 -0
- data/lib/mcc2/locales.rb +27 -0
- data/lib/mcc2/mcc2_mainwin.rb +453 -0
- data/lib/mcc2/mcc2gui.rb +295 -0
- data/lib/mcc2/mcc2print.rb +54 -0
- data/lib/mcc2/picture_data_edit_dlg.rb +139 -0
- data/lib/mcc2/print_dlg.rb +520 -0
- data/lib/mcc2/printer/base.rb +148 -0
- data/lib/mcc2/printer/card_printer.rb +45 -0
- data/lib/mcc2/printer/file_printer.rb +135 -0
- data/lib/mcc2/printer/gdi_card_printer.rb +14 -0
- data/lib/mcc2/printer/gdi_printer.rb +197 -0
- data/lib/mcc2/printer/linux_cups_printer.rb +92 -0
- data/lib/mcc2/printer/preview.rb +277 -0
- data/lib/mcc2/printer/printer_cab.rb +118 -0
- data/lib/mcc2/printer/printer_cx320_direct.rb +286 -0
- data/lib/mcc2/printer/printer_epl.rb +118 -0
- data/lib/mcc2/printer/printer_ttp34x.rb +124 -0
- data/lib/mcc2/printer/printer_zebra.rb +324 -0
- data/lib/mcc2/printer/svg_drawer.rb +689 -0
- data/lib/mcc2/printerview.rb +604 -0
- data/lib/mcc2/printerview_dlg.rb +1248 -0
- data/lib/mcc2/version.rb +3 -0
- data/lib/mcc2.rb +11 -0
- data/locales/de.yml +6 -0
- data/locales/en.yml +502 -0
- data/locales/rs.yml +502 -0
- data/mcc2.gemspec +41 -0
- data/test/dummy.rb +19 -0
- data/test/layouts/bart_simpson.png +0 -0
- data/test/layouts/color_test.xml +151 -0
- data/test/layouts/ms.xml +67 -0
- data/test/ruby_print.rb +380 -0
- data/test/test.csv +4 -0
- data/test/test.dbf +0 -0
- data/test/test.str +4 -0
- data/test/test_mcc2.rb +8 -0
- data.tar.gz.sig +6 -0
- metadata +421 -0
- 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
|