> show canvas only <


/* built with Studio Sketchpad: 
 *   https://sketchpad.cc
 * 
 * observe the evolution of this sketch: 
 *   https://studio.sketchpad.cc/sp/pad/view/ro.E0ohiB$D7c6/rev.5181
 * 
 * authors: 
 *   John Conwell

 * license (unless otherwise specified): 
 *   creative commons attribution-share alike 3.0 license.
 *   https://creativecommons.org/licenses/by-sa/3.0/ 
 */ 



/*
*    NOTE: !!!!Its working now, YAY!!!
*
*    This is a rough port of the Processing library of the same name
*    written by Michael Ogawa (http://code.google.com/p/wordookie/)
*
*    Usage: you only need to do two things if you want to change things
*        First: change the TEXT_ALIGNMENT variable below to one of
*        the 6 options.
*        Second: change the json object to include the words you want
*        to be Wordookied.  The weight is just a relative weight that 
*        gets normalized, so use any weight that makes sense to you.
*        The type key isnt used yet.
*
*    Another Note: currently it tried  500 times to randomly pick
*    a blank place to write a word.  After 500 tries, it just slaps
*    it in, otherwise it could run for a long time
*/

var ANYWAY = 1;
var HORIZONTAL = 2;
var MOSTLY_HORIZONTAL = 3;
var HALF_AND_HALF = 4;
var MOSTLY_VERTICAL = 5;
var VERTICAL = 6;

int minFontSize = 30;
int maxFontSize = 70;
color backgroundColor = color(0);
color fontColor = color(204, 102, 0);
var fontName = "FFScala.ttf";
PFont wordFont;

var TEXT_ALIGNMENT = MOSTLY_HORIZONTAL;

var wordList = {"words": [
        {"text": "one",  "weight": "70",  "type": "2"},
        {"text": "two",  "weight": "40",  "type": "1"},
        {"text": "three",  "weight": "60",  "type": "2"},
        {"text": "four",  "weight": "45",  "type": "1"},
        {"text": "five",  "weight": "40",  "type": "2"},
        {"text": "six",  "weight": "25",  "type": "1"},
        {"text": "seven",  "weight": "70",  "type": "2"},
        {"text": "eight",  "weight": "40",  "type": "1"},
        {"text": "nine",  "weight": "60",  "type": "2"},
        {"text": "ten",  "weight": "45",  "type": "1"},
        {"text": "eleven",  "weight": "40",  "type": "2"},
        {"text": "twelve",  "weight": "25",  "type": "1"},
        {"text": "thirteen",  "weight": "70",  "type": "2"},
        {"text": "fourteen",  "weight": "40",  "type": "1"},
        {"text": "fifteen",  "weight": "60",  "type": "2"},
        {"text": "sixteen",  "weight": "45",  "type": "1"},
        {"text": "seventeen",  "weight": "40",  "type": "2"},
        {"text": "eighteen",  "weight": "25",  "type": "1"},                
    ]
};

PFont font;
PGraphics buffer;

void setup() {  // this is run once.   
    //width, height
    size(700, 600); 

    // limit the number of frames per second
    frameRate(30);

    wordFont = loadFont("Meta-Bold.ttf");
    
    //setup graphics buffer
    buffer = createGraphics(700, 600, JAVA2D);
    buffer.background(backgroundColor);
    buffer.smooth();
    buffer.textMode(SCREEN);

    wordList.nextWordToDraw = 0;
    parseMinMaxWeights(wordList);    
    println(wordList.minWeight + ", " + wordList.maxWeight);
} 

void draw() {  // this is run repeatedly.
    var index = wordList.nextWordToDraw;
    if (int(index) < wordList.words.length) {
        var word = wordList.words[index];        
        calcLayoutOfWord(word, TEXT_ALIGNMENT);        
        buffer.fill(fontColor);
        paintWord(word, buffer);
        image(buffer, 0, 0);
        wordList.nextWordToDraw = index + 1;    
    }
    else {
        println("finished Wordookie layout");
        noLoop();
    }
}

void paintWord(Word word, PGraphics graphics) {
    graphics.beginDraw();    
    graphics.pushMatrix();    
    graphics.translate(word.x, word.y);
    graphics.rotate(word.angle);
    graphics.textFont(wordFont, word.fontSize);
    graphics.textAlign(CENTER, CENTER);    
    graphics.text(word.text, 0, 0);
    graphics.popMatrix();
    graphics.endDraw();
}

