Module: Emfsvg::Translation::DecimalDetector

Defined in:
lib/emfsvg/translation/decimal_detector.rb

Overview

Walks an Svg::Document tree to decide whether any coordinate attribute has a non-integer value. If yes, the renderer installs Scaler::Fixed and emits MapMode scaling records so the decimals survive the int32 truncation in EMF.

Also detects overflow: if any decimal coord is so large that scaling would overflow int32, the renderer falls back to Identity (accepting the truncation for that fixture).

Class Method Summary collapse

Class Method Details

.any_decimal?(element) ⇒ Boolean

---- private ----

Returns:

  • (Boolean)


26
27
28
29
30
31
32
33
34
35
36
# File 'lib/emfsvg/translation/decimal_detector.rb', line 26

def any_decimal?(element)
  return false unless element

  element_attrs = decimal_attrs_for(element)
  return true if element_attrs.any? { |attr| has_decimal?(element.send(attr)) }

  path = path_commands(element)
  return true if path && path.any? { |cmd| cmd.args.any? { |a| a.is_a?(Float) && a != a.to_i } }

  element.children.any? { |c| any_decimal?(c) }
end

.any_unsafe?(element, scaler) ⇒ Boolean

Returns:

  • (Boolean)


38
39
40
41
42
43
44
45
46
47
48
# File 'lib/emfsvg/translation/decimal_detector.rb', line 38

def any_unsafe?(element, scaler)
  return false unless element

  element_attrs = decimal_attrs_for(element)
  return true if element_attrs.any? { |attr| unsafe_value?(element.send(attr), scaler) }

  path = path_commands(element)
  return true if path && path.any? { |cmd| cmd.args.any? { |a| scaler.unsafe?(a) } }

  element.children.any? { |c| any_unsafe?(c, scaler) }
end

.decimal_attrs_for(element) ⇒ Object

Coordinate-bearing attributes per element class. Returns [] for elements with no coords (Group, Defs, etc).



52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/emfsvg/translation/decimal_detector.rb', line 52

def decimal_attrs_for(element)
  case element
  when Svg::Elements::Rect then %i[x y width height rx ry]
  when Svg::Elements::Ellipse then %i[cx cy rx ry]
  when Svg::Elements::Circle then %i[cx cy r]
  when Svg::Elements::Line then %i[x1 y1 x2 y2]
  when Svg::Elements::Polyline, Svg::Elements::Polygon then [:points]
  when Svg::Elements::Path then [:commands]
  when Svg::Elements::Text then %i[x y font_size]
  when Svg::Elements::Image then %i[x y width height]
  else []
  end
end

.extract_floats(str) ⇒ Object



107
108
109
# File 'lib/emfsvg/translation/decimal_detector.rb', line 107

def extract_floats(str)
  str.to_s.scan(/-?\d+(?:\.\d+)?/).map(&:to_f)
end

.has_decimal?(value) ⇒ Boolean

value may be: Float, Integer, Array of [x,y] pairs, Array of PathData::Command, or nil.

Returns:

  • (Boolean)


74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/emfsvg/translation/decimal_detector.rb', line 74

def has_decimal?(value)
  return false if value.nil?

  case value
  when Float then !value.finite? || (value != value.to_i)
  when Integer then false
  when Array
    value.any? { |v| pair_or_command?(v) ? has_decimal?(v) : v.is_a?(Float) && v != v.to_i }
  when String then value.match?(/\.\d/)
  else false
  end
end

.needs_scaling?(document) ⇒ Boolean

Returns:

  • (Boolean)


16
17
18
# File 'lib/emfsvg/translation/decimal_detector.rb', line 16

def needs_scaling?(document)
  any_decimal?(document.root_element)
end

.pair_or_command?(value) ⇒ Boolean

Returns:

  • (Boolean)


87
88
89
# File 'lib/emfsvg/translation/decimal_detector.rb', line 87

def pair_or_command?(value)
  value.is_a?(Array) || value.is_a?(Svg::PathData::Command)
end

.path_commands(element) ⇒ Object



66
67
68
69
70
# File 'lib/emfsvg/translation/decimal_detector.rb', line 66

def path_commands(element)
  return nil unless element.is_a?(Svg::Elements::Path)

  element.commands
end

.path_unsafe?(path_str, scaler) ⇒ Boolean

Returns:

  • (Boolean)


103
104
105
# File 'lib/emfsvg/translation/decimal_detector.rb', line 103

def path_unsafe?(path_str, scaler)
  extract_floats(path_str).any? { |f| scaler.unsafe?(f) }
end

.safe_for_fixed_scale?(document, scaler) ⇒ Boolean

Returns:

  • (Boolean)


20
21
22
# File 'lib/emfsvg/translation/decimal_detector.rb', line 20

def safe_for_fixed_scale?(document, scaler)
  !any_unsafe?(document.root_element, scaler)
end

.unsafe_value?(value, scaler) ⇒ Boolean

Returns:

  • (Boolean)


91
92
93
94
95
96
97
98
99
100
101
# File 'lib/emfsvg/translation/decimal_detector.rb', line 91

def unsafe_value?(value, scaler)
  return false if value.nil?

  case value
  when Float then scaler.unsafe?(value)
  when Integer then false
  when Array then value.any? { |v| unsafe_value?(v, scaler) }
  when String then extract_floats(value).any? { |f| scaler.unsafe?(f) }
  else false
  end
end