I'm using Plain TeX (actually XeTeX for font reasons but I am using it basically like it's Plain, which I'm used to). I'm typing a poem up for someone and i want to create an effect along the lines of

it's magic

I thought maybe I could use \phantom for this purpose, like

it's magic
\hphantom{it's} tragic

I used \hphantom because I want horizontal space, but I don't totally know the difference. The output with either \hphantom and \phantom is the same:

enter image description here

It also doesn't seem to matter whether I put the space inside the \phantom box or outside.

I'm not so sure why the phantom is being put on its own line, but I imagine it's something to do with \obeylines that I don't understand. Thanks in advance for the help, and sorry if this is perhaps some stupid mistake.

  • 1
    \obeylines makes every line its own paragraph, and \hphantom doesn't switch into hmode, you need \indent\hphantom{it's} tragic. Apr 21 at 17:18
  • Thanks! This worked wonderfully. If you post you comment as an answer I would gladly accept it!
    – marcelgoh
    Apr 21 at 17:34

3 Answers 3


enter image description here

If you use LaTeX the markup you show has the effect you intended.



it's magic
\hphantom{it's} tragic


In plain TeX \hphantom doesn't start a paragraph so:

it's magic
\leavevmode\hphantom{it's} tragic


But I would only use plain TeX for small test examples, not real documents.

  • 1
    I would only use LaTeX for small test examples, not real documents.
    – wipet
    Apr 21 at 19:36
  • @wipet I know:-) But actually I would assume you'd be more likely to use optex than plain, which is same idea a format with more facilities than plain which is really just appendix B "basic macros" as an example format for the texbook. Apr 21 at 20:24
  • Ah, I have used Plain TeX for a few years and find it really hard to switch to LaTeX in the times that it's required of me (e.g., a journal only has a style file in LaTeX). My only problem with Plain TeX is the relative lack of resources when things go wrong, and in this regard @DavidCarlisle thanks for all the help, because I think you've answered many of my questions over the past few years!
    – marcelgoh
    Apr 21 at 23:26
  • 1
    @marcelgoh plain really makes no sense as a format for a document, it really is just a "starter kit" enough for the texbook excercises. It has no automatic numbering or cross referencing no support for non ascii characters, no support for different font sizes ... you don't have to use latex, wipet's optex is an "extended plain" for example, as is the venerable eplain, or context or ... Apr 22 at 7:57
  • @DavidCarlisle if I need non-ascii, I use XeTeX for that, but usually I don't, and as for numbering and cross-referencing, I have my own scripts for that! Not the easiest thing but it's nice to be able to fine tune stuff by changing my own lines of code.
    – marcelgoh
    Apr 23 at 14:30

Using \halign:

  it's &magic\cr

enter image description here

  • The poem I'm typesetting has a ton of possible points of alignment, so it would require a lot of &s, but this is definitely a good solution for ones that are a bit more regularly spaced!
    – marcelgoh
    Apr 21 at 23:24

If you're willing to use LuaTeX (which should generally be compatible with XeTeX), we can define an environment that automatically aligns any initial indents with the previous line, without needing any additional markup:

%%% Formatting %%%

% Make LuaTeX load fonts like XeTeX
\input luaotfload.sty
    config.luaotfload.default_features.global.tlig = true
    config.luaotfload.default_features.global.trep = true

% Load the fonts
\font\seventeenrm="[lmroman17-regular]" at 17pt
\font\adventorbold="TeX Gyre Adventor/B" at 12pt

% Set the main font


%%% TeX Code %%%


        \alignspacesattr=1% Mark all nodes with this attribute
        \catcode`\ =12% Make the space into a regular character
        \obeylines% Process line-by-line
        \toksapp\everypar{\setbox0=\lastbox}% Remove the indent from every line
        \par% Make sure that the last line is processed

%%% Lua Code %%%

    % Helper function to traverse through any marked nodes
    local n_attr
    local function get_next_attr(n)
        local next = n.next
        if next then
            n_attr = select(2, node.find_attribute(next, "alignspacesattr"))
            return n_attr

    % Replaces a node with another node
    local function replace_node(head, n1, n2)
        local head, current = node.remove(head, n1)
        head, n2 = node.insert_before(head, current, n2)
        return head, n2

    local prev_widths = {}

    % Define the callback, which runs for every line
    luatexbase.add_to_callback("pre_linebreak_filter", function (head)
        % Remove any discretionaries
        local head = head
        if get_next_attr(head) then
            head = node.flatten_discretionaries(head)

        % Initialize the line-local variables
        local this_widths = {}
        local before_chars = true
        n_attr = head

        % Iterate over every character in a marked line
        while get_next_attr(n_attr) do
            local char = n_attr.char

            % Replace space glyphs with some sort of glue
            if char == string.byte(" ") then
                local hskip = node.new("glue")

                if before_chars then
                    % If we're at the start of the line, then make the space the
                    % same width as the character immediately above it.
                    hskip.width = prev_widths[rawlen(this_widths) + 1]
                    % If we're in the middle of the line, set it to the regular
                    % space width.
                    local params = font.getfont(n_attr.font).parameters
                        params.space, params.space_stretch, params.space_shrink,
                        0, 0
                head, n_attr = replace_node(head, n_attr, hskip)
                % If we're not at a space, then set that we're not at the start
                % of the line.
                before_chars = false

            % Recurse through any ligatures
            local chars = {}
            local function check_components(n)
                if n.components then
                    for m in node.traverse(n.components) do
                elseif char and
                       (n.id == node.id("glyph") or n.id == node.id("glue"))
                    chars[rawlen(chars) + 1] = n

            % Save the widths of the characters on this line
            for _, n in ipairs(chars) do
                this_widths[rawlen(this_widths) + 1] = (n.width or 0)
        prev_widths = this_widths

        return head
    end, "align_spaces")

%%% Demo %%%

% Works with different character widths
abc def ghi jkl mno pqr
    def ghi
         hi jkl m
                 no pqr
                      r stu
      f ghi jkl mno pqr stu
% And with em-dashes
  M l M --- M
    l M --- M
      M --- M
        --- M
% And with ligatures
% And with different fonts
            ABCDEF G {\adventorbold H I} J K
                     {\adventorbold   I} J
                EF G {\adventorbold H I}
            ABCDEF G {\adventorbold H  }
abM l M --- ABC



You must log in to answer this question.

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