If you simply want the words in linear order, TH's simpler answer should work nicely. My code typesets the words from the most frequent to the least frequent, as close as possible to the center of the cloud, avoiding overlaps of course. This is rather slow (a minute or so on my laptop).

You need to update the l3kernel and l3experimental bundles to the latest version (end of February 2012): some of the functions I use (in particular sorting) are very recent --- and still unstable.
% Word cloud.
\RequirePackage{xparse,graphicx,l3regex,l3sort}
\ExplSyntaxOn
\prop_new:N \l_wordcloud_count_prop
\seq_new:N \l_wordcloud_words_seq
\tl_new:N \l_wordcloud_tl
\tl_new:N \l_wordcloud_b_tl
\int_new:N \l_wordcloud_word_number_int
\dim_new:N \l_wordcloud_fboxrule_dim
\dim_new:N \l_wordcloud_fboxsep_dim
\dim_new:N \l_wordcloud_surround_fboxrule_dim
\dim_new:N \l_wordcloud_surround_fboxsep_dim
\fp_new:N \l_wordcloud_angle_increment_fp
\fp_new:N \l_wordcloud_angle_fp
\fp_set_eq:NN \l_wordcloud_angle_fp \c_pi_fp
\fp_mul:Nn \l_wordcloud_angle_fp { 2 }
\fp_const:Nn \c_wordcloud_two_pi_fp { \l_wordcloud_angle_fp }
\cs_generate_variant:Nn \prop_put:Nnn { Nno }
\tl_new:N \l_wordcloud_regex_tl
\keys_define:nn { wordcloud }
{
number-of-words .int_set:N = \l_wordcloud_word_number_int ,
style .code:n =
{ \cs_set_protected:Npn \wordcloud_word_style:n ##1 {#1} } ,
surround-fboxrule .dim_set:N = \l_wordcloud_surround_fboxrule_dim ,
surround-fboxset .dim_set:N = \l_wordcloud_surround_fboxsep_dim ,
regex .tl_set:N = \l_wordcloud_regex_tl ,
angle-step .fp_set:N = \l_wordcloud_angle_increment_fp ,
}
\DeclareDocumentCommand { \wordcloud } { O { } m }
{
\dim_set_eq:NN \l_wordcloud_fboxrule_dim \fboxrule
\dim_set_eq:NN \l_wordcloud_fboxsep_dim \fboxsep
\dim_set_eq:NN \l_wordcloud_surround_fboxrule_dim \fboxrule
\dim_set_eq:NN \l_wordcloud_surround_fboxsep_dim \fboxsep
\int_set_eq:NN \l_wordcloud_word_number_int \c_max_int
\fp_set:Nn \l_wordcloud_angle_increment_fp { 10 }
\tl_set:Nn \l_wordcloud_regex_tl { [[:alpha:]]+ }
\keys_set:nn { wordcloud } {#1}
\fp_mul:Nn \l_wordcloud_angle_increment_fp { \c_pi_fp }
\fp_div:Nn \l_wordcloud_angle_increment_fp { 180 }
\iow_term:x { Finding,~counting,~and~sorting~the~words }
\wordcloud_find_words:n {#2}
\wordcloud_tally_words:
\wordcloud_sort_words_using_tally:
\iow_term:x { Typesetting~the~words }
\wordcloud_typeset_words:n { \l_wordcloud_word_number_int }
}
\cs_new_protected:Npn \wordcloud_find_words:n #1
{
\exp_args:No \regex_extract_all:nnN
\l_wordcloud_regex_tl
{#1}
\l_wordcloud_words_seq
}
\cs_new_protected_nopar:Npn \wordcloud_tally_words:
{
% Count occurrences, keeping track of repetition number in a prop.
\prop_clear:N \l_wordcloud_count_prop
\seq_map_inline:Nn \l_wordcloud_words_seq
{
\prop_get:NnNTF \l_wordcloud_count_prop {##1} \l_wordcloud_tl
{
% If the word is not new, add 1 to its count.
\tl_set:Nx \l_wordcloud_tl
{ \int_eval:n { \l_wordcloud_tl + 1 } }
}
{
% Otherwise, initialize the count at 1.
\tl_set:Nx \l_wordcloud_tl { 1 }
}
\prop_put:Nno \l_wordcloud_count_prop {##1} \l_wordcloud_tl
}
}
\cs_new_protected:Npn \wordcloud_sort_words_using_tally:
{
% Remove duplicate words from the seq
% (better done by extracting keys from the prop instead??)
\seq_remove_duplicates:N \l_wordcloud_words_seq
\seq_sort:Nn \l_wordcloud_words_seq
{
% Find word count for the two words "##1" and "##2".
\prop_get:NnN \l_wordcloud_count_prop {##1} \l_wordcloud_tl
\prop_get:NnN \l_wordcloud_count_prop {##2} \l_wordcloud_b_tl
% If the second word count is > to the first, then the two words
% were in the wrong order (reversed), otherwise, the order is
% kept (ordered).
\int_compare:nNnTF { \l_wordcloud_tl } < { \l_wordcloud_b_tl }
{ \sort_reversed: } { \sort_ordered: }
}
}
\int_new:N \l_wordcloud_int
\box_new:N \l_wordcloud_result_box
\cs_new_protected:Npn \wordcloud_typeset_words:n #1
{
\dim_gzero_new:N \g_wordcloud_xmin_dim
\dim_gzero_new:N \g_wordcloud_xmax_dim
\dim_gzero_new:N \g_wordcloud_ymin_dim
\dim_gzero_new:N \g_wordcloud_ymax_dim
\hbox_set:Nn \l_wordcloud_result_box
{
% \rlap{*}
\seq_clear:N \l_wordcloud_boxes_seq
\int_zero:N \l_wordcloud_int
\seq_clear:N \l_wordcloud_typeset_words_seq
\seq_map_inline:Nn \l_wordcloud_words_seq
{
\int_compare:nF { \l_wordcloud_int < #1 } { \seq_map_break: }
\int_incr:N \l_wordcloud_int
\iow_term:x { ##1 }
\wordcloud_typeset_one_word:n {##1}
\seq_put_right:Nn \l_wordcloud_typeset_words_seq {##1}
}
}
% \iow_term:x
% {
% Result~box~dimensions:~
% x = \dim_use:N \g_wordcloud_xmin_dim
% .. \dim_use:N \g_wordcloud_xmax_dim , ~
% y = \dim_use:N \g_wordcloud_ymin_dim
% .. \dim_use:N \g_wordcloud_ymax_dim
% }
\box_set_wd:Nn \l_wordcloud_result_box { 0pt }
\box_set_ht:Nn \l_wordcloud_result_box { 0pt }
\box_set_dp:Nn \l_wordcloud_result_box { 0pt }
\group_begin:
\dim_set_eq:NN \fboxsep \l_wordcloud_surround_fboxsep_dim
\dim_set_eq:NN \fboxrule \l_wordcloud_surround_fboxrule_dim
\fbox
{
\vbox:n
{
\skip_vertical:N \g_wordcloud_ymax_dim
\hbox:n
{
\skip_horizontal:n { - \g_wordcloud_xmin_dim }
\box_use:N \l_wordcloud_result_box
\skip_horizontal:N \g_wordcloud_xmax_dim
}
\skip_vertical:n { - \g_wordcloud_ymin_dim }
}
}
\group_end:
}
\box_new:N \l_wordcloud_word_box
\cs_new_protected:Npn \wordcloud_typeset_one_word:n #1
{
\hbox_set:Nn \l_wordcloud_word_box
{
\group_begin:
\wordcloud_word_style:n {#1}
\group_end:
}
\prop_get:NnN \l_wordcloud_count_prop {#1} \l_wordcloud_tl
\box_scale:Nnn \l_wordcloud_word_box
{ \l_wordcloud_tl } { \l_wordcloud_tl }
% \iow_term:x
% {
% Word~'#1'~dimensions:~
% wd = \dim_eval:n { \box_wd:N \l_wordcloud_word_box } ,~
% ht+dp =
% \dim_eval:n
% {
% \box_ht:N \l_wordcloud_word_box
% + \box_dp:N \l_wordcloud_word_box
% }
% }
\fp_set:Nn \l_wordcloud_min_radius_fp { 1e9 }
\fp_zero:N \l_wordcloud_min_angle_fp
\fp_zero:N \l_wordcloud_angle_fp
\wordcloud_one_angle:
\fp_cos:Nn \l_wordcloud_cos_fp { \l_wordcloud_min_angle_fp }
\fp_mul:Nn \l_wordcloud_cos_fp { \l_wordcloud_min_radius_fp }
\fp_sin:Nn \l_wordcloud_sin_fp { \l_wordcloud_min_angle_fp }
\fp_mul:Nn \l_wordcloud_sin_fp { \l_wordcloud_min_radius_fp }
\dim_zero_new:c { l_wordcloud_word_#1_xmin_dim }
\dim_set:cn { l_wordcloud_word_#1_xmin_dim }
{
\fp_to_dim:N \l_wordcloud_cos_fp
- .5 \box_wd:N \l_wordcloud_word_box
}
\dim_zero_new:c { l_wordcloud_word_#1_xmax_dim }
\dim_set:cn { l_wordcloud_word_#1_xmax_dim }
{
\fp_to_dim:N \l_wordcloud_cos_fp
+ .5 \box_wd:N \l_wordcloud_word_box
}
\dim_zero_new:c { l_wordcloud_word_#1_ymin_dim }
\dim_set:cn { l_wordcloud_word_#1_ymin_dim }
{
\fp_to_dim:N \l_wordcloud_sin_fp
- .5 \box_ht:N \l_wordcloud_word_box
- .5 \box_dp:N \l_wordcloud_word_box
}
\dim_zero_new:c { l_wordcloud_word_#1_ymax_dim }
\dim_set:cn { l_wordcloud_word_#1_ymax_dim }
{
\fp_to_dim:N \l_wordcloud_sin_fp
+ .5 \box_ht:N \l_wordcloud_word_box
+ .5 \box_dp:N \l_wordcloud_word_box
}
\dim_gset_min:Nn \g_wordcloud_xmin_dim
{ \dim_use:c { l_wordcloud_word_#1_xmin_dim } }
\dim_gset_max:Nn \g_wordcloud_xmax_dim
{ \dim_use:c { l_wordcloud_word_#1_xmax_dim } }
\dim_gset_min:Nn \g_wordcloud_ymin_dim
{ \dim_use:c { l_wordcloud_word_#1_ymin_dim } }
\dim_gset_max:Nn \g_wordcloud_ymax_dim
{ \dim_use:c { l_wordcloud_word_#1_ymax_dim } }
% \iow_term:x
% {
% Word~'#1'~at~
% x = \dim_use:c { l_wordcloud_word_#1_xmin_dim }
% .. \dim_use:c { l_wordcloud_word_#1_xmax_dim } , ~
% y = \dim_use:c { l_wordcloud_word_#1_ymin_dim }
% .. \dim_use:c { l_wordcloud_word_#1_ymax_dim }
% }
\box_move_up:nn
{
\use:c { l_wordcloud_word_#1_ymin_dim }
+ \box_dp:N \l_wordcloud_word_box
}
{
\hbox_to_wd:nn { 0pt }
{
\skip_horizontal:c { l_wordcloud_word_#1_xmin_dim }
\box_use_clear:N \l_wordcloud_word_box
\tex_hss:D
}
}
}
\cs_new_protected_nopar:Npn \wordcloud_one_angle:
{
\fp_cos:Nn \l_wordcloud_cos_fp { \l_wordcloud_angle_fp }
\fp_sin:Nn \l_wordcloud_sin_fp { \l_wordcloud_angle_fp }
\fp_zero:N \l_wordcloud_radius_fp
\seq_map_inline:Nn \l_wordcloud_typeset_words_seq
{
\use:x
{
\wordcloud_get_intersection:nnnn
{
\dim_eval:n
{
\dim_use:c { l_wordcloud_word_##1_xmin_dim }
- .5 \box_wd:N \l_wordcloud_word_box
}
}
{
\dim_eval:n
{
\dim_use:c { l_wordcloud_word_##1_xmax_dim }
+ .5 \box_wd:N \l_wordcloud_word_box
}
}
{
\dim_eval:n
{
\dim_use:c { l_wordcloud_word_##1_ymin_dim }
- .5 \box_ht:N \l_wordcloud_word_box
- .5 \box_dp:N \l_wordcloud_word_box
}
}
{
\dim_eval:n
{
\dim_use:c { l_wordcloud_word_##1_ymax_dim }
+ .5 \box_ht:N \l_wordcloud_word_box
+ .5 \box_dp:N \l_wordcloud_word_box
}
}
}
}
\fp_compare:NNNT \l_wordcloud_radius_fp < \l_wordcloud_min_radius_fp
{
\fp_set_eq:NN \l_wordcloud_min_radius_fp \l_wordcloud_radius_fp
\fp_set_eq:NN \l_wordcloud_min_angle_fp \l_wordcloud_angle_fp
% \iow_term:x
% {
% At~##1~degrees:~
% r = \fp_use:N \l_wordcloud_min_radius_fp ,~
% a = \fp_use:N \l_wordcloud_min_angle_fp
% }
}
\fp_add:Nn \l_wordcloud_angle_fp \l_wordcloud_angle_increment_fp
\fp_compare:NNNT \l_wordcloud_angle_fp < \c_wordcloud_two_pi_fp
{ \wordcloud_one_angle: }
}
\cs_new_protected:Npn \wordcloud_get_intersection:nnnn #1#2#3#4
{
\wordcloud_get_intersection_aux:NnNnn
\l_wordcloud_cos_fp {#1}
\l_wordcloud_sin_fp {#3} {#4}
\wordcloud_get_intersection_aux:NnNnn
\l_wordcloud_cos_fp {#2}
\l_wordcloud_sin_fp {#3} {#4}
\wordcloud_get_intersection_aux:NnNnn
\l_wordcloud_sin_fp {#3}
\l_wordcloud_cos_fp {#1} {#2}
\wordcloud_get_intersection_aux:NnNnn
\l_wordcloud_sin_fp {#4}
\l_wordcloud_cos_fp {#1} {#2}
}
\cs_new_protected:Npn \wordcloud_get_intersection_aux:NnNnn #1#2#3#4#5
{
\fp_set_from_dim:Nn \l_wordcloud_r_fp {#2}
\fp_div:Nn \l_wordcloud_r_fp { #1 }
\fp_set_eq:NN \l_wordcloud_b_fp \l_wordcloud_r_fp
\fp_mul:Nn \l_wordcloud_b_fp { #3 }
\fp_set_from_dim:Nn \l_wordcloud_bound_min_fp {#4}
\fp_set_from_dim:Nn \l_wordcloud_bound_max_fp {#5}
\fp_compare:NNNF \l_wordcloud_b_fp < \l_wordcloud_bound_min_fp
{
\fp_compare:NNNF \l_wordcloud_b_fp > \l_wordcloud_bound_max_fp
{
\fp_compare:NNNT \l_wordcloud_r_fp > \l_wordcloud_radius_fp
{ \fp_set_eq:NN \l_wordcloud_radius_fp \l_wordcloud_r_fp }
}
}
}
\cs_new_protected:Npn \wordcloud_word_style:n #1 {#1}
\ExplSyntaxOff
\documentclass{article}
\begin{document}
\pagestyle{empty}
\wordcloud
[
number-of-words = 20 ,
style = {\fboxsep=1pt\fboxrule=0pt\fbox{#1}} ,
angle-step = 15
]
{
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Ut purus
elit, vest ibulum ut, placerat ac, adipiscing vitae,
felis. Curabitur dictum gravida mauris. Nam arcu libero, nonummy
eget, consectetuer id, vulputate a, magna. Donec ve hicula augue eu
neque. Pellentesque habitant morbi tristique senectus et netus et
malesuada fames ac turpis egestas. Mauris ut leo. Cras viverra metus
rhoncus sem. Nulla et lectus vestibulum urna fringilla
ultrices. Phasellus eu tellus sit amet tortor gravida
placerat. Integer sapien est, iaculis in, pretium quis, viverra ac,
nunc. Praesent eget sem vel leo ultrices bibendum. Aenean faucibus.
Morbi dolor nulla, malesuada eu, pulvinar at, mollis ac,
nulla. Curabitur auct or semper nulla. Donec varius orci eget
risus. Duis nibh mi, congue eu, accumsan eleifend, sagittis quis,
diam. Duis eget orci sit amet orci dignissim rutrum.
}
\end{document}