16

While we have \textit and \textsc, \itshape and \scshape etc., there's no \textuc and \ucshape to typeset its argument in uppercase. There is \MakeUppercase, but -- as uppercase is not a font style -- it's not a font selection command and cannot be used like one. That's why it's of no use in, e.g., formatting headings. There's the seamus-egreg workaround, but that has its limitations, for example when exotic languages are used: \section{daß} produces »DAß« rather than »DASS«, etc.

Traditionally, the only way towards a proper \textuc/\ucshape seemed to be via creating a virtual font in which all lowercase letters are replaced by uppercase ones. While I've seen a couple of people suggesting that approach, I don't think anyone has ever done it, probably because it's tedious and inflexible, as it's always tied to one specific font.

My question is if it might be a good idea to rethink that issue now that we have Xe and Lua, where we can use OpenType Fonts and, via fontspec, OpenType feature files. In those feature files, we can define substitution rules that can be switched on and off. In OpenType, an f_i ligature, for example, is produced via such a substitution rule, saying »whenever in the code you come across an f followed by an i, replace the two by the glyph f_i in the output«. To tentatively answer my own question: yes, it might be. Consider the following example. [edit: now including a comparison with the \MakeTextUppercase command from the textcase package barbara mentioned.]

\documentclass{scrartcl}
\usepackage{fontspec,blindtext,microtype,filecontents,textcase}

\begin{filecontents*}{universalcaps.fea}
languagesystem DFLT dflt;
languagesystem latn dflt;

feature caps {

lookup ligatures {
  sub f_i by F I;
  sub f_l by F L;
  sub f_f_l by F F L;
  sub f_f_i by F F I;
  sub f_f by F F;
  sub f_j by F J;
  sub f_f_j by F F J;
  sub f_t by F T;
  sub f_f_t by F F T;
} ligatures;

lookup eszett {
  sub germandbls by S S;
} eszett;

lookup single {
  sub [a-z] by [A-Z];
  sub agrave by Agrave;
  sub aacute by Aacute;
  sub acircumflex by Acircumflex;
  sub atilde by Atilde;
  sub adieresis by Adieresis;
  sub aring by Aring;
  sub ccedilla by Ccedilla;
  sub egrave by Egrave;
  sub eacute by Eacute;
  sub ecircumflex by Ecircumflex;
  sub edieresis by Edieresis;
  sub igrave by Igrave;
  sub iacute by Iacute;
  sub icircumflex by Icircumflex;
  sub idieresis by Idieresis;
  sub eth by Eth;
  sub ntilde by Ntilde;
  sub ograve by Ograve;
  sub oacute by Oacute;
  sub ocircumflex by Ocircumflex;
  sub otilde by Otilde;
  sub odieresis by Odieresis;
  sub ugrave by Ugrave;
  sub uacute by Uacute;
  sub ucircumflex by Ucircumflex;
  sub udieresis by Udieresis;
  sub yacute by Yacute;
  sub thorn by Thorn;
  sub ydieresis by Ydieresis;
  sub oe by OE;
  sub ae by AE;
  sub scaron by Scaron;
  sub zcaron by Zcaron;
} single;

} caps;
\end{filecontents*}

\setmainfont[FeatureFile=universalcaps.fea]{TeX Gyre Termes}
\setsansfont[FeatureFile=universalcaps.fea]{TeX Gyre Heros}