void parseMinMaxWeights(words) {
    var minWeight = 9999999;
    var maxWeight = -9999999;
      
    for(var i = 0; i< words.words.length; i++){        
        // update min/max weights
        var weight = words.words[i].weight;
        if ( weight < minWeight ) 
            minWeight = weight;
        if ( weight > maxWeight ) 
            maxWeight = weight;
    }
 
     //sort list largest to smallest to have better chance of finding a 
     //spot for big words
     words.words.sort(sortByWeight);
 
     words.minWeight = minWeight;
     words.maxWeight = maxWeight;
}

var centerPoint = new Object;
void calcLayoutOfWord(word, angleType) {        
    //set font size
    word.fontSize = int(map(word.weight, wordList.minWeight, wordList.maxWeight, minFontSize, maxFontSize));
    buffer.textFont(wordFont, word.fontSize);        
    //position the word
    makeInitialPosition(word, angleType);
    centerPoint.x = int(word.x);
    centerPoint.y = int(word.y);
    int tries = 1;
    while (intersects(word)) {
        tries++;
        makeInitialPosition(word);
        if (tries == 500)
            break;
    }
    println("Found spot for " + word.text + " in " + tries + " tries");
}

void makeInitialPosition(word, angleType) {
    //set the word angle
    word.angle = generateAngle(angleType);

    //set word height and width
    var dim = wordHalfDimensions(word);

    float x, y;
    do {
        x = (float)rnd_snd() * width/8 + width/2;
        y = (float)rnd_snd() * height/8 + height/2;
    }
    
    while( x > width - dim.width || x < dim.width
        || y < dim.height || y > height - dim.height );

    word.x = (float)x;
    word.y = (float)y;
}

int generateAngle(angleType) {
    switch(angleType) {
        case ANYWAY:
            return (float)( Math.random() * PI - HALF_PI );
        case HORIZONTAL:
            return 0f;
        case MOSTLY_HORIZONTAL:
            if ( Math.random() < 0.75 )
                return 0f;
            else
                return -HALF_PI;
        case HALF_AND_HALF:
            if ( Math.random() < 0.5 )
                return 0f;
            else
                return -HALF_PI;
        case MOSTLY_VERTICAL:
            if ( Math.random() < 0.25 )
                return 0f;
            else
                return -HALF_PI;
        case VERTICAL:
                return -HALF_PI;
    }
    return 0f;
}

Object wordHalfDimensions(Word word) {
    buffer.textFont(wordFont, word.fontSize);
    float wWidth = buffer.textWidth(word.text) / 2 + 1;
    float wHeight = buffer.textAscent();

    // tilted word dimensions
    float aAngle = word.angle;
    float bAngle = word.angle > HALF_PI ? word.angle - HALF_PI : HALF_PI - word.angle;
    float cosA = abs( cos( aAngle ) );
    float cosB = abs( cos( bAngle ) );
    float sinA = abs( sin( aAngle ) );
    float sinB = abs( sin( bAngle ) );
    float e = wWidth * cosA;
    float f = wHeight * cosB;
    float h = wWidth * sinA;
    float g = wHeight * sinB;
    float aWidth = e + f;
    float aHeight = h + g;
    
    var dim = new Object;
    dim.width = int(aWidth);
    dim.height = int(aHeight);
    return dim;
}

boolean intersects(Word word) {        
    // test each pixel
    // optimize by searching within new word's bounding box
    var dim = wordHalfDimensions(word);
    int xStart, xEnd, yStart, yEnd;
    int sideBuffer = 3;
    xStart = (int)( word.x - dim.width - sideBuffer );
    xEnd = (int)( word.x + dim.width + sideBuffer );
    yStart = (int)( word.y - dim.height - sideBuffer );
    yEnd = (int)( word.y + dim.height + sideBuffer );

    // sanitize bounds
    yStart = max( yStart, 0 );
    xStart = max( xStart, 0 );
    yEnd = min( yEnd, buffer.height );
    xEnd = min( xEnd, buffer.width );

    // test each pixel for overlap
    // note: using pixels[] is faster than get()
    buffer.loadPixels();

    for( int y = yStart; y < yEnd; y++ ) {
        for( int x = xStart; x < xEnd; x++ ) {
            color c1 = buffer.pixels[ y * buffer.width + x ];            
            if (c1 != backgroundColor) {
                return true;
            }
        }
    }

    return false;        // no intersection found
}

/*
*    returns an approximation of random.nextGaussian, where the mean is 0,
*    and std dev of 1
*/
function rnd_snd() {
        return (Math.random()*2-1)+(Math.random()*2-1)+(Math.random()*2-1);
}

/*
*    sort words largest to smallest
*/
function sortByWeight(word1, word2) {
    return word2.weight - word1.weight;
}