2009-06-21

How to configure dynamic DNS (dyndns) with OpenWRT and LuCI

This blog post tells you how to configure dynamic DNS (using dyndns.org) on your router running OpenWRT Kamikaze. The instructions havene be tested on a Linksys WRT54GL running OpenWRT Kamikaze (r14417).

Go to the menu System / Software. If you don't see package descriptions, click on the link Update package lists. Make sure you don't have the obsolete packages updatedd or ipupdate or ez-ipupdate. (You'll still find some old, obsolete forum posts recommending them on the net.) Install package luci-app-ddns.

Reboot (power-cycle) the router, or run rm -rf /var/luci-* in the SSH prompt. Without this step, the menu Services / Dynamic DNS doesn't appear.

Go to the menu Services / Dynamic DNS. Adjust your settings. The package supports dyndns.org, changeip.com, zoneedit.com, no-ip.com, and freedns.afraid.org . Please note that if you specify a force update interval less then 28 hours (the default is 72 hours), then dyndns.org will ban you. Click on the button Save & Apply.

Reboot (power-cycle) the router, or run INTERFACE=wan ACTION=ifup sh /etc/hotplug.d/iface/25-ddns in the SSH prompt. Without this step, the updater daemon script (/bin/sh /usr/lib/ddns/dynamic_dns_updater.sh myddns 0) is not started. This script sends the IP address update to the dynamic DNS provider. Wait a few minutes for the DNS update to take effect, then try to ping your new host name.

2009-06-15

How to render SVG with sharp text and manual kerning

If you want to render SVG vector graphics with text (i.e. convert it to a PNG), you might experience some problems: 1. the antialiasing glitch: the edges of the characters (even horizontal and vertical edges) are blurry; 2. the manual kerning glitch: if you specify manual kerning (by pressing Alt-Left or Alt-Right between two characters) in Inkscape, this gets ignored upon rendering. This blog post gives a solution to these problems.

To get rid of the antialiasing glitch, make sure your text is a text object, not a path. To check this, try to edit it with the text tool of Inkscape. If you can edit the text, then it's a text object. (If you have your text as path, you'll get the antialiasing glitch.) Then render the SVG with rsvg or The GIMP. There will be no antialiasing glitch in the output PNG. (If you render by exporting from Inkscape 0.46 or earlier, you'll get the antialiasing glitch. We tested it on Linux and Windows.)

To get rid of the manual kerning glitch, export the SVG to PNG in Inkscape. However, this introduces the antialiasing glitch. To get rid of both, you should use a patched rsvg, i.e. downloading, patching, compiling and installing rsvg for yourself. Get the patch from http://code.google.com/p/pts-mini-gpl/source/browse/#svn/trunk/pts-librsvg-manual-kerning-patch. As of now, the patch is for librsvg-2.26.0, but it is reported to apply cleanly to librsvg-2.22.3 as well. If you use Ubuntu Intrepid on an x86 system, you can get .deb packages from the attachments on https://www.epointsystem.org/trac/vending_machine/wiki/HotSpotLogo.

An example rendered image which avoids both problems is visible on https://www.epointsystem.org/trac/vending_machine/wiki/HotSpotLogo .

2009-06-13

How to make Ghostscript embed a Type 1 font to the PDF in one part, without splitting

I was facing a problem with Ghostscript 8.54 -sDEVICE=pdfwrite. When embedding a Type 1 font to the PDF, Ghostscript splitted the font to two font objects (and the corresponding FontDescriptor and CFF stream objects as well), instead of generating only one font object. This blog post explains how I solved the problem.

Please note that the problem didn't happen with a newer Ghostscript (such as GPL Ghostscript 8.61 (2007-11-21), it always generated a single PDF font object.

In a few hours I was able to reduce the source font to two glyphs, Ghostscript still splitting it to two 1-glyph fonts. Then I had the idea to change the /Encoding array, putting the two glyph names next to each other. It solved the problem, Ghostscript didn't split the font anymore. In fact, it turned out that one of the glyphs was missing from the /Encoding, and once I added it (anywhere), the problem was solved.

See the source PostScript file on http://www.math.bme.hu/~pts/twob.ps.txt . It contains the 2-glyph font, demonstrates the problem and shows the solution as well (when compiled with -dfill_encoding=true).

2009-06-12

Using \romannumeral in TeX to do multiple macro expansions

This blog post demonstrates a TeX macro hack: using \romannumeral to expand many macros in a single expansion. The example goal is to define a macro \stars which (when called properly) expands to the specified number of stars as a single expansion. Using a single expansion only makes the macro work in an \expandafter\def...{...} context. The similar macro given in The TeXbook doesn't work in this context because it needs multiple expansions.
\documentclass{article} 
%** Usage: \romannumeral\stars{NUMSTARS}
%** This expands to a NUMSTARS stars (* tokens) in a single expansion step.
\def\stars#1{%
\expandafter\mtostar\expandafter{\expandafter}\romannumeral\number#1 000z}
\def\firstoftwo#1#2{#1}%
\def\secondoftwo#1#2{#2}%
\def\mtostar#1#2{%
\ifx#2z\expandafter\firstoftwo\else\expandafter\secondoftwo\fi
{0 #1}{\mtostar{#1*}}%
}
\begin{document}
\expandafter\def\expandafter\mystars\expandafter{\romannumeral\stars{4}}%
\texttt{(\meaning\mystars)}
\end{document}

2009-06-09

How to typeset text with bilingual fragments with LaTeX

This post gives a possible solution for typesetting two similar versions of the same LaTeX document differing only in some text fragments.

Here is the code:
\documentclass{article}

\makeatletter
\newif\ifsecondlanguage
\def\@bracedfirst#1#2{{#1}}
\def\@bracedsecond#1#2{{#2}}

\def\do@localized#1[#2]{%
\let\@@localized\@undefined
\newcommand\@@localized[#2]{#1}\@@localized}%

%** Typeset different text depending on the current value of \ifsecondlanguage.
%** Usage: \bilingual{LANG1PAT}{LANG2PAT}[ARGCOUNT]{ARG1}{ARG2}...
%** If \ifsecondlanguage is true, pattern LANG2PAT is used, else LANG1PAT is
%** used. Arguments #1, #2 etc. in the pattern are substituted with
%** ARG1, ARG2 etc.
\def\bilingual{%
\expandafter\expandafter\expandafter\do@localized\csname
@braced\ifsecondlanguage second\else first\fi\endcsname
}

\begin{document}

\secondlanguagetrue
\bilingual
{This is addition: #1; and this is multiplication: #2.}
{Translation to another language: #1 and another translation: #2.}
[2]{$x+y$}{$x*y$}

\secondlanguagefalse
\bilingual
{These are additions: #1, #2; and this is multiplication: #3.}
{Translation to another language: #1, #2 and another translation: #3.}
[3]{$x+y$}{$x\oplus y$}{$x*y$}

\end{document}
Select the language to use with \secondlanguagetrue or \secondlanguagefalse. Whenever you have a text fragment which needs to be typeset differently depending on the languagem, use the \bilingual command, as demonstrated above.

2009-06-01

How to migrate or merge a CVS or SVN repository to a remote SVN repository

Let's suppose you have one or more local CVS and/or SVN repositories, and you want to merge them (with their change history) to new directories of an existing, possibly remote target SVN repository. This blog post explains how to do this using Unix tools.

The following tools will be used:Please note that we won't use svnsync, because it requires the target repository to be empty. We won't use svn-merge-repos.pl much either, because it requires the target repository to be local. We won't use svnadmin load much either, because it requires the target repository to be local.

Access remote repositories for the first time

For some repositories, you have to specify your username (usually in the command line) and password (usually answering to an interactive prompt) in order to be able to connect. For each machine, you have to do it once, because SVN records your username and password to files under $HOME/.subversion/auth. To avoid problems connecting later, make sure you access the the remote repositories on each machine you'll be working on, so your credentials get saved. The easiest way to do it is to run
svn ls URL://TO/SVN/REPOSITORY --username MYUSER

Install svn-pusher

svn-pusher can add any SVN repository to another SVN repository (as a subdirectory) no matter local or remote, keeping the commit history of the specified revision interval. You can skip this installation step now and come back only if you are asked to use svn-pusher in some of the steps below.

svn-pusher is implemented as a Perl script using SVN's Perl bindings (SVN::Core). The easiest way to install svn-pusher is with root access on a Unix system. For example, on Debian or Ubuntu, run this as root:
# apt-get update
# apt-get install libsvn-core-perl
# echo no | cpan -i SVN::Pusher # this may take about a minute
# type -p svn-pusher
/usr/local/bin/svn-pusher
# svn-pusher help
For the sake of completeness we mention (but we recommend against using) svn-push as an alternative of svn-pusher. svn-push is a tool written in C, available in the SVN contrib directory, and it does something like svn-pusher, but it's dumber: it can commit only a single revision at once, and it needs the head revision number. Here is how to compile it:
$ wget http://svn.collab.net/repos/svn/trunk/contrib/client-side/svn-push/svn-push.c
$ sudo apt-get install libsvn-dev
$ gcc -W -Wall -I/usr/include/subversion-1 -I/usr/include/apr-1.0 \
-Doff64_t='unsigned long long' -o svn-push svn-push.c -lsvn_client-1
$ ./svn-push
Usage : svn-push -r N:M SRC_URL DEST_URL
For the sake of completeness, we also mention the SVN::Push Perl module, which provides the svnpush command. SVN::Pusher seems to be more up-to-date.

Convert the CVS repository to an SVN repository dump

You can skip this step if your source repository is not a CVS repository.

A CVS repository is a directory containing *,v files (possibly in subdirectories), and containing a directory named CVSROOT (or one of its parents containing the CVSROOT).

Download and install cvs2svn from here. It's a Python script, so install Python as well (2.4 or 2.5 should be OK). You don't need root access to run cvs2svn; in fact, it can be run as extracted from the tarball. Example:
$ wget http://cvs2svn.tigris.org/files/documents/1462/44372/cvs2svn-2.2.0.tar.gz
$ tar xzvf cvs2svn-2.2.0.tar.gz
$ cvs2svn-2.2.0/cvs2svn --help
You don't need Subversion itself for this step – cvs2svn (if run with --dumpfile=) needs only Python and the standard Unix sort utility.

Make sure you have your CVS repository on the same machine as cvs2svn. (Copy with scp -r or rsync if necessary.) It is a good and safe idea to make a copy and to use it for the purpose of the conversion. Make sure you have a neighbor or parent directory named CVSROOT next to the repository. The CVSROOT directory can be empty. Make sure that all files in your CVS repository directory are named *,v (i.e. their name ends with ,v). If you don't need all files or all directories, feel free to remove them now. The effect would be as if those files and/or directories have never been added to the CVS repository.

Run cvs2svn --dumpfile=PROJECT.dump PATH/TO/CVS/REPOSITORY . This creates the file PROJECT.dump, which contains all files in the CVS repository, with their full commit history.

Convert the SVN repository to an SVN repository dump

You can skip this step if your source repository is not a SVN repository.

If your source repository is local and you have read access to it, just run
svnadmin dump PATH/TO/SVN/REPOSITORY >PROJECT.dump
Otherwise use svn-pusher like this:
$ svnadmin create PROJECT.copy
$ svn-pusher push URL://OF/SVN/REPOSITORY PROJECT.copy
$ svnadmin dump PROJECT.copy >PROJECT.dump # this may take some time
$ rm -rf PROJECT.copy
As an alternative to svn-pusher, you can use svnsync as well (part of the standard SVN installation), see blog post Dump a SVN repository from a URL how to do it. Please note that both svn-pusher and svnsync are quite slow (as compared to svnadmin dump), and svnsync is better supported and documented since it is part of standard SVN.

Once you have your PROJECT.dump file, use svndumpfilter (part of standard SVN) to get rid of the unnecessary files and directories. You may also edit the file in a text editor to do some other modifications (such as renaming files). The file format should be self-explanatory.

If you expect a file name conflict between the repositories (between source1 and source2 or source1 and target), e.g. multiple repositories have trunk/version.h, it is safest to move/rename all the source repositories to their own directories, and once the merge is done, do a careful and safe svn mv. Here is how to rename everything (e.g. from DIR/TO/FILE to PROJECT.merge/DIR/TO/FILE) in a *.dump file:
$ perl -pi -e's@^(Node-path: )@${1}PROJECT.merge/@' PROJECT.dump
After that, please make sure you add the directory creation into revision 1 of PROJECT.dump. Use your text editor to insert the following lines just below the first PROPS-END line:
Node-path: PROJECT.merge
Node-kind: dir
Node-action: add

Merge an SVN repository dump to a local target SVN repository

You can skip this step if the target repository is not local.
The contents of the dump file PROJECT.dump can be added to an existing local target SVN repository using svnadmin load PATH/TO/TARGET/SVN/REPOSITROY <PROJECT.dump . An example for creating a new target SVN repostiory, and adding multiple projects to it:
$ svnadmin create myprojects
$ cvs2svn --dumpfile=myproject1.dump cvsrepo/dir/myproject1
$ svnadmin load myprojects <myproject1.dump
$ cvs2svn --dumpfile=myproject2.dump cvsrepo/dir/myproject2
$ # (edit myproject2.dump, see below)
$ svnadmin load myprojects <myproject2.dump
$ svn ls -R file://$PWD/myprojects
Please note that cvs2svn adds the creation of the directories trunk, tags and branches to the *.dump file. This will be a problem in svnadmin load myprojects <myproject2.dump, because this tries to add directory trunk, which already exists in repository mpyrojects, so the operation will fail. The solution is to edit the file myproject2.dump, and remove the following lines from near the beginning:
Node-path: trunk
Node-kind: dir
Node-action: add


Node-path: branches
Node-kind: dir
Node-action: add


Node-path: tags
Node-kind: dir
Node-action: add

Merge an SVN repository dump to a target SVN repository

This steps works for both local and remote target SVN repositories, but it's a lot slower than the svnadmin load method described above (which works only if the target SVN repository is local). Install svn-pusher (see the step for it above). It doesn't matter which machine you install svn-pusher to as long as it can connect to the target repository. Copy PROJECT.dump created above to the machine you've installed svn-pusher to. Then run
$ svnadmin create PROJECT.copy
$ svnadmin load PROJECT <PROJECT.dump
$ svn-pusher push file://$PWD/PROJECT.copy URL://TO/TARGET/SVN/REPOSITORY # slow
$ rm -rf PROJECT.copy
Please note that revision numbers in messages reported by svn-pusher are usually off by one, e.g. Committed revision 1 from revision 0. This is normal, the revision numbers will match perfectly in the target repository.

You can use svn-pusher multiple times on the same target repository to merge multiple source repositories. If the target repository is not empty by the time you start svn-pusher, it is safest to dump it first (to have a backup), and then please pay attention to the following facts. svn-pusher (unlike svnadmin load) reports a warning if a directory (e.g. trunk) has already been added. This warning is usally harmless. If svn-pusher wants to add a file which already exists, it will skip merging that source revision, but it will proceed merging subsequent source revisions. This is not always what you want, because you may want to fix the conflict first by renaming files, and only then proceed with subsequent revisions. Should this happen, you may have to rebuild the target repository from scratch using the backup.