\newcommand*{\ucshape}{\addfontfeature{RawFeature=+caps}}
\newcommand{\textuc}[1]{{\ucshape #1}}

\setkomafont{section}{\ucshape}

\begin{document}
\section{Lorem Ipsum}

\textuc{\blindtext
àéîàáâãäåæ çèéêëìíîï ðñòóôõö ùúûüýþÿœš
fi ff fl ffi ffl fj ffj}

\MakeTextUppercase{daß}
\textuc{daß}

\begin{titlepage}
\ucshape
\begin{center}
John Doe\par
{\huge Title}
\end{center}
\end{titlepage}

\end{document}

This will create a feature file, adding to whatever font is loaded a new feature called caps that can be turned on and off like any other. Note that this will work with TrueType fonts as well, {Georgia} etc; fontspec seems to transfer what's in the .fea file to even non-OpenType fonts.

  • Are there any potential drawbacks that I may not have had in mind? I'm still thinking it seems too simple to be true. (While it may be simple, it may of course still be a lot of work to take are of all lowercase glyphs in advanced multi-language fonts).

  • Has anyone else ever done or seen something like that? I find it hard to believe I should be the first to come up with that idea...

  • If noone has done it so far, would it be a good idea to turn it into a little package?

16
  • 1
    I don't know why it isn't a standard feature for OT fonts.
    – egreg
    Feb 28, 2013 at 15:59
  • 1
    indeed it would be a piece of cake for a font designer to add it to the font they're designing. I've never seen it though, and I've looked at lots of OT fonts over the past years, even ones that are far beyond my budget. I guess one of the reasons it's not implemented is that any DTP software or word processor is able to turn text into uppercase with just one click. So for a font designer there's simply no need to implement that as a font feature, unless they're thinking of people like us.
    – Nils L
    Feb 28, 2013 at 16:04
  • 3
    Feature files are font specific (they depend on the actual glyph names in the font which may vary from font to another), and does not work with XeTeX of course. Since you are using a LuaTeX-only solution, you are better writing lua code to process node lists and work on actual Unicode values and add all the intelligence you want (upper-casing can be complex and is language sensitive). Mar 2, 2013 at 11:42
  • 2
    I don't think, that \ucshape and \itshape, \scshape,… should be completely interchangeable. They work on different levels: \ucshape would change unicode codepoints; \itshape, \scshape only change the glyphs representing the same unicode codepoint. An all uppercase text has less information than a mixed case text, in contrast to a \itshape text compared to an \upshape text. This might be not important in English, but in German it is, as nouns always start with uppercase letters.
    – Toscho
    Apr 19, 2013 at 17:02
  • 2
    As for drawbacks, replacing ß by SS via a feature file wouldn't adapt to changes in hyphenation. Consider, e.g., bei-ßen vs. BEIS-SEN in German reformed orthography. Apr 20, 2013 at 20:26

2 Answers 2

11

A full implementation in LuaTeX:

\documentclass{article}
\usepackage{fontspec}
\usepackage{xcolor}
\usepackage{luacode}
\usepackage{luatexbase}
\usepackage{luatexbase-attr}

\newluatexattribute\uppercaseattr


\begin{luacode*}
  local ucattr = luatexbase.attributes.uppercaseattr
  local GLYPH = node.id("glyph")
  local function makeuppercase(head)
      local orighead = head
      local string = unicode.utf8
      while head do
          if head.id == GLYPH then
              local att = node.has_attribute(head,ucattr)
              if att then
                  if head.char == 223 then -- ß
                      -- insert two 'S' glyphs
                      head.char = 83
                      orighead = node.insert_before(orighead,head,node.copy(head))
                  elseif head.char == 64258 then -- fl
                      head.char = 70 -- F
                      orighead = node.insert_before(orighead,head,node.copy(head))
                      head.char = 76 -- L
                  elseif head.char == 64256 then -- ff
                      head.char = 70 -- F
                      orighead = node.insert_before(orighead,head,node.copy(head))
                      head.char = 70 -- F
                  elseif head.char == 64257 then -- fi
                      head.char = 70 -- F
                      orighead = node.insert_before(orighead,head,node.copy(head))
                      head.char = 73 -- I
                  elseif head.char == 64259 then -- ffi
                      head.char = 70 -- F
                      orighead = node.insert_before(orighead,head,node.copy(head))
                      head.char = 70 -- F
                      orighead = node.insert_before(orighead,head,node.copy(head))
                      head.char = 73 -- I
                  elseif head.char == 64260 then -- ffl
                      head.char = 70 -- F
                      orighead = node.insert_before(orighead,head,node.copy(head))
                      head.char = 70 -- F
                      orighead = node.insert_before(orighead,head,node.copy(head))
                      head.char = 76 -- L
                  else
                      head.char = string.byte(string.upper(string.char(head.char)))
                  end
              end
          end
          head = head.next
      end
      return orighead
  end

  function makeuppercase_hbox(head,groupcode)
      local orighead = head
      if groupcode == "adjusted_hbox" or groupcode == "hbox" then
           makeuppercase(head)
      end
      return orighead
  end

  luatexbase.add_to_callback("hpack_filter",makeuppercase_hbox,"makeuppercasehbox")
  luatexbase.add_to_callback("pre_linebreak_filter",makeuppercase,"makeuppercase")
\end{luacode*}

\newcommand*\ucshape{\uppercaseattr=1}
\DeclareTextFontCommand{\textuc}{\ucshape}

\begin{document}
\hsize 7.2cm
\newcommand\sample{Draußen \i \ij\ ffl fluffiest fish \textit{König} \textcolor{blue}{àéîàáâãäåæ} çèéêëìíîï  ff \hbox{ðñòóôõö} ùúûüýþÿœš}


Lowercase {\ucshape \sample} Lowercase

Lowercase \textuc{\sample} Lowercase

% textcolor doesn't work in LaTeX's \MakeUppercase
% Lowercase \MakeUppercase{\sample} Lowercase


\end{document}

which yields

enter image description here

The program above just manipulates the nodelist iff the given attribute is set (to any value). Attributes are grouped, just like any TeX assignment.

It's a bit more complicated than I thought because we need to treat \hbox{}es separately.

10
  • I'm impressed! I doesn't yet seem to treat \textuc{ffi or ß} correctly (they're not turned into FFI and SS), but I'm assuming this would be possible with some additions (?).
    – Nils L
    Apr 19, 2013 at 8:26
  • 1
    @NilsL I've updated the proof of concept to a full implementation.
    – topskip
    Apr 19, 2013 at 15:00
  • 1
    @Toscho Somebody else has to do that :) I will never ever create code that produces an so called upper case 'ß'...
    – topskip
    Apr 19, 2013 at 17:50
  • 1
    @Toscho for technocrats, for people lacking any typographic sensitivity, or for anyone who can't deal with the fact that the upper- and lower-case Roman alphabets are two different things in their own rights and with their own logics -- it might be good to include that as an option. However, into my texts, that blown-up version of a lower-case ligature will find its way only over my dead body. //:-=(
    – Nils L
    Apr 20, 2013 at 8:17
  • 2
    @NilsL: Nowadays, no regular reader thinks of ß as a ligature. It has become a regular if special letter instead. Some time ago, the ligature ß was the exception to the usual ss/sz writing. Nowadays, all uppercase SS and ss for hyphenation seem the exception to the usual ß.
    – Toscho
    Apr 20, 2013 at 9:57
0

Whilst I can't offer a macro-based \ucshape, I can point to an implementation for \textuc which can cope with accents in a flexible way.

\documentclass{article}
\usepackage{fontspec}
\usepackage{microtype}
\usepackage{textcase}
\usepackage{xcolor}

\setmainfont{TeX Gyre Termes}
\setsansfont{TeX Gyre Heros}

\usepackage{xparse}
\ExplSyntaxOn
% Allow for likely changes to expl3
\cs_if_exist:NTF \text_uppercase:n
  { \tl_put_right:Nn \l_text_exclude_arg_tl { \textcolor } }
  {
    \cs_new_eq:NN \text_uppercase:n \tl_upper_case:n
    \tl_put_right:Nn \l_tl_change_case_exclude_tl { \textcolor } 
  }
\NewExpandableDocumentCommand \textuc { +m } { \text_uppercase:n {#1} }
\ExplSyntaxOff

\begin{document}
\section{Lorem Ipsum}

\textuc{Draußen \i \ij\ ffl fluffiest fish
  \textit{König} \textcolor{blue}{àéîàáâãäåæ} çèéêëìíîï  ff \hbox{ðñòóôõö} ùúûüýþÿœš}

\MakeTextUppercase{daß}
\textuc{daß}

\end{document}

This is expandable and will copy with a range of LaTeX input, though at present there are edge cases: you need something that produces 'pure text'. As it happens at the macro layer, it will work with XeTeX as well as LuaTeX. A version which recognises language-specific changes (Turkic i/dotless-i, Greek accent remove, Lithuanian accent 'fun', Dutch IJ ligature) is also available.

At present, the implementation in expl3 is called \tl_upper_case:n, though this is likely to be renamed to \text_uppercase:n (with improved features) in the near future: my answer reflects that.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .