Make fireworks with only text

2016 Christmas Edition

I experimented here with trying to produce a geometric figure near the firework core, by using simple glyphs like + and -. When duplicated with rotation and shift, I think the effect is very nice way to highlight the event.

enter image description here

2015 CHRISTMAS EDITION

Here I introduce my first triple-burst firework, with "For unto us" as the inner burst, "a child is born" as the second burst, and "Lord!" as the outer burst. The code can be found in the MWE later in this post. I wish a blessed Christmas season to all.

enter image description here

2015 NEW YEAR'S EDITION

enter image description here

2014 EDITION:

Merry Christmas!

The syntax is

\textwheel{string}{inner_color}{outer_color}{reps}{fade_length}

where reps is the number of repetitions, and fade_length is the length over which the fade from the inner to outer color occurs (upon a recent EDIT, it can now be smaller or larger than the string length and work appropriately). If set to 0pt, it sets the fade_length to the length of string.

See end of post for a description of the PROGRAM LOGIC.

EDIT for nested textwheels (I feel like Ptolemy with epicycles), which can be formulated as such:

\savestack\ST{\tiny\smash{\textwheel{Hallelujah!}{red}{blue}{13}{1.4cm}}}
\textwheel{MerryChristmas~~\ST}{yellow}{red}{28}{0pt}

where one \textwheel is saved into a box (\ST) and then placed at the end of the subsequent \textwheeel.

EDITED to remove stray spaces. EDITED to remove \strut bias which produced a small amount of ovalization. EDITED to minimize roundoff error by working in thousandths of degrees. RE-EDITED (based on comment suggestion) to reintroduce optional level of \strut bias, which is now controlled by two parameters: \biasfrac and \biaslength. Symmetry is retained when \biaslength=0pt (the default).

In the second figure, the inner firework says "Merry Christmas" 28 times. And at the end of each "Merry Christmas", there is a \tiny explosion that says "Hallelujah!" 13 times.

P.S. I just coincidentally realized that 28x13 'Hallelujah!'s make 364... one for every day of the year. God bless!

\documentclass{article}
\usepackage{rotating, xcolor}
\usepackage[margin=4cm]{geometry}
\newcounter{Kdegrees}
\newcounter{KdegreeIncrement}
\newcounter{degrees}

% More than 360 repetitions will max out TeX's memory
% Print error message and end document if more than 360 reps are requested
\newcommand{\textwheelErrorTest}[1]{%
    \ifnum#1 > 360 
        \typeout{%
            ERROR: You asked for #1 repetitions in 
            \noexpand\textwheel, but the maximum is 360.
            I am stopping before TeX exceeds its memory capacity.%
        }%
        \def\erroraction{\end{document}}
    \else
        \def\erroraction{\relax}
    \fi
    \erroraction
}

