Merge pull request #11587 from MoonE/fix-text-along-path-orientation
Determine orientation by actual text start and end x
This commit is contained in:
@@ -34,74 +34,114 @@ export function drawTextOnPath(
|
|||||||
cache,
|
cache,
|
||||||
rotation
|
rotation
|
||||||
) {
|
) {
|
||||||
const result = [];
|
let x2 = flatCoordinates[offset];
|
||||||
|
let y2 = flatCoordinates[offset + 1];
|
||||||
|
let x1 = 0;
|
||||||
|
let y1 = 0;
|
||||||
|
let segmentLength = 0;
|
||||||
|
let segmentM = 0;
|
||||||
|
|
||||||
|
function advance() {
|
||||||
|
x1 = x2;
|
||||||
|
y1 = y2;
|
||||||
|
offset += stride;
|
||||||
|
x2 = flatCoordinates[offset];
|
||||||
|
y2 = flatCoordinates[offset + 1];
|
||||||
|
segmentM += segmentLength;
|
||||||
|
segmentLength = Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
advance();
|
||||||
|
} while (offset < end - stride && segmentM + segmentLength < startM);
|
||||||
|
|
||||||
|
let interpolate = (startM - segmentM) / segmentLength;
|
||||||
|
const beginX = lerp(x1, x2, interpolate);
|
||||||
|
const beginY = lerp(y1, y2, interpolate);
|
||||||
|
|
||||||
|
const startOffset = offset - stride;
|
||||||
|
const startLength = segmentM;
|
||||||
|
const endM = startM + scale * measureAndCacheTextWidth(font, text, cache);
|
||||||
|
while (offset < end - stride && segmentM + segmentLength < endM) {
|
||||||
|
advance();
|
||||||
|
}
|
||||||
|
interpolate = (endM - segmentM) / segmentLength;
|
||||||
|
const endX = lerp(x1, x2, interpolate);
|
||||||
|
const endY = lerp(y1, y2, interpolate);
|
||||||
|
|
||||||
// Keep text upright
|
// Keep text upright
|
||||||
let reverse;
|
let reverse;
|
||||||
if (rotation) {
|
if (rotation) {
|
||||||
const rotatedCoordinates = rotate(
|
const flat = [beginX, beginY, endX, endY];
|
||||||
flatCoordinates,
|
rotate(flat, 0, 4, 2, rotation, flat, flat);
|
||||||
offset,
|
reverse = flat[0] > flat[2];
|
||||||
end,
|
|
||||||
stride,
|
|
||||||
rotation,
|
|
||||||
[flatCoordinates[offset], flatCoordinates[offset + 1]]
|
|
||||||
);
|
|
||||||
reverse =
|
|
||||||
rotatedCoordinates[0] >
|
|
||||||
rotatedCoordinates[rotatedCoordinates.length - stride];
|
|
||||||
} else {
|
} else {
|
||||||
reverse = flatCoordinates[offset] > flatCoordinates[end - stride];
|
reverse = beginX > endX;
|
||||||
}
|
}
|
||||||
|
|
||||||
const numChars = text.length;
|
const PI = Math.PI;
|
||||||
|
const result = [];
|
||||||
|
const singleSegment = startOffset + stride === offset;
|
||||||
|
|
||||||
let x1 = flatCoordinates[offset];
|
offset = startOffset;
|
||||||
let y1 = flatCoordinates[offset + 1];
|
segmentLength = 0;
|
||||||
offset += stride;
|
segmentM = startLength;
|
||||||
let x2 = flatCoordinates[offset];
|
x2 = flatCoordinates[offset];
|
||||||
let y2 = flatCoordinates[offset + 1];
|
y2 = flatCoordinates[offset + 1];
|
||||||
let segmentM = 0;
|
|
||||||
let segmentLength = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
|
|
||||||
let angleChanged = false;
|
|
||||||
|
|
||||||
let index, previousAngle;
|
// All on the same segment
|
||||||
for (let i = 0; i < numChars; ++i) {
|
if (singleSegment) {
|
||||||
index = reverse ? numChars - i - 1 : i;
|
advance();
|
||||||
const char = text[index];
|
|
||||||
const charLength = scale * measureAndCacheTextWidth(font, char, cache);
|
let previousAngle = Math.atan2(y2 - y1, x2 - x1);
|
||||||
const charM = startM + charLength / 2;
|
if (reverse) {
|
||||||
while (offset < end - stride && segmentM + segmentLength < charM) {
|
previousAngle += previousAngle > 0 ? -PI : PI;
|
||||||
x1 = x2;
|
|
||||||
y1 = y2;
|
|
||||||
offset += stride;
|
|
||||||
x2 = flatCoordinates[offset];
|
|
||||||
y2 = flatCoordinates[offset + 1];
|
|
||||||
segmentM += segmentLength;
|
|
||||||
segmentLength = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
|
|
||||||
}
|
}
|
||||||
const segmentPos = charM - segmentM;
|
const x = (endX + beginX) / 2;
|
||||||
|
const y = (endY + beginY) / 2;
|
||||||
|
result[0] = [x, y, (endM - startM) / 2, previousAngle, text];
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
let previousAngle;
|
||||||
|
for (let i = 0, ii = text.length; i < ii; ) {
|
||||||
|
advance();
|
||||||
let angle = Math.atan2(y2 - y1, x2 - x1);
|
let angle = Math.atan2(y2 - y1, x2 - x1);
|
||||||
if (reverse) {
|
if (reverse) {
|
||||||
angle += angle > 0 ? -Math.PI : Math.PI;
|
angle += angle > 0 ? -PI : PI;
|
||||||
}
|
}
|
||||||
if (previousAngle !== undefined) {
|
if (previousAngle !== undefined) {
|
||||||
let delta = angle - previousAngle;
|
let delta = angle - previousAngle;
|
||||||
angleChanged = angleChanged || delta !== 0;
|
delta += delta > PI ? -2 * PI : delta < -PI ? 2 * PI : 0;
|
||||||
delta +=
|
|
||||||
delta > Math.PI ? -2 * Math.PI : delta < -Math.PI ? 2 * Math.PI : 0;
|
|
||||||
if (Math.abs(delta) > maxAngle) {
|
if (Math.abs(delta) > maxAngle) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
previousAngle = angle;
|
previousAngle = angle;
|
||||||
const interpolate = segmentPos / segmentLength;
|
|
||||||
|
const iStart = i;
|
||||||
|
let charLength = 0;
|
||||||
|
for (; i < ii; ++i) {
|
||||||
|
const index = reverse ? ii - i - 1 : i;
|
||||||
|
const len = scale * measureAndCacheTextWidth(font, text[index], cache);
|
||||||
|
if (
|
||||||
|
offset + stride < end &&
|
||||||
|
segmentM + segmentLength < startM + charLength + len / 2
|
||||||
|
) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
charLength += len;
|
||||||
|
}
|
||||||
|
if (i === iStart) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const chars = reverse
|
||||||
|
? text.substring(ii - iStart, ii - i)
|
||||||
|
: text.substring(iStart, i);
|
||||||
|
interpolate = (startM + charLength / 2 - segmentM) / segmentLength;
|
||||||
const x = lerp(x1, x2, interpolate);
|
const x = lerp(x1, x2, interpolate);
|
||||||
const y = lerp(y1, y2, interpolate);
|
const y = lerp(y1, y2, interpolate);
|
||||||
result[index] = [x, y, charLength / 2, angle, char];
|
result.push([x, y, charLength / 2, angle, chars]);
|
||||||
startM += charLength;
|
startM += charLength;
|
||||||
}
|
}
|
||||||
return angleChanged
|
return result;
|
||||||
? result
|
|
||||||
: [[result[0][0], result[0][1], result[0][2], result[0][3], text]];
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -918,7 +918,9 @@ class Executor {
|
|||||||
part = parts[c]; // x, y, anchorX, rotation, chunk
|
part = parts[c]; // x, y, anchorX, rotation, chunk
|
||||||
chars = /** @type {string} */ (part[4]);
|
chars = /** @type {string} */ (part[4]);
|
||||||
label = this.createLabel(chars, textKey, '', strokeKey);
|
label = this.createLabel(chars, textKey, '', strokeKey);
|
||||||
anchorX = /** @type {number} */ (part[2]) + strokeWidth;
|
anchorX =
|
||||||
|
/** @type {number} */ (part[2]) +
|
||||||
|
(textScale[0] < 0 ? -strokeWidth : strokeWidth);
|
||||||
anchorY =
|
anchorY =
|
||||||
baseline * label.height +
|
baseline * label.height +
|
||||||
((0.5 - baseline) * 2 * strokeWidth * textScale[1]) /
|
((0.5 - baseline) * 2 * strokeWidth * textScale[1]) /
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ describe('ol.geom.flat.drawTextOnPath', function () {
|
|||||||
'',
|
'',
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
expect(instructions).to.eql([[40, 0, 5, 0, 'foo']]);
|
expect(instructions).to.eql([[50, 0, 15, 0, 'foo']]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('left-aligns text on a horizontal line', function () {
|
it('left-aligns text on a horizontal line', function () {
|
||||||
@@ -45,7 +45,7 @@ describe('ol.geom.flat.drawTextOnPath', function () {
|
|||||||
'',
|
'',
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
expect(instructions).to.eql([[5, 0, 5, 0, 'foo']]);
|
expect(instructions).to.eql([[15, 0, 15, 0, 'foo']]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('right-aligns text on a horizontal line', function () {
|
it('right-aligns text on a horizontal line', function () {
|
||||||
@@ -63,7 +63,7 @@ describe('ol.geom.flat.drawTextOnPath', function () {
|
|||||||
'',
|
'',
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
expect(instructions).to.eql([[75, 0, 5, 0, 'foo']]);
|
expect(instructions).to.eql([[85, 0, 15, 0, 'foo']]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('draws text on a vertical line', function () {
|
it('draws text on a vertical line', function () {
|
||||||
@@ -82,7 +82,7 @@ describe('ol.geom.flat.drawTextOnPath', function () {
|
|||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
const a = (90 * Math.PI) / 180;
|
const a = (90 * Math.PI) / 180;
|
||||||
expect(instructions).to.eql([[0, 40, 5, a, 'foo']]);
|
expect(instructions).to.eql([[0, 50, 15, a, 'foo']]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('draws text on a diagonal line', function () {
|
it('draws text on a diagonal line', function () {
|
||||||
@@ -138,19 +138,19 @@ describe('ol.geom.flat.drawTextOnPath', function () {
|
|||||||
'',
|
'',
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
expect(instructions[0]).to.eql([-20, 0, 5, 0, 'foo-foo-foo-foo']);
|
expect(instructions[0]).to.eql([50, 0, 75, 0, 'foo-foo-foo-foo']);
|
||||||
expect(instructions.length).to.be(1);
|
expect(instructions.length).to.be(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders angled text', function () {
|
it('renders angled text', function () {
|
||||||
const length = lineStringLength(angled, 0, angled.length, 2);
|
const length = lineStringLength(angled, 0, angled.length, 2);
|
||||||
const startM = length / 2 - 15;
|
const startM = length / 2 - 20;
|
||||||
const instructions = drawTextOnPath(
|
const instructions = drawTextOnPath(
|
||||||
angled,
|
angled,
|
||||||
0,
|
0,
|
||||||
angled.length,
|
angled.length,
|
||||||
2,
|
2,
|
||||||
'foo',
|
'fooo',
|
||||||
startM,
|
startM,
|
||||||
Infinity,
|
Infinity,
|
||||||
1,
|
1,
|
||||||
@@ -159,11 +159,9 @@ describe('ol.geom.flat.drawTextOnPath', function () {
|
|||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
expect(instructions[0][3]).to.eql((45 * Math.PI) / 180);
|
expect(instructions[0][3]).to.eql((45 * Math.PI) / 180);
|
||||||
expect(instructions[0][4]).to.be('f');
|
expect(instructions[0][4]).to.be('fo');
|
||||||
expect(instructions[1][3]).to.eql((45 * Math.PI) / 180);
|
expect(instructions[1][3]).to.eql((-45 * Math.PI) / 180);
|
||||||
expect(instructions[1][4]).to.be('o');
|
expect(instructions[1][4]).to.be('oo');
|
||||||
expect(instructions[2][3]).to.eql((-45 * Math.PI) / 180);
|
|
||||||
expect(instructions[2][4]).to.be('o');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('respects maxAngle', function () {
|
it('respects maxAngle', function () {
|
||||||
|
|||||||
Reference in New Issue
Block a user