Class: Emfsvg::Svg::Stroke

Inherits:
Object
  • Object
show all
Defined in:
lib/emfsvg/svg/stroke.rb

Overview

Parsed SVG stroke style: color, width, dash pattern, line cap, line join. Maps directly to the EMF LOGPEN / EXTLOGPEN32 fields.

Pen style bits (per MS-WMF 2.1.1.8 / emr_visitor.rb mirror):

PS_SOLID=0, PS_DASH=1, PS_DOT=2, PS_DASHDOT=3, PS_DASHDOTDOT=4,
PS_NULL=5

bits 8-11: PS_ENDCAP_ROUND=0x0000, _SQUARE=0x0100, _FLAT=0x0200
bits 12-15: PS_JOIN_ROUND=0x0000, _BEVEL=0x1000, _MITER=0x2000

emfsvg's EMF→SVG renderer emits stroke-width="1px" (with the px suffix) AND no linecap/linejoin attrs as the fallback for a NULL pen with a solid brush. When the SVG→EMF side sees that exact form, it round-trips back to a NULL pen to preserve the rendering.

Constant Summary collapse

END_CAP_BITS =
{ "butt" => 0x0200, "round" => 0x0000, "square" => 0x0100 }.freeze
LINE_JOIN_BITS =
{ "miter" => 0x2000, "round" => 0x0000, "bevel" => 0x1000 }.freeze
NULL_PEN_WIDTH_SENTINEL =
"1px"
PS_GEOMETRIC =
0x00010000

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(paint:, width: 1.0, dash_array: nil, line_cap: "round", line_join: "round", raw_width: nil, line_cap_explicit: false, line_join_explicit: false) ⇒ Stroke

Returns a new instance of Stroke.



28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/emfsvg/svg/stroke.rb', line 28

def initialize(paint:, width: 1.0, dash_array: nil, line_cap: "round",
               line_join: "round", raw_width: nil,
               line_cap_explicit: false, line_join_explicit: false)
  @paint = paint
  @width = width.to_f
  @dash_array = dash_array
  @line_cap = line_cap
  @line_join = line_join
  @raw_width = raw_width
  @line_cap_explicit = line_cap_explicit
  @line_join_explicit = line_join_explicit
end

Instance Attribute Details

#dash_arrayObject (readonly)

Returns the value of attribute dash_array.



25
26
27
# File 'lib/emfsvg/svg/stroke.rb', line 25

def dash_array
  @dash_array
end

#line_capObject (readonly)

Returns the value of attribute line_cap.



25
26
27
# File 'lib/emfsvg/svg/stroke.rb', line 25

def line_cap
  @line_cap
end

#line_cap_explicitObject (readonly)

Returns the value of attribute line_cap_explicit.



25
26
27
# File 'lib/emfsvg/svg/stroke.rb', line 25

def line_cap_explicit
  @line_cap_explicit
end

#line_joinObject (readonly)

Returns the value of attribute line_join.



25
26
27
# File 'lib/emfsvg/svg/stroke.rb', line 25

def line_join
  @line_join
end

#line_join_explicitObject (readonly)

Returns the value of attribute line_join_explicit.



25
26
27
# File 'lib/emfsvg/svg/stroke.rb', line 25

def line_join_explicit
  @line_join_explicit
end

#paintObject (readonly)

Returns the value of attribute paint.



25
26
27
# File 'lib/emfsvg/svg/stroke.rb', line 25

def paint
  @paint
end

#raw_widthObject (readonly)

Returns the value of attribute raw_width.



25
26
27
# File 'lib/emfsvg/svg/stroke.rb', line 25

def raw_width
  @raw_width
end

#widthObject (readonly)

Returns the value of attribute width.



25
26
27
# File 'lib/emfsvg/svg/stroke.rb', line 25

def width
  @width
end

Class Method Details

.parse(stroke:, width:, dash_array:, line_cap:, line_join:) ⇒ Object



66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/emfsvg/svg/stroke.rb', line 66

def self.parse(stroke:, width:, dash_array:, line_cap:, line_join:)
  new(
    paint: Paint.from_stroke(stroke),
    width: parse_width(width),
    dash_array: parse_dash_array(dash_array),
    line_cap: line_cap || "round",
    line_join: line_join || "round",
    raw_width: width,
    line_cap_explicit: !line_cap.nil?,
    line_join_explicit: !line_join.nil?
  )
end

.parse_dash_array(value) ⇒ Object



99
100
101
102
103
104
# File 'lib/emfsvg/svg/stroke.rb', line 99

def self.parse_dash_array(value)
  return nil if value.nil? || value.strip == "none" || value.strip.empty?

  parts = value.split(/[\s,]+/).map(&:to_f)
  parts.empty? ? nil : parts
end

.parse_width(value) ⇒ Object



79
80
81
82
83
84
# File 'lib/emfsvg/svg/stroke.rb', line 79

def self.parse_width(value)
  return 1.0 if value.nil? || value.strip.empty?

  match = value.match(/\A(-?\d+(?:\.\d+)?)/)
  match ? match[1].to_f : 1.0
end

Instance Method Details

#dash_styleObject

SVG dasharray is a comma/space separated list of lengths; "none" means solid. We map the first length to a discrete dash style: a single length matching roughly the width → PS_DOT, longer → PS_DASH. Anything more complex is approximated as PS_DASH for v1.



90
91
92
93
94
95
96
97
# File 'lib/emfsvg/svg/stroke.rb', line 90

def dash_style
  return 0 if dash_array.nil? || dash_array.empty?

  first = dash_array.first
  return 0 if first.zero?

  first < (width * 2) ? 2 : 1 # PS_DOT=2, PS_DASH=1
end

#null_pen_sentinel?Boolean

Returns:

  • (Boolean)


41
42
43
44
45
# File 'lib/emfsvg/svg/stroke.rb', line 41

def null_pen_sentinel?
  raw_width == NULL_PEN_WIDTH_SENTINEL &&
    !line_cap_explicit &&
    !line_join_explicit
end

#pen_styleObject

Pen style bitmask (full 32 bits). Includes:

* dash style (bits 0-7)
* end-cap (bits 8-11)
* line-join (bits 12-15)
* pen type GEOMETRIC (bit 16) — set when width > 1, so
emfsvg's EMF→SVG renderer uses the actual pen.width
instead of hardcoding 1.


56
57
58
59
60
61
62
63
64
# File 'lib/emfsvg/svg/stroke.rb', line 56

def pen_style
  base = if paint.null? || null_pen_sentinel?
           5 # PS_NULL
         else
           dash_style
         end
  type_bit = width > 1.0 && !paint.null? ? PS_GEOMETRIC : 0
  base | type_bit | END_CAP_BITS.fetch(line_cap, 0x0000) | LINE_JOIN_BITS.fetch(line_join, 0x0000)
end