Clipper2 brings robust 2D polygon clipping and offsetting to Ruby. It wraps Angus Johnson's Clipper2 C export API with FFI and keeps all native geometry on a quantized signed-integer grid.
The gem supports union, intersection, difference, XOR, open paths, polygon and line offsets, rectangle clipping, Minkowski sums, point containment, path simplification, and dependency-free Earcut triangulation into GPU-ready buffers.
Add the gem to your bundle:
bundle add clipper2Or install it directly:
gem install clipper2Platform gems contain a precompiled shared library. When a platform gem is unavailable, the
source gem builds the pinned Clipper2 C++17 sources with Ruby's standard extension toolchain and
therefore requires a C++ compiler and make. CMake is used by maintainers to build platform gems.
Ruby callers use ordinary numeric coordinates. Path quantizes them to signed 64-bit integers
using a scale and restores floats when enumerated:
require "clipper2"
Clipper2.default_scale = 1_000 # 0.001 coordinate-unit precision
path = Clipper2::Path[[0, 0], [10, 0], [10, 10], [0, 10]]
path.to_a # => [[0.0, 0.0], [10.0, 0.0], [10.0, 10.0], [0.0, 10.0]]
path.area # => 100.0 (signed; positive is counter-clockwise)
path.orientation # => :ccw
path.bounds # => [0.0, 0.0, 10.0, 10.0]Every Paths collection owns one scale. Operations between different scales raise
Clipper2::ScaleError; coordinates that cannot be represented safely raise
Clipper2::RangeError. Choose a scale that gives the precision you need without making
coordinate * scale unnecessarily large.
All convenience methods accept a Path, Paths, one raw point array, or an array of raw paths,
and return Clipper2::Paths:
a = Clipper2::Path[[0, 0], [10, 0], [10, 10], [0, 10]]
b = Clipper2::Path[[5, 0], [15, 0], [15, 10], [5, 10]]
Clipper2.union(a, b)
Clipper2.intersect(a, b, fill_rule: :non_zero)
Clipper2.difference(a, b)
Clipper2.xor(a, b)Fill rules are :even_odd, :non_zero, :positive, and :negative. Clipper2 intentionally
handles self-intersecting input according to the selected fill rule.
For open subject paths, use the general API. It returns closed and open solutions separately:
line = Clipper2::Path[[-5, 5], [15, 5]]
closed, open = Clipper2.boolean(:intersection, [], a, subject_open: line)grown = Clipper2.inflate(a, 2.5, join: :round, end_type: :polygon)
shrunk = Clipper2.inflate(a, -1.0)
open_line = Clipper2::Path[[0, 0], [10, 0]]
stroke = Clipper2.inflate(open_line, 3.0, join: :round, end_type: :round)
inside = Clipper2.rect_clip(grown, [0, 0, 12, 12])
line_segments = Clipper2.rect_clip_lines(open_line, [2, -1, 8, 1])Join types are :square, :bevel, :round, and :miter. End types are :polygon, :joined,
:butt, :square, and :round. arc_tolerance defaults to 0.25 public coordinate units and
is scaled automatically.
Minkowski sums and differences use the same integer path representation:
pattern = Clipper2::Path[[0, 0], [1, 0], [1, 1], [0, 1]]
Clipper2.minkowski_sum(pattern, a)
Clipper2.minkowski_difference(pattern, a)path.contains?([5, 5]) # boundary points are included
path.reverse
path.simplify(0.05)
path.to_packed # little-endian float32 x/y pairsPaths#simplify applies the same tolerance to each member.
Clipper2.triangulate uses the bundled pure Ruby Earcut implementation. Counter-clockwise rings
are outer contours and clockwise rings contained by an outer contour are holes:
outer = Clipper2::Path[[0, 0], [10, 0], [10, 10], [0, 10]]
hole = Clipper2::Path[[2, 2], [2, 8], [8, 8], [8, 2]] # clockwise
mesh = Clipper2.triangulate(Clipper2::Paths[outer, hole])
mesh.indices # little-endian packed u32 triangle indices
mesh.vertices # little-endian packed float32 x/y vertices
mesh.index_count
mesh.vertex_count
indices, vertices = mesh # array destructuring is also supportedEvery emitted triangle is counter-clockwise in a y-up coordinate system. The two packed strings
can be uploaded directly by renderers that accept raw buffers. See
examples/rugl_mesh.rb and
examples/larb_transform.rb.
Empty inputs return empty Paths or an empty Triangulation. Invalid options and rectangles
raise ArgumentError; scale, range, codec, triangulation, and native failures use subclasses of
Clipper2::Error.
Native arrays are always copied once into Ruby and released through Clipper2's matching
DisposeArray64 export.
Ruby 3.2 or newer, CMake, and a C++17 compiler are required.
bundle install
bundle exec rake # generated-binding check, native build, and specs
bundle exec rake native:build # native library only
bundle exec rake native:gem # platform gem in pkg/
bundle exec rake build # source gem in pkg/ext/clipper2/generate_bindings.rb derives enum values and export names from the vendored headers.
Run bundle exec rake bindings:generate after updating Clipper2 and commit the generated Ruby file.
The SVG boolean demo is available at examples/svg_boolean.rb.
The Ruby wrapper is MIT licensed. Vendored Clipper2 2.0.1 is distributed under the Boost Software
License 1.0, and the Ruby Earcut port carries Mapbox's ISC license. The corresponding notices are
included in LICENSE.txt, licenses/CLIPPER2-BSL-1.0.txt, and licenses/EARCUT-ISC.txt.