Export template source from e33714a5ca38429d1bdb77a961a14461697944f5:examples/local-first/packages/language-reader-presentation

This commit is contained in:
Quixos Template Publisher
2000-01-01 00:00:00 +00:00
commit 8f327c9ac8
63 changed files with 10474 additions and 0 deletions
+118
View File
@@ -0,0 +1,118 @@
import type { Layout, Range, Rect } from "./types.js";
const sourceLines = new WeakMap<Layout, Layout["lines"]>();
type SourceRect = Rect & Range;
function rangeRows(layout: Layout, range: Range): SourceRect[] {
// Layout source offsets are monotonic, including empty/blank lines. Search
// the first candidate row rather than scanning a long document on every hover.
let populated = sourceLines.get(layout);
if (!populated) {
populated = layout.lines.filter((line) => line.glyphs.length);
sourceLines.set(layout, populated);
}
let low = 0,
high = populated.length;
while (low < high) {
const middle = (low + high) >>> 1;
if (populated[middle].glyphs.at(-1)!.end <= range.start) low = middle + 1;
else high = middle;
}
const rects: SourceRect[] = [];
for (let index = low; index < populated.length; index++) {
const line = populated[index];
if (line.glyphs[0].start >= range.end) break;
const members = line.glyphs.filter((glyph) => glyph.start < range.end && glyph.end > range.start);
if (!members.length || members.every((glyph) => /^\s*$/.test(glyph.text))) continue;
rects.push({
start: members[0].start,
end: members.at(-1)!.end,
x: members[0].x,
y: Math.min(...members.map((glyph) => glyph.y)),
width: members.at(-1)!.x + members.at(-1)!.width - members[0].x,
height: Math.max(...members.map((glyph) => glyph.height)),
});
}
return rects;
}
export function rangeRects(layout: Layout, range: Range): Rect[] {
return rangeRows(layout, range).map(({ start, end, ...rect }) => rect);
}
type Point = { x: number; y: number };
function rounded(points: Point[], radius: number) {
const corners = points.map((point, index) => {
const before = points[(index + points.length - 1) % points.length],
after = points[(index + 1) % points.length];
const incoming = Math.hypot(point.x - before.x, point.y - before.y),
outgoing = Math.hypot(after.x - point.x, after.y - point.y);
const distance = Math.min(radius, incoming / 2, outgoing / 2);
return {
point,
before: {
x: point.x + ((before.x - point.x) * distance) / incoming,
y: point.y + ((before.y - point.y) * distance) / incoming,
},
after: {
x: point.x + ((after.x - point.x) * distance) / outgoing,
y: point.y + ((after.y - point.y) * distance) / outgoing,
},
};
});
return (
corners
.map(
({ point, before, after }, index) =>
`${index ? "L" : "M"}${before.x},${before.y}Q${point.x},${point.y} ${after.x},${after.y}`,
)
.join("") + "Z"
);
}
/** A broad band: only the first and last rows follow source edges. Paragraph
* gaps and intermediate short rows stay inside one quiet, full-width outline. */
export function rangeOutline(layout: Layout, range: Range, padding = 3): string {
const rows = rangeRows(layout, range);
if (!rows.length) return "";
const first = rows[0],
last = rows.at(-1)!;
const left = 1,
right = layout.width - 1;
const firstLeft = Math.max(left, first.x - padding);
const lastRight = Math.min(right, last.x + last.width + padding);
const top = Math.max(1, first.y - padding),
bottom = last.y + last.height + padding;
let points: Point[];
if (rows.length === 1) {
points = [
{ x: firstLeft, y: top },
{ x: lastRight, y: top },
{ x: lastRight, y: bottom },
{ x: firstLeft, y: bottom },
];
} else {
const firstJoin = first.y + first.height + padding;
const lastJoin = last.y - padding;
// Very tightly stacked rows get a plain box rather than a crossing contour.
points =
firstJoin >= lastJoin
? [
{ x: left, y: top },
{ x: right, y: top },
{ x: right, y: bottom },
{ x: left, y: bottom },
]
: [
{ x: firstLeft, y: top },
{ x: right, y: top },
{ x: right, y: lastJoin },
{ x: lastRight, y: lastJoin },
{ x: lastRight, y: bottom },
{ x: left, y: bottom },
{ x: left, y: firstJoin },
{ x: firstLeft, y: firstJoin },
];
}
points = points.filter((p, i) => {
const before = points[(i + points.length - 1) % points.length],
after = points[(i + 1) % points.length];
return !((before.x === p.x && p.x === after.x) || (before.y === p.y && p.y === after.y));
});
return rounded(points, 4);
}