10

I've already encountered in this site complaints that in the standard font, the horizontal lines (serifs) in the image of square brackets are too short and difficult to visually distinguish. As a workaround, some suggest using overlapping "ceil" and "floor" symbols instead of square brackets. However, this results in unacceptably long serifs.

I need a new command for something intermediate and very limited in use. I don't intend to use it for matrices or any other multi-line environment. Just one short text string between brackets. Unable to find a suitable ready-made solution, I decided to write such a command myself. After all, it's just one vertical bar and two horizontal serifs. This is what I got so far.

\documentclass[12pt,a4paper,notitlepage]{report}
\usepackage[cp1251]{inputenc}
\usepackage[T1,T2A]{fontenc}
\usepackage[russian]{babel}
\usepackage{amsmath, amssymb}
\usepackage{mathrsfs}
\usepackage{enumitem}
\usepackage{relsize}
\usepackage{exscale}
\usepackage{perpage}
\usepackage{nicefrac}
\usepackage[all,cmtip]{xy}
\MakePerPage{footnote}

\newcommand{\LBR}{
    \hspace{0.2em}\rule[0.75em]{0.2em}{0.05em}% top serif
    \hspace{-0.2em}\rule[-0.2em]{0.2em}{0.05em}% bottom serif
    \hspace{-0.3em}\rule[-0.2em]{0.1em}{1.0em}% vertical bar
    \hspace{0.2em}
}
\newcommand{\RBR}{
    \hspace{0.1em}\rule[0.75em]{0.2em}{0.05em}% top serif
    \hspace{-0.2em}\rule[-0.2em]{0.2em}{0.05em}% bottom serif
    \rule[-0.2em]{0.1em}{1.0em}% vertical bar
    \hspace{0.2em}
}
\newcommand{\opni}[1] {{\RBR #1 \LBR}}
\newcommand{\opcli}[1]{{\RBR #1 \RBR}}
\newcommand{\clopi}[1]{{\LBR #1 \LBR}}
\newcommand{\clsi}[1] {{\LBR #1 \RBR}}

\begin{document}

\[
\begin{array}{ll}
\opni{a, b}       &=\ \{x\in A: a < x < b\},\\
\opcli{a, b}      &=\ \{x\in A: a < x\leqslant b\},\\
\clopi{a, b}      &=\ \{x\in A: a\leqslant x < b\},\\
\clsi{a, b}       &=\ \{x\in A: a\leqslant x\leqslant b\},\\
\opni{{\gets},a}  &=\ \{x\in A: x < a\},\\
\opcli{{\gets},a} &=\ \{x\in A: x\leqslant a\},\\
\opni{a,{\to}}    &=\ \{x\in A: a < x\},\\
\clopi{a,{\to}}   &=\ \{x\in A: a\leqslant x\}.
\end{array}
\]

$\mathbb{R}^\clsi{a,b}$
\end{document}

Although not very pretty, this solution is more or less satisfactory - except for two problems. First, in some random cases the vertical bar and one of serifs are too thick. Second and more important, the brackets are not scaled inside superscript, for example, in an expression like $\mathbb{R}^\clsi{a,b}$.

The reason for the first problem I don't understand. The reason for the second I understand, but have no idea what to do about it. Any help is appreciated.

7
  • You can use \mathchoice to define automatically applied variants for different size contexts, see tex.stackexchange.com/questions/148740/what-is-mathchoice for more information. Commented yesterday
  • 2
    The first issue could be a viewer problem, check if the issue persists if you zoom in or out or when you use a different pdf viewer. If this causes the issue to go away then there is not much you can do on the LaTeX side except for trying to find a math font that already has brackets that suit your preference. Commented yesterday
  • If possible, can you post your desired output as Image format? Commented yesterday
  • See also \vert, \ulcorner, \urcorner, \llcorner and \lrcorner. Commented yesterday
  • @samcarter_is_at_topanswers.xyz I edited the question as you required. Commented yesterday

2 Answers 2

10

You may want to adjust the parameters…

\documentclass{article}
\usepackage{amsmath}

\makeatletter
\newcommand{\LBR}{\mathopen{\,\mathpalette\BR@{{}{\hss}}\,}}
\newcommand{\RBR}{\mathclose{\,\mathpalette\BR@{{\hss}{}}\,}}
\newcommand{\BR@}[2]{\BR@@#1#2}
\newcommand{\BR@@}[3]{%
  \vcenter{\offinterlineskip
    \dimen@=\fontdimen 8
      \ifx#1\displaystyle\textfont\else\ifx#1\textstyle\textfont
      \else\ifx#1\scriptstyle\scriptfont\else\scriptscriptfont\fi\fi\fi 3
    \sbox\z@{$\m@th#1[]$}% height
    \sbox\tw@{$\m@th#1\mskip4mu$}% width
    \hbox to\wd\tw@{\leaders\hrule height \dimen@ \hfill}
    \kern-\dimen@
    \hbox to\wd\tw@{#2\vrule height \ht\z@ depth \dp\z@ width 2\dimen@ #3}
    \kern-\dimen@
    \hbox to\wd\tw@{\leaders\hrule height \dimen@ \hfill}
  }%
}
\makeatother
\newcommand{\opni}[1] {\mathopen{\RBR} #1 \mathclose{\LBR}}
\newcommand{\opcli}[1]{\mathopen{\RBR} #1 \RBR}
\newcommand{\clopi}[1]{\LBR #1 \mathclose{\LBR}}
\newcommand{\clsi}[1] {\LBR #1 \RBR}

\begin{document}

$\opni{-a,b}[]$

$\clsi{a,b}$

$X^{\opni{a,b}^{\opni{a,b}}}$

\end{document}

output

The main idea is to build a vertical box that's centered with respect to the math axis with \vcenter; its vertical size is governed by the size of a bracket with \sbox\z@{$\m@th#1[]$} (for a bracket in the current math style, which is accessed via \mathpalette as #1). In this box I stack a horizontal rule as wide as the desired width, which is computed with respect to the current math style as 5mu (a unit that depends on the math style), then a vertical kern as high as the rule, then a box which is left or right aligned according whether we want a left or a right brace (here #2 and #3 are “nothing” and \hss, filling glue, in the desired order). The same as the top is done at the bottom.

The thickness of the rules is a multiple of \fontdimen8 of the current math extension font, which is the thickness of the fraction line, so it changes according to the math style.

The left brace is defined as an opening atom, the right brace as a closing atom; however, in the definition of the open intervals, where the brace is on the wrong side, the type of atom is reversed.

2
  • Yes! It works. Thank you very much. There's an issue, however: it's too complicated for me, and I can't figure out how it works. So I simply copied your solution and pasted it in my file. I'm just a LaTeX user and never intended to program in LaTeX except for the simplest cases. I thought the brackets task should be simple, but it turns out it's not. So, could you please write some comments explaining your code? Commented yesterday
  • @Ilia I added some explanations Commented yesterday
3

If you're able to use LuaLaTeX, you can freely modify any glyph in the font, so you can stretch the square brackets to give the results you're looking for:

\documentclass{article}

\usepackage{luacode}
\begin{luacode*}
    -----------------------------
    --- Adjustable Parameters ---
    -----------------------------

    -- We can specify the adjustments for each character in each font using
    -- the following table. All units are relative to the original width of the
    -- glyph.
    local horizontal_adjustments = {
        ["latinmodern-math.otf"] = {
            ["["] = {
                middle = 0.6,
                left_stretch = 1.0,
                right_stretch = 5.0,
                left_sidebearing = 0.0,
                right_sidebearing = -0.75,
            },
            ["]"] = {
                middle = 0.4,
                left_stretch = 5.0,
                right_stretch = 1.0,
                left_sidebearing = -0.5,
                right_sidebearing = 0.0,
            },
        },
        ["texgyrepagella-math.otf"] = {
            ["("] = {
                middle = 0.5,
                left_stretch = 1.0,
                right_stretch = 3.0,
                left_sidebearing = 0.0,
                right_sidebearing = -0.5,
            },
            [")"] = {
                middle = 0.5,
                left_stretch = 3.0,
                right_stretch = 1.0,
                left_sidebearing = -0.5,
                right_sidebearing = 0.0,
            },
        },
        ["texgyrepagella-regular.otf"] = {
            ["r"] = {
                middle = 0.6,
                left_stretch = 1.0,
                right_stretch = 5.0,
                left_sidebearing = 0.0,
                right_sidebearing = -0.7,
            },
            ["L"] = {
                middle = 0.6,
                left_stretch = 1.0,
                right_stretch = 5.0,
                left_sidebearing = 0.0,
                right_sidebearing = -0.5,
            },
        },
    }

    ----------------------
    --- Implementation ---
    ----------------------

    -- Load the font-cff module from ConTeXt, which is required to use
    -- "pack_result_tagged"
    do
        local data = io.loaddata(kpse.find_file("font-cff.lmt"))
        data = data:gsub("<const>", ""):gsub("pack_result_tagged =", "fonts.handlers.otf.pack_result_tagged =")
        load(data, "font-cff.lua", "t", luaotfload.fontloader)()
    end

    -- Cache the seen segments so that we only stretch each unique shape once.
    local seen = {}

    -- Stretch the glyph segments according to the provided adjustment
    -- parameters.
    local function stretch_glyph(segments, original_width, adjustment)
        -- Extract the parameters from the adjustment table.
        local middle = adjustment.middle * original_width
        local left_stretch = adjustment.left_stretch
        local right_stretch = adjustment.right_stretch
        local left_sidebearing = adjustment.left_sidebearing
        local right_sidebearing = adjustment.right_sidebearing

        -- Calculate the overall scale factor
        local scale_factor = (left_stretch + right_stretch) / 2
        scale_factor = scale_factor + left_sidebearing + right_sidebearing

        -- If we've already seen this shape, return the cached result.
        if seen[segments] then
            return segments, scale_factor
        end
        seen[segments] = true

        -- Loop over each segment and stretch the x-coordinates according to
        -- the provided parameters.
        for _, segment in ipairs(segments) do
            for i = 1, #segment - 1, 2 do
                -- Get the x and y coordinates of the current point.
                local x, y = segment[i], segment[i + 1]
                if type(x) ~= "number" or type(y) ~= "number" then
                    goto continue
                end

                -- Stretch the x-coordinate
                if x < middle then
                    segment[i] = left_stretch * (x - middle) + middle
                else
                    segment[i] = right_stretch * (x - middle) + middle
                end

                -- Offset the x-coordinate to keep the glyph centered
                segment[i] = segment[i] + (left_stretch - 1) * middle

                -- Adjust the left sidebearing
                segment[i] = segment[i] + (left_sidebearing * original_width)

                ::continue::
            end
        end
        return segments, scale_factor
    end

    luatexbase.add_to_callback(
        "luaotfload.patch_font",
        function(tfmdata, specification, font_id)
            -- Only apply the adjustments if the font filename matches one of
            -- the keys in the adjustments table.
            local path = tfmdata.specification.filename
            local filename = file.basename(path)
            local by_filename = horizontal_adjustments[filename]

            if not by_filename then
                return
            end

            -- Parameters that apply to the whole font.
            tfmdata.streamprovider = 1
            local size = tfmdata.size
            local units_per_em = tfmdata.units_per_em

            -- Loop over each character in the adjustments table for this font.
            for character, adjustment in pairs(by_filename) do
                -- Get the original data from the font.
                local codepoint = utf8.codepoint(character)
                local index = tfmdata.characters[codepoint].index

                local character = tfmdata.characters[codepoint]
                local shapes    = fonts.hashes.shapes[font_id].glyphs
                local streams   = fonts.hashes.streams[font_id].streams

                local original_shape = shapes[index]

                -- Stretch the glyph segments according to the provided
                -- adjustment parameters.
                local new_segments, scale_factor = stretch_glyph(
                    original_shape.segments,
                    original_shape.width,
                    adjustment
                )

                -- Update the font data with the new segments and width.
                local new_stream = fonts.handlers.otf.pack_result_tagged(
                    new_segments, scale_factor * original_shape.width, 0, 0
                )
                streams[index] = new_stream
                character.width = scale_factor * character.width
            end
        end,
        "rewrite-characters"
    )
\end{luacode*}


%%%%%%%%%%%%%%%%%%%%%
%%% Demonstration %%%
%%%%%%%%%%%%%%%%%%%%%

\usepackage{fontspec}
\usepackage{unicode-math}

\pagestyle{empty}
\setlength{\parskip}{\baselineskip}
\setlength{\parindent}{0pt}

\begin{document}
    \Huge

    \setmathfont{Latin Modern Math}
    \setmainfont{Latin Modern Roman}
    $\displaystyle [a, b]^{[a, b]^{[a, b]}}$

    \setmathfont{TeX Gyre Pagella Math}
    \setmainfont{TeX Gyre Pagella}
    $\displaystyle (a, b)_{(a, b)_{(a, b)}}$

    really Long
\end{document}

output

1
  • I'm using the MikTex package; I don't know if it supports LuaLatex. Even if it does, this solution seems overly complex for such a simple task. Still, it's good to know about this possibility. Thanks! Commented 14 hours ago

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.