#!/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 and direction. a = Math::PI / 13 * (ARGV.size > 0 ? -1 : 1) # 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 = [] cursor_x = cursor_y = cx = cy = 0 ARGF.each_line{|line| line.each_grapheme_cluster{|c| if (s = c.ord) < 33 cursor_x += s == 32 ? 1 : s == 9 ? 8 - cursor_x % 8 : s == 10 ? -cursor_x : 0 cursor_y += s == 10 ? 1 : 0 else data += [[cursor_y, cursor_x, c]] cx += cursor_x cy += cursor_y cursor_x += 1 end } } # Only process input if there are non-whitespace characters. if (s = data.size) > 0 # Compute center of mass. # # We rotate by center of mass instead of by center of bounding box. # The latter would have been more straightforward to calculate, but # also tend to be more unstable depending on input shape. In # comparison, if we rotate by center of mass, we will often be able # to recover the original text by reversing the rotation direction. # # The down side is that the rotation center is now less predictable, # but it really only mattered if we are concerned about reversing # rotation. cx /= s cy /= s # Apply transformations. min_y, min_x = data[0] rx = Math::tan(a / 2) ry = Math::sin(a) data.map!{|t| # Center contents. x = t[1] - cx y = t[0] - cy # Shear in X direction. # [1 -tan(A/2) * [x # 0 1 ] y] x -= (rx * y).round # Shear in Y direction. # [1 0 * [x # sin(A) 1] y] y += (ry * x).round # Shear in X direction again. # [1 -tan(A/2) * [x # 0 1 ] y] x -= (rx * y).round # Update upper left corner of bounding box. min_x = [min_x, x].min min_y = [min_y, y].min [y, x, t[2]] } # Reassemble output. cursor_x = min_x cursor_y = min_y data.sort.each{|t| y, x = t cursor_x = cursor_y < y ? min_x : cursor_x print "\n" * (y - cursor_y), " " * (x - cursor_x), t[2] cursor_y = y cursor_x = x + 1 } # Always end with newline. print "\n" end