Jump to content

User:Saric/Roadmap

From Wikipedia, the free encyclopedia

This is the script I wrote to generate this image. Feel free to update the $data_string, run the program, and replace the image once the unused code points get assigned.

#!/usr/bin/perl

use warnings;
use strict;
use SVG;

# ---------------------------------------------------------------
# Options
# ---------------------------------------------------------------

our $side_length = 500;
  # Height and width of the roadmap square in pixels.
# The following sizes are expressed as fractions of
# $side_length.
our $line_width = 1/250;
  # The width of the divider lines.
our $legend_width = 1/2;
  # Width of the margin used for the legend.
our $legend_box_space = 1/30;
  # Space between the rectangle for each legend and the
  # right edge of the roadmap square.
our $legend_top_margin = 1/30;
  # Space between the first box of the legend and the top of the
  # image.
our $legend_box_width = 1/20;
our $legend_box_height = 1/40;
our $legend_text_space = 1/50;
  # Space between the left edge of each legend box and
  # its descriptive text.
our $legend_line_break = 1/50;

our $line_color = '#DADADA';
our $roadmap_font =
    '"DejaVu Sans Mono", "Andale Mono", monospace';
our $legend_font =
    '"DejaVu Sans", Arial, "sans-serif"';

our %text_colors =
   (map({$_ => 'white'}
        qw(black darkgray blue darkgreen purple)),
    map({$_ => 'black'}
        qw(white lightgray lightblue cyan orange
           lightgreen red yellow salmon magenta)));

our @scripts =
   (['latin', 'Latin scripts and symbols', 'black'],
    ['ling', 'Linguistic scripts', 'lightblue'],
    ['euro', 'Other European scripts', 'blue'],
    ['meswa', "Middle Eastern and\nSouthwest Asian scripts", 'orange'],
    ['africa', 'African scripts', 'lightgreen'],
    ['Sasian', 'South Asian scripts', 'darkgreen'],
    ['SEasian', 'Southeast Asian scripts', 'purple'],
    ['Easian', 'East Asian scripts', 'red'],
    ['han', 'Unified CJK Han', 'salmon'],
    ['canada', 'Canadian Aboriginal scripts', 'yellow'],
    ['symbol', 'Symbols', 'magenta'],
    ['diacritics', 'Diacritics', 'darkgray'],
    ['private', "UTF-16 surrogates and\nprivate use", 'lightgray'],
    ['misc', 'Miscellaneous characters', 'cyan'],
    ['unused', 'Unallocated code points', 'white']);

# http://www.unicode.org/roadmaps/bmp/
# Format of each line:
#   First 2 hex digits, 3rd digit, script name   # Comment
our $data_string =
 qq[00 0 latin
    02 5 ling
    03 0 diacritics
    03 7 euro
    05 9 meswa
    07 8 Sasian
    07 C africa
    08 4 unused
    09 0 Sasian
    0E 0 SEasian
    10 A meswa
    11 0 Easian
    12 0 africa
    13 A canada
    16 8 euro
    17 0 Easian
    17 8 SEasian
    18 0 Easian
    18 B unused
    19 0 Sasian
    19 5 Easian
    19 E SEasian
    1A B unused
    1B 0 Sasian
    1B C unused
    1C 0 Sasian
    1D 0 ling
    1D C diacritics
    1E 0 latin
    1F 0 euro
    20 0 symbol
    20 7 latin
    20 A symbol
    21 0 latin
    21 9 symbol
    24 6 latin
    25 0 symbol
    2C 0 euro
    2C 6 latin
    2C 8 euro
    2D 0 meswa
    2D 3 africa
    2D E euro
    2E 0 SEasian
    2E 8 han
    30 4 Easian
    31 A han  # The third digit is a wild guess, really.
    31 F Easian
    34 0 han
    4D C symbol
    4E 0 han
    A0 0 Easian
    A5 0 africa  # Vai counts as an African script, right?
    A6 4 euro
    A6 A africa
    A7 0 Easian
    A7 2 latin
    A8 0 Sasian
    A9 0 Easian
    A9 3 SEasian
    A9 6 Easian
    A9 8 SEasian
    A9 E unused
    AA 0 SEasian
    AA 6 unused
    AA 8 SEasian
    AA E unused
    AB 0 unused
    AC 0 Easian
    D8 0 private
    F9 0 han
    FB 0 misc];

# ---------------------------------------------------------------
# Other declarations
# ---------------------------------------------------------------

our $grad_defs;
our $grad_id = -1;

sub stripes
# This creates a "gradient" of distinct vertical stripes. Its
# arguments should be the starting x-coordinate of the gradient,
# the ending x-coordinate, an SVG color, and then any number of
# stops. Each stop should be an array reference containing a stop
# location (expressed as a number between 0 and 1) and a color.
# The subroutine returns a string you can set a stroke or fill
# attribute to to use the gradient.
 {my ($x1, $x2, $first_color, @stops) = @_;
  my $grad = $grad_defs->gradient
   (-type => 'linear',
    gradientUnits => "userSpaceOnUse",
    id => 'grad' . ++$grad_id,
    x1 => $x1, x2 => $x2);
  $grad->stop
     (offset => '0%',
      'stop-color' => $first_color);
  my $last_color = $first_color;
  foreach (@stops)
     {my $percent = 100*$_->[0] . '%';
      $grad->stop
         (offset => $percent,
          'stop-color' => $last_color);
      $grad->stop
         (offset => $percent,
          'stop-color' => $_->[1]);
      $last_color = $_->[1];}
  $grad->stop
     (offset => '100%',
      'stop-color' => $last_color);
  return "url(#grad$grad_id)";}

