class Text

Represents a Text with a font

Constants

FONT_DIRECTORIES

Attributes

draw[R]

Public Class Methods

available_fonts() click to toggle source

def debug_output

out = @paths.map.with_index do |chain,i|
  s = ""
  s << '# ' if i != 0

  chain.each { |p_i| s << "translate(#{@points[p_i].to_s}) cube(1);\n" }
  s
end

out.each { |o| puts o, "/"*30 }
exit

end

# File lib/openscad-text/text.rb, line 184
def self.available_fonts
  FONT_DIRECTORIES.map do |dir|
    Dir[dir + "/**/*.ttf"]
  end.flatten!
end
new(text, font_path=nil) click to toggle source
# File lib/openscad-text/text.rb, line 11
def initialize(text, font_path=nil)
  @text = text

  # setup the draw
  @draw = Draw.new
  @draw.font = font_path || Text.available_fonts.sample
  @draw.gravity = CenterGravity
  @draw.pointsize = 128
end

Public Instance Methods

to_openscad() click to toggle source
# File lib/openscad-text/text.rb, line 149
def to_openscad
  @points = []
  @paths  = []

  # draw an image of the text and create a matrix from its pixels
  @matrix = create_image.pixel_matrix

  # go through each point aka pixel to make sure it gets used once
  # and try to retrace the letters
  @matrix.each_with_index do |_,x,y|
    create_pixel_chain(Vector[x,y])
  end

  # align them!
  align_points

  # finished woop woop
  "polygon(points=#{@points.map(&:to_a).to_s}, paths=#{@paths.to_s});"
end

Private Instance Methods

align_points() click to toggle source

aligns the text to the bottom-left corner of the first quadrant

# File lib/openscad-text/text.rb, line 139
def align_points
  x_min = @points.map { |p| p[0] }.min
  y_min = @points.map { |p| p[1] }.min

  vec = Vector[x_min,y_min]

  @points.map! { |p| p - vec }
end
create_image() click to toggle source
# File lib/openscad-text/text.rb, line 21
def create_image
  # calculate and store the image dimensions
  dimensions = @draw.get_type_metrics @text
  @x = dimensions.width.ceil 
  @y = dimensions.height.ceil

  # create the image and draw text
  @image  = Image.new(@x, @y) { self.background_color = 'white' }
  @draw.annotate(@image, *[0]*4, @text)

  @image
end
create_pixel_chain(current_point) click to toggle source

starting with point(x,y), try to create a path (or chain) until the starting point is reached again

# File lib/openscad-text/text.rb, line 119
def create_pixel_chain(current_point)
  # can't create a chain if the point is invalid
  return if point_invalid?(current_point)

  # create a new ary in the paths ary
  @paths << []

  while current_point
    # add the point to the points array
    @points << current_point

    # add the index of the last point in points aka current_point to faces
    @paths.last << @points.count - 1

    # try to find next point (nil if none was found)
    current_point = find_next_point(current_point)
  end
end
find_direct_neighbours(point) click to toggle source
# File lib/openscad-text/text.rb, line 55
def find_direct_neighbours(point)
  # vecs to the 4 non-daigonal points
  vecs = [
    Vector[-1, 0],
    Vector[ 1, 0],
    Vector[ 0, 1],
    Vector[ 0,-1]
  ]

  neighbours = vecs.map { |vec| point + vec }
  neighbours.delete_if { |point| point.any? { |i| i < 0 } }
end
find_next_point(current) click to toggle source

finds next point in chain from current point

# File lib/openscad-text/text.rb, line 69
def find_next_point(current)
  # 8 vectors turned counter-clockwise 45 degrees each
  vecs = [
    Vector[-1, 0],
    Vector[-1,-1],
    Vector[ 0,-1],
    Vector[ 1,-1],
    Vector[ 1, 0],
    Vector[ 1, 1],
    Vector[ 0, 1],
    Vector[-1, 1],
  ]

  # color of the last pixel
  last_color = @matrix[*current+vecs.last]

  # turn the vector and find each which touches a white pixel
  touchy_vecs = vecs.map.with_index do |vec,i|
    current_color = @matrix[*current+vec]
    color_changed = current_color != last_color
    last_color = current_color

    # return a vec or nil
    if color_changed
      # return the black point of the two touching the borderline
      if current_color == :black
        vec
      else
        # the one before vec, if i==0 the last one is the one before
        i-1 >= 0 ? vecs[i-1] : vecs.last
      end
    end
  end

  #remove nil(s) and duplicates
  touchy_vecs.compact!
  touchy_vecs.uniq!

  # possible next points
  touchy_points = touchy_vecs.map { |vec| current + vec }

  # remove the invalid ones
  touchy_points.delete_if { |point| point_invalid? point }

  # return the next point or nil
  touchy_points[0]
end
point_invalid?(point) click to toggle source

checks if a point is already in the points ary aka if it has already been used and also if a point is surrounded by too many other black points

# File lib/openscad-text/text.rb, line 37
def point_invalid?(point)
  # white points are always invalid
  return true if @matrix[*point] == :white

  # point already taken
  return true if @points.any? { |p| p == point }

  # if not already taken border points are always valid
  return false if point[0] == 0 or point[1] == 0 or point[0] == @x or point[1] == @y

  # if all non-diagonal neighbours are black, the point must be invalid
  neighbours = find_direct_neighbours(point)
  return true if neighbours.count == 4 and neighbours.all? { |neighbour| @matrix[*neighbour] == :black }

  # point is valid
  false
end