3

I want to achieve a perfect alignment of the right (and/or left) margins of all the lines of paragraph (to the ink), among the lines themselves and eventually with other geometric elements of a page.

It can be done in xetex provided that you know the text in advance, and then to use the information provided by the bounding box of the last (or the first) glyph to offset the gap, using the proper sidebearing. Precision copyfitting in xcoffin

But the solution is unsuitable with unknown material that might appear in templates or to implement in chapter’s styles, where you need to let the TeX linebreaking finish its job before being able to see the final layout.

A general approach is to use the post_linebreak_filter in LuaTex. It was provided by Marcel Krüger in sidebearings and precision left/right alignment

“In LuaTeX, you can use the post_linebreak_filter to intercept the broken lines and add some offsets. The sidebearings can be extracted from rawdata saved by luaotfload. After adding the offsets, the hboxes have to be repacked to determine the new glue settings.”

Unfortunately Marcel's solution used xadvance, an undocumented "field", which was dropped in Luatex 1.08.0.

I must say that the effect (or defect) it is only noticeable in long lines with large fonts, or when you want to align text with geometric elements present in the page. See the MWE that follows.

You should notice that even in “normal”, justified text, the uneven gap is not suppressed.

Is there an alternative solution?

% !TeX TS-program = lualatex

%%% IT DOES NOT WORK WITH LUATEX 1.08 or LATER !!!
%%% IT FAILS WITH LUATEX 1.08 or LATER !!!
%%% >>  warning (node filter): error: [\directlua]:11: attempt to perform 
%%%     arithmetic on field 'xadvance' (a nil value) 

\documentclass[12pt]{article}    
\RequirePackage{xcolor}
\RequirePackage{luacode}

\newcount\dropsidebearings   

\begin{luacode*}
    local function drop_sidebearing(head, groupcode)
        if tex.count['dropsidebearings'] == 0 then
            return true
        end
        for n in node.traverse_id(node.id'hlist', head) do
            local char = node.has_glyph(n.head)
                if char then
                local f = font.getfont(char.font)
                    if f.shared then
                        local off = f.shared.rawdata.descriptions[char.char].boundingbox[1]*f.size/1000
                        char.xadvance = char.xadvance - off
                        char.xoffset = char.xoffset - off
                    end
                end
            for ch in node.traverse_id(node.id'glyph', n.head) do
                char = ch
            end
            if char then
                local f = font.getfont(char.font)
                if f.shared then
                    local desc = f.shared.rawdata.descriptions[char.char]
                    char.xadvance = char.xadvance - (desc.width-desc.boundingbox[3])*f.size/1000
                end
            end
            local new_list = node.hpack(n.head, n.width, 'exactly')
            new_list.head = nil
            n.glue_order = new_list.glue_order 
            n.glue_set = new_list.glue_set
            n.glue_sign = new_list.glue_sign 
            node.free(new_list)
        end
        return true
    end
    luatexbase.add_to_callback('post_linebreak_filter', drop_sidebearing, 'Drop sidebearings after linebreaking')
\end{luacode*}


