From 2afa4cbf7eafcc9efef4ea5219c9508fff7989a8 Mon Sep 17 00:00:00 2001 From: Omikhleia Date: Sat, 20 Jan 2024 12:40:20 +0100 Subject: [PATCH 1/3] feat(typesetters): Support for speaker change introduced by em-dash Some conventions denote speaker changes in dialogues by starting a paragraph with an em-dash. In these cases, the space after the em-dash shall be fixed by default. --- documentation/c07-settings.sil | 5 +++++ typesetters/base.lua | 28 ++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/documentation/c07-settings.sil b/documentation/c07-settings.sil index 88f26e445..835b6ef85 100644 --- a/documentation/c07-settings.sil +++ b/documentation/c07-settings.sil @@ -189,6 +189,11 @@ Note that non-breaking spaces (U+00A0), following the guidelines of Unicode Anne If you want to disable this behavior, the \autodoc:setting{languages.fixedNbsp} setting may be set to \code{true} to enforce fixed-width non-breaking spaces. +Some typography conventions use an em-dash at the start of a paragraph line to denote a speaker change in a dialogue. +This is the case in particular in French and Turkish typography. +By default, all spaces following an em-dash at the beginning of a paragraph in your input are replaced by a single \em{fixed} inter-word space, so that subsequent dialogue lines all start identically, while other inter-word spaces may still be variable for justification purposes. +To cancel this behavior, the \autodoc:setting{typesetter.fixedSpacingAfterInitialEmdash} setting may be set to \code{false}. + \subsection{Letter spacing settings} You can also put spaces in between \em{letters} with the \autodoc:setting{document.letterspaceglue} setting. diff --git a/typesetters/base.lua b/typesetters/base.lua index 6ab5293e7..a4213bb62 100644 --- a/typesetters/base.lua +++ b/typesetters/base.lua @@ -145,6 +145,13 @@ function typesetter.declareSettings(_) default = false, help = "When true, a warning is issued when a soft hyphen is encountered" }) + + SILE.settings:declare({ + parameter = "typesetter.fixedSpacingAfterInitialEmdash", + type = "boolean", + default = true, + help = "When true, em-dash starting a paragraph is considered as a speaker change in a dialogue" + }) end function typesetter:initState () @@ -330,6 +337,12 @@ function typesetter:endline () SILE.documentState.documentClass.endPar(self) end +-- Just compute once, to avoid unicode characters in source code. +local speakerChangePattern = "^" + .. luautf8.char(0x2014) -- emdash + .. "[ " .. luautf8.char(0x00A0) .. luautf8.char(0x202F) -- regular space or NBSP or NNBSP + .. "]+" + -- Takes string, writes onto self.state.nodes function typesetter:setpar (text) text = text:gsub("\r?\n", " "):gsub("\t", " ") @@ -338,6 +351,21 @@ function typesetter:setpar (text) text = text:gsub("^%s+", "") end self:initline() + + if SILE.settings:get("typesetter.fixedSpacingAfterInitialEmdash") and not SILE.settings:get("typesetter.obeyspaces") then + local speakerChange = false + local dialogue = luautf8.gsub(text, speakerChangePattern, function () + speakerChange = true + return "" + end) + if speakerChange then + text = dialogue + local fontoptions = SILE.font.loadDefaults({}) + local fiwsp = SILE.shaper:measureSpace(fontoptions).length -- fixed inter-word space + self:pushUnshaped({ text = luautf8.char(0x2014), options = fontoptions }) + self:pushHorizontal(SILE.nodefactory.kern(fiwsp)) + end + end end if #text >0 then self:pushUnshaped({ text = text, options= SILE.font.loadDefaults({})}) From ae5b949b5afa0182ab307e33ee204ff915fbb045 Mon Sep 17 00:00:00 2001 From: Omikhleia Date: Sat, 20 Jan 2024 15:18:19 +0100 Subject: [PATCH 2/3] refactor(typesetters): Speaker change space handling should occur after shaping --- typesetters/base.lua | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/typesetters/base.lua b/typesetters/base.lua index a4213bb62..bfd369f23 100644 --- a/typesetters/base.lua +++ b/typesetters/base.lua @@ -342,6 +342,25 @@ local speakerChangePattern = "^" .. luautf8.char(0x2014) -- emdash .. "[ " .. luautf8.char(0x00A0) .. luautf8.char(0x202F) -- regular space or NBSP or NNBSP .. "]+" +local speakerChangeReplacement = luautf8.char(0x2014) .. " " + +-- Special unshaped node subclass to handle space after a speaker change in dialogues +-- introduced by an em-dash. +local speakerChangeNode = pl.class(SILE.nodefactory.unshaped) +function speakerChangeNode:shape() + local node = self._base.shape(self) + local spc = node[2] + if spc and spc.is_glue then + -- Switch the variable space glue to a fixed kern + node[2] = SILE.nodefactory.kern({ width = spc.width.length }) + node[2].parent = self.parent + else + -- Should not occur: + -- How could it possibly be shaped differently? + SU.warn("Speaker change logic met an unexpected case, this might be a bug.") + end + return node +end -- Takes string, writes onto self.state.nodes function typesetter:setpar (text) @@ -356,14 +375,12 @@ function typesetter:setpar (text) local speakerChange = false local dialogue = luautf8.gsub(text, speakerChangePattern, function () speakerChange = true - return "" + return speakerChangeReplacement end) if speakerChange then - text = dialogue - local fontoptions = SILE.font.loadDefaults({}) - local fiwsp = SILE.shaper:measureSpace(fontoptions).length -- fixed inter-word space - self:pushUnshaped({ text = luautf8.char(0x2014), options = fontoptions }) - self:pushHorizontal(SILE.nodefactory.kern(fiwsp)) + local node = speakerChangeNode({ text = dialogue, options = SILE.font.loadDefaults({})}) + self:pushHorizontal(node) + return -- done here: speaker change space handling is done after nnode shaping end end end From 43ae9da87a730c61854c55b6e49a356d7015d399 Mon Sep 17 00:00:00 2001 From: Omikhleia Date: Sat, 20 Jan 2024 15:42:59 +0100 Subject: [PATCH 3/3] test(typesetters): Add test case for speaker changes in dialogues --- tests/feat-emdash-dialogue.expected | 70 +++++++++++++++++++++++++++++ tests/feat-emdash-dialogue.sil | 16 +++++++ 2 files changed, 86 insertions(+) create mode 100644 tests/feat-emdash-dialogue.expected create mode 100644 tests/feat-emdash-dialogue.sil diff --git a/tests/feat-emdash-dialogue.expected b/tests/feat-emdash-dialogue.expected new file mode 100644 index 000000000..ad2582edd --- /dev/null +++ b/tests/feat-emdash-dialogue.expected @@ -0,0 +1,70 @@ +Set paper size 209.7637818 297.6377985 +Begin page +Mx 10.4882 +My 31.5323 +Set font Gentium Plus;22;400;;normal;;;LTR +T 179 w=16.8545 (—) +Mx 33.1564 +T 47 82 85 72 80 w=58.0830 (Lorem) +Mx 102.5788 +T 76 83 86 88 80 w=55.4404 (ipsum) +Mx 169.3586 +T 71 82 w=22.5049 (do) +Mx 191.8635 +T 16 w=7.4121 (-) +Mx 10.4882 +My 57.9323 +T 79 82 85 w=25.7383 (lor) +Mx 49.1531 +T 86 76 87 w=22.0322 (sit) +Mx 71.1853 +T 17 w=5.0381 (.) +Mx 89.1500 +T 179 w=16.8545 (—) +Mx 118.9310 +T 54 72 71 w=32.0762 (Sed) +Mx 163.9338 +T 81 82 81 w=35.3418 (non) +Mx 10.4882 +My 84.3323 +T 85 76 86 88 86 w=43.3232 (risus) +Mx 53.8114 +T 17 w=5.0381 (.) +Mx 10.4882 +My 110.7323 +T 179 w=16.8545 (—) +Mx 33.1564 +T 49 82 81 w=37.4795 (Non) +Mx 70.6358 +T 17 w=5.0381 (.) +Mx 10.4882 +My 137.1323 +T 179 w=16.8545 (—) +Mx 36.8402 +T 47 82 85 72 80 w=58.0830 (Lorem) +Mx 104.4207 +T 76 83 86 88 80 w=55.4404 (ipsum) +Mx 169.3586 +T 71 82 w=22.5049 (do) +Mx 191.8635 +T 16 w=7.4121 (-) +Mx 10.4882 +My 163.5323 +T 79 82 85 w=25.7383 (lor) +Mx 49.1531 +T 86 76 87 w=22.0322 (sit) +Mx 71.1853 +T 17 w=5.0381 (.) +Mx 89.1500 +T 179 w=16.8545 (—) +Mx 118.9310 +T 54 72 71 w=32.0762 (Sed) +Mx 163.9338 +T 81 82 81 w=35.3418 (non) +Mx 10.4882 +My 189.9323 +T 85 76 86 88 86 w=43.3232 (risus) +Mx 53.8114 +T 17 w=5.0381 (.) +End page +Finish diff --git a/tests/feat-emdash-dialogue.sil b/tests/feat-emdash-dialogue.sil new file mode 100644 index 000000000..c872927ce --- /dev/null +++ b/tests/feat-emdash-dialogue.sil @@ -0,0 +1,16 @@ +\begin[papersize=a7]{document} +\nofolios +\neverindent +\font[size=22pt] +\set[parameter=linebreak.emergencyStretch, value=50pt] + +% Affect paragraph-leading em-dash but not later... +— Lorem ipsum dolor sit. — Sed non risus. + +— Non. + +\set[parameter=typesetter.fixedSpacingAfterInitialEmdash, value=false] + +— Lorem ipsum dolor sit. — Sed non risus. + +\end{document}