• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

sile-typesetter / sile / 7054606668

01 Dec 2023 01:43AM UTC coverage: 70.141% (-4.2%) from 74.329%
7054606668

push

github

web-flow
Merge 14837a0c3 into a6c229613

11050 of 15754 relevant lines covered (70.14%)

3938.65 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.94
/packages/ruby/init.lua
1
local base = require("packages.base")
1✔
2

3
local package = pl.class(base)
1✔
4
package._name = "ruby"
1✔
5

6
local isLatin = function (char)
7
  return (char > 0x20 and char <= 0x24F) or (char >= 0x300 and char <= 0x36F)
7✔
8
    or (char >= 0x1DC0 and char <= 0x1EFF) or (char >= 0x2C60 and char <= 0x2c7F)
7✔
9
end
10

11
local checkIfSpacerNeeded = function (reading)
12
  -- First, did we have a ruby node at all?
13
  if not SILE.scratch.lastRubyBox then return end
6✔
14
  -- Does the current reading start with a latin?
15
  if not isLatin(SU.codepoint(SU.firstChar(reading))) then return end
20✔
16
  -- Did we have some nodes recently?
17
  local top = #SILE.typesetter.state.nodes
4✔
18
  if top < 2 then return end
4✔
19
  -- Have we had other stuff since the last ruby node?
20
  if SILE.typesetter.state.nodes[top] ~= SILE.scratch.lastRubyBox
4✔
21
     and SILE.typesetter.state.nodes[top-1] ~= SILE.scratch.lastRubyBox then
4✔
22
    return
2✔
23
  end
24
  -- Does the previous reading end with a latin?
25
  if not isLatin(SU.codepoint(SU.lastChar(SILE.scratch.lastRubyText))) then return end
8✔
26
  -- OK, we need a spacer!
27
  SILE.typesetter:pushGlue(SILE.settings:get("ruby.latinspacer"))
2✔
28
end
29

30
function package:_init ()
1✔
31
  base._init(self)
1✔
32
  -- Japanese language support defines units which are useful here
33
  self:loadPackage("font-fallback")
1✔
34
  SILE.call("font:add-fallback", { family = "Noto Sans CJK JP" })
1✔
35
  SILE.languageSupport.loadLanguage("ja")
1✔
36
end
37

38
function package.declareSettings (_)
1✔
39

40
  SILE.settings:declare({
2✔
41
    parameter = "ruby.height",
42
    type = "measurement",
43
    default = SILE.measurement("1zw"),
2✔
44
    help = "Vertical offset between the ruby and the main text"
×
45
  })
46

47
  SILE.settings:declare({
2✔
48
    parameter = "ruby.latinspacer",
49
    type = "glue",
50
    default = SILE.nodefactory.glue("0.25em"),
2✔
51
    help = "Glue added between consecutive Latin ruby"
×
52
  })
53

54
end
55

56
function package:registerCommands ()
1✔
57

58
  self:registerCommand("ruby:font", function (_, _)
2✔
59
    SILE.call("font", { size = "0.6zw", weight = 800 })
6✔
60
  end)
61

62
  self:registerCommand("ruby", function (options, content)
2✔
63
    local reading = SU.required(options, "reading", "\\ruby")
6✔
64
    SILE.typesetter:setpar("")
6✔
65

66
    checkIfSpacerNeeded(reading)
6✔
67

68
    local rubybox = SILE.call("hbox", {}, function ()
12✔
69
      SILE.settings:temporarily(function ()
12✔
70
        SILE.call("noindent")
6✔
71
        SILE.call("ruby:font")
6✔
72
        SILE.typesetter:typeset(reading)
6✔
73
      end)
74
    end)
75
    rubybox.outputYourself = function (box, typesetter, line)
76
      local ox = typesetter.frame.state.cursorX
6✔
77
      local oy = typesetter.frame.state.cursorY
6✔
78
      typesetter.frame:advanceWritingDirection(rubybox.width)
6✔
79
      typesetter.frame:advancePageDirection(-SILE.settings:get("ruby.height"))
18✔
80
      SILE.outputter:setCursor(typesetter.frame.state.cursorX, typesetter.frame.state.cursorY)
6✔
81
      for i = 1, #(box.value) do
20✔
82
        local node = box.value[i]
14✔
83
        node:outputYourself(typesetter, line)
14✔
84
      end
85
      typesetter.frame.state.cursorX = ox
6✔
86
      typesetter.frame.state.cursorY = oy
6✔
87
    end
88
    -- measure the content
89
    local cbox = SILE.call("hbox", {}, content)
6✔
90
    SU.debug("ruby", "base box is", cbox)
6✔
91
    SU.debug("ruby", "reading is", rubybox)
6✔
92
    if cbox:lineContribution() > rubybox:lineContribution() then
18✔
93
      SU.debug("ruby", "Base is longer, offsetting ruby to fit")
×
94
      -- This is actually the offset against the base
95
      rubybox.width = SILE.length(cbox:lineContribution() - rubybox:lineContribution())/2
×
96
    else
97
      local diff = rubybox:lineContribution() - cbox:lineContribution()
18✔
98
      local to_insert = SILE.length(diff / 2)
12✔
99
      SU.debug("ruby", "Ruby is longer, inserting", to_insert, "either side of base")
6✔
100
      cbox.width = rubybox:lineContribution()
12✔
101
      rubybox.height = 0
6✔
102
      rubybox.width = 0
6✔
103
      -- add spaces at beginning and end
104
      table.insert(cbox.value, 1, SILE.nodefactory.glue(to_insert))
12✔
105
      table.insert(cbox.value, SILE.nodefactory.glue(to_insert))
12✔
106
    end
107
    SILE.scratch.lastRubyBox = rubybox
6✔
108
    SILE.scratch.lastRubyText = reading
6✔
109
  end)
110

111
end
112

113
package.documentation = [[
114
\begin{document}
115
\font:add-fallback[family=Noto Sans CJK JP]
116
\use[module=packages.ruby]
117
Japanese texts often contain pronunciation hints (called \em{furigana}) for difficult kanji or foreign words.
118
These hints are traditionally placed either above (in horizontal typesetting) or beside (in vertical typesetting) the word that they explain.
119
The typesetting term for these glosses is \em{ruby}.
120

121
The \autodoc:package{ruby} package provides the \autodoc:command[check=false]{\ruby[reading=<ruby text>]{<base text>}} command which sets a piece of ruby above or beside the base text.
122
For example:
123

124
\set[parameter=ruby.height,value=12pt]
125
\define[command=ja]{\font[family=Noto Sans CJK JP,language=ja]{\process}}
126

127
\begin{autodoc:codeblock}
128
\\ruby[reading=\ja{れいわ}]\{\ja{令和}\}
129
\end{autodoc:codeblock}
130

131
Produces:
132
\medskip
133
\ja{\ruby[reading=れいわ]{令和}}
134

135
\font:remove-fallback
136
\end{document}
137
]]
1✔
138

139
return package
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc