PPTX import: flat horizontal / vertical lines, no more slight tilt

PPTX expresses a horizontal line as cy=0 in the bounding box (the
line has no vertical extent) and a vertical line as cx=0. My
previous default of corner-to-corner (0,0)→(100,100) viewBox
endpoints turned that degenerate axis — after clamping to 2 pixels
of layer thickness — into a 0.26° slope across the line length,
which read on screen as a slight tilt + anti-aliasing fuzz that
also made the stroke look thicker than it really was.

Fix: detect the source extents and route endpoints by orientation:
  horizontal (cy=0, cx>0): x1=0, y1=50, x2=100, y2=50 (flat;
    flipH swaps x).
  vertical   (cx=0, cy>0): x1=50, y1=0, x2=50, y2=100 (flat;
    flipV swaps y).
  diagonal   (both > 0):   corner-to-corner with flipH/flipV
    swapping as before.

Also:
- Drop the min line thickness from 2px to 1px now that we don't
  need extra room for an angled diagonal to live inside.
- Centre the degenerate-axis clamp on the source position so the
  line stays exactly where PowerPoint put it (bounds.y -=
  MIN/2 + bounds.h = MIN, instead of just extending in +y).

Affects the 16 horizontal lines (slides 1-14, 16) and 4 vertical
lines (slide 15) in gallery_template_layout_set.pptx. The
remaining thickness overhead vs source (~30-40%) is the
renderer's hard 0.5-viewBox-unit stroke-width floor; truly exact
hairlines would need a different shape representation (e.g. thin
filled rect) which is a future enhancement.
This commit is contained in:
thePR0M3TH3AN committed 2026-05-30 08:44:41 -04:00
1 parent 9a7a0841df
commit 4f2ec01016
1 file changed
+42 -18
+42 -18
View File
@@ -1053,29 +1053,53 @@ window.ArtstrPptxImporter = (function () {
// zero area are dropped (nothing to render).
if (shape.kind !== 'line' && (bounds.w <= 0 || bounds.h <= 0)) return null;
// PPTX lines and straight connectors go corner-to-corner of their
// bounding box; flipH / flipV choose which diagonal. Bent and
// curved connectors get a straight-line approximation (Phase 7
// doesn't resolve the per-segment a:gd guides yet).
// PPTX lines come in three flavours determined by the SOURCE
// bounding-box extents: horizontal (cy=0), vertical (cx=0), and
// diagonal (both > 0). Earlier versions always used corner-to-
// corner endpoints (0,0)→(100,100), which gave a 0.26° slope
// across a 2px-clamped degenerate axis — i.e. slightly-tilted
// "horizontal" lines. Detect the source extents and pick endpoints
// that stay flat for horizontal / vertical, only using the
// diagonal for genuinely diagonal lines.
if (shape.kind === 'line') {
const flipH = xfrm.getAttribute('flipH') === '1';
const flipV = xfrm.getAttribute('flipV') === '1';
// Base diagonal: top-left → bottom-right.
let x1 = 0, y1 = 0, x2 = 100, y2 = 100;
if (flipH) { x1 = 100; x2 = 0; }
if (flipV) { y1 = (y1 === 0 ? 100 : 0); y2 = (y2 === 0 ? 100 : 0); }
const offNode = _directChild(xfrm, 'off');
const extNode = _directChild(xfrm, 'ext');
const srcCx = Number(extNode?.getAttribute('cx')) || 0;
const srcCy = Number(extNode?.getAttribute('cy')) || 0;
const isHorizontal = srcCy === 0 && srcCx > 0;
const isVertical = srcCx === 0 && srcCy > 0;
let x1, y1, x2, y2;
if (isHorizontal) {
// Flat: both endpoints at vertical midpoint.
x1 = 0; y1 = 50; x2 = 100; y2 = 50;
if (flipH) { x1 = 100; x2 = 0; }
} else if (isVertical) {
// Flat: both endpoints at horizontal midpoint.
x1 = 50; y1 = 0; x2 = 50; y2 = 100;
if (flipV) { y1 = 100; y2 = 0; }
} else {
// Diagonal: corner-to-corner with flip handling.
x1 = 0; y1 = 0; x2 = 100; y2 = 100;
if (flipH) { x1 = 100; x2 = 0; }
if (flipV) { [y1, y2] = [y2, y1]; }
}
shape.x1 = x1; shape.y1 = y1; shape.x2 = x2; shape.y2 = y2;
// Horizontal lines arrive with cy=0 and vertical with cx=0. The
// Artstr renderer scales an inner SVG into the layer's actual
// pixel box, so a zero-thickness layer renders nothing even
// though the stroke would otherwise stick out via overflow:
// visible. Expand the degenerate dimension to a small minimum
// so the layer has rendering surface for the stroke. The line
// itself stays at the source coordinate within the expanded box.
const MIN_LINE_THICKNESS_IN = 2 / PX_PER_IN; // ~2px
if (bounds.w <= 0) bounds.w = MIN_LINE_THICKNESS_IN;
if (bounds.h <= 0) bounds.h = MIN_LINE_THICKNESS_IN;
// Expand a degenerate-axis bounding box to a small minimum so
// the layer has render surface (a 0-pixel-tall DOM box renders
// nothing even with overflow:visible). Centre the expansion on
// the source axis so the line stays at its original position.
const MIN_LINE_THICKNESS_IN = 1 / PX_PER_IN; // ~1px
if (bounds.w <= 0) {
bounds.x -= MIN_LINE_THICKNESS_IN / 2;
bounds.w = MIN_LINE_THICKNESS_IN;
}
if (bounds.h <= 0) {
bounds.y -= MIN_LINE_THICKNESS_IN / 2;
bounds.h = MIN_LINE_THICKNESS_IN;
}
if (APPROXIMATED_AS_LINE.has(prst)) {
_warn(ctx.report, ctx.slideIndex, 'UNSUPPORTED_CONNECTOR_APPROXIMATED',