Monday, September 1, 2014

Making Up Words

In addition to being a software engineer and a web comic author, I also create new words. Well, not with any sort of legitimacy I suppose. As far as I know, none of the words I've made up have ever found their way into the dictionary.

The title of today's comic is one such example.

"Verdosynthesis"

Much like photosynthesis (the suggested technology in the comic) means comes from the roots "photo" and "synthesis" to mean creation of something from light, verdosynthesis is to mean the creation of something from greenness. But in this case, I am talking about the 21st century American definition of green which is of course best translated as "environmentally friendly."

Why did I use "verdo" as the first part of this word? You may be familiar with the Spanish word verde which translates to green in English. But the Spanish (and Italian) word traces back to the Latin viridis which was most likely from an even older root word that meant "growing plant." The same Latin root also carried forward into Old French as vert (which is the same word for green as in modern French - some things never change). From here, we got verdure in English which was used to describe a fresh, green color. But no one has really used that word since the 15th century. Words starting with verd- as an indication of something being green are not common in English today. There is verdigris, but that's used almost exclusively in chemistry (the green color of oxidized copper - like the Statue of Liberty) and not in the vernacular. For whatever reason, modern English uses the word green which I believe has Germanic roots.

The next question you should be asking yourself is why I know any of this. I guess you would say that I have an unusual interest in etymology. I say unusual because, seriously, how many people have an interest in etymology? Twelve? Maybe fifteen?

Also, don't confuse etymology with entomology. One is the study of word origins and the other is the study of insects. Very different. Although I suppose frogs would be more interested in entomology.

Amphibian.com comic for September 1, 2014






Friday, August 29, 2014

I'm Not Spam

Open mail relays are no joke. Except when I joke about them in my comics. But seriously, you have to be careful if you run a server to prevent spammers or botnets from using you to spam people.

When I installed postfix it listened publicly on port 25 by default. While it probably wouldn't let anyone log in to send an email, that still seemed a bit dangerous to me. I don't need somebody running a port scanner on me, seeing that I run a mail server, and then attacking me constantly trying to break in.

I had to change the following line in /etc/postfix/main.cf
inet_interfaces = 127.0.0.1
Then I had to restart postfix.

# /etc/init.d/postfix restart

It was the last line in my file. It just tells postfix to listen only on localhost and not on my public IP address. You can't connect to my server on port 25 from anywhere but my server.

SPAM and Eggs
Spam, spam, spam, spam, spam, spam, and eggs.
Alas, it did me no good. For some reason, amphibian.com is still listed as a spam site on Barracuda Central. Unfortunately, this means that many networks will block access to my comics because they assume that I am some kind of villain.

You can tell if your site is in some "bad" category by using their lookup tool. You can even request to change your category if it is wrong. I did this, but haven't gotten any response yet.

They have other informative sections of their site, where you can look at global spam statistics in graphs. The United States sends quite a bit more spam than the rest of the world, and penny stocks are an extremely popular spam subject.

While I certainly don't appreciate being labeled a spammer, the history of why unwanted email is called spam is quite interesting. It really traces back to a Monty Python sketch from 1970. In it, a couple is in a diner where every item on the menu contains Spam. The word spam is used so many times in the sketch, and Monty Python sketches are so popular among early adopters of the Internet, that people used to annoy each other in things like Usenet groups and MUDs by typing the word spam repeatedly. Just like in the sketch, the overuse of the word made it undesirable and over time the word became associated with unwanted stuff on your network, primarily email.

Amphibian.com comic for August 29, 2014




Wednesday, August 27, 2014

Try a Local Fair

Many years ago, like in maybe 2002 or something, I made a website for the local fair in Juniata County, Pennsylvania. Not a big deal, right? Well, this one was different.

At many fairs, people enter all kinds of contests for stuff they grow in their gardens. Biggest pumpkin. Best sweet corn. Reddest tomatoes. Chocolate chip cookies, cakes, maple syrup, a million other things too. In the last century, they had to fill out paper entry forms for all these things and mail them in.

My website was revolutionary in that it allowed online entry of all these things. And not just some form that looked like the paper form where you had to type in all the contest names - it was backed by a complex database of thousands of individual item classes that could be entered in competition. You could browse the list of available contest classes, click on the ones you wanted to enter, fill out your name and contact information, and when you showed up with your pickles they had a label all printed out and ready for you.

Vegetable contest entries at the Juniata County Fair
The system also printed forms for the judges to use, allowed the judges to enter the winners, and then printed all the checks for the contestants. It saved hundreds of hours of labor for the fair employees.

It was back around the turn of the century, so the technology used was pretty awful by today's standards. It was ASP and Microsoft SQL Server. Yeah, it makes me kinda sick now.

I did it all for free. I always liked the fair. I don't really know why. I grew up in a rural area and went to the fair every year, but I was never interested in farming or even gardening. I was very interested in funnel cakes, however.

Don't worry, I re-wrote the whole system twice since then. It's Java now, with ReST web services, and uses MySQL as the backend database. I might re-do it again someday, when the present technology becomes un-maintainable.