sub tcolor
# Given the same arguments as &stripes, returns a value to use
# for the "fill" of text overlaying the given colors. This may be
# a solid color instead of a gradient.
 {my ($x1, $x2, $first_color, @stops) = @_;
  my $last_tc = $text_colors{$first_color};
  $first_color = $last_tc;
  for (my $n = 0 ; $n < @stops ; ++$n)
     {my $this_tc = $text_colors{$stops[$n][1]};
      if ($this_tc eq $last_tc)
         # This stop is redundant, so we can remove it.
         {splice(@stops, $n, 1);
          $n < @stops ? redo : last;}
      $stops[$n][1] = $this_tc;
      $last_tc = $this_tc;}
  return (@stops
    ? stripes($x1, $x2, $first_color, @stops) 
    : # We can just return a solid color.
      $first_color);}

# ---------------------------------------------------------------
# Process $data_string
# ---------------------------------------------------------------

$data_string =~ s {\#.+} {}gm;
our @d = ();
 {my %script_colors = ();
  $script_colors{$_->[0]} = $_->[2] foreach @scripts;
  foreach (split /\s*\n\s*/, $data_string)
     {/\S/ or next;
      /(.)(.)\s+(.)\s+(.+)/;
      push( @d, [hex($1), hex($2), hex($3),
                 $script_colors{$4}] );}}

# ---------------------------------------------------------------
# Set up the SVG
# ---------------------------------------------------------------

$$_ *= $side_length foreach
   (\$line_width, \$legend_width, \$legend_box_space,
    \$legend_box_height, \$legend_top_margin,
    \$legend_box_width, \$legend_text_space,
    \$legend_line_break);

our $svg = new SVG
   (width => $side_length + $legend_width,
    height => $side_length);
$svg->title->cdata('Roadmap to the Unicode BMP');
$grad_defs = $svg->defs;
  # I declare this here to ensure that the gradient definitions
  # appear in the file before anything else, especially the
  # rectangles that reference them.
$svg->rectangle
   (x => 0, 'y' => 0,
    width => $side_length + $legend_width,
    height => $side_length,
    'stroke-width' => 0,
    'fill' => 'white');
our $rectgrp = $svg->group
   ('stroke-width' => ($line_width . 'px'),
    'stroke' => $line_color);
our $sq_side_length =
    ($side_length - $line_width) / 16;
our $roadmap_tgrp = $svg->group
   ('text-anchor' => 'middle',
    'font-family' => $roadmap_font,
    'font-size' => ($sq_side_length/2 . 'px'),
    'stroke-width' => 0);
our $legend_tgrp = $svg->group
   ('text-anchor' => 'left',
    'font-family' => $legend_font,
    'font-size' => ($legend_box_height . 'px'),
    'stroke-width' => 0,
    'fill' => 'black');

# ---------------------------------------------------------------
# Draw the roadmap square
# ---------------------------------------------------------------

 {my $last_c = shift(@d)->[3];
    # The last color we used.
  my @next = @{shift @d};
    # The next stop (equivalent to one line of the $data_string).
  foreach my $y (0 .. 15)
     {foreach my $x (0 .. 15)
         # $y and $x correspond to the first and second digits,
         # respectively, of each character's code point
         {my $xp = $line_width/2 + $x*$sq_side_length;
          my $yp = $line_width/2 + $y*$sq_side_length;
          my ($sq_fill, $t_fill);
          my @stops_here = ();
            # Stops that occur in this square.
          while (@next and $next[0] == $y and $next[1] == $x)
             {push(@stops_here, [@next]);
              @next = (@d ? @{shift @d} : ());}
          if (@stops_here)
             {$stops_here[0][2]
                or $last_c = shift(@stops_here)->[3];
              my @args =
                 ($xp, 
                  $xp + $sq_side_length,
                  $last_c,
                  map {[ $_->[2]/16, $_->[3] ]} @stops_here);
              $sq_fill = stripes(@args);
              $t_fill = tcolor(@args);
              @stops_here
                and $last_c = $stops_here[-1][3];}
          else
             {$sq_fill = $last_c;
              $t_fill = $text_colors{$sq_fill};}
          $rectgrp->rectangle
             (x => $xp, 'y' => $yp,
              width => $sq_side_length,
              height => $sq_side_length,
              fill => $sq_fill);
          $roadmap_tgrp->text
             (x => ($xp + $sq_side_length/2),
              'y' => ($yp + (2/3)*$sq_side_length),
              fill => $t_fill)
             ->cdata(sprintf('%X%X', $y, $x));}}}

# ---------------------------------------------------------------
# Draw the legend
# ---------------------------------------------------------------

 {my $x = $side_length + $legend_box_space + $line_width / 2;
  my $y = $legend_top_margin + $line_width / 2;
  foreach (@scripts)
     {$rectgrp->rectangle
         (x => $x, 'y' => $y,
          width => $legend_box_width,
          height => $legend_box_height,
          fill => $_->[2]);
      my @txt = split /\n/, $_->[1];
      foreach (@txt) 
         {$legend_tgrp->text
             (x => ($x + $legend_box_width + $legend_text_space),
              'y' => ($y + (4/5)*$legend_box_height))
             ->cdata($_);
          $y += (5/4)*$legend_box_height;}
      $y += $legend_line_break;}}

# ---------------------------------------------------------------
# Output
# ---------------------------------------------------------------

my $txt = $svg->xmlify;
# Remove extra space in <text> elements. Inkscape ignores it, but
# librsvg treats it like a normal character, thus screwing up
# text alignment.
$txt =~ s{\s+</text>\s} {</text>\n}g;
# Do the same for the <title>, for good measure.
$txt =~ s{\s+</title>\s} {</title>\n};
# Change to Unix-style newlines if necessary.
$txt =~ s{\015\012?} {\012}g;
print $txt;