\newcommand{\hairlineiv}[1][green]{% 
  \leavevmode%
  \kern-0.1pt %
  \smash{\color{#1}\vrule height 6\baselineskip depth 5pt width 0.1pt}%
  \kern-0.1pt
}

\begin{document}  
\pagestyle{empty}  

\newcommand{\longtitles}{Long titles must be exactly aligned with the vertical green bar.}% the main title

Note the {\bfseries uneven} gap between the text and the green line.
\vspace{3\baselineskip}

\begin{minipage}{5in}
    \dropsidebearings=0  %correction OFF    
    \raggedleft\sffamily\fontsize{60}{72}\selectfont\bfseries \longtitles\hairlineiv
\end{minipage}

\newpage

Perfect aligment! Each whole line was right shifted by a different amount.
\vspace{3\baselineskip}

\begin{minipage}{5in}
  \dropsidebearings=1  %correction ON     
 \raggedleft\sffamily\fontsize{60}{72}\selectfont\bfseries \longtitles\hairlineiv
\end{minipage}

\end{document} 
  • Why don't you use microtype and set suitable protrusion values? – Ulrike Fischer Jan 16 at 17:38
  • To my knowledge microtype does not achieve exact alignment nor claim to do so. Certainly improves the ragged margins to improve their optical appearance, see pages 39–50 of Hàn Thế Thành’s dissertation. pragma-ade.com/pdftex/thesis.pdf From page 46: "Sample 5.4: Justified text with margin kerning. Both left and right columns were typeset with level 1 character protruding. Margin edges were put to the right column to show that positions of certain characters are slightly adjusted. The margins are mechanically subtly ragged but they appear more smooth to the human eye." – Simon Dispa Jan 16 at 18:36
3

You can just insert kerns instead of using the xadvance and xoffset fields.

\documentclass[12pt]{article}    
\RequirePackage{xcolor}
\RequirePackage{luacode}

\newcount\dropsidebearings

\begin{luacode*}
local function drop_sidebearing(head, groupcode)
    if tex.count['dropsidebearings'] == 0 then
        return true
    end
    for n in node.traverse_id(node.id'hlist', head) do
        local char = node.has_glyph(n.head)
        if char then
            local f = font.getfont(char.font)
            if f.shared then
                local kern = node.new(node.id'kern')
                kern.kern = - f.shared.rawdata.descriptions[char.char].boundingbox[1]*f.size/1000
                n.head = node.insert_before(n.head, char, kern)
            end
        end
        for ch in node.traverse_id(node.id'glyph', n.head) do
            char = ch
        end
        if char then
            local f = font.getfont(char.font)
            if f.shared then
                local desc = f.shared.rawdata.descriptions[char.char]
                local kern = node.new(node.id'kern')
                kern.kern = - (desc.width-desc.boundingbox[3])*f.size/1000
                node.insert_after(n.head, char, kern)
            end
        end
        local new_list = node.hpack(n.head, n.width, 'exactly')
        new_list.head = nil
        n.glue_order = new_list.glue_order 
        n.glue_set = new_list.glue_set
        n.glue_sign = new_list.glue_sign 
        node.free(new_list)
    end
    return true
end
luatexbase.add_to_callback('post_linebreak_filter', drop_sidebearing, 'Drop sidebearings after linebreaking')
\end{luacode*}


\newcommand{\hairlineiv}[1][green]{% 
  \leavevmode%
  \kern-0.1pt %
  \smash{\color{#1}\vrule height 6\baselineskip depth 5pt width 0.1pt}%
  \kern-0.1pt
}

\newcommand{\hairlinevi}[1][green]{% 
  \leavevmode%
  \kern-0.1pt %
  \smash{\color{#1}\vrule depth 6\baselineskip height 2ex width 0.1pt}%
  \kern-0.1pt
}
\begin{document}  
\pagestyle{empty}  

\newcommand{\longtitles}{Long titles must be exactly aligned with the vertical green bar.}% the main title

Note the {\bfseries uneven} gap between the text and the green line.
\vspace{3\baselineskip}

\begin{minipage}{5in}
    \dropsidebearings=0  %correction OFF    
    \raggedleft\sffamily\fontsize{60}{72}\selectfont\bfseries \longtitles\hairlineiv
\end{minipage}

\newpage

Perfect aligment! Each whole line was right shifted by a different amount.
\vspace{3\baselineskip}

\begin{minipage}{5in}
  \dropsidebearings=1  %correction ON     
 \raggedleft\sffamily\fontsize{60}{72}\selectfont\bfseries \longtitles\hairlineiv
\end{minipage}

\begin{minipage}{5in}
  \dropsidebearings=1  %correction ON     
 \raggedright\sffamily\fontsize{60}{72}\selectfont\bfseries \hairlinevi\longtitles
\end{minipage}

\end{document}
  • Thank you for sending your report. Lets see what come out. Still I fail to see how a kern, which is applied to pairs of glyphs will help to get the exact "advance" (in the case of the last letter) to get the right alignment of several lines. It amaze me that, in spite of the maturity LuaTeX is achieving, this simple capability it is not obvious. – Simon Dispa Jan 16 at 17:14
  • It doesn't segfault in texlive 2018 but I get an lua error attempt to node.insert_before() a non-existing node. – Ulrike Fischer Jan 16 at 17:17
  • @UlrikeFischer That is probably why it segfaults in 1.09.2. I don't think that error should happen. – Henri Menke Jan 16 at 20:25
  • @MarcelKrüger Thank you, but are you sure we can omit node.copy and node.free? – Henri Menke Jan 16 at 21:04
  • @HenriMenke Oh, I forgot that I changed that too. Yes, it is safe but doesn't make a real difference. node.copy would just be necessary if we would still need the original node, but if we free the original node anyway we can just use it directly to avoid the overhead of the additional copy. Of course, without the copy we are no longer allowed to free the original node because it now belongs to the list. – Marcel Krüger Jan 16 at 21:13
3

You can achieve the effect you are after by setting suitable protrusion values. The values can be adjusted quite finely by font and font family (the example doesn't try this). It is probably possible to create the values with some lua code. (I moved the hairline, so that it doesn't disturb the protrusion of the period)

\documentclass[12pt]{article}
\RequirePackage{xcolor}
\RequirePackage{microtype}
\SetProtrusion
{encoding= *}
{l = { ,178},
 h = { ,120},
 . = { ,270}}

\newcommand{\hairlineiv}[1][green]{%
  \leavevmode%
  \kern-0.1pt %
  \smash{\color{#1}\vrule height 6\baselineskip depth 5pt width 0.1pt}%
  \kern-0.1pt
}

\begin{document}
\pagestyle{empty}

\newcommand{\longtitles}{Long titles must be exactly aligned with the vertical green bar.}% the main title

\begin{minipage}{5in}    
    \raggedleft\sffamily\fontsize{60}{72}\selectfont\bfseries \longtitles

    x\hairlineiv
\end{minipage}

\end{document} 

enter image description here

  • My original question was how to achieve the exact alignment WITHOUT knowing forehand where TeX will break the line. If you already know that, then you can also apply the solution posted in tex.stackexchange.com/questions/34563/… which is independent of the font, family or series. The "longtitle", in the MWE included, has forced line breakers to show the effect on different characters, including the final dot. But this is not usual the case. You let TeX do the job. – Simon Dispa Jan 16 at 19:51
  • I only added the linebreaks to force the chars for which I adjusted the protrusion to be at the end of lines. It works also without them, see my edit. – Ulrike Fischer Jan 16 at 20:02
  • Just change "\begin{minipage}{5n}" to "\begin{minipage}{4in}" and see what happens with the now endings "s", "y", "d", "s", .. – Simon Dispa Jan 16 at 20:57
  • Sure. I only set the values for 3 chars. I really don't have the time to go through all the alphabet. You will have to setup this yourself. – Ulrike Fischer Jan 16 at 21:00
  • OK, Now try " \raggedleft\normalfont\fontsize{60}{72}\selectfont\bfseries \longtitles" and look at the dot. – Simon Dispa Jan 16 at 22:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.