Why am I talking about this today? Well, this time of year is fair season. The Juniata County Fair starts this weekend. Other nearby fairs have either just completed or start soon too. If you are reading this from anywhere in the United States, odds are that there will be a fair somewhat close to you around this time of year. You should totally go.

Funnel cakes are awesome.

Amphibian.com comic for August 27, 2014

Monday, August 25, 2014

PhantomJS for Image Capture

I don't know if you've ever noticed, but when you share one of my comics on Twitter, Facebook, or Pinterest (hint: you should be sharing my comic) it is able to grab a preview of the comic that is a static image. This meta tags on the page tell the social media sites which image to use:

<meta property="og:image" content="http://amphibian.com/cell/25.png">

Dynamically Created Image of a Comic
Since the comics are actually SVGs positioned inside <div> tags, it is necessary to create these images somehow. Early on, I created some manually but I really needed an automated method that was integrated with the online editor. Every time I create or update a comic it needs to automatically generate new images.

The solution I came up with was integrating PhantomJS with my web application. You can do a lot of cool stuff with PhantomJS, like automated testing of web applications, but I am just using it for its image capture capabilities right now.

To make an image out of a web page, you just need to make a JavaScript file to control PhantomJS. Here is an example similar to what I use.

var page = require('webpage').create();
page.viewportSize = { width: 1202, height: 5000 };
page.open('http://amphibian.com/basic/25/1', function() {
    page.clipRect = page.evaluate(function() {
        var areaRect = document.getElementById('comicArea').getBoundingClientRect();
        var cellRect = document.getElementById('cell-0').getBoundingClientRect();
        var r = JSON.parse(JSON.stringify(areaRect));
        r.height += (r.top * 2);
        r.top = 0;
        r.left = cellRect.left - 102;
        r.right = cellRect.right + 102;
        r.width = cellRect.width + 204;
        return r;
    });
    page.render('demo.png');
    phantom.exit();
});

Line 1 is just pretty standard, to make a page object. You can read more about the different modules in the PhantomJS documentation.

One line 2, I set the viewport size. This controls how wide my "virtual" web browser client will be. Remember that my comics resize themselves based on the client, so I want to use the maximum size in order to make the rendered image very large. The social media sites almost always shrink the images down and starting with the largest size will result in the cleanest picture in the end.

Line 3 is where I make the call to open the page. When the page is opened, the callback function is called. That's where the good stuff happens.

When you want to make an image out of a web page you can either do the whole thing or just part of the page. If you want to do a partial page, you need to set the clipRect property of the page object before calling render. That's what I'm doing starting at line 4, with the call to page.evaluate.

When calling evaluate on the page object, you pass in a function that should be executed as JavaScript in the context of the page. It would be the same as if the page had JavaScript in a script block.

To make the clip rectangle that I want, I need to get the dimensions of 2 parts of the page - the comicArea div and the div of the first cell. I get those on lines 5 and 6, by simply getting the elements by id and then calling their getBoundingClientRect methods. However, the rectangle I want to return is neither of these exactly. I need to make some adjustments.

On line 7 I do something that you might find a bit strange. I create a new variable r by parsing the JSON returned from a stringifying the areaRect object. Why am I doing this? Because the rectangles returned from the calls to getBoundingClientRect are immutable and I want to make changes. By dumping the object to a JSON string and then reading that string into a new object, I get a mutable copy.

Lines 8 through 12 are the adjustments to the clip rectangle I want to return. I change the top and height of the rectangle around the comicArea and then adjust the left, right, and width values using the corresponding values from the cell as a starting point.

Try it yourself and you should get an image of a single cell of one of my comics. Or change some stuff around and try getting images of other pages.

The complete solution in my web application is slightly more complicated, because I have to dynamically generate the JavaScript for PhantomJS, call PhantomJS from Node, read the resulting image into the database, and then cleanup the temporary files. But we can talk about that some other time...

Amphibian.com comic for August 25, 2014

Friday, August 22, 2014

Eggs from Real Chickens

I like to get my eggs from real chickens. I tell my kids that the eggs in the grocery store come from fake chickens. Like robot chickens or something.

The Chicken Machine
When I was a kid, there was this thing in the grocery store that was full of plastic eggs and had a plastic chicken inside, sitting on top of the eggs. If you put in a quarter, the chicken would move a little and one of the eggs would fall out. Inside was some silly toy prize. That's pretty much what I think of when I think about eggs and the grocery store.

But seriously, I think eggs from chickens that I know taste better than eggs from chickens that I do not know. I am fortunate (well, sometimes I think so) to live in a rural area where I can get to know the chickens that give me eggs. Sometimes I can even feed the chickens that feed me.

Why do these eggs taste better? I don't know. Maybe it's because they are fresher. Maybe it's because of the chicken's diet. These "free range" chickens tend to eat whatever bugs they can peck up out of the yard. Maybe it's the breed of chicken, or the color of the egg shells. The eggs I buy locally are generally brown. I've also noticed that the eggs from local chickens are harder to crack. Tougher shells I guess.

I feed them, they feed me
Once I determined that real eggs from real chickens were better than factory-made eggs, I started to wonder about other agricultural products.

