File size: 4,688 Bytes
2409829 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
<script lang="ts" context="module">
export type RulerDirection = "Horizontal" | "Vertical";
</script>
<script lang="ts">
import { onMount } from "svelte";
const RULER_THICKNESS = 16;
const MAJOR_MARK_THICKNESS = 16;
const MINOR_MARK_THICKNESS = 6;
const MICRO_MARK_THICKNESS = 3;
export let direction: RulerDirection = "Vertical";
export let origin: number;
export let numberInterval: number;
export let majorMarkSpacing: number;
export let minorDivisions = 5;
export let microDivisions = 2;
let rulerInput: HTMLDivElement | undefined;
let rulerLength = 0;
let svgBounds = { width: "0px", height: "0px" };
$: svgPath = computeSvgPath(direction, origin, majorMarkSpacing, minorDivisions, microDivisions, rulerLength);
$: svgTexts = computeSvgTexts(direction, origin, majorMarkSpacing, numberInterval, rulerLength);
function computeSvgPath(direction: RulerDirection, origin: number, majorMarkSpacing: number, minorDivisions: number, microDivisions: number, rulerLength: number): string {
const isVertical = direction === "Vertical";
const lineDirection = isVertical ? "H" : "V";
const offsetStart = mod(origin, majorMarkSpacing);
const shiftedOffsetStart = offsetStart - majorMarkSpacing;
const divisions = majorMarkSpacing / minorDivisions / microDivisions;
const majorMarksFrequency = minorDivisions * microDivisions;
let dPathAttribute = "";
let i = 0;
for (let location = shiftedOffsetStart; location < rulerLength; location += divisions) {
let length;
if (i % majorMarksFrequency === 0) length = MAJOR_MARK_THICKNESS;
else if (i % microDivisions === 0) length = MINOR_MARK_THICKNESS;
else length = MICRO_MARK_THICKNESS;
i += 1;
const destination = Math.round(location) + 0.5;
const startPoint = isVertical ? `${RULER_THICKNESS - length},${destination}` : `${destination},${RULER_THICKNESS - length}`;
dPathAttribute += `M${startPoint}${lineDirection}${RULER_THICKNESS} `;
}
return dPathAttribute;
}
function computeSvgTexts(direction: RulerDirection, origin: number, majorMarkSpacing: number, numberInterval: number, rulerLength: number): { transform: string; text: string }[] {
const isVertical = direction === "Vertical";
const offsetStart = mod(origin, majorMarkSpacing);
const shiftedOffsetStart = offsetStart - majorMarkSpacing;
const svgTextCoordinates = [];
let labelNumber = (Math.ceil(-origin / majorMarkSpacing) - 1) * numberInterval;
for (let location = shiftedOffsetStart; location < rulerLength; location += majorMarkSpacing) {
const destination = Math.round(location);
const x = isVertical ? 9 : destination + 2;
const y = isVertical ? destination + 1 : 9;
let transform = `translate(${x} ${y})`;
if (isVertical) transform += " rotate(270)";
const text = numberInterval >= 1 ? `${labelNumber}` : labelNumber.toFixed(Math.abs(Math.log10(numberInterval))).replace(/\.0+$/, "");
svgTextCoordinates.push({ transform, text });
labelNumber += numberInterval;
}
return svgTextCoordinates;
}
export function resize() {
if (!rulerInput) return;
const isVertical = direction === "Vertical";
const newLength = isVertical ? rulerInput.clientHeight : rulerInput.clientWidth;
const roundedUp = (Math.floor(newLength / majorMarkSpacing) + 1) * majorMarkSpacing;
if (roundedUp !== rulerLength) {
rulerLength = roundedUp;
const thickness = `${RULER_THICKNESS}px`;
const length = `${roundedUp}px`;
svgBounds = isVertical ? { width: thickness, height: length } : { width: length, height: thickness };
}
}
// Modulo function that works for negative numbers, unlike the JS `%` operator
function mod(n: number, m: number): number {
const remainder = n % m;
return Math.floor(remainder >= 0 ? remainder : remainder + m);
}
onMount(resize);
</script>
<div class={`ruler-input ${direction.toLowerCase()}`} bind:this={rulerInput}>
<svg style:width={svgBounds.width} style:height={svgBounds.height}>
<path d={svgPath} />
{#each svgTexts as svgText}
<text transform={svgText.transform}>{svgText.text}</text>
{/each}
</svg>
</div>
<style lang="scss" global>
.ruler-input {
flex: 1 1 100%;
background: var(--color-2-mildblack);
overflow: hidden;
position: relative;
box-sizing: border-box;
&.horizontal {
height: 16px;
border-bottom: 1px solid var(--color-5-dullgray);
}
&.vertical {
width: 16px;
border-right: 1px solid var(--color-5-dullgray);
svg text {
text-anchor: end;
}
}
svg {
position: absolute;
path {
stroke-width: 1px;
stroke: var(--color-5-dullgray);
}
text {
font-size: 12px;
fill: var(--color-8-uppergray);
}
}
}
</style>
|