mirror of
https://github.com/storytold/wordcraft.git
synced 2026-10-29 21:07:24 -06:00
Shapes: a right triangle keeps its right angle (#1599)
DrawingML's rtTriangle was read as an isosceles triangle, so a cover page's white corner cut-outs pointed up into a peak instead of cutting a V into the picture above them. It is now its own shape kind, drawn with the right angle at the bottom left, wrapped and glued along that outline, and saved back as rtTriangle. The .doc reader maps msosptRightTriangle (6) to it too. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
Brandon Thomas
parent
952eb57688
commit
90d8844e44
@@ -100,6 +100,8 @@ pub fn sites(kind: ShapeKind, w: f32, h: f32) -> Vec<(f32, f32)> {
|
||||
vec![(cx, 0.0), (cx - dx, cy - dy), (0.0, cy), (cx - dx, cy + dy), (cx, h), (cx + dx, cy + dy), (w, cy), (cx + dx, cy - dy)]
|
||||
}
|
||||
ShapeKind::Triangle => vec![(cx, 0.0), (cx / 2.0, cy), (0.0, h), (cx, h), (w, h), (cx + cx / 2.0, cy)],
|
||||
// The corners, the legs' midpoints and the hypotenuse's.
|
||||
ShapeKind::RightTriangle => vec![(0.0, 0.0), (0.0, cy), (0.0, h), (cx, h), (w, h), (cx, cy)],
|
||||
ShapeKind::Line | ShapeKind::StraightConnector | ShapeKind::ElbowConnector | ShapeKind::CurvedConnector => vec![(0.0, 0.0), (w, h)],
|
||||
_ => vec![(cx, 0.0), (0.0, cy), (cx, h), (w, cy)],
|
||||
}
|
||||
|
||||
@@ -289,6 +289,8 @@ pub enum ShapeKind {
|
||||
RoundedRectangle,
|
||||
Ellipse,
|
||||
Triangle,
|
||||
/// A right triangle (DrawingML's `rtTriangle`): the right angle at the bottom left.
|
||||
RightTriangle,
|
||||
Diamond,
|
||||
Line,
|
||||
Arrow,
|
||||
|
||||
@@ -88,6 +88,7 @@ pub fn shape_outline(kind: ShapeKind, w: f32, h: f32, corner: Option<u32>) -> Op
|
||||
pts
|
||||
}
|
||||
ShapeKind::Triangle => vec![(0.5, 0.0), (1.0, 1.0), (0.0, 1.0)],
|
||||
ShapeKind::RightTriangle => vec![(0.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
|
||||
ShapeKind::Diamond => vec![(0.5, 0.0), (1.0, 0.5), (0.5, 1.0), (0.0, 0.5)],
|
||||
ShapeKind::Arrow => {
|
||||
// As drawn: the head is half the height long, the shaft 40% of the height thick.
|
||||
@@ -217,6 +218,7 @@ mod tests {
|
||||
ShapeKind::Ellipse,
|
||||
ShapeKind::RoundedRectangle,
|
||||
ShapeKind::Triangle,
|
||||
ShapeKind::RightTriangle,
|
||||
ShapeKind::Diamond,
|
||||
ShapeKind::Arrow,
|
||||
ShapeKind::Star,
|
||||
|
||||
@@ -186,7 +186,8 @@ fn leaf(sp: &Sp, w: f32, h: f32, float: Float, b: &Build, pics: &mut Pictures) -
|
||||
2 => ShapeKind::RoundedRectangle,
|
||||
3 => ShapeKind::Ellipse,
|
||||
4 => ShapeKind::Diamond,
|
||||
5 | 6 => ShapeKind::Triangle,
|
||||
5 => ShapeKind::Triangle,
|
||||
6 => ShapeKind::RightTriangle,
|
||||
12 | 187 => ShapeKind::Star,
|
||||
13 | 66 | 67 | 68 | 69 => ShapeKind::Arrow,
|
||||
20 | 32 => ShapeKind::Line,
|
||||
|
||||
@@ -29,7 +29,8 @@ pub(crate) fn preset_kind(prst: &str) -> ShapeKind {
|
||||
"ellipse" | "wedgeEllipseCallout" | "cloudCallout" | "cloud" | "flowChartConnector" | "donut" | "flowChartOnlineStorage" => {
|
||||
ShapeKind::Ellipse
|
||||
}
|
||||
"triangle" | "rtTriangle" | "flowChartExtract" | "flowChartMerge" => ShapeKind::Triangle,
|
||||
"triangle" | "flowChartExtract" | "flowChartMerge" => ShapeKind::Triangle,
|
||||
"rtTriangle" => ShapeKind::RightTriangle,
|
||||
"diamond" | "flowChartDecision" => ShapeKind::Diamond,
|
||||
"line" | "lineInv" => ShapeKind::Line,
|
||||
"straightConnector1" => ShapeKind::StraightConnector,
|
||||
|
||||
@@ -986,6 +986,7 @@ impl Writer<'_> {
|
||||
ShapeKind::RoundedRectangle => "roundRect",
|
||||
ShapeKind::Ellipse => "ellipse",
|
||||
ShapeKind::Triangle => "triangle",
|
||||
ShapeKind::RightTriangle => "rtTriangle",
|
||||
ShapeKind::Diamond => "diamond",
|
||||
ShapeKind::Line => "line",
|
||||
ShapeKind::StraightConnector => "straightConnector1",
|
||||
|
||||
@@ -2567,6 +2567,21 @@ fn text_frames_read_and_round_trip() {
|
||||
assert!(q.iter().any(|x| x.props.drop_cap == Some(2) && x.props.frame.is_none()));
|
||||
}
|
||||
|
||||
/// A right triangle (`rtTriangle`) has its right angle at the bottom left: a cover page's white
|
||||
/// corner cut-outs drawn as isosceles triangles pointed the wrong way.
|
||||
#[test]
|
||||
fn a_right_triangle_keeps_its_right_angle() {
|
||||
let body = anchored_wsp("Corner", 1270000, 635000, r#"<a:prstGeom prst="rtTriangle"><a:avLst/></a:prstGeom>"#, "", "<wps:cNvSpPr/>");
|
||||
let d = read_body(&format!("<w:p>{body}</w:p>"));
|
||||
let kind = |d: &Document| match ¶s(d)[0].objects[0] {
|
||||
InlineObject::Shape { kind, .. } => *kind,
|
||||
o => panic!("{o:?}"),
|
||||
};
|
||||
assert_eq!(kind(&d), ShapeKind::RightTriangle);
|
||||
let saved = wordcraft_docx::write(&d).unwrap();
|
||||
assert_eq!(kind(&wordcraft_docx::read(&saved).unwrap()), ShapeKind::RightTriangle);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hyperlink_with_empty_target_is_no_link() {
|
||||
// LibreOffice writes `Target=""` for the Hyperlink character style without an address (#1005).
|
||||
|
||||
@@ -558,6 +558,7 @@ fn shape_path(kind: ShapeKind, r: &Rect, corner: Option<u32>) -> Option<Path> {
|
||||
}
|
||||
ShapeKind::Ellipse => ellipse(&mut pb, cx, cy, r.w / 2.0, r.h / 2.0),
|
||||
ShapeKind::Triangle => poly(&mut pb, &[(cx, y0), (x1, y1), (x0, y1)]),
|
||||
ShapeKind::RightTriangle => poly(&mut pb, &[(x0, y0), (x1, y1), (x0, y1)]),
|
||||
ShapeKind::Diamond => poly(&mut pb, &[(cx, y0), (x1, cy), (cx, y1), (x0, cy)]),
|
||||
// DrawingML's `line`: top-left to bottom-right (flips give the other directions).
|
||||
ShapeKind::Line => {
|
||||
|
||||
@@ -1039,6 +1039,7 @@ pub fn shape_path(kind: ShapeKind, r: kurbo::Rect, corner: Option<u32>) -> BezPa
|
||||
}
|
||||
ShapeKind::Ellipse => kurbo::Ellipse::from_rect(r).to_path(0.1),
|
||||
ShapeKind::Triangle => poly(&[(cx, r.y0), (r.x1, r.y1), (r.x0, r.y1)]),
|
||||
ShapeKind::RightTriangle => poly(&[(r.x0, r.y0), (r.x1, r.y1), (r.x0, r.y1)]),
|
||||
ShapeKind::Diamond => poly(&[(cx, r.y0), (r.x1, cy), (cx, r.y1), (r.x0, cy)]),
|
||||
// DrawingML's `line`: top-left to bottom-right (flips give the other directions).
|
||||
ShapeKind::Line => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# File-format parity with Microsoft Word
|
||||
|
||||
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-14 · **Change:** trivial (legacy DOC DIB conversion honours the BLIP byte limit; related #1334 and #1554; previously trivial: section `w:vAlign` "both" round-trips as Justified, #1320; previously trivial: Word's text watermark written back to DOCX headers, #1290; previously trivial: VML preset shapes drawn, #1377; previously trivial: VML text boxes keep their internal margins, fit to their text and vertical alignment; hidden drawings not shown; previously trivial: equations as MathML in HTML export, copy and paste, #816; previously trivial: symbol-font text exported as Unicode, #776; previously trivial: text-box margins, Resize shape to fit text and freeforms in groups, #743; previously trivial: formatted paste from the system clipboard's HTML and RTF, #687; previously trivial: independent complex-script formatting inheritance and round-trip regression tests; previously trivial: import/export fixes for TXT, RTF, ODT, HTML and LaTeX, #433–#440; previously major: first version; every format Word reads or writes, measured from the readers and writers on origin/main) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4, plus Word for Windows' extra formats
|
||||
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-14 · **Change:** trivial (legacy DOC DIB conversion honours the BLIP byte limit; related #1334 and #1554; previously trivial: right triangles keep their right angle; previously trivial: section `w:vAlign` "both" round-trips as Justified, #1320; previously trivial: Word's text watermark written back to DOCX headers, #1290; previously trivial: VML preset shapes drawn, #1377; previously trivial: VML text boxes keep their internal margins, fit to their text and vertical alignment; hidden drawings not shown; previously trivial: equations as MathML in HTML export, copy and paste, #816; previously trivial: symbol-font text exported as Unicode, #776; previously trivial: text-box margins, Resize shape to fit text and freeforms in groups, #743; previously trivial: formatted paste from the system clipboard's HTML and RTF, #687; previously trivial: independent complex-script formatting inheritance and round-trip regression tests; previously trivial: import/export fixes for TXT, RTF, ODT, HTML and LaTeX, #433–#440; previously major: first version; every format Word reads or writes, measured from the readers and writers on origin/main) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4, plus Word for Windows' extra formats
|
||||
|
||||
Every format Word opens or saves, what WordCraft does with it, and how it's tested. Fidelity is the
|
||||
share of a typical real-world file's content and formatting that survives (read: shows correctly;
|
||||
@@ -196,6 +196,7 @@ attributes inside stories and style or list definitions; a regenerated theme. Te
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-14 | minor | Legacy DOC DIB helper honours the supplied byte limit for its BMP header/copy, decoder and PNG output, with a bounded BMP fallback. Generated 1-pixel regression only; resource-exhaustion consequences unmeasured, fidelity/readiness estimates unchanged. Related: #1334 and #1554 |
|
||||
| 2026-10-14 | trivial | Right triangles (`rtTriangle`) keep their right angle at the bottom left instead of becoming isosceles triangles, and save back as `rtTriangle` |
|
||||
| 2026-10-13 | trivial | Section `w:vAlign="both"` reads as Justified and writes back as `both` (#1320) |
|
||||
| 2026-10-13 | trivial | DOCX: the document's watermark is written as a VML text shape in every header, adding a header where pages had none (#1290) |
|
||||
| 2026-10-13 | trivial | DOCX: Word's VML text watermark in headers is read as the document's watermark (#1290) |
|
||||
|
||||
Reference in New Issue
Block a user