While it is generally accepted that we have access to better nutrition today than we did 100 years ago, that may be true only in the variety of foods to which we have access. There is quite a bit of evidence that individual foods are becoming less nutritious over time. This is most likely due to modern crops being genetically modified for better yields. Bigger crops, but still the same amount of nutrients to go around.

Have a look at these articles, though, and see what you think.

Industrially Farmed Foods Have Lower Nutritional Content

Dirt Poor: Have Fruits and Vegetables Become Less Nutritious?

Breeding the Nutrition Out of Our Food

Amphibian.com comic for August 22, 2014

Wednesday, August 20, 2014

An Interview Question

Here's a great interview question, well, assuming that you're interviewing a software engineer.

What is the easiest way to determine if a given string is the rotation of another string?

First, I guess there could be some question of what rotation of a string even is. If you were to take letters off the end of the string and stick them on the beginning one at a time, you would be making a rotation of the string. For example, the following are all rotations of the string "obfuscate"
ateobfusc
fuscateob
scateobfu
eobfuscat
There are lots of ways you could try to determine if one string is a rotation of another. You could easily rule out some strings by checking to make sure the lengths are the same first. Then you could maybe start at the first character and attempt a character-by-character comparison and move your pointer back to the front if you reach the end...or some other complicated algorithm.

But there's a really easy way to tell if a string is a rotation of another string. Engineers who value the simplest way to solve a problem will do it this way.

Stick one of the strings together with a copy of itself and then see if the other string appears inside that new string. Like this, using the examples above:
ateobfuscateobfusc
fuscateobfuscateob
scateobfuscateobfu
eobfuscateobfuscat
You still need a sanity check for length, because you can find "ob" inside of "fuscateobfuscateob" but that doesn't make it a rotation.

Here it is as a JavaScript function.

function isRotation(a, b) {
    return (a.length === b.length) && ((a + a).indexOf(b) !== -1)
}

That's all there is to it. You can do it in one line. I think it's a good question.

Amphibian.com comic for August 20, 2014

Monday, August 18, 2014

CSS Speech Bubbles

I'd like to take a moment to talk about my CSS speech bubbles. My comic uses CSS rules for making <p> tags render as speech bubbles for the frogs. If you view the page source, you'll see that there are no tables or images involved - just pure CSS.

There are many examples out there on the Internet about how to achieve this effect. I based mine on this one: Bubbler - CSS Speech Bubble Generator. Of all the examples I saw, I just like this one the best.

There was one minor issue I came across though. The frogs are in different places in each cell, but because the bubble stems are positioned using the CSS psuedo-elements ::before and ::after, the values that determine the stem location cannot be set via JavaScript.

I decided to solve this issue by removing the stem position from the .bubble::before and .bubble::after classes in the CSS, and creating another set of classes just for the stem positions.

I called these new classes bubble25, bubble50, and bubble75. You can probably guess that the number represents the percentage used for the left attribute in the stem position.

So now, if I want to create a speech bubble with the stem on the right side (75% of the bubble width) I create a tag like this:

<p class="bubble bubble75">here is some content for the bubble</p>

While I can't position the stems at every possible position this way, having the 3 different options has proven sufficient so far. This is a good example of a design trade-off. I could create a hundred different classes and never use 90% of them, or I could create just 3 and get a good-enough position 99% of the time.

Here's a snippet of my CSS that shows more clearly what I'm talking about.

.bubble {
 position: absolute;
 width: 44%;
 padding: 2%;
 text-align: center;
 background: #FFFFFF;
 border: #000000 solid 3px;
 font-family: 'Sniglet', sans-serif;
 line-height: initial;
 color: #000000;
 box-sizing: content-box;
 -moz-box-sizing: content-box;
 -webkit-box-sizing: content-box;
 -webkit-border-radius: 20px;
 -moz-border-radius: 20px;
 border-radius: 20px;
}

.bubble:after {
 content: '';
 position: absolute;
 border-style: solid;
 border-color: #FFFFFF transparent;
 display: none;
 width: 0;
 z-index: 1;
 box-sizing: initial;
 -moz-box-sizing: initial;
 -webkit-box-sizing: initial;
 border-width: 21px 7px 0;
 bottom: -21px;
 margin-left: -12px;
}

.bubble:before {
 content: '';
 position: absolute;
 border-style: solid;
 border-color: #000000 transparent;
 display: none;
 width: 0;
 z-index: 0;
 box-sizing: initial;
 -moz-box-sizing: initial;
 -webkit-box-sizing: initial;
 border-width: 25px 9px 0;
 bottom: -27px;
 margin-left: -14px;
}


.bubble25:after {
 left: 25%;
 display: block;
}

.bubble25:before {
 left: 25%;
 display: block;
}

.bubble50:after {
 display: block;
 left: 50%;
}

.bubble50:before {
 display: block;
 left: 50%;
}

.bubble75:after {
 display: block;
 left: 75%;
}

.bubble75:before {
 display: block;
 left: 75%;
}

Make sure you check out John Clifford's Bubbler to make your own bubble CSS. Speech bubbles can be a welcome addition to any web site.

Amphibian.com comic for August 18, 2014