% For calculating length of string for kerning
\newlength{\strlen}
\newlength\biaslength
\def\biasfrac{.0}% 0 to 1
\biaslength=0pt% \ge 0pt (0pt produces circular symmetry
\def\biasstrut{\rule[-\biasfrac\biaslength]{0pt}{\biaslength}}
% #1 text to repeat, #2 color of text, #3 number of times to repeat it
\newcommand{\textwheel}[5]{\leavevmode\smash{%
    \gdef\innercolor{#2}%
    \gdef\outercolor{#3}%
    \textwheelErrorTest{#4}%
    \def\FadeL{#5}%
    %
    \setcounter{KdegreeIncrement}{360000}%
    \setcounter{Kdegrees}{0}%
    \divide\value{KdegreeIncrement} by #4%
    \settowidth{\strlen}{#1}%
    \ifdim\FadeL=0pt\def\FadeL{\the\strlen}\fi%
    %
    \loop
      \setcounter{degrees}{\value{Kdegrees}}%
      \divide\value{degrees} by 1000\relax%
      \ifnum\value{degrees}<90
        \rlap{\rotatebox[origin=l]{\value{degrees}}{%
         \biasstrut\color{#2}\smash{\FadeAfter{\FadeL}{#1}}}%
        }%
      \else
        \ifnum\value{degrees}<270%
          \kern+\biaslength%
          \llap{\rotatebox[origin=l]{\value{degrees}}{%
           \biasstrut\color{#2}\smash{\FadeAfter{\FadeL}{#1}}}%
          }%
          \kern-\biaslength%
        \else
          \rlap{\rotatebox[origin=l]{\value{degrees}}{%
           \biasstrut\color{#2}\smash{\FadeAfter{\FadeL}{#1}}}%
          }%
        \fi
      \fi
    \addtocounter{Kdegrees}{\value{KdegreeIncrement}}%
    \ifnum\value{Kdegrees} < 359000%
    \repeat%
}}
%%%
\usepackage{xcolor,ifthen}
\newcounter{tmpcounter}
\newlength\cumlength
\newlength\critlength
\newlength\tmplength
\newcount\mynum
\newcount\myden
\makeatletter
\newcommand\FadeAfter[2]{%
  \critlength=#1\relax\cumlength=0pt\relax%
  \def\cumstring{}\fahelp{#1}{#2}}
\newcommand\fahelp[2]{\prefahelper#2 \relax\fahelper#2\relax}
\def\prefahelper#1 #2\relax{\gdef\wordremaining{#1}}
\def\fahelper#1#2\relax{%
  \global\protected@edef\cumstring{\cumstring#1}%
  \ifthenelse{\equal{#1}{\wordremaining}}{%
    \global\protected@edef\cumstring{\cumstring\ }}{}%
  \setbox0=\hbox{\cumstring}%
  \tmplength=.01\critlength\relax%
  \mynum=\wd0\relax%
  \myden=\tmplength\relax%
  \divide\mynum by\myden%
  \setcounter{tmpcounter}{\numexpr100-\the\mynum}%
  \ifnum\thetmpcounter<0\setcounter{tmpcounter}{0}\fi%
  \textcolor{\innercolor!\thetmpcounter!\outercolor!90}{#1}%
  \ifthenelse{\equal{#1}{\wordremaining}}{\ }{}%
    \ifx\relax#2\relax\else\fahelp{\critlength}{#2}\fi%
}
\makeatother
\usepackage{stackengine}
\begin{document}
% CAN EXPERIMENT WITH ASYMMETRIC BIAS WITH THESE TWO PARAMETERS
%\def\biasfrac{.4}% 0 to 1 ROTATES THE ASYMMETRY OF \biaslength
%\biaslength=8pt% \ge 0pt (0pt produces circular symmetry)

\pagecolor{black}

\begin{minipage}{0.45\linewidth}
%
\vspace{-1cm}
\textwheel{Thisisatest}{red}{blue}{35}{2cm}

\vspace{2.8cm}
\savestack\ST{\tiny\smash{\textwheel{Hallelujah!}{red}{blue}{13}{1.3cm}}}
\ST\hspace{6cm}
\textwheel{MerryChristmas~~\ST}{yellow}{red}{28}{2.6cm}

\vspace{6.3cm}
\textwheel{FFFFFadingTest}{yellow}{red}{28}{1.5cm}
\hspace{5cm}
\textwheel{FFFFFadingTest}{yellow}{red}{28}{3cm}

%2015 NEW YEAR'S EDITION
\vspace{6.9cm}
\savestack\ST{\scriptsize\smash{\textwheel{~New~Year!}{orange}{cyan}{7}{2.3cm}}}
\textwheel{~~Have~a~Happy~~\ST}{yellow}{green}{7}{6cm}

%2015 CHRISTMAS EDITION
\hspace{10.15cm}
\savestack\XT{\tiny\smash{\textwheel{~Lord!}{blue!80!orange}{green}{6}{.7cm}}}
\savestack\ST{\scriptsize\smash{\textwheel{~a child is born~\XT}{%
  orange!90!red}{blue!80!orange}{4}{2.1cm}}}
\LARGE\textwheel{~~For unto us~\ST}{red}{orange!90!red}{15}{4cm}

\end{minipage}
\clearpage
\begin{minipage}{0.45\linewidth}
\vspace{2cm}

%2016 CHRISTMAS EDITION
\hspace{1.15cm}
\savestack\ST{\small\smash{\textwheel{~Deo!}{yellow!60!orange}{green!60!orange}{6}{1.2cm}}}
\LARGE\textwheel{+-~Gloria in excelsis~\ST}{red}{blue}{21}{8cm}

\end{minipage}

\end{document}

enter image description here

enter image description here

Here is a zoom on the prior figure:

enter image description here

This show two cases with only the fade_length altered (from 1.5cm to 3cm):

enter image description here

And finally, an example of \strut bias asymmetry introduced with a nonzero \biaslength:

% CAN EXPERIMENT WITH ASYMMETRIC BIAS WITH THESE TWO PARAMETERS
\def\biasfrac{.4}% 0 to 1 ROTATES THE ASYMMETRY OF \biaslength
\biaslength=8pt% \ge 0pt (0pt produces circular symmetry)

Note the difference from the image above, especially around the center of the "firework". Additionally, the firework takes on a slight eccentricity.

enter image description here


PROGRAM LOGIC

The OP has asked for a description of the logic. The OP used the \turnbox macro, which is described in the rotating.sty documentation as "a macro version of the rotate environment." I was not familiar with that macro, so I stuck with what I knew, which is the \rotatebox macro of graphicx.sty, using the [origin=l] option to rotate around the left edge of the box.

I knew I wanted to lay (via lapping) an array of \rotateboxes concentrically, and the OP had laid out logic for such a loop, which I used directly. The many edits to my post indicate that many corrections had to be made along the way. One issue, for rotations between 90 and 270, is that the "tail" of the box cannot push leftward past the x=0 position, and so the left-side origin of the box gets pushed rightward. The remedy for that was to use \llap between 90 and 270, and \rlap elsewhere on the circle. The \ifnum\value{degrees}... branch logic inside \textwheel addresses that point.

The next issue is that the vertical height of the rotated box also limits the leftward push of the box. This introduced a discontinuity at 90 and 270 degrees of rotation. This is what I call \strut-bias, since differing vertical heights would introduce differing levels of discontinuity. My initial correction was the \kern introduced between 90 and 270 degrees of rotation. But even the kern-correction left a slightly ovalized result.

Later, I realized that \smashing the text to be rotated would take care of the problem making the result beautifully symmetrical. One user actually missed the \strut-bias, however, and so I reintroduced it as a controllable parameter through \biaslength (the total height of the reintroduced strut) and \biasfrac (that fraction of \biaslength applied below the baseline).

One of my early ideas for improving the look over the OP's question was the use of continuous color transition of the firework. I had recently answered the question at How can I 'fade out' text after a certain length? in which I made text fade from black to white. I grabbed that \FadeAfter logic and had to tweak it a little to make it work from color1 to color2. It essentially grabs the argument letter by letter, measures the cumulative length to that point, and uses that cumulative length (relative to a specified fadeout length) to decide the color of that letter. Whereas in the original logic, I turned off the fade after reaching the "fade length" since it was white, here I just continued processing the string using color2.

Another issue that arose is that TeX's \divide command truncates the result to an integer. This macro was used to divide 360 by the number of repetitions in order to get the angular increment. The bad news is that it may give the wrong result (I found that asking for 28 repetitions gave me actually 30). Also, all the roundoff errors became visually obvious between the final box and the 0-degree box. My solution was to perform the calculation in thousandths of degrees (thus the loop to 360000), so that the roundoff error would be smaller. Even so, the loop is set to only lay boxes up to 359 degrees to avoid two nearly overlaid boxes (at 0 and 359.xx) degrees.

Perhaps the best addition I made to the OP's proposal was the introduction of nested boxes. For this I used a \savestack from my stackengine package (though a \savebox/\usebox would work equally well. It is quite straightforward. One just saves a small \textwheel in a box, and then uses that box as part of the core \textwheel, laying it at the end of the string. Since the two wheels can have different color combinations, the possibilities are endless.

The \textwheels, because of the laps and smashes, have zero height and width. A non-zero \biaslength may actually change that slightly, I'm not sure, which is why I \smashed the nested \textwheels. Thus, I use a series of \vspacees and \hspacees to lay the \textwheels around the page. It makes it easy to overlap two independent \textwheels if that is desired.


Fireworks, in any flavor of TeX, but only text?

Then the old package happy4th meets the requirements. Really is only an little obfuscated TeX file:

% Author:  Brian Blackmore <[email protected]>
% Date: July 4, 2007
% Version:  20120102
% Original source:  efnet #TeX
% License:  Public Domain
% Requirements:  Plain TeX
%
\nopagenumbers \centerline{Fireworks  Display} \centerline{Brian Blackmore}
\centerline{Page Down As Quickly As You Wish}\centerline{Enjoy}\vfill\eject
\catcode`~\active\let~\catcode~`[\active \let[\let~`]\active ~`Y=1[]\active
~`@=2~`|=][|\expandafter ~`>][>\advance~`:][:\romannumeral~`+][+\count~`H10
~`&][&\def~`*][*\multiply[~\dimen[]\endcsname[[\csname&\burst{\bst{30}{29@}
&\bdYO} &\ss{\rsta{-2.2}{.2}{3@H\rstaY-2.2}{-0.5}{8@ \rsta{-2.2}{0.45}{0}@%
&\ssf{\dra\adva} &\draY+0=1H\loop\dr{x:+0}{y:+0}\vskip-\baselineskip >+0by1
\ifnum+0<\bc\repeat\vfill\eject}&\msa{\rsta{-2}Y0}{4.24@\rsta{-1.5}0Y4.24}}
&\adv#1#2#3#4{ ~0=[#1] ~1=[#3] >~0Hby~1 |\edef[#1]{[the]~0@ ~0=[#2] ~1=[#4]
>~0by~1 |\edef[#2]{[the]~0}~0=[#4]H~1=0.3in>~0by~1|\edef[#4]{\the~0}>+0by1}
&\sf{[ssf]\ssf\ssf\ssf\ssf\ssf@\catcode`!0&\fs{!let\vyi\vxi&\ssfY\sdra\adva
~0=\yi >~0by0inH\ifdim~0<8in \elseH&\yi{8in}&\vyi{0in}\fi}\mf@ +3=\the\time
&\rs{*+3 by12 >+3 by5H+1=+3 [divide]+1by151 *+1by151 >+3by-+1}&\mf{\ssf\ssf
[ssf]&\bd{o}\ssf\ssf} &\asm{\rsta{[email protected]}&!bd{\ifnum+0=1{\bf\TeX}\else
o!fi}} &\adva{+0=1H\loop \adv{x:+0@Yy:+0@{vx:+0}{vy:+0}\ifnum+0<\bc\repeat@
&\ms{[msa][asm][sf]} &\dr#1#2{\vbox to0pt{\parindent0pt\vskip-1in\vskip[#2]
\hboxHto\hsize{\hskip-1in!hskip[#1]\hbox to 0ptY\hss[bd]\hss}\hss}\vss}@\rs
&\gen{|\edef[x:+0]{\xi}|\edef[y:+0]{\yi} +1=+3>+3by+0\rs~0=+3pt\rs\divide~0
by151*~0 by151H>~0by-50.5pt|\edef[vx:+0]Y\the~0}~0=+3pt\rs\divide~0by151*~0
by151>~0by-60.5pt>+3by+1|!edef[vy:+0]Y\the~0}@&\bst#1#2{+0=1 \loop\gen>+0by
1 \ifnum+0<#1\repeat&\bc{#2}&\bd{$\bullet$}}&\sdra{\dra~0=\yi \divide~0Hby3
[font]\tf=cmr10 at~0H!vbox to 0pt{[parindent]0pt\vskip-1in\vskip\yi\hboxHto
\hsize{\hskip-1in\hskip\xi\hbox to0pt{\hss\tf\TeX\hss@\hss}\vss}}&\mburstY%
\bst{60}{59@}&\rst#1#2#3Y&\yi{11in@&\xi{#3in}&!vyi{#1in}&\vxiY#2in}&!uat{O@
\ambd&\bc{1}}&\ambd{&\bdY\ifnum+0=1O\else o\fi}&!bc{10@}H\catcode`\Z\active
&\rsta{\sbm\rst}&\sbm{\sf\burst\mf}\rstY-2@{-0.2}{5.25}!ss\sf&Z{bye@&!ssY13
wEbeDonE37}&!bc{1}\rsta{[email protected]}\ms\mburst&\vxi{0in}\fs!sf\sdra[vskip]%
-\baselineskip\centerline{H\char`\HappyHHJuly 4th\char`\!H}\expandafter[Z]Z

The generated page is a PDF with a lot of pages, that most probaply hou have in your hard disk (Run texdoc happy4th if you use TeXLive or MikTeX. For even more weird people using another TeX distributions, you can donwload the PDF from CTAN). The PDF is only amusing when in full screen mode you press Page Down very quickly. But for lazy people, you can see the effect whit this animation kindly made by Paul Gaborit:

Animation made by Paul Galborit

Have fun. Happy 4th July New Year everybody.


Brian Blackmore / Frans's example with sound added using media9 & animated using animate.

Requires A-Reader on Windows or Mac, sorry.

Run pdflatex -shell-escape on the code twice or thrice.

The sound file is loaded from the web during viewing time of the pdf. For offline use, download the mp3 and adjust the addresource=... and source=... code lines before building the PDF.

enter image description here

\documentclass{standalone}
\usepackage{animate}
\usepackage{media9}
\usepackage{filecontents}
\usepackage{multido}

\begin{filecontents*}{\jobname-frames.tex}
\nopagenumbers \centerline{Fireworks  Display} \centerline{Brian Blackmore}
\centerline{Enjoy}\vfill\eject%
\catcode`~\active\let~\catcode~`[\active \let[\let~`]\active ~`Y=1[]\active
~`@=2~`|=][|\expandafter ~`>][>\advance~`:][:\romannumeral~`+][+\count~`H10
~`&][&\def~`*][*\multiply[~\dimen[]\endcsname[[\csname&\burst{\bst{30}{29@}
&\bdYO} &\ss{\rsta{-2.2}{.2}{3@H\rstaY-2.2}{-0.5}{8@ \rsta{-2.2}{0.45}{0}@%
&\ssf{\dra\adva} &\draY+0=1H\loop\dr{x:+0}{y:+0}\vskip-\baselineskip >+0by1
\ifnum+0<\bc\repeat\vfill\eject}&\msa{\rsta{-2}Y0}{4.24@\rsta{-1.5}0Y4.24}}
&\adv#1#2#3#4{ ~0=[#1] ~1=[#3] >~0Hby~1 |\edef[#1]{[the]~0@ ~0=[#2] ~1=[#4]
>~0by~1 |\edef[#2]{[the]~0}~0=[#4]H~1=0.3in>~0by~1|\edef[#4]{\the~0}>+0by1}
&\sf{[ssf]\ssf\ssf\ssf\ssf\ssf@\catcode`!0&\fs{!let\vyi\vxi&\ssfY\sdra\adva
~0=\yi >~0by0inH\ifdim~0<8in \elseH&\yi{8in}&\vyi{0in}\fi}\mf@ +3=\the\time
&\rs{*+3 by12 >+3 by5H+1=+3 [divide]+1by151 *+1by151 >+3by-+1}&\mf{\ssf\ssf
[ssf]&\bd{o}\ssf\ssf} &\asm{\rsta{[email protected]}&!bd{\ifnum+0=1{\bf\TeX}\else
o!fi}} &\adva{+0=1H\loop \adv{x:+0@Yy:+0@{vx:+0}{vy:+0}\ifnum+0<\bc\repeat@
&\ms{[msa][asm][sf]} &\dr#1#2{\vbox to0pt{\parindent0pt\vskip-1in\vskip[#2]
\hboxHto\hsize{\hskip-1in!hskip[#1]\hbox to 0ptY\hss[bd]\hss}\hss}\vss}@\rs
&\gen{|\edef[x:+0]{\xi}|\edef[y:+0]{\yi} +1=+3>+3by+0\rs~0=+3pt\rs\divide~0
by151*~0 by151H>~0by-50.5pt|\edef[vx:+0]Y\the~0}~0=+3pt\rs\divide~0by151*~0
by151>~0by-60.5pt>+3by+1|!edef[vy:+0]Y\the~0}@&\bst#1#2{+0=1 \loop\gen>+0by
1 \ifnum+0<#1\repeat&\bc{#2}&\bd{$\bullet$}}&\sdra{\dra~0=\yi \divide~0Hby3
[font]\tf=cmr10 at~0H!vbox to 0pt{[parindent]0pt\vskip-1in\vskip\yi\hboxHto
\hsize{\hskip-1in\hskip\xi\hbox to0pt{\hss\tf\TeX\hss@\hss}\vss}}&\mburstY%
\bst{60}{59@}&\rst#1#2#3Y&\yi{11in@&\xi{#3in}&!vyi{#1in}&\vxiY#2in}&!uat{O@
\ambd&\bc{1}}&\ambd{&\bdY\ifnum+0=1O\else o\fi}&!bc{10@}H\catcode`\Z\active
&\rsta{\sbm\rst}&\sbm{\sf\burst\mf}\rstY-2@{-0.2}{5.25}!ss\sf&Z{bye@&!ssY13
wEbeDonE37}&!bc{1}\rsta{[email protected]}\ms\mburst&\vxi{0in}\fs!sf\sdra[vskip]%
0pt\expandafter[Z]Z
\end{filecontents*}
\immediate\write18{pdftex \jobname-frames.tex}

\newwrite\TimeLineFile
\immediate\openout\TimeLineFile=\jobname-tln.txt
\multido{\i=0+1}{103}{
  \def\js{}
  \ifthenelse{
    \i=7 \OR \i=18 \OR \i=29 \OR \i=46 \OR \i=57 \OR \i=68 \OR \i=80 \OR \i=91
  }{
    \def\js{:annotRM.boom.callAS('rewind');annotRM.boom.callAS('play');}
  }{}
  \immediate\write\TimeLineFile{::\i \js}
}
\immediate\write\TimeLineFile{::102:anim.fworks.frameNum=1;}
\immediate\closeout\TimeLineFile

\begin{document}
\makebox[0pt][r]{\includemedia[
  width=1ex,height=1ex,
  label=boom,
  activate=pageopen,transparent,noplaybutton,
  flashvars={
    source=http://www.freesfx.co.uk/rx2/mp3s/4/5269_1334666108.mp3
%    source=5269_1334666108.mp3     % uncomment for offline use
   &hideBar=true
  }
%  addresource=5269_1334666108.mp3  % uncomment for offline use
]{}{APlayer.swf}}%
\animategraphics[
  width=\linewidth,
  timeline=\jobname-tln.txt,
  controls,
  label=fworks]{6}{\jobname-frames}{}{}
\end{document}