Skip to content

Commit 34df103

Browse files
committed
engine_spx2html: tidy up special handling and properly exit init on tdux:asp, tdux:dt, etc.
1 parent d2ff8c7 commit 34df103

1 file changed

Lines changed: 205 additions & 133 deletions

File tree

  • crates/engine_spx2html/src

crates/engine_spx2html/src/lib.rs

Lines changed: 205 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,21 @@ impl<'a> EngineState<'a> {
109109

110110
Ok(())
111111
}
112+
113+
/// Return true if we're in the initializing phase, but not in the midst of
114+
/// a multi-step construct like startDefineFontFamily. In such situations,
115+
/// if we see an event that is associated with the beginning of the actual
116+
/// content, we should end the initialization phase.
117+
fn in_endable_init(&self) -> bool {
118+
match &self.state {
119+
State::Invalid => false,
120+
State::Initializing(s) => {
121+
s.cur_font_family_definition.is_none()
122+
&& s.cur_font_family_tag_associations.is_none()
123+
}
124+
State::Emitting(_) => false,
125+
}
126+
}
112127
}
113128

114129
impl<'a> XdvEvents for EngineState<'a> {
@@ -125,14 +140,46 @@ impl<'a> XdvEvents for EngineState<'a> {
125140
fn handle_special(&mut self, x: i32, y: i32, contents: &[u8]) -> Result<()> {
126141
let contents = atry!(std::str::from_utf8(contents); ["could not parse \\special as UTF-8"]);
127142

128-
if contents == "tdux:emit" || contents.starts_with("tdux:provideFile") {
129-
self.state.ensure_initialized()?;
143+
// str.split_once() would be nice but it was introduced in 1.52 which is
144+
// a bit recent for us.
145+
146+
let mut pieces = contents.splitn(2, ' ');
147+
148+
let (tdux_command, remainder) = if let Some(p) = pieces.next() {
149+
if let Some(cmd) = p.strip_prefix("tdux:") {
150+
(Some(cmd), pieces.next().unwrap_or_default())
151+
} else {
152+
(None, contents)
153+
}
154+
} else {
155+
(None, contents)
156+
};
157+
158+
// Might we need to end the initialization phase?
159+
160+
if self.in_endable_init() {
161+
let end_init = if let Some(cmd) = tdux_command {
162+
match cmd {
163+
"emit" | "provideFile" | "asp" | "aep" | "cs" | "ce" | "mfs" | "me" | "dt" => {
164+
true
165+
}
166+
_ => false,
167+
}
168+
} else {
169+
false
170+
};
171+
172+
if end_init {
173+
self.state.ensure_initialized()?;
174+
}
130175
}
131176

177+
// Ready to dispatch.
178+
132179
match &mut self.state {
133180
State::Invalid => panic!("invalid spx2html state leaked"),
134-
State::Initializing(s) => s.handle_special(contents, &mut self.common),
135-
State::Emitting(s) => s.handle_special(x, y, contents, &mut self.common),
181+
State::Initializing(s) => s.handle_special(tdux_command, remainder, &mut self.common),
182+
State::Emitting(s) => s.handle_special(x, y, tdux_command, remainder, &mut self.common),
136183
}
137184
}
138185

@@ -145,20 +192,7 @@ impl<'a> XdvEvents for EngineState<'a> {
145192
x: &[i32],
146193
y: &[i32],
147194
) -> Result<()> {
148-
// Here we have a quasi-hack because the initialization stage needs to
149-
// handle text-and-glyphs, *if* it's in a font-family definition.
150-
// Otherwise it's time to exit the initialization mode.
151-
152-
let do_init = match &mut self.state {
153-
State::Invalid => false,
154-
State::Initializing(s) => {
155-
s.cur_font_family_definition.is_none()
156-
&& s.cur_font_family_tag_associations.is_none()
157-
}
158-
State::Emitting(_) => false,
159-
};
160-
161-
if do_init {
195+
if self.in_endable_init() {
162196
self.state.ensure_initialized()?;
163197
}
164198

@@ -378,26 +412,37 @@ impl InitializationState {
378412
Ok(())
379413
}
380414

381-
fn handle_special(&mut self, contents: &str, common: &mut Common) -> Result<()> {
382-
if let Some(texpath) = contents.strip_prefix("tdux:addTemplate ") {
383-
self.handle_add_template(texpath, common)
384-
} else if let Some(texpath) = contents.strip_prefix("tdux:setTemplate ") {
385-
self.handle_set_template(texpath, common)
386-
} else if let Some(texpath) = contents.strip_prefix("tdux:setOutputPath ") {
387-
self.handle_set_output_path(texpath, common)
388-
} else if let Some(remainder) = contents.strip_prefix("tdux:setTemplateVariable ") {
389-
self.handle_set_template_variable(remainder, common)
390-
} else if contents == "tdux:startDefineFontFamily" {
391-
self.handle_start_define_font_family()
392-
} else if contents == "tdux:endDefineFontFamily" {
393-
self.handle_end_define_font_family(common)
394-
} else if contents == "tdux:startFontFamilyTagAssociations" {
395-
self.handle_start_font_family_tag_associations()
396-
} else if contents == "tdux:endFontFamilyTagAssociations" {
397-
self.handle_end_font_family_tag_associations(common)
398-
} else if let Some(_remainder) = contents.strip_prefix("tdux:provideFile ") {
399-
tt_warning!(common.status, "ignoring too-soon tdux:provideFile special");
400-
Ok(())
415+
fn handle_special(
416+
&mut self,
417+
tdux_command: Option<&str>,
418+
remainder: &str,
419+
common: &mut Common,
420+
) -> Result<()> {
421+
if let Some(cmd) = tdux_command {
422+
match cmd {
423+
"addTemplate" => self.handle_add_template(remainder, common),
424+
"setTemplate" => self.handle_set_template(remainder, common),
425+
"setOutputPath" => self.handle_set_output_path(remainder, common),
426+
"setTemplateVariable" => self.handle_set_template_variable(remainder, common),
427+
428+
"startDefineFontFamily" => self.handle_start_define_font_family(),
429+
"endDefineFontFamily" => self.handle_end_define_font_family(common),
430+
431+
"startFontFamilyTagAssociations" => {
432+
self.handle_start_font_family_tag_associations()
433+
}
434+
435+
"endFontFamilyTagAssociations" => {
436+
self.handle_end_font_family_tag_associations(common)
437+
}
438+
439+
"provideFile" => {
440+
tt_warning!(common.status, "ignoring too-soon tdux:provideFile special");
441+
Ok(())
442+
}
443+
444+
_ => Ok(()),
445+
}
401446
} else {
402447
Ok(())
403448
}
@@ -976,105 +1021,132 @@ impl EmittingState {
9761021
&mut self,
9771022
x: i32,
9781023
y: i32,
979-
contents: &str,
1024+
tdux_command: Option<&str>,
1025+
remainder: &str,
9801026
common: &mut Common,
9811027
) -> Result<()> {
982-
if contents == "tdux:asp" {
983-
if self.content_finished {
984-
self.warn_finished_content("auto start paragraph", common);
985-
} else if self.cur_elstate().do_auto_tags {
986-
// Why are we using <div>s instead of <p>? As the HTML spec
987-
// emphasizes, <p> tags are structural, not semantic. You cannot
988-
// put tags like <ul> or <div> inside <p> -- they automatically
989-
// close the paragraph. This does not align with TeX's idea of a
990-
// paragraph, and there's no upside to trying to use <p>'s -- as
991-
// the spec notes, the <p> tag does not activate any important
992-
// semantics itself. The HTML spec explicitly recommends that
993-
// you can use <div> elements to group logical paragraphs. So
994-
// that's what we do.
995-
let el = self.create_elem("div", true, common);
996-
self.push_space_if_needed(x, None);
997-
self.current_content.push_str("<div class=\"tdux-p\">");
998-
self.push_elem(el, ElementOrigin::EngineAuto);
999-
}
1000-
Ok(())
1001-
} else if contents == "tdux:aep" {
1002-
if self.content_finished {
1003-
self.warn_finished_content("auto end paragraph", common);
1004-
} else if self.cur_elstate().do_auto_tags {
1005-
self.pop_elem("div", common);
1006-
}
1007-
Ok(())
1008-
} else if let Some(kind) = contents.strip_prefix("tdux:cs ") {
1009-
if self.content_finished {
1010-
self.warn_finished_content("canvas start", common);
1011-
} else if let Some(canvas) = self.current_canvas.as_mut() {
1012-
canvas.depth += 1;
1013-
} else {
1014-
self.current_canvas = Some(CanvasState::new(kind, x, y));
1015-
}
1016-
Ok(())
1017-
} else if let Some(_kind) = contents.strip_prefix("tdux:ce ") {
1018-
if self.content_finished {
1019-
self.warn_finished_content("canvas end", common);
1020-
} else if let Some(canvas) = self.current_canvas.as_mut() {
1021-
canvas.depth -= 1;
1022-
if canvas.depth == 0 {
1023-
self.handle_end_canvas(common)?;
1028+
if let Some(cmd) = tdux_command {
1029+
match cmd {
1030+
"asp" => {
1031+
if self.content_finished {
1032+
self.warn_finished_content("auto start paragraph", common);
1033+
} else if self.cur_elstate().do_auto_tags {
1034+
// Why are we using <div>s instead of <p>? As the HTML spec
1035+
// emphasizes, <p> tags are structural, not semantic. You cannot
1036+
// put tags like <ul> or <div> inside <p> -- they automatically
1037+
// close the paragraph. This does not align with TeX's idea of a
1038+
// paragraph, and there's no upside to trying to use <p>'s -- as
1039+
// the spec notes, the <p> tag does not activate any important
1040+
// semantics itself. The HTML spec explicitly recommends that
1041+
// you can use <div> elements to group logical paragraphs. So
1042+
// that's what we do.
1043+
let el = self.create_elem("div", true, common);
1044+
self.push_space_if_needed(x, None);
1045+
self.current_content.push_str("<div class=\"tdux-p\">");
1046+
self.push_elem(el, ElementOrigin::EngineAuto);
1047+
}
1048+
Ok(())
1049+
}
1050+
1051+
"aep" => {
1052+
if self.content_finished {
1053+
self.warn_finished_content("auto end paragraph", common);
1054+
} else if self.cur_elstate().do_auto_tags {
1055+
self.pop_elem("div", common);
1056+
}
1057+
Ok(())
1058+
}
1059+
1060+
"cs" => {
1061+
if self.content_finished {
1062+
self.warn_finished_content("canvas start", common);
1063+
} else if let Some(canvas) = self.current_canvas.as_mut() {
1064+
canvas.depth += 1;
1065+
} else {
1066+
self.current_canvas = Some(CanvasState::new(remainder, x, y));
1067+
}
1068+
Ok(())
1069+
}
1070+
1071+
"ce" => {
1072+
if self.content_finished {
1073+
self.warn_finished_content("canvas end", common);
1074+
} else if let Some(canvas) = self.current_canvas.as_mut() {
1075+
canvas.depth -= 1;
1076+
if canvas.depth == 0 {
1077+
self.handle_end_canvas(common)?;
1078+
}
1079+
} else {
1080+
tt_warning!(
1081+
common.status,
1082+
"ignoring unpaired tdux:c[anvas]e[nd] special for `{}`",
1083+
remainder
1084+
);
1085+
}
1086+
Ok(())
1087+
}
1088+
1089+
"mfs" => {
1090+
if self.content_finished {
1091+
self.warn_finished_content(
1092+
&format!("manual flexible start tag {:?}", remainder),
1093+
common,
1094+
);
1095+
Ok(())
1096+
} else {
1097+
self.handle_flexible_start_tag(x, y, remainder, common)
1098+
}
1099+
}
1100+
1101+
"me" => {
1102+
if self.content_finished {
1103+
self.warn_finished_content(
1104+
&format!("manual end tag </{}>", remainder),
1105+
common,
1106+
);
1107+
} else {
1108+
self.pop_elem(remainder, common);
1109+
}
1110+
Ok(())
1111+
}
1112+
1113+
"dt" => {
1114+
if self.content_finished {
1115+
self.warn_finished_content("direct text", common);
1116+
} else {
1117+
html_escape::encode_safe_to_string(remainder, &mut self.current_content);
1118+
}
1119+
Ok(())
1120+
}
1121+
1122+
"emit" => self.finish_file(common),
1123+
1124+
"setTemplate" => {
1125+
self.next_template_path = remainder.to_owned();
1126+
Ok(())
1127+
}
1128+
1129+
"setOutputPath" => {
1130+
self.next_output_path = remainder.to_owned();
1131+
Ok(())
1132+
}
1133+
1134+
"setTemplateVariable" => self.handle_set_template_variable(remainder, common),
1135+
1136+
"provideFile" => self.handle_provide_file(remainder, common),
1137+
1138+
"contentFinished" => self.content_finished(common),
1139+
1140+
other => {
1141+
tt_warning!(
1142+
common.status,
1143+
"ignoring unrecognized special: tdux:{} {}",
1144+
other,
1145+
remainder
1146+
);
1147+
Ok(())
10241148
}
1025-
} else {
1026-
tt_warning!(
1027-
common.status,
1028-
"ignoring unpaired tdux:c[anvas]e[nd] special `{}`",
1029-
contents
1030-
);
1031-
}
1032-
Ok(())
1033-
} else if let Some(remainder) = contents.strip_prefix("tdux:mfs ") {
1034-
if self.content_finished {
1035-
self.warn_finished_content(
1036-
&format!("manual flexible start tag {:?}", remainder),
1037-
common,
1038-
);
1039-
Ok(())
1040-
} else {
1041-
self.handle_flexible_start_tag(x, y, remainder, common)
1042-
}
1043-
} else if let Some(element) = contents.strip_prefix("tdux:me ") {
1044-
if self.content_finished {
1045-
self.warn_finished_content(&format!("manual end tag </{}>", element), common);
1046-
} else {
1047-
self.pop_elem(element, common);
1048-
}
1049-
Ok(())
1050-
} else if let Some(direct_text) = contents.strip_prefix("tdux:dt ") {
1051-
if self.content_finished {
1052-
self.warn_finished_content("direct text", common);
1053-
} else {
1054-
html_escape::encode_safe_to_string(direct_text, &mut self.current_content);
10551149
}
1056-
Ok(())
1057-
} else if contents == "tdux:emit" {
1058-
self.finish_file(common)
1059-
} else if let Some(texpath) = contents.strip_prefix("tdux:setTemplate ") {
1060-
self.next_template_path = texpath.to_owned();
1061-
Ok(())
1062-
} else if let Some(texpath) = contents.strip_prefix("tdux:setOutputPath ") {
1063-
self.next_output_path = texpath.to_owned();
1064-
Ok(())
1065-
} else if let Some(remainder) = contents.strip_prefix("tdux:setTemplateVariable ") {
1066-
self.handle_set_template_variable(remainder, common)
1067-
} else if let Some(remainder) = contents.strip_prefix("tdux:provideFile ") {
1068-
self.handle_provide_file(remainder, common)
1069-
} else if contents == "tdux:contentFinished" {
1070-
self.content_finished(common)
1071-
} else if let Some(remainder) = contents.strip_prefix("tdux:") {
1072-
tt_warning!(
1073-
common.status,
1074-
"ignoring unrecognized special: tdux:{}",
1075-
remainder
1076-
);
1077-
Ok(())
10781150
} else {
10791151
Ok(())
10801152
}

0 commit comments

Comments
 (0)