Skip to content

Commit

Permalink
feat: add character style support for text casing
Browse files Browse the repository at this point in the history
  • Loading branch information
Omikhleia authored and Didier Willis committed Aug 2, 2023
1 parent 5282d4d commit 9d29721
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 120 deletions.
10 changes: 1 addition & 9 deletions classes/resilient/resume.lua
Original file line number Diff line number Diff line change
Expand Up @@ -230,15 +230,7 @@ end

-- RESUME PROCESSING

-- Hacky-whacky way to create a ptable tree programmatically
-- loosely inspired by what inputfilter.createCommand() does.
local function C(command, options, content)
local result = content
result.options = options
result.command = command
result.id = "command"
return result
end
local C = utils.createStructuredCommand

local function doEntry (rows, _, content)
local topic = utils.extractFromTree(content, "topic")
Expand Down
6 changes: 3 additions & 3 deletions examples/manual-styling/basics/character.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@ A regular character style obeys to the following specification
color: "⟨color specification⟩"
properties:
position: "normal|super|sub"
case: "normal|upper|lower|title"
```
The ⟨font specification⟩ is an object which can contain any of the usual elements, as
used in the SILE `\font` command.

The ⟨color specification⟩ follows the same syntax as defined in the SILE **color** package.

The "properties" might be extended in a future revision; for now they support a position
element, to specify a superscript or subscript formatting. The "normal" value may be used
to override a parent style definition, when style inheritance is used.
The "properties" might be extended in a future revision; for now they support a position element, to specify a superscript or subscript formatting, and a text case element.
The "normal" values may be used to override a parent style definition, when style inheritance is used.

As an example, the following style results in a blue italic superscript in the Libertinus
Serif font.
Expand Down
41 changes: 22 additions & 19 deletions packages/resilient/epigraph/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -77,27 +77,30 @@ function package:registerCommands ()
end

SILE.settings:set("document.parindent", parindent)
SILE.call("style:apply:paragraph", { name = "epigraph-text" }, function()
SILE.process(content)
if rule:tonumber() ~= 0 then
SILE.typesetter:leaveHmode()
SILE.call("noindent")
SILE.call("raise", { height = "0.5ex" }, function ()
-- HACK. Oh my. When left-aligned, the rule is seen as longer than The
-- line and a new line is inserted. Tweaking it by 0.05pt seems to avoid
-- it. Rounding issue somewhere? I feel tired.
SILE.call("hrule", {width = epigraphw - 0.05, height = rule })
end)
end
if source then
SILE.typesetter:leaveHmode(1)
if rule:tonumber() == 0 then
SILE.call("style:apply:paragraph", { name = "epigraph-source-norule" }, source)
else
SILE.call("style:apply:paragraph", { name = "epigraph-source-rule" }, source)
SILE.call("style:apply:paragraph", { name = "epigraph-text" }, {
utils.subTreeContent(content),
function ()
if rule:tonumber() ~= 0 then
SILE.typesetter:leaveHmode()
SILE.call("noindent")
SILE.call("raise", { height = "0.5ex" }, function ()
-- HACK. Oh my. When left-aligned, the rule is seen as longer than The
-- line and a new line is inserted. Tweaking it by 0.05pt seems to avoid
-- it. Rounding issue somewhere? I feel tired.
SILE.call("hrule", {width = epigraphw - 0.05, height = rule })
end)
end
if source then
SILE.typesetter:leaveHmode(1)
if rule:tonumber() == 0 then
SILE.call("style:apply:paragraph", { name = "epigraph-source-norule" }, source)
else
SILE.call("style:apply:paragraph", { name = "epigraph-source-rule" }, source)
end
end
end
end)
})

SILE.typesetter:leaveHmode()
end)
end, "Displays an epigraph.")
Expand Down
47 changes: 23 additions & 24 deletions packages/resilient/lists/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -106,31 +106,30 @@ function package:doItem (options, content)
end

local mark = hboxer.makeHbox(function ()
SILE.call("style:apply", { name = styleName }, function ()
if enumStyle.character then
local cp = unichar(enumStyle.character)
if cp then
SILE.typesetter:typeset(luautf8.char(cp + counter - 1))
else
SU.error("Invalid enumeration symbol in style '" .. styleName .. "'")
end
elseif enumStyle.display then
SILE.typesetter:typeset(enumStyle.before)
SILE.typesetter:typeset(self.class.packages.counters:formatCounter({
value = counter,
display = enumStyle.display })
)
SILE.typesetter:typeset(enumStyle.after)
else -- enumStyle.symbol
local bullet = options.bullet or enumStyle.symbol
local cp = unichar(bullet)
if cp then
SILE.typesetter:typeset(luautf8.char(cp))
else
SILE.typesetter:typeset(bullet)
end
local text
if enumStyle.character then
local cp = unichar(enumStyle.character)
if cp then
text = luautf8.char(cp + counter - 1)
else
SU.error("Invalid enumeration symbol in style '" .. styleName .. "'")
end
elseif enumStyle.display then
text = enumStyle.before
.. self.class.packages.counters:formatCounter({
value = counter,
display = enumStyle.display })
.. enumStyle.after
else -- enumStyle.symbol
local bullet = options.bullet or enumStyle.symbol
local cp = unichar(bullet)
if cp then
text = luautf8.char(cp)
else
text = bullet
end
end)
end
SILE.call("style:apply", { name = styleName }, { text })
end)

local stepback
Expand Down
62 changes: 34 additions & 28 deletions packages/resilient/sectioning/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
-- License: MIT
--
local base = require("packages.resilient.base")
local utils = require("resilient.utils")

local package = pl.class(base)
package._name = "resilient.sectioning"
Expand Down Expand Up @@ -107,45 +108,50 @@ function package:registerCommands ()
-- 3. Process the section content
local numSty = secStyle.numberstyle.main and self:resolveStyle(secStyle.numberstyle.main)
local numDisplay = numSty and numSty.numbering and numSty.numbering.display or "arabic"
SILE.call("style:apply:paragraph", { name = name }, function ()
-- 3A. Counter for numbered sections
local number
if numbering then
SILE.call("increment-multilevel-counter", {
id = secStyle.counter.id,
level = secStyle.counter.level,
display = numDisplay
})
number = self.class.packages.counters:formatMultilevelCounter(
self.class:getMultilevelCounter(secStyle.counter.id), { noleadingzeros = true }
)
end
-- FIXME We could refactor more here: some bits do not have to be in the paragraph style.
SILE.call("style:apply:paragraph", { name = name }, {
function ()
-- 3A. Counter for numbered sections
local number
if numbering then
SILE.call("increment-multilevel-counter", {
id = secStyle.counter.id,
level = secStyle.counter.level,
display = numDisplay
})
number = self.class.packages.counters:formatMultilevelCounter(
self.class:getMultilevelCounter(secStyle.counter.id), { noleadingzeros = true }
)
end

-- 3B. TOC entry
local toclevel = secStyle.settings.toclevel
local bookmark = secStyle.settings.bookmark
if toclevel and toc then
SILE.call("tocentry", { level = toclevel, number = number, bookmark = bookmark }, SU.subContent(content))
end
-- 3B. TOC entry
local toclevel = secStyle.settings.toclevel
local bookmark = secStyle.settings.bookmark
if toclevel and toc then
SILE.call("tocentry", { level = toclevel, number = number, bookmark = bookmark }, SU.subContent(content))
end

-- 3C. Show section number (if numbering is true AND a main style is defined)
if numbering then
if secStyle.numberstyle.main then
SILE.call("style:apply:number", { name = secStyle.numberstyle.main, text = number })
if SU.boolean(numSty.numbering and numSty.numbering.standalone, false) then
SILE.call("break") -- HACK. Pretty weak unless the parent paragraph style is ragged.
-- 3C. Show section number (if numbering is true AND a main style is defined)
if numbering then
if secStyle.numberstyle.main then
SILE.call("style:apply:number", { name = secStyle.numberstyle.main, text = number })
if SU.boolean(numSty.numbering and numSty.numbering.standalone, false) then
SILE.call("break") -- HACK. Pretty weak unless the parent paragraph style is ragged.
end
end
end
end
end,
-- 3D. Section (title) content
SILE.process(content)
utils.subTreeContent(content),
-- 3E. Cross-reference label
function ()
-- If the \label command is defined, assume a cross-reference package
-- is loaded and allow specifying a label marker. This makes it less clumsy
-- than having to put it in the section title content, or just after the section
-- (with the risk of impacting indent/noindent and novbreak decisions here)
if marker and SILE.Commands["label"] then SILE.call("label", { marker = marker }) end
end)
end
})
-- Was present in the original book class for section and subsection
-- But seems to behave weird = cancelled for now.
-- SILE.typesetter:inhibitLeading()
Expand Down
114 changes: 78 additions & 36 deletions packages/resilient/styles/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ function package:_init (options)
base._init(self, options)

self.class:loadPackage("textsubsuper")
self.class:loadPackage("textcase")

self.class:registerHook("finish", self.writeStyles)

Expand Down Expand Up @@ -158,6 +159,12 @@ SILE.scratch.styles = {
raggedright = "style:align:left",
raggedleft = "style:align:right",
},
-- Known casing options
cases = {
upper = "uppercase",
lower = "lowercase",
title = "titlecase",
},
-- Known skip options.
-- Packages and classes can register custom skips there.
skips = {
Expand Down Expand Up @@ -257,37 +264,65 @@ function package:registerCommands ()
end, "Applies a font, with additional support for relative sizes.")

-- Very naive cascading...
local styleForProperties = function (style, content)
if style.properties and style.properties.position and style.properties.position ~= "normal" then
local positionCommand = SILE.scratch.styles.positions[style.properties.position]
if not positionCommand then
SU.error("Invalid style position '"..style.position.position.."'")
end
SILE.call(positionCommand, {}, content)
else
SILE.process(content)
local characterStyle = function (style, content, options)
options = options or {}
local dump = false
if style.properties then
for k, v in ipairs(content) do
-- FIXME STYLES functions are annoyinng here...
if type(v) ~= "function" then
if style.properties.position and style.properties.position ~= "normal" then
local positionCommand = SILE.scratch.styles.positions[style.properties.position]
if not positionCommand then
SU.error("Invalid style position '"..style.properties.position.."'")
end
content[k] = utils.createCommand(positionCommand, {}, content[k])
end
if style.properties.case then
if style.properties.case and style.properties.case ~= "normal" then
local caseCommand = SILE.scratch.styles.cases[style.properties.case]
if not caseCommand then
SU.error("Invalid style case '"..style.properties.case.."'")
end
content[k] = utils.createCommand(caseCommand, {}, content[k])
end
end
end
end
end
end
local styleForColor = function (style, content)
if style.color then
SILE.call("color", { color = style.color }, function ()
styleForProperties(style, content)
end)
else
styleForProperties(style, content)
content = utils.createCommand("color", { color = style.color }, content)
end
if style.font and SU.boolean(options.font, true) then
content = utils.createCommand("style:font", style.font, content)
end
if dump then SU.dump(content) end
return content
end

local characterStyleNoFont = function (style, content)
return characterStyle(style, content, { font = false })
end
local styleForFont = function (style, content)

local characterStyleFontOnly = function (style, content)
if style.font then
SILE.call("style:font", style.font, function ()
styleForColor(style, content)
end)
else
styleForColor(style, content)
content = utils.createCommand("style:font", style.font, content)
end
return content
end

local styleForAlignment = function (style, content, breakafter)
-- FIXME STYLES This messy, to extract the subContent and skip id nodes
local toProcess
if type(content) == "table" and content.command then
toProcess = {}
for _, v in ipairs(content) do
toProcess[#toProcess+1] = v
end
else
toProcess = content
end

if style.paragraph and style.paragraph.align then
if style.paragraph.align then
local alignCommand = SILE.scratch.styles.alignments[style.paragraph.align]
Expand All @@ -300,28 +335,23 @@ function package:registerCommands ()
-- correct even on the last paragraph. But the color introduces hboxes so
-- must be applied last, no to cause havoc with the noindent/indent and
-- centering etc. environments
local recontent = utils.createCommand(alignCommand, {}, {
characterStyleNoFont(style, toProcess),
not breakafter and utils.createCommand("novbreak") or nil
})
if style.font then
SILE.call("style:font", style.font, function ()
SILE.call(alignCommand, {}, function ()
styleForColor(style, content)
if not breakafter then SILE.call("novbreak") end
end)
end)
else
SILE.call(alignCommand, {}, function ()
styleForColor(style, content)
if not breakafter then SILE.call("novbreak") end
end)
recontent = characterStyleFontOnly(style, recontent)
end
SILE.process({ recontent })
else
styleForFont(style, content)
SILE.process({ characterStyle(style, toProcess) })
if not breakafter then SILE.call("novbreak") end
-- NOTE: SILE.call("par") would cause a parskip to be inserted.
-- Not really sure whether we expect this here or not.
SILE.typesetter:leaveHmode()
end
else
styleForFont(style, content)
SILE.process({ characterStyle(style, toProcess) })
end
end

Expand All @@ -331,7 +361,19 @@ function package:registerCommands ()
local name = SU.required(options, "name", "style:apply")
local styledef = self:resolveStyle(name, options.discardable)

styleForFont(styledef, content)
-- FIXME STYLES This messy, to extract the subContent and skip id nodes
local toProcess
if type(content) == "table" and (content.command or content.id) then
toProcess = {}
for _, v in ipairs(content) do
toProcess[#toProcess+1] = v
end
else
toProcess = content
end

toProcess = characterStyle(styledef, toProcess)
SILE.process({ toProcess })
end, "Applies a named character style to the content.")

-- APPLY A PARAGRAPH STYLE
Expand Down
Loading

0 comments on commit 9d29721

Please sign in to comment.