#!/usr/bin/ruby -w # https://www.ocf.berkeley.edu/~fricke/projects/israel/paeth/rotation_by_shearing.html # 1. Shear in X by -tan(angle/2) # 2. Shear in Y by sin(angle) # 3. Shear in X by -tan(angle/2) # Rotation angle. A = Math::PI / 13 # Load input as a list of (y,x,grapheme) tuples. # # This is expensive in terms of memory use. A different way to do it would # be to maintain input as a list of lines, and adjust leading whitespaces to # do all the transforms, but it's easier to do the transformations and # spacing adjustments as separate passes. data = [] min_x = nil max_x = nil min_y = nil max_y = nil cursor_x = 0 cursor_y = 0 ARGF.each_line{|line| line.each_grapheme_cluster{|c| case c when " " cursor_x += 1 when "\n" cursor_y += 1 cursor_x = 0 when "\t" cursor_x += 8 - (cursor_x & 7) when "\0".."\x1f" # All other control characters are silently dropped. else data += [[cursor_y, cursor_x, c]] if not min_x then min_x = max_x = cursor_x min_y = max_y = cursor_y else min_x = [min_x, cursor_x].min max_x = [max_x, cursor_x].max max_y = [max_y, cursor_y].max # Don't need to update min_y since we can't go backwards. end cursor_x += 1 end } } # Output nothing if there are no non-whitespace characters. if not min_x exit 0 end # Center contents. shift_x = max_x + min_x >> 1 shift_y = max_y + min_y >> 1 # Apply transformations. min_x = nil min_y = nil data.map!{|t| # Center contents. x = t[1] - shift_x y = t[0] - shift_y # Shear in X direction. # [1 -tan(A/2) * [x # 0 1 ] y] x1 = x - (Math::tan(A / 2) * y).round # Shear in Y direction. # [1 0 * [x # sin(A) 1] y] y1 = (Math::sin(A) * x1).round + y # Shear in X direction again. # [1 -tan(A/2) * [x # 0 1 ] y] x = x1 - (Math::tan(A / 2) * y1).round y = y1 # Update upper left corner of bounding box. if min_x min_x = [min_x, x].min min_y = [min_y, y].min else min_x = x min_y = y end [y, x, t[2]] } # Reassemble output. cursor_x = min_x cursor_y = min_y data.sort.each{|t| if cursor_y < t[0] print "\n" * (t[0] - cursor_y) cursor_y = t[0] cursor_x = min_x end print " " * (t[1] - cursor_x), t[2] cursor_x = t[1] + 1 } # Always end with newline. print "\n"