emfsvg 0.1.2 → 0.1.4
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 +4 -4
- data/README.adoc +98 -14
- data/lib/emfsvg/device_context.rb +9 -6
- data/lib/emfsvg/dib_decoder.rb +12 -8
- data/lib/emfsvg/drawing_hint.rb +88 -0
- data/lib/emfsvg/error.rb +11 -0
- data/lib/emfsvg/format_helpers.rb +46 -2
- data/lib/emfsvg/glyph_index_mapper.rb +5 -6
- data/lib/emfsvg/object_table.rb +7 -15
- data/lib/emfsvg/record_access.rb +38 -0
- data/lib/emfsvg/renderer.rb +114 -55
- data/lib/emfsvg/svg/clip_path_registry.rb +287 -4
- data/lib/emfsvg/svg/color.rb +32 -9
- data/lib/emfsvg/svg/document.rb +7 -0
- data/lib/emfsvg/svg/element.rb +4 -4
- data/lib/emfsvg/svg/elements/clip_path.rb +89 -24
- data/lib/emfsvg/svg/elements/defs.rb +85 -4
- data/lib/emfsvg/svg/elements/group.rb +30 -4
- data/lib/emfsvg/svg/elements/path.rb +239 -3
- data/lib/emfsvg/svg/elements/rect.rb +5 -2
- data/lib/emfsvg/svg/elements/stylable.rb +41 -11
- data/lib/emfsvg/svg/elements/text.rb +101 -16
- data/lib/emfsvg/svg/elements.rb +1 -1
- data/lib/emfsvg/svg/paint.rb +8 -2
- data/lib/emfsvg/svg/parser.rb +15 -4
- data/lib/emfsvg/svg/path_data/command.rb +1 -1
- data/lib/emfsvg/svg/path_data.rb +47 -36
- data/lib/emfsvg/svg/stroke.rb +78 -12
- data/lib/emfsvg/svg.rb +1 -1
- data/lib/emfsvg/svg_builder.rb +38 -9
- data/lib/emfsvg/translation/context.rb +262 -10
- data/lib/emfsvg/translation/decimal_detector.rb +26 -8
- data/lib/emfsvg/translation/emf_renderer.rb +16 -12
- data/lib/emfsvg/translation/handler_registry.rb +16 -6
- data/lib/emfsvg/translation/handlers/circular_arc.rb +103 -0
- data/lib/emfsvg/translation/handlers/defs_handler.rb +33 -4
- data/lib/emfsvg/translation/handlers/ellipse_handler.rb +2 -2
- data/lib/emfsvg/translation/handlers/elliptical_arc.rb +125 -0
- data/lib/emfsvg/translation/handlers/group_handler.rb +7 -0
- data/lib/emfsvg/translation/handlers/image_handler.rb +53 -8
- data/lib/emfsvg/translation/handlers/line_handler.rb +2 -2
- data/lib/emfsvg/translation/handlers/mapped_arc.rb +41 -0
- data/lib/emfsvg/translation/handlers/mapped_circular_arc.rb +55 -0
- data/lib/emfsvg/translation/handlers/mapped_pie.rb +30 -0
- data/lib/emfsvg/translation/handlers/path_flattener.rb +70 -49
- data/lib/emfsvg/translation/handlers/path_handler.rb +169 -14
- data/lib/emfsvg/translation/handlers/polyline_handler.rb +2 -2
- data/lib/emfsvg/translation/handlers/rect_handler.rb +21 -5
- data/lib/emfsvg/translation/handlers/shared_gdi.rb +75 -12
- data/lib/emfsvg/translation/handlers/text_handler.rb +92 -29
- data/lib/emfsvg/translation/handlers.rb +5 -0
- data/lib/emfsvg/translation/header_builder.rb +20 -0
- data/lib/emfsvg/translation/local_origin.rb +40 -0
- data/lib/emfsvg/translation/record_emitter.rb +8 -5
- data/lib/emfsvg/translation/scaler/fixed.rb +8 -2
- data/lib/emfsvg/translation.rb +1 -0
- data/lib/emfsvg/version.rb +1 -1
- data/lib/emfsvg/visitors/emr_visitor.rb +197 -129
- data/lib/emfsvg.rb +49 -2
- metadata +24 -10
|
@@ -95,7 +95,6 @@ module Emfsvg
|
|
|
95
95
|
Emf::Emr::Binary::Records::DrawEscape => :visit_draw_escape,
|
|
96
96
|
Emf::Emr::Binary::Records::ExtEscape => :visit_ext_escape,
|
|
97
97
|
Emf::Emr::Binary::Records::NamedEscape => :visit_named_escape,
|
|
98
|
-
Emf::Emr::Binary::Records::SmallTextOut => :visit_small_text_out,
|
|
99
98
|
Emf::Emr::Binary::Records::SetLinkedUfis => :visit_set_linked_ufis,
|
|
100
99
|
Emf::Emr::Binary::Records::ForceUfiMapping => :visit_force_ufi_mapping,
|
|
101
100
|
Emf::Emr::Binary::Records::ColorCorrectPalette => :visit_color_correct_palette,
|
|
@@ -129,9 +128,16 @@ module Emfsvg
|
|
|
129
128
|
Emf::Emr::Binary::Records::PaintRgn => :visit_paint_rgn
|
|
130
129
|
}.freeze
|
|
131
130
|
|
|
131
|
+
# Output-space mapping shared by every record: the bounds-to-image
|
|
132
|
+
# scale, the reference origin and the device pixels per millimetre.
|
|
133
|
+
Frame = Struct.new(:scaling, :ref_x, :ref_y, :px_per_mm, keyword_init: true) do
|
|
134
|
+
def initialize(scaling: 1.0, ref_x: 0.0, ref_y: 0.0, px_per_mm: 1.0)
|
|
135
|
+
super
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
132
139
|
def initialize(builder, dc, object_table, transform_stack, bounds, fix_y, options,
|
|
133
|
-
|
|
134
|
-
path_actions: {}, path_transforms: {})
|
|
140
|
+
frame: Frame.new, path_actions: {}, path_transforms: {})
|
|
135
141
|
@builder = builder
|
|
136
142
|
@dc = dc
|
|
137
143
|
@object_table = object_table
|
|
@@ -139,10 +145,10 @@ module Emfsvg
|
|
|
139
145
|
@bounds = bounds
|
|
140
146
|
@fix_y = fix_y
|
|
141
147
|
@options = options
|
|
142
|
-
@scaling = scaling
|
|
143
|
-
@ref_x = ref_x
|
|
144
|
-
@ref_y = ref_y
|
|
145
|
-
@px_per_mm = px_per_mm
|
|
148
|
+
@scaling = frame.scaling
|
|
149
|
+
@ref_x = frame.ref_x
|
|
150
|
+
@ref_y = frame.ref_y
|
|
151
|
+
@px_per_mm = frame.px_per_mm
|
|
146
152
|
@path_actions = path_actions
|
|
147
153
|
@path_transforms = path_transforms
|
|
148
154
|
@current_x = 0
|
|
@@ -171,17 +177,24 @@ module Emfsvg
|
|
|
171
177
|
|
|
172
178
|
# The Emf wire-record-adapter calls this. Dispatch by wire class.
|
|
173
179
|
def visit_emr_wire_record(adapter)
|
|
174
|
-
|
|
175
|
-
|
|
180
|
+
pending_hint = @pending_drawing_hint
|
|
181
|
+
@pending_drawing_hint = nil
|
|
182
|
+
@drawing_hint = pending_hint if pending_hint && pending_hint["record"] == DrawingHint.fingerprint(adapter)
|
|
183
|
+
klass = RecordAccess.wire_class(adapter)
|
|
184
|
+
method_name = HANDLERS[klass]
|
|
176
185
|
if method_name
|
|
177
|
-
send(method_name,
|
|
186
|
+
send(method_name, RecordAccess.fields(adapter, klass))
|
|
178
187
|
elsif @options.verbose
|
|
179
|
-
warn "emfsvg: no handler for #{
|
|
188
|
+
warn "emfsvg: no handler for #{klass.name&.split('::')&.last}"
|
|
180
189
|
end
|
|
190
|
+
rescue ParseError
|
|
191
|
+
raise
|
|
181
192
|
rescue StandardError => e
|
|
182
193
|
raise unless @options.verbose
|
|
183
194
|
|
|
184
195
|
warn "emfsvg: handler #{method_name} raised #{e.class}: #{e.message}"
|
|
196
|
+
ensure
|
|
197
|
+
@drawing_hint = nil
|
|
185
198
|
end
|
|
186
199
|
|
|
187
200
|
def flush_open_groups
|
|
@@ -237,6 +250,15 @@ module Emfsvg
|
|
|
237
250
|
FormatHelpers.fmt_xy(x, y)
|
|
238
251
|
end
|
|
239
252
|
|
|
253
|
+
# Record fields come back from the parser as BinData primitives, or as
|
|
254
|
+
# plain numbers from a value view. Device context state is compared
|
|
255
|
+
# against integer constants on every drawn point, and each such
|
|
256
|
+
# comparison on a BinData value goes through BinData::Base#==. Unwrap
|
|
257
|
+
# once when the state is set instead.
|
|
258
|
+
def int(value)
|
|
259
|
+
value.is_a?(Numeric) ? value : value.snapshot
|
|
260
|
+
end
|
|
261
|
+
|
|
240
262
|
# C integer division truncates toward zero; Ruby's Integer#/ floors.
|
|
241
263
|
# For negative operands the results differ (e.g. -333/2: C=-166,
|
|
242
264
|
# Ruby=-167). This helper replicates C's truncation semantics.
|
|
@@ -257,11 +279,11 @@ module Emfsvg
|
|
|
257
279
|
end
|
|
258
280
|
|
|
259
281
|
def visit_set_bk_mode(wire)
|
|
260
|
-
@dc.bk_mode = wire.bk_mode
|
|
282
|
+
@dc.bk_mode = int(wire.bk_mode)
|
|
261
283
|
end
|
|
262
284
|
|
|
263
285
|
def visit_set_map_mode(wire)
|
|
264
|
-
@dc.map_mode = wire.map_mode
|
|
286
|
+
@dc.map_mode = int(wire.map_mode)
|
|
265
287
|
end
|
|
266
288
|
|
|
267
289
|
def visit_set_rop2(wire)
|
|
@@ -269,15 +291,15 @@ module Emfsvg
|
|
|
269
291
|
end
|
|
270
292
|
|
|
271
293
|
def visit_set_poly_fill_mode(wire)
|
|
272
|
-
@dc.poly_fill_mode = wire.poly_fill_mode
|
|
294
|
+
@dc.poly_fill_mode = int(wire.poly_fill_mode)
|
|
273
295
|
end
|
|
274
296
|
|
|
275
297
|
def visit_set_text_align(wire)
|
|
276
|
-
@dc.text_align = wire.text_align_mode
|
|
298
|
+
@dc.text_align = int(wire.text_align_mode)
|
|
277
299
|
end
|
|
278
300
|
|
|
279
301
|
def visit_set_stretch_blt_mode(wire)
|
|
280
|
-
@dc.stretch_mode = wire.stretch_mode
|
|
302
|
+
@dc.stretch_mode = int(wire.stretch_mode)
|
|
281
303
|
end
|
|
282
304
|
|
|
283
305
|
def visit_save_dc(_wire)
|
|
@@ -319,10 +341,10 @@ module Emfsvg
|
|
|
319
341
|
}.freeze
|
|
320
342
|
|
|
321
343
|
def visit_select_object(wire)
|
|
322
|
-
ih = wire.ih_object
|
|
344
|
+
ih = int(wire.ih_object)
|
|
323
345
|
|
|
324
346
|
# Stock object?
|
|
325
|
-
if (
|
|
347
|
+
if ih.anybits?(STOCK_OBJECT_FLAG)
|
|
326
348
|
index = ih & 0x7FFFFFFF
|
|
327
349
|
apply_stock_object(index)
|
|
328
350
|
return
|
|
@@ -363,48 +385,48 @@ module Emfsvg
|
|
|
363
385
|
end
|
|
364
386
|
|
|
365
387
|
def visit_delete_object(wire)
|
|
366
|
-
@object_table.delete(wire.ih_object)
|
|
388
|
+
@object_table.delete(int(wire.ih_object))
|
|
367
389
|
end
|
|
368
390
|
|
|
369
391
|
def visit_create_pen(wire)
|
|
370
392
|
pen = Pen.new(
|
|
371
|
-
style: wire.pen_style,
|
|
372
|
-
width: wire.width.x,
|
|
393
|
+
style: int(wire.pen_style),
|
|
394
|
+
width: int(wire.width.x),
|
|
373
395
|
color: Emf::Model::Geometry::Color.from_wire(wire.color)
|
|
374
396
|
)
|
|
375
|
-
@object_table.store(wire.ih_pen, :pen, pen)
|
|
397
|
+
@object_table.store(int(wire.ih_pen), :pen, pen)
|
|
376
398
|
end
|
|
377
399
|
|
|
378
400
|
def visit_create_brush_indirect(wire)
|
|
379
401
|
brush = Brush.new(
|
|
380
|
-
style: wire.brush_style,
|
|
402
|
+
style: int(wire.brush_style),
|
|
381
403
|
color: Emf::Model::Geometry::Color.from_wire(wire.color),
|
|
382
|
-
hatch: wire.brush_hatch
|
|
404
|
+
hatch: int(wire.brush_hatch)
|
|
383
405
|
)
|
|
384
|
-
@object_table.store(wire.ih_brush, :brush, brush)
|
|
406
|
+
@object_table.store(int(wire.ih_brush), :brush, brush)
|
|
385
407
|
end
|
|
386
408
|
|
|
387
409
|
def visit_set_window_org_ex(wire)
|
|
388
|
-
@dc.window_org = [wire.origin.x, wire.origin.y]
|
|
410
|
+
@dc.window_org = [int(wire.origin.x), int(wire.origin.y)]
|
|
389
411
|
end
|
|
390
412
|
|
|
391
413
|
def visit_set_window_ext_ex(wire)
|
|
392
|
-
@dc.window_ext = [wire.extent.cx, wire.extent.cy]
|
|
414
|
+
@dc.window_ext = [int(wire.extent.cx), int(wire.extent.cy)]
|
|
393
415
|
@dc.window_ext_set = true
|
|
394
416
|
end
|
|
395
417
|
|
|
396
418
|
def visit_set_viewport_org_ex(wire)
|
|
397
|
-
@dc.viewport_org = [wire.origin.x, wire.origin.y]
|
|
419
|
+
@dc.viewport_org = [int(wire.origin.x), int(wire.origin.y)]
|
|
398
420
|
end
|
|
399
421
|
|
|
400
422
|
def visit_set_viewport_ext_ex(wire)
|
|
401
|
-
@dc.viewport_ext = [wire.extent.cx, wire.extent.cy]
|
|
423
|
+
@dc.viewport_ext = [int(wire.extent.cx), int(wire.extent.cy)]
|
|
402
424
|
@dc.viewport_ext_set = true
|
|
403
425
|
end
|
|
404
426
|
|
|
405
427
|
def visit_move_to_ex(wire)
|
|
406
|
-
@current_x = wire.origin.x
|
|
407
|
-
@current_y = wire.origin.y
|
|
428
|
+
@current_x = int(wire.origin.x)
|
|
429
|
+
@current_y = int(wire.origin.y)
|
|
408
430
|
return unless @in_path
|
|
409
431
|
|
|
410
432
|
# In path: emit `M X,Y ` directly to the open d-string.
|
|
@@ -416,9 +438,9 @@ module Emfsvg
|
|
|
416
438
|
if @in_path
|
|
417
439
|
# In path: emit `L X,Y ` to the open d-string, no wrapping.
|
|
418
440
|
@builder.raw("L ")
|
|
419
|
-
@builder.raw(fmt_xy(cal_x(wire.origin.x), cal_y(wire.origin.y)))
|
|
420
|
-
@current_x = wire.origin.x
|
|
421
|
-
@current_y = wire.origin.y
|
|
441
|
+
@builder.raw(fmt_xy(cal_x(int(wire.origin.x)), cal_y(int(wire.origin.y))))
|
|
442
|
+
@current_x = int(wire.origin.x)
|
|
443
|
+
@current_y = int(wire.origin.y)
|
|
422
444
|
return
|
|
423
445
|
end
|
|
424
446
|
|
|
@@ -426,10 +448,10 @@ module Emfsvg
|
|
|
426
448
|
# C's startPathDraw creates U_POINT (int32) from cur_x/cur_y,
|
|
427
449
|
# truncating any fractional part before point_cal. We must match.
|
|
428
450
|
d = "M #{fmt(cal_x(@current_x.to_i))},#{fmt(cal_y(@current_y.to_i))} "
|
|
429
|
-
d << "L #{fmt(cal_x(wire.origin.x))},#{fmt(cal_y(wire.origin.y))} "
|
|
451
|
+
d << "L #{fmt(cal_x(int(wire.origin.x)))},#{fmt(cal_y(int(wire.origin.y)))} "
|
|
430
452
|
emit_path_element(d, closed: false, leading_fill_space: true)
|
|
431
|
-
@current_x = wire.origin.x
|
|
432
|
-
@current_y = wire.origin.y
|
|
453
|
+
@current_x = int(wire.origin.x)
|
|
454
|
+
@current_y = int(wire.origin.y)
|
|
433
455
|
end
|
|
434
456
|
|
|
435
457
|
def visit_rectangle(wire)
|
|
@@ -473,7 +495,8 @@ module Emfsvg
|
|
|
473
495
|
end
|
|
474
496
|
rx = scale_dim_x(wire.szl_corner.cx.to_i)
|
|
475
497
|
ry = scale_dim_x(wire.szl_corner.cy.to_i)
|
|
476
|
-
geom = "x=\"#{fmt(lt_x)}\" y=\"#{fmt(lt_y)}\" width=\"#{fmt(dim_x)}\" height=\"#{fmt(dim_y)}\"
|
|
498
|
+
geom = "x=\"#{fmt(lt_x)}\" y=\"#{fmt(lt_y)}\" width=\"#{fmt(dim_x)}\" height=\"#{fmt(dim_y)}\" " \
|
|
499
|
+
"rx=\"#{fmt(rx)}\" ry=\"#{fmt(ry)}\""
|
|
477
500
|
emit_shape_element("rect", geom)
|
|
478
501
|
end
|
|
479
502
|
|
|
@@ -528,8 +551,8 @@ module Emfsvg
|
|
|
528
551
|
@builder.raw("L ")
|
|
529
552
|
@builder.raw(point_str(p))
|
|
530
553
|
end
|
|
531
|
-
@current_x = points.last.x
|
|
532
|
-
@current_y = points.last.y
|
|
554
|
+
@current_x = int(points.last.x)
|
|
555
|
+
@current_y = int(points.last.y)
|
|
533
556
|
return
|
|
534
557
|
end
|
|
535
558
|
|
|
@@ -540,8 +563,8 @@ module Emfsvg
|
|
|
540
563
|
d << point_str(p)
|
|
541
564
|
end
|
|
542
565
|
emit_path_element(d, closed: false, leading_fill_space: true)
|
|
543
|
-
@current_x = points.last.x
|
|
544
|
-
@current_y = points.last.y
|
|
566
|
+
@current_x = int(points.last.x)
|
|
567
|
+
@current_y = int(points.last.y)
|
|
545
568
|
end
|
|
546
569
|
|
|
547
570
|
def visit_poly_bezier(wire)
|
|
@@ -580,8 +603,8 @@ module Emfsvg
|
|
|
580
603
|
end
|
|
581
604
|
emit_bezier_points(points, starting_point: starting_point)
|
|
582
605
|
if points.last
|
|
583
|
-
@current_x = points.last.x
|
|
584
|
-
@current_y = points.last.y
|
|
606
|
+
@current_x = int(points.last.x)
|
|
607
|
+
@current_y = int(points.last.y)
|
|
585
608
|
end
|
|
586
609
|
return
|
|
587
610
|
end
|
|
@@ -595,8 +618,8 @@ module Emfsvg
|
|
|
595
618
|
emit_path_element(d, closed: false, leading_fill_space: true)
|
|
596
619
|
return unless points.last
|
|
597
620
|
|
|
598
|
-
@current_x = points.last.x
|
|
599
|
-
@current_y = points.last.y
|
|
621
|
+
@current_x = int(points.last.x)
|
|
622
|
+
@current_y = int(points.last.y)
|
|
600
623
|
end
|
|
601
624
|
|
|
602
625
|
# Emit "C p1 p2 p3 C p4 p5 p6 ..." matching cubic_bezier*_draw's
|
|
@@ -748,26 +771,30 @@ module Emfsvg
|
|
|
748
771
|
pt = entry[key]
|
|
749
772
|
return unless pt
|
|
750
773
|
|
|
751
|
-
if pt.mode.nil? || pt.mode.zero?
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
774
|
+
@dc.world_transform = if pt.mode.nil? || pt.mode.zero?
|
|
775
|
+
pt.matrix
|
|
776
|
+
else
|
|
777
|
+
case pt.mode
|
|
755
778
|
when 1 then Emf::Model::Geometry::Matrix.identity
|
|
756
779
|
when 2 then multiply_matrix(pt.matrix, @dc.world_transform)
|
|
757
780
|
when 3 then multiply_matrix(@dc.world_transform, pt.matrix)
|
|
758
781
|
else pt.matrix
|
|
759
782
|
end
|
|
760
|
-
|
|
783
|
+
end
|
|
761
784
|
emit_transform_group(@dc.world_transform)
|
|
762
785
|
end
|
|
763
786
|
|
|
764
787
|
def visit_set_miter_limit(wire)
|
|
765
|
-
|
|
766
|
-
#
|
|
767
|
-
|
|
788
|
+
limit = wire.miter_limit.to_f
|
|
789
|
+
# libemf2svg-produced EMFs store this field as the integer payload's
|
|
790
|
+
# float bit pattern, while native SetMiterLimit records store a float.
|
|
791
|
+
limit = [limit].pack("f").unpack1("L<") if limit.abs < 1.1754943508222875e-38
|
|
792
|
+
@dc.miter_limit = limit
|
|
768
793
|
end
|
|
769
794
|
|
|
770
|
-
def visit_comment(
|
|
795
|
+
def visit_comment(wire)
|
|
796
|
+
@pending_drawing_hint = DrawingHint.decode(wire)
|
|
797
|
+
end
|
|
771
798
|
|
|
772
799
|
def visit_set_pixel_v(_wire)
|
|
773
800
|
# FLAG_IGNORED in libemf2svg — emits nothing.
|
|
@@ -776,7 +803,7 @@ module Emfsvg
|
|
|
776
803
|
def visit_create_font_indirect_w(wire)
|
|
777
804
|
body = wire.body.to_s
|
|
778
805
|
font = parse_logfont(body, utf16: true)
|
|
779
|
-
@object_table.store(wire.ih_object, :font, font)
|
|
806
|
+
@object_table.store(int(wire.ih_object), :font, font)
|
|
780
807
|
end
|
|
781
808
|
|
|
782
809
|
# Parse a LOGFONT struct from body bytes (MS-WMF 2.2.13).
|
|
@@ -796,8 +823,8 @@ module Emfsvg
|
|
|
796
823
|
|
|
797
824
|
height = body.unpack1("l<")
|
|
798
825
|
_width = body.unpack("l<", offset: 4)[0]
|
|
799
|
-
#
|
|
800
|
-
escapement = body.unpack("l<", offset: 8)[0]
|
|
826
|
+
# C remainder preserves the dividend sign (so -2700 stays -2700).
|
|
827
|
+
escapement = body.unpack("l<", offset: 8)[0].remainder(3600)
|
|
801
828
|
weight = body.unpack("l<", offset: 16)[0]
|
|
802
829
|
italic = body.getbyte(20) != 0
|
|
803
830
|
underline = body.getbyte(21) != 0
|
|
@@ -828,8 +855,8 @@ module Emfsvg
|
|
|
828
855
|
# Per MS-EMF 2.3.5.37: ETO_NO_RECT (0x100) UNSET means a U_RECTL
|
|
829
856
|
# (16 bytes) precedes the text. ETO_SMALL_CHARS (0x200) selects
|
|
830
857
|
# 8-bit per char instead of 16-bit.
|
|
831
|
-
text_offset = (
|
|
832
|
-
small_chars = (
|
|
858
|
+
text_offset = fu.nobits?(0x100) ? 16 : 0
|
|
859
|
+
small_chars = fu.anybits?(0x200) # ETO_SMALL_CHARS = 0x200
|
|
833
860
|
c_chars = wire.c_chars.to_i
|
|
834
861
|
bytes_per_char = small_chars ? 1 : 2
|
|
835
862
|
text_bytes = body[text_offset, c_chars * bytes_per_char]
|
|
@@ -852,7 +879,7 @@ module Emfsvg
|
|
|
852
879
|
if small_chars
|
|
853
880
|
bytes.force_encoding("CP1252").encode("UTF-8", undef: :replace, replace: "?")
|
|
854
881
|
else
|
|
855
|
-
bytes
|
|
882
|
+
decode_utf16_text(bytes)
|
|
856
883
|
end
|
|
857
884
|
end
|
|
858
885
|
|
|
@@ -1098,7 +1125,7 @@ module Emfsvg
|
|
|
1098
1125
|
brush = Brush.new(style: Brush::MONOPATTERN,
|
|
1099
1126
|
color: @dc.brush.color,
|
|
1100
1127
|
image_id: image_id)
|
|
1101
|
-
@object_table.store(wire.ih_brush, :brush, brush)
|
|
1128
|
+
@object_table.store(int(wire.ih_brush), :brush, brush)
|
|
1102
1129
|
end
|
|
1103
1130
|
|
|
1104
1131
|
# Emit <defs><image id="img-N" .../><pattern id="img-N-ref"
|
|
@@ -1166,9 +1193,9 @@ module Emfsvg
|
|
|
1166
1193
|
width: width,
|
|
1167
1194
|
color: Emf::Model::Geometry::Color.new(red: r, green: g, blue: b)
|
|
1168
1195
|
)
|
|
1169
|
-
@object_table.store(wire.ih_pen, :pen, pen)
|
|
1196
|
+
@object_table.store(int(wire.ih_pen), :pen, pen)
|
|
1170
1197
|
else
|
|
1171
|
-
@object_table.store(wire.ih_pen, :pen, Pen.new)
|
|
1198
|
+
@object_table.store(int(wire.ih_pen), :pen, Pen.new)
|
|
1172
1199
|
end
|
|
1173
1200
|
end
|
|
1174
1201
|
|
|
@@ -1366,14 +1393,20 @@ module Emfsvg
|
|
|
1366
1393
|
region.path_d
|
|
1367
1394
|
elsif region.bounds
|
|
1368
1395
|
b = region.bounds
|
|
1369
|
-
|
|
1370
|
-
|
|
1396
|
+
x_left = cal_x(b.left)
|
|
1397
|
+
x_right = cal_x(b.right)
|
|
1398
|
+
y_top = cal_y(b.top)
|
|
1399
|
+
y_bottom = cal_y(b.bottom)
|
|
1371
1400
|
d = +"M "
|
|
1372
|
-
d
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1401
|
+
FormatHelpers.append_xy(d, x_left, y_top)
|
|
1402
|
+
d << "L "
|
|
1403
|
+
FormatHelpers.append_xy(d, x_right, y_top)
|
|
1404
|
+
d << "L "
|
|
1405
|
+
FormatHelpers.append_xy(d, x_right, y_bottom)
|
|
1406
|
+
d << "L "
|
|
1407
|
+
FormatHelpers.append_xy(d, x_left, y_bottom)
|
|
1408
|
+
d << "L "
|
|
1409
|
+
FormatHelpers.append_xy(d, x_left, y_top)
|
|
1377
1410
|
d << "Z "
|
|
1378
1411
|
d
|
|
1379
1412
|
else
|
|
@@ -1416,7 +1449,7 @@ module Emfsvg
|
|
|
1416
1449
|
decoded = raw.pack("C*").force_encoding("UTF-16LE").encode("UTF-8",
|
|
1417
1450
|
undef: :replace,
|
|
1418
1451
|
replace: "?")
|
|
1419
|
-
if (nul = decoded.index("
|
|
1452
|
+
if (nul = decoded.index("\0"))
|
|
1420
1453
|
decoded = decoded[0, nul]
|
|
1421
1454
|
end
|
|
1422
1455
|
decoded.empty? ? nil : decoded
|
|
@@ -1435,39 +1468,42 @@ module Emfsvg
|
|
|
1435
1468
|
|
|
1436
1469
|
(0...bytes.size).step(2).each do |i|
|
|
1437
1470
|
code = bytes[i] | (bytes[i + 1] << 8)
|
|
1438
|
-
return false if code
|
|
1471
|
+
return false if code.between?(0xD800, 0xDFFF)
|
|
1439
1472
|
end
|
|
1440
1473
|
true
|
|
1441
1474
|
end
|
|
1442
1475
|
|
|
1443
|
-
|
|
1476
|
+
# Decodes the string carried by an EXTTEXTOUT record; "" when the
|
|
1477
|
+
# record holds no (in-range) string bytes.
|
|
1478
|
+
def ext_text_content(wire, utf16:)
|
|
1444
1479
|
record_bytes = wire.to_binary_s
|
|
1445
1480
|
off_string = wire.off_string
|
|
1446
1481
|
n_chars = wire.n_chars
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1482
|
+
return "" unless off_string&.positive? && off_string < record_bytes.bytesize
|
|
1483
|
+
|
|
1484
|
+
string_bytes = record_bytes[off_string, utf16 ? n_chars * 2 : n_chars]
|
|
1485
|
+
return "" if string_bytes.nil? || string_bytes.empty?
|
|
1486
|
+
|
|
1487
|
+
if wire.f_options.to_i.anybits?(GlyphIndexMapper::ETO_GLYPH_INDEX)
|
|
1488
|
+
decode_glyph_indices(string_bytes, n_chars)
|
|
1489
|
+
elsif utf16
|
|
1490
|
+
decode_utf16_text(string_bytes)
|
|
1491
|
+
else
|
|
1492
|
+
cleaned = string_bytes.bytes.map { |b| b > 0x7F ? 0x20 : b }
|
|
1493
|
+
cleaned.pack("C*").force_encoding("UTF-8")
|
|
1494
|
+
end
|
|
1495
|
+
end
|
|
1496
|
+
|
|
1497
|
+
def decode_utf16_text(string_bytes)
|
|
1498
|
+
string_bytes.force_encoding("UTF-16LE").encode("UTF-8", invalid: :replace, undef: :replace, replace: "?")
|
|
1499
|
+
end
|
|
1500
|
+
|
|
1501
|
+
def emit_ext_text(wire, utf16:)
|
|
1502
|
+
text = ext_text_content(wire, utf16: utf16)
|
|
1467
1503
|
|
|
1468
1504
|
# libemf2svg emits the <text> element even when the string is empty.
|
|
1469
|
-
x = cal_x(wire.ptl_reference.x)
|
|
1470
|
-
y = cal_y(wire.ptl_reference.y)
|
|
1505
|
+
x = cal_x(int(wire.ptl_reference.x))
|
|
1506
|
+
y = cal_y(int(wire.ptl_reference.y))
|
|
1471
1507
|
|
|
1472
1508
|
font = @dc.font
|
|
1473
1509
|
font_height = scale_dim_x(font.height.to_i).abs
|
|
@@ -1490,13 +1526,14 @@ module Emfsvg
|
|
|
1490
1526
|
@builder.raw("font-family=\"#{font.face_name}\" ") if font.face_name
|
|
1491
1527
|
@builder.raw("fill=\"#{rgb_hex(@dc.text_color)}\" ")
|
|
1492
1528
|
|
|
1493
|
-
orientation = sf_y
|
|
1529
|
+
orientation = sf_y.positive? ? -1 : 1
|
|
1494
1530
|
if font.escapement != 0
|
|
1495
1531
|
# C's integer division truncates toward zero; Ruby's Integer#/ floors.
|
|
1496
1532
|
# Use float division + truncate to match C exactly for negative values.
|
|
1497
1533
|
rotation = (orientation.to_f * font.escapement / 10).truncate
|
|
1498
1534
|
adj_y = y + (font_height * 0.9)
|
|
1499
|
-
@builder.raw("transform=\"rotate(#{rotation}, #{fmt(x)}, #{fmt(adj_y)})
|
|
1535
|
+
@builder.raw("transform=\"rotate(#{rotation}, #{fmt(x)}, #{fmt(adj_y)}) " \
|
|
1536
|
+
"translate(0, #{fmt(font_height * 0.9)})\" ")
|
|
1500
1537
|
end
|
|
1501
1538
|
|
|
1502
1539
|
@builder.raw("font-style=\"italic\" ") if font.italic
|
|
@@ -1517,9 +1554,9 @@ module Emfsvg
|
|
|
1517
1554
|
# libemf2svg checks both U_TA_CENTER (0x06) AND U_TA_CENTER2 (0x04)
|
|
1518
1555
|
# for "middle" anchor. Mask 0x06 doesn't include 0x04, so handle
|
|
1519
1556
|
# both explicitly to match C.
|
|
1520
|
-
anchor = if (
|
|
1557
|
+
anchor = if align.allbits?(0x06) || align.allbits?(0x04)
|
|
1521
1558
|
"middle"
|
|
1522
|
-
elsif (
|
|
1559
|
+
elsif align.allbits?(0x02)
|
|
1523
1560
|
"end"
|
|
1524
1561
|
else
|
|
1525
1562
|
"start"
|
|
@@ -1528,7 +1565,7 @@ module Emfsvg
|
|
|
1528
1565
|
|
|
1529
1566
|
# Vertical position: TA_BOTTOM (0x08) or TA_BASELINE (0x18) use Org.y;
|
|
1530
1567
|
# otherwise add font_height * 0.9.
|
|
1531
|
-
y_pos = if (
|
|
1568
|
+
y_pos = if align.allbits?(0x18) || (align & 0x18) == 0x08
|
|
1532
1569
|
y
|
|
1533
1570
|
else
|
|
1534
1571
|
y + (font_height * 0.9)
|
|
@@ -1600,15 +1637,15 @@ module Emfsvg
|
|
|
1600
1637
|
# floors. For negative sums (e.g. (-161 + -172) / 2), C gives -166
|
|
1601
1638
|
# but Ruby gives -167 — a 1-pixel offset. We must replicate C's
|
|
1602
1639
|
# truncation by converting to float and truncating.
|
|
1603
|
-
def int_el_rad(
|
|
1640
|
+
def int_el_rad(point, rect)
|
|
1604
1641
|
center_x = c_int_div(rect.right + rect.left, 2)
|
|
1605
1642
|
center_y = c_int_div(rect.bottom + rect.top, 2)
|
|
1606
1643
|
radii_x = c_int_div(rect.right - rect.left, 2)
|
|
1607
1644
|
radii_y = c_int_div(rect.bottom - rect.top, 2)
|
|
1608
1645
|
return [center_x, center_y] if radii_x.zero? || radii_y.zero?
|
|
1609
1646
|
|
|
1610
|
-
pt_no_x =
|
|
1611
|
-
pt_no_y =
|
|
1647
|
+
pt_no_x = point.x.to_f - center_x
|
|
1648
|
+
pt_no_y = point.y.to_f - center_y
|
|
1612
1649
|
|
|
1613
1650
|
if pt_no_x.zero?
|
|
1614
1651
|
return [center_x, ((pt_no_y.negative? ? -1 : 1) * radii_y) + center_y]
|
|
@@ -1697,10 +1734,10 @@ module Emfsvg
|
|
|
1697
1734
|
end
|
|
1698
1735
|
# Mirror libemf2svg's point*_draw: the final emitted point becomes
|
|
1699
1736
|
# the new current position for subsequent drawing records.
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1737
|
+
return unless last_pt
|
|
1738
|
+
|
|
1739
|
+
@current_x = int(last_pt.x)
|
|
1740
|
+
@current_y = int(last_pt.y)
|
|
1704
1741
|
end
|
|
1705
1742
|
|
|
1706
1743
|
def emit_poly_draw(points, types, _point_size:)
|
|
@@ -1719,15 +1756,15 @@ module Emfsvg
|
|
|
1719
1756
|
i += 1
|
|
1720
1757
|
case t & 0x07
|
|
1721
1758
|
when pt_moveto
|
|
1722
|
-
d << "M #{fmt(cal_x(pt.x))} #{fmt(cal_y(pt.y))} "
|
|
1759
|
+
d << "M #{fmt(cal_x(int(pt.x)))} #{fmt(cal_y(int(pt.y)))} "
|
|
1723
1760
|
when pt_lineto
|
|
1724
|
-
d << "L #{fmt(cal_x(pt.x))} #{fmt(cal_y(pt.y))} "
|
|
1761
|
+
d << "L #{fmt(cal_x(int(pt.x)))} #{fmt(cal_y(int(pt.y)))} "
|
|
1725
1762
|
when pt_bezierto
|
|
1726
1763
|
bezier_buf << pt
|
|
1727
1764
|
if bezier_buf.size == 3
|
|
1728
|
-
d << "C #{fmt(cal_x(bezier_buf[0].x))} #{fmt(cal_y(bezier_buf[0].y))} "
|
|
1729
|
-
d << "#{fmt(cal_x(bezier_buf[1].x))} #{fmt(cal_y(bezier_buf[1].y))} "
|
|
1730
|
-
d << "#{fmt(cal_x(bezier_buf[2].x))} #{fmt(cal_y(bezier_buf[2].y))} "
|
|
1765
|
+
d << "C #{fmt(cal_x(int(bezier_buf[0].x)))} #{fmt(cal_y(int(bezier_buf[0].y)))} "
|
|
1766
|
+
d << "#{fmt(cal_x(int(bezier_buf[1].x)))} #{fmt(cal_y(int(bezier_buf[1].y)))} "
|
|
1767
|
+
d << "#{fmt(cal_x(int(bezier_buf[2].x)))} #{fmt(cal_y(int(bezier_buf[2].y)))} "
|
|
1731
1768
|
bezier_buf.clear
|
|
1732
1769
|
end
|
|
1733
1770
|
end
|
|
@@ -1753,7 +1790,8 @@ module Emfsvg
|
|
|
1753
1790
|
# libemf2svg's U_EMRSTRETCHDIBITS_draw uses point_cal for both size
|
|
1754
1791
|
# (cDest) and position (Dest). Match that exactly so window/viewport
|
|
1755
1792
|
# origins are applied to the dimensions too.
|
|
1756
|
-
geom = "width=\"#{fmt(cal_x(width))}\" height=\"#{fmt(cal_y(height))}\"
|
|
1793
|
+
geom = "width=\"#{fmt(cal_x(width))}\" height=\"#{fmt(cal_y(height))}\" " \
|
|
1794
|
+
"x=\"#{fmt(cal_x(x))}\" y=\"#{fmt(cal_y(y))}\""
|
|
1757
1795
|
@builder.raw("<image ")
|
|
1758
1796
|
@builder.raw(geom)
|
|
1759
1797
|
@builder.raw(" ")
|
|
@@ -1795,6 +1833,8 @@ module Emfsvg
|
|
|
1795
1833
|
# chars instead of '=' (the C base64_encode allocates length+3
|
|
1796
1834
|
# for nulls and the partial-triple bytes default to 0).
|
|
1797
1835
|
jpeg = result.pixels.to_s
|
|
1836
|
+
return "data:image/jpeg;base64,#{[jpeg].pack('m0')}" if @drawing_hint&.fetch("kind") == "jpeg"
|
|
1837
|
+
|
|
1798
1838
|
rem = jpeg.bytesize % 3
|
|
1799
1839
|
jpeg << ("\x00" * ((3 - rem) % 3)) if rem != 0
|
|
1800
1840
|
"data:image/jpg;base64,#{[jpeg].pack('m0')}"
|
|
@@ -1820,6 +1860,13 @@ module Emfsvg
|
|
|
1820
1860
|
brush = @dc.brush
|
|
1821
1861
|
return unless brush.style == Brush::SOLID
|
|
1822
1862
|
|
|
1863
|
+
if legacy_rectangle?(wire)
|
|
1864
|
+
values = @drawing_hint.fetch("values")
|
|
1865
|
+
@builder.raw(%(<rect x="#{values[0]}" y="#{values[1]}" width="#{values[2]}" ))
|
|
1866
|
+
@builder.raw(%(height="#{values[3]}" fill="#{values[4]}"/>))
|
|
1867
|
+
return
|
|
1868
|
+
end
|
|
1869
|
+
|
|
1823
1870
|
style = format("fill:#%02x%02x%02x", brush.color.red, brush.color.green, brush.color.blue)
|
|
1824
1871
|
size_x = scale_dim_x(wire.cx_dest.to_i)
|
|
1825
1872
|
size_y = scale_dim_y(wire.cy_dest.to_i)
|
|
@@ -1833,6 +1880,15 @@ module Emfsvg
|
|
|
1833
1880
|
@builder.raw("Z\" />")
|
|
1834
1881
|
end
|
|
1835
1882
|
|
|
1883
|
+
def legacy_rectangle?(wire)
|
|
1884
|
+
return false unless @drawing_hint&.fetch("kind") == "rect" && wire.dw_rop.to_i == 0x00F00021
|
|
1885
|
+
|
|
1886
|
+
values = @drawing_hint.fetch("values")
|
|
1887
|
+
geometry = [cal_x(wire.x_dest), cal_y(wire.y_dest),
|
|
1888
|
+
scale_dim_x(wire.cx_dest.to_i), scale_dim_y(wire.cy_dest.to_i)]
|
|
1889
|
+
values.first(4).map(&:to_f) == geometry && values.last == rgb_hex(@dc.brush.color)
|
|
1890
|
+
end
|
|
1891
|
+
|
|
1836
1892
|
def emit_path_polygon(points, closed:)
|
|
1837
1893
|
return if points.empty?
|
|
1838
1894
|
|
|
@@ -1849,8 +1905,8 @@ module Emfsvg
|
|
|
1849
1905
|
# Mirror libemf2svg's point*_draw which updates cur_x,cur_y on
|
|
1850
1906
|
# every emitted point — the LAST point becomes the new current
|
|
1851
1907
|
# position, used by subsequent PolyBezier/LineTo/etc.
|
|
1852
|
-
@current_x = points.last.x
|
|
1853
|
-
@current_y = points.last.y
|
|
1908
|
+
@current_x = int(points.last.x)
|
|
1909
|
+
@current_y = int(points.last.y)
|
|
1854
1910
|
return
|
|
1855
1911
|
end
|
|
1856
1912
|
|
|
@@ -1861,8 +1917,8 @@ module Emfsvg
|
|
|
1861
1917
|
d << point_str(p)
|
|
1862
1918
|
end
|
|
1863
1919
|
emit_path_element(d, closed: closed)
|
|
1864
|
-
@current_x = points.last.x
|
|
1865
|
-
@current_y = points.last.y
|
|
1920
|
+
@current_x = int(points.last.x)
|
|
1921
|
+
@current_y = int(points.last.y)
|
|
1866
1922
|
end
|
|
1867
1923
|
|
|
1868
1924
|
# Emits a <path> element matching libemf2svg's exact byte layout:
|
|
@@ -1916,13 +1972,22 @@ module Emfsvg
|
|
|
1916
1972
|
end
|
|
1917
1973
|
|
|
1918
1974
|
# Format a single point as libemf2svg's point_draw would: "X,Y "
|
|
1919
|
-
def point_str(
|
|
1920
|
-
fmt_xy(cal_x(
|
|
1975
|
+
def point_str(point)
|
|
1976
|
+
fmt_xy(cal_x(int(point.x)), cal_y(int(point.y)))
|
|
1921
1977
|
end
|
|
1922
1978
|
|
|
1923
1979
|
# libemf2svg prints colors as `#RRGGBB` always (drops alpha/reserved).
|
|
1980
|
+
# The text depends only on the three channels, so each distinct colour
|
|
1981
|
+
# is formatted once and the frozen string is shared.
|
|
1924
1982
|
def rgb_hex(color)
|
|
1925
|
-
|
|
1983
|
+
red = color.red
|
|
1984
|
+
green = color.green
|
|
1985
|
+
blue = color.blue
|
|
1986
|
+
return format("#%02X%02X%02X", red, green, blue) unless (red | green | blue).nobits?(-256)
|
|
1987
|
+
|
|
1988
|
+
@rgb_hex_cache ||= {}
|
|
1989
|
+
@rgb_hex_cache[(red << 16) | (green << 8) | blue] ||=
|
|
1990
|
+
format("#%02X%02X%02X", red, green, blue).freeze
|
|
1926
1991
|
end
|
|
1927
1992
|
|
|
1928
1993
|
# Mirror libemf2svg's clipset_draw: emits " clip-path=\"url(#clip-N)\" "
|
|
@@ -1959,19 +2024,16 @@ module Emfsvg
|
|
|
1959
2024
|
when Brush::SOLID
|
|
1960
2025
|
@builder.raw(fill_rule)
|
|
1961
2026
|
@builder.raw(fill_color_attr(brush))
|
|
1962
|
-
@last_fill_set = true
|
|
1963
2027
|
when Brush::NULL
|
|
1964
2028
|
@builder.raw("fill=\"none\" ")
|
|
1965
|
-
@last_fill_set = true
|
|
1966
2029
|
when Brush::MONOPATTERN
|
|
1967
2030
|
@builder.raw("fill=\"#img-#{brush.image_id}-ref\" ")
|
|
1968
|
-
@last_fill_set = true
|
|
1969
2031
|
else
|
|
1970
2032
|
# BS_HATCHED, BS_PATTERN, etc. — C's default branch emits ONLY the
|
|
1971
2033
|
# fill attr (fill_rule is computed but discarded).
|
|
1972
2034
|
@builder.raw(fill_color_attr(brush))
|
|
1973
|
-
@last_fill_set = true
|
|
1974
2035
|
end
|
|
2036
|
+
@last_fill_set = true
|
|
1975
2037
|
end
|
|
1976
2038
|
|
|
1977
2039
|
def fill_color_attr(brush)
|
|
@@ -2016,7 +2078,8 @@ module Emfsvg
|
|
|
2016
2078
|
when Pen::DASHDOT
|
|
2017
2079
|
@builder.raw("stroke-dasharray=\"#{fmt(dash_len)},#{fmt(dash_len)},#{fmt(dot_len)},#{fmt(dash_len)}\" ")
|
|
2018
2080
|
when Pen::DASHDOTDOT
|
|
2019
|
-
@builder.raw("stroke-dasharray=\"#{fmt(dash_len)},#{fmt(dash_len)},#{fmt(dot_len)}
|
|
2081
|
+
@builder.raw("stroke-dasharray=\"#{fmt(dash_len)},#{fmt(dash_len)},#{fmt(dot_len)}," \
|
|
2082
|
+
"#{fmt(dot_len)},#{fmt(dot_len)},#{fmt(dash_len)}\" ")
|
|
2020
2083
|
end
|
|
2021
2084
|
|
|
2022
2085
|
# Line cap (bits 8-11)
|
|
@@ -2042,7 +2105,10 @@ module Emfsvg
|
|
|
2042
2105
|
w = [width.to_i, 1].max
|
|
2043
2106
|
tmp_w = scale_dim_x(w)
|
|
2044
2107
|
if (tmp_w / @scaling) < 1.0
|
|
2045
|
-
@
|
|
2108
|
+
legacy = @drawing_hint&.fetch("kind") == "stroke" &&
|
|
2109
|
+
@drawing_hint.fetch("values").first == rgb_hex(@dc.pen.color) &&
|
|
2110
|
+
(1..4).cover?(@dc.pen.style & 0xFF)
|
|
2111
|
+
@builder.raw("stroke-width=\"#{legacy ? '1.0000' : '1px'}\" ")
|
|
2046
2112
|
else
|
|
2047
2113
|
@builder.raw("stroke-width=\"#{fmt(tmp_w)}\" ")
|
|
2048
2114
|
end
|
|
@@ -2115,7 +2181,8 @@ module Emfsvg
|
|
|
2115
2181
|
when Pen::DASHDOT
|
|
2116
2182
|
attrs["stroke-dasharray"] = "#{fmt(dash_len)},#{fmt(dash_len)},#{fmt(dot_len)},#{fmt(dash_len)}"
|
|
2117
2183
|
when Pen::DASHDOTDOT
|
|
2118
|
-
attrs["stroke-dasharray"] =
|
|
2184
|
+
attrs["stroke-dasharray"] =
|
|
2185
|
+
"#{fmt(dash_len)},#{fmt(dash_len)},#{fmt(dot_len)},#{fmt(dot_len)},#{fmt(dot_len)},#{fmt(dash_len)}"
|
|
2119
2186
|
end
|
|
2120
2187
|
|
|
2121
2188
|
# Line cap (bits 8-11)
|
|
@@ -2204,7 +2271,8 @@ module Emfsvg
|
|
|
2204
2271
|
end
|
|
2205
2272
|
dx_scaled = matrix.dx * sf_x * @scaling
|
|
2206
2273
|
dy_scaled = matrix.dy * sf_y * @scaling
|
|
2207
|
-
@builder.rawln("<g transform=\"matrix(#{fmt(matrix.m11)} #{fmt(matrix.m12)} #{fmt(matrix.m21)}
|
|
2274
|
+
@builder.rawln("<g transform=\"matrix(#{fmt(matrix.m11)} #{fmt(matrix.m12)} #{fmt(matrix.m21)} " \
|
|
2275
|
+
"#{fmt(matrix.m22)} #{fmt(dx_scaled)} #{fmt(dy_scaled)})\">")
|
|
2208
2276
|
@open_groups += 1
|
|
2209
2277
|
end
|
|
2210
2278
|
|