Merge pull request #11037 from mike-000/patch-11
Allow icon and text styles to be scaled in two dimensions
This commit is contained in:
@@ -63,7 +63,7 @@ import {toString} from '../transform.js';
|
||||
* @property {boolean} [overflow]
|
||||
* @property {import("../style/Fill.js").default} [backgroundFill]
|
||||
* @property {import("../style/Stroke.js").default} [backgroundStroke]
|
||||
* @property {number} [scale]
|
||||
* @property {import("../size.js").Size} [scale]
|
||||
* @property {Array<number>} [padding]
|
||||
*/
|
||||
|
||||
@@ -410,7 +410,7 @@ export function rotateAtOffset(context, rotation, offsetX, offsetY) {
|
||||
* @param {number} h Height.
|
||||
* @param {number} x X.
|
||||
* @param {number} y Y.
|
||||
* @param {number} scale Scale.
|
||||
* @param {import("../size.js").Size} scale Scale.
|
||||
*/
|
||||
export function drawImageOrLabel(
|
||||
context,
|
||||
@@ -437,10 +437,25 @@ export function drawImageOrLabel(
|
||||
if (/** @type {*} */ (labelOrImage).contextInstructions) {
|
||||
// label
|
||||
context.translate(x, y);
|
||||
context.scale(scale, scale);
|
||||
context.scale(scale[0], scale[1]);
|
||||
executeLabelInstructions(/** @type {Label} */ (labelOrImage), context);
|
||||
} else if (scale[0] < 0 || scale[1] < 0) {
|
||||
// flipped image
|
||||
context.translate(x, y);
|
||||
context.scale(scale[0], scale[1]);
|
||||
context.drawImage(
|
||||
/** @type {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} */ (labelOrImage),
|
||||
originX,
|
||||
originY,
|
||||
w,
|
||||
h,
|
||||
0,
|
||||
0,
|
||||
w,
|
||||
h
|
||||
);
|
||||
} else {
|
||||
// image
|
||||
// if image not flipped translate and scale can be avoided
|
||||
context.drawImage(
|
||||
/** @type {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} */ (labelOrImage),
|
||||
originX,
|
||||
@@ -449,8 +464,8 @@ export function drawImageOrLabel(
|
||||
h,
|
||||
x,
|
||||
y,
|
||||
w * scale,
|
||||
h * scale
|
||||
w * scale[0],
|
||||
h * scale[1]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -194,7 +194,10 @@ class Executor {
|
||||
const fillState = fillKey ? this.fillStates[fillKey] : null;
|
||||
const textState = this.textStates[textKey];
|
||||
const pixelRatio = this.pixelRatio;
|
||||
const scale = textState.scale * pixelRatio;
|
||||
const scale = [
|
||||
textState.scale[0] * pixelRatio,
|
||||
textState.scale[1] * pixelRatio,
|
||||
];
|
||||
const align = TEXT_ALIGN[textState.textAlign || defaultTextAlign];
|
||||
const strokeWidth =
|
||||
strokeKey && strokeState.lineWidth ? strokeState.lineWidth : 0;
|
||||
@@ -207,15 +210,17 @@ class Executor {
|
||||
const height = lineHeight * numLines;
|
||||
const renderWidth = width + strokeWidth;
|
||||
const contextInstructions = [];
|
||||
// make canvas 2 pixels wider to account for italic text width measurement errors
|
||||
const w = (renderWidth + 2) * scale[0];
|
||||
const h = (height + strokeWidth) * scale[1];
|
||||
/** @type {import("../canvas.js").Label} */
|
||||
const label = {
|
||||
// make canvas 2 pixels wider to account for italic text width measurement errors
|
||||
width: Math.ceil((renderWidth + 2) * scale),
|
||||
height: Math.ceil((height + strokeWidth) * scale),
|
||||
width: w < 0 ? Math.floor(w) : Math.ceil(w),
|
||||
height: h < 0 ? Math.floor(h) : Math.ceil(h),
|
||||
contextInstructions: contextInstructions,
|
||||
};
|
||||
if (scale != 1) {
|
||||
contextInstructions.push('scale', [scale, scale]);
|
||||
if (scale[0] != 1 || scale[1] != 1) {
|
||||
contextInstructions.push('scale', scale);
|
||||
}
|
||||
contextInstructions.push('font', textState.font);
|
||||
if (strokeKey) {
|
||||
@@ -317,7 +322,7 @@ class Executor {
|
||||
* @param {number} originX Origin X.
|
||||
* @param {number} originY Origin Y.
|
||||
* @param {number} rotation Rotation.
|
||||
* @param {number} scale Scale.
|
||||
* @param {import("../../size.js").Size} scale Scale.
|
||||
* @param {boolean} snapToPixel Snap to pixel.
|
||||
* @param {number} width Width.
|
||||
* @param {Array<number>} padding Padding.
|
||||
@@ -347,8 +352,8 @@ class Executor {
|
||||
strokeInstruction
|
||||
) {
|
||||
const fillStroke = fillInstruction || strokeInstruction;
|
||||
anchorX *= scale;
|
||||
anchorY *= scale;
|
||||
anchorX *= scale[0];
|
||||
anchorY *= scale[1];
|
||||
x -= anchorX;
|
||||
y -= anchorY;
|
||||
|
||||
@@ -360,8 +365,8 @@ class Executor {
|
||||
height + originY > imageOrLabel.height
|
||||
? imageOrLabel.height - originY
|
||||
: height;
|
||||
const boxW = padding[3] + w * scale + padding[1];
|
||||
const boxH = padding[0] + h * scale + padding[2];
|
||||
const boxW = padding[3] + w * scale[0] + padding[1];
|
||||
const boxH = padding[0] + h * scale[1] + padding[2];
|
||||
const boxX = x - padding[3];
|
||||
const boxY = y - padding[0];
|
||||
|
||||
@@ -415,7 +420,7 @@ class Executor {
|
||||
);
|
||||
const canvas = context.canvas;
|
||||
const strokePadding = strokeInstruction
|
||||
? (strokeInstruction[2] * scale) / 2
|
||||
? (strokeInstruction[2] * scale[0]) / 2
|
||||
: 0;
|
||||
const renderBuffer = this.renderBuffer_;
|
||||
const intersects =
|
||||
@@ -612,7 +617,7 @@ class Executor {
|
||||
strokeState && strokeState.lineWidth ? strokeState.lineWidth : 0;
|
||||
|
||||
// Remove the 2 pixels we added in createLabel() for the anchor
|
||||
const width = label.width / pixelRatio - 2 * textState.scale;
|
||||
const width = label.width / pixelRatio - 2 * textState.scale[0];
|
||||
const anchorX = align * width + 2 * (0.5 - align) * strokeWidth;
|
||||
const anchorY =
|
||||
(baseline * label.height) / pixelRatio +
|
||||
@@ -791,7 +796,7 @@ class Executor {
|
||||
const originY = /** @type {number} */ (instruction[10]);
|
||||
const rotateWithView = /** @type {boolean} */ (instruction[11]);
|
||||
let rotation = /** @type {number} */ (instruction[12]);
|
||||
const scale = /** @type {number} */ (instruction[13]);
|
||||
const scale = /** @type {import("../../size.js").Size} */ (instruction[13]);
|
||||
let width = /** @type {number} */ (instruction[14]);
|
||||
|
||||
if (!image && instruction.length >= 19) {
|
||||
@@ -914,11 +919,17 @@ class Executor {
|
||||
const strokeWidth = /** @type {number} */ (instruction[11]);
|
||||
text = /** @type {string} */ (instruction[12]);
|
||||
textKey = /** @type {string} */ (instruction[13]);
|
||||
const pixelRatioScale = /** @type {number} */ (instruction[14]);
|
||||
const pixelRatioScale = [
|
||||
/** @type {number} */ (instruction[14]),
|
||||
/** @type {number} */ (instruction[14]),
|
||||
];
|
||||
|
||||
const textState = this.textStates[textKey];
|
||||
const font = textState.font;
|
||||
const textScale = textState.scale * measurePixelRatio;
|
||||
const textScale = [
|
||||
textState.scale[0] * measurePixelRatio,
|
||||
textState.scale[1] * measurePixelRatio,
|
||||
];
|
||||
|
||||
let cachedWidths;
|
||||
if (font in this.widths_) {
|
||||
@@ -930,7 +941,8 @@ class Executor {
|
||||
|
||||
const pathLength = lineStringLength(pixelCoordinates, begin, end, 2);
|
||||
const textLength =
|
||||
textScale * measureAndCacheTextWidth(font, text, cachedWidths);
|
||||
Math.abs(textScale[0]) *
|
||||
measureAndCacheTextWidth(font, text, cachedWidths);
|
||||
if (overflow || textLength <= pathLength) {
|
||||
const textAlign = this.textStates[textKey].textAlign;
|
||||
const startM = (pathLength - textLength) * TEXT_ALIGN[textAlign];
|
||||
@@ -942,7 +954,7 @@ class Executor {
|
||||
text,
|
||||
startM,
|
||||
maxAngle,
|
||||
textScale,
|
||||
Math.abs(textScale[0]),
|
||||
measureAndCacheTextWidth,
|
||||
font,
|
||||
cachedWidths
|
||||
@@ -958,7 +970,8 @@ class Executor {
|
||||
anchorX = /** @type {number} */ (part[2]) + strokeWidth;
|
||||
anchorY =
|
||||
baseline * label.height +
|
||||
(0.5 - baseline) * 2 * strokeWidth -
|
||||
((0.5 - baseline) * 2 * strokeWidth * textScale[1]) /
|
||||
textScale[0] -
|
||||
offsetY;
|
||||
rendered =
|
||||
this.replayImageOrLabel_(
|
||||
|
||||
@@ -82,7 +82,7 @@ class CanvasImageBuilder extends CanvasBuilder {
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {number|undefined}
|
||||
* @type {import("../../size.js").Size|undefined}
|
||||
*/
|
||||
this.scale_ = undefined;
|
||||
|
||||
@@ -145,7 +145,7 @@ class CanvasImageBuilder extends CanvasBuilder {
|
||||
this.originY_,
|
||||
this.rotateWithView_,
|
||||
this.rotation_,
|
||||
this.scale_ * this.pixelRatio,
|
||||
[this.scale_[0] * this.pixelRatio, this.scale_[1] * this.pixelRatio],
|
||||
this.width_,
|
||||
]);
|
||||
this.hitDetectionInstructions.push([
|
||||
@@ -202,7 +202,7 @@ class CanvasImageBuilder extends CanvasBuilder {
|
||||
this.originY_,
|
||||
this.rotateWithView_,
|
||||
this.rotation_,
|
||||
this.scale_ * this.pixelRatio,
|
||||
[this.scale_[0] * this.pixelRatio, this.scale_[1] * this.pixelRatio],
|
||||
this.width_,
|
||||
]);
|
||||
this.hitDetectionInstructions.push([
|
||||
@@ -268,7 +268,7 @@ class CanvasImageBuilder extends CanvasBuilder {
|
||||
this.originY_ = origin[1];
|
||||
this.rotateWithView_ = imageStyle.getRotateWithView();
|
||||
this.rotation_ = imageStyle.getRotation();
|
||||
this.scale_ = imageStyle.getScale();
|
||||
this.scale_ = imageStyle.getScaleArray();
|
||||
this.width_ = size[0];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,9 +188,9 @@ class CanvasImmediateRenderer extends VectorContext {
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {number}
|
||||
* @type {import("../../size.js").Size}
|
||||
*/
|
||||
this.imageScale_ = 0;
|
||||
this.imageScale_ = [0, 0];
|
||||
|
||||
/**
|
||||
* @private
|
||||
@@ -230,9 +230,9 @@ class CanvasImmediateRenderer extends VectorContext {
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {number}
|
||||
* @type {import("../../size.js").Size}
|
||||
*/
|
||||
this.textScale_ = 0;
|
||||
this.textScale_ = [0, 0];
|
||||
|
||||
/**
|
||||
* @private
|
||||
@@ -297,35 +297,51 @@ class CanvasImmediateRenderer extends VectorContext {
|
||||
for (let i = 0, ii = pixelCoordinates.length; i < ii; i += 2) {
|
||||
const x = pixelCoordinates[i] - this.imageAnchorX_;
|
||||
const y = pixelCoordinates[i + 1] - this.imageAnchorY_;
|
||||
if (rotation !== 0 || this.imageScale_ != 1) {
|
||||
if (
|
||||
rotation !== 0 ||
|
||||
this.imageScale_[0] != 1 ||
|
||||
this.imageScale_[1] != 1
|
||||
) {
|
||||
const centerX = x + this.imageAnchorX_;
|
||||
const centerY = y + this.imageAnchorY_;
|
||||
composeTransform(
|
||||
localTransform,
|
||||
centerX,
|
||||
centerY,
|
||||
this.imageScale_,
|
||||
this.imageScale_,
|
||||
1,
|
||||
1,
|
||||
rotation,
|
||||
-centerX,
|
||||
-centerY
|
||||
);
|
||||
context.setTransform.apply(context, localTransform);
|
||||
context.translate(centerX, centerY);
|
||||
context.scale(this.imageScale_[0], this.imageScale_[1]);
|
||||
context.drawImage(
|
||||
this.image_,
|
||||
this.imageOriginX_,
|
||||
this.imageOriginY_,
|
||||
this.imageWidth_,
|
||||
this.imageHeight_,
|
||||
-this.imageAnchorX_,
|
||||
-this.imageAnchorY_,
|
||||
this.imageWidth_,
|
||||
this.imageHeight_
|
||||
);
|
||||
context.setTransform(1, 0, 0, 1, 0, 0);
|
||||
} else {
|
||||
context.drawImage(
|
||||
this.image_,
|
||||
this.imageOriginX_,
|
||||
this.imageOriginY_,
|
||||
this.imageWidth_,
|
||||
this.imageHeight_,
|
||||
x,
|
||||
y,
|
||||
this.imageWidth_,
|
||||
this.imageHeight_
|
||||
);
|
||||
}
|
||||
context.drawImage(
|
||||
this.image_,
|
||||
this.imageOriginX_,
|
||||
this.imageOriginY_,
|
||||
this.imageWidth_,
|
||||
this.imageHeight_,
|
||||
x,
|
||||
y,
|
||||
this.imageWidth_,
|
||||
this.imageHeight_
|
||||
);
|
||||
}
|
||||
if (rotation !== 0 || this.imageScale_ != 1) {
|
||||
context.setTransform(1, 0, 0, 1, 0, 0);
|
||||
}
|
||||
if (this.imageOpacity_ != 1) {
|
||||
context.globalAlpha = alpha;
|
||||
@@ -366,28 +382,39 @@ class CanvasImmediateRenderer extends VectorContext {
|
||||
for (; offset < end; offset += stride) {
|
||||
const x = pixelCoordinates[offset] + this.textOffsetX_;
|
||||
const y = pixelCoordinates[offset + 1] + this.textOffsetY_;
|
||||
if (rotation !== 0 || this.textScale_ != 1) {
|
||||
if (
|
||||
rotation !== 0 ||
|
||||
this.textScale_[0] != 1 ||
|
||||
this.textScale_[1] != 1
|
||||
) {
|
||||
const localTransform = composeTransform(
|
||||
this.tmpLocalTransform_,
|
||||
x,
|
||||
y,
|
||||
this.textScale_,
|
||||
this.textScale_,
|
||||
1,
|
||||
1,
|
||||
rotation,
|
||||
-x,
|
||||
-y
|
||||
);
|
||||
context.setTransform.apply(context, localTransform);
|
||||
context.translate(x, y);
|
||||
context.scale(this.textScale_[0], this.textScale_[1]);
|
||||
if (this.textStrokeState_) {
|
||||
context.strokeText(this.text_, 0, 0);
|
||||
}
|
||||
if (this.textFillState_) {
|
||||
context.fillText(this.text_, 0, 0);
|
||||
}
|
||||
context.setTransform(1, 0, 0, 1, 0, 0);
|
||||
} else {
|
||||
if (this.textStrokeState_) {
|
||||
context.strokeText(this.text_, x, y);
|
||||
}
|
||||
if (this.textFillState_) {
|
||||
context.fillText(this.text_, x, y);
|
||||
}
|
||||
}
|
||||
if (this.textStrokeState_) {
|
||||
context.strokeText(this.text_, x, y);
|
||||
}
|
||||
if (this.textFillState_) {
|
||||
context.fillText(this.text_, x, y);
|
||||
}
|
||||
}
|
||||
if (rotation !== 0 || this.textScale_ != 1) {
|
||||
context.setTransform(1, 0, 0, 1, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -994,22 +1021,30 @@ class CanvasImmediateRenderer extends VectorContext {
|
||||
if (!imageStyle) {
|
||||
this.image_ = null;
|
||||
} else {
|
||||
const imageAnchor = imageStyle.getAnchor();
|
||||
// FIXME pixel ratio
|
||||
const imageImage = imageStyle.getImage(1);
|
||||
const imageOrigin = imageStyle.getOrigin();
|
||||
const imageSize = imageStyle.getSize();
|
||||
this.imageAnchorX_ = imageAnchor[0];
|
||||
this.imageAnchorY_ = imageAnchor[1];
|
||||
this.imageHeight_ = imageSize[1];
|
||||
this.image_ = imageImage;
|
||||
this.imageOpacity_ = imageStyle.getOpacity();
|
||||
this.imageOriginX_ = imageOrigin[0];
|
||||
this.imageOriginY_ = imageOrigin[1];
|
||||
this.imageRotateWithView_ = imageStyle.getRotateWithView();
|
||||
this.imageRotation_ = imageStyle.getRotation();
|
||||
this.imageScale_ = imageStyle.getScale() * this.pixelRatio_;
|
||||
this.imageWidth_ = imageSize[0];
|
||||
if (!imageSize) {
|
||||
this.image_ = null;
|
||||
} else {
|
||||
const imageAnchor = imageStyle.getAnchor();
|
||||
// FIXME pixel ratio
|
||||
const imageImage = imageStyle.getImage(1);
|
||||
const imageOrigin = imageStyle.getOrigin();
|
||||
const imageScale = imageStyle.getScaleArray();
|
||||
this.imageAnchorX_ = imageAnchor[0];
|
||||
this.imageAnchorY_ = imageAnchor[1];
|
||||
this.imageHeight_ = imageSize[1];
|
||||
this.image_ = imageImage;
|
||||
this.imageOpacity_ = imageStyle.getOpacity();
|
||||
this.imageOriginX_ = imageOrigin[0];
|
||||
this.imageOriginY_ = imageOrigin[1];
|
||||
this.imageRotateWithView_ = imageStyle.getRotateWithView();
|
||||
this.imageRotation_ = imageStyle.getRotation();
|
||||
this.imageScale_ = [
|
||||
this.pixelRatio_ * imageScale[0],
|
||||
this.pixelRatio_ * imageScale[1],
|
||||
];
|
||||
this.imageWidth_ = imageSize[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1078,7 +1113,7 @@ class CanvasImmediateRenderer extends VectorContext {
|
||||
const textOffsetY = textStyle.getOffsetY();
|
||||
const textRotateWithView = textStyle.getRotateWithView();
|
||||
const textRotation = textStyle.getRotation();
|
||||
const textScale = textStyle.getScale();
|
||||
const textScale = textStyle.getScaleArray();
|
||||
const textText = textStyle.getText();
|
||||
const textTextAlign = textStyle.getTextAlign();
|
||||
const textTextBaseline = textStyle.getTextBaseline();
|
||||
@@ -1099,8 +1134,10 @@ class CanvasImmediateRenderer extends VectorContext {
|
||||
this.textRotateWithView_ =
|
||||
textRotateWithView !== undefined ? textRotateWithView : false;
|
||||
this.textRotation_ = textRotation !== undefined ? textRotation : 0;
|
||||
this.textScale_ =
|
||||
this.pixelRatio_ * (textScale !== undefined ? textScale : 1);
|
||||
this.textScale_ = [
|
||||
this.pixelRatio_ * textScale[0],
|
||||
this.pixelRatio_ * textScale[1],
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,6 +303,27 @@ class CanvasTextBuilder extends CanvasBuilder {
|
||||
|
||||
this.beginGeometry(geometry, feature);
|
||||
|
||||
// adjust padding for negative scale
|
||||
let padding = textState.padding;
|
||||
if (
|
||||
padding != defaultPadding &&
|
||||
(textState.scale[0] < 0 || textState.scale[1] < 0)
|
||||
) {
|
||||
let p0 = textState.padding[0];
|
||||
let p1 = textState.padding[1];
|
||||
let p2 = textState.padding[2];
|
||||
let p3 = textState.padding[3];
|
||||
if (textState.scale[0] < 0) {
|
||||
p1 = -p1;
|
||||
p3 = -p3;
|
||||
}
|
||||
if (textState.scale[1] < 0) {
|
||||
p0 = -p0;
|
||||
p2 = -p2;
|
||||
}
|
||||
padding = [p0, p1, p2, p3];
|
||||
}
|
||||
|
||||
// The image is unknown at this stage so we pass null; it will be computed at render time.
|
||||
// For clarity, we pass NaN for offsetX, offsetY, width and height, which will be computed at
|
||||
// render time.
|
||||
@@ -321,11 +342,11 @@ class CanvasTextBuilder extends CanvasBuilder {
|
||||
0,
|
||||
this.textRotateWithView_,
|
||||
this.textRotation_,
|
||||
1,
|
||||
[1, 1],
|
||||
NaN,
|
||||
textState.padding == defaultPadding
|
||||
padding == defaultPadding
|
||||
? defaultPadding
|
||||
: textState.padding.map(function (p) {
|
||||
: padding.map(function (p) {
|
||||
return p * pixelRatio;
|
||||
}),
|
||||
!!textState.backgroundFill,
|
||||
@@ -338,6 +359,7 @@ class CanvasTextBuilder extends CanvasBuilder {
|
||||
this.textOffsetY_,
|
||||
geometryWidths,
|
||||
]);
|
||||
const scale = 1 / pixelRatio;
|
||||
this.hitDetectionInstructions.push([
|
||||
CanvasInstruction.DRAW_IMAGE,
|
||||
begin,
|
||||
@@ -352,9 +374,9 @@ class CanvasTextBuilder extends CanvasBuilder {
|
||||
0,
|
||||
this.textRotateWithView_,
|
||||
this.textRotation_,
|
||||
1 / this.pixelRatio,
|
||||
[scale, scale],
|
||||
NaN,
|
||||
textState.padding,
|
||||
padding,
|
||||
!!textState.backgroundFill,
|
||||
!!textState.backgroundStroke,
|
||||
this.text_,
|
||||
@@ -431,9 +453,8 @@ class CanvasTextBuilder extends CanvasBuilder {
|
||||
|
||||
const offsetY = this.textOffsetY_ * pixelRatio;
|
||||
const text = this.text_;
|
||||
const textScale = textState.scale;
|
||||
const strokeWidth = strokeState
|
||||
? (strokeState.lineWidth * textScale) / 2
|
||||
? (strokeState.lineWidth * Math.abs(textState.scale[0])) / 2
|
||||
: 0;
|
||||
|
||||
this.instructions.push([
|
||||
@@ -529,7 +550,7 @@ class CanvasTextBuilder extends CanvasBuilder {
|
||||
textState = this.textState_;
|
||||
const font = textStyle.getFont() || defaultFont;
|
||||
registerFont(font);
|
||||
const textScale = textStyle.getScale();
|
||||
const textScale = textStyle.getScaleArray();
|
||||
textState.overflow = textStyle.getOverflow();
|
||||
textState.font = font;
|
||||
textState.maxAngle = textStyle.getMaxAngle();
|
||||
@@ -540,7 +561,7 @@ class CanvasTextBuilder extends CanvasBuilder {
|
||||
textState.backgroundFill = textStyle.getBackgroundFill();
|
||||
textState.backgroundStroke = textStyle.getBackgroundStroke();
|
||||
textState.padding = textStyle.getPadding() || defaultPadding;
|
||||
textState.scale = textScale === undefined ? 1 : textScale;
|
||||
textState.scale = textScale === undefined ? [1, 1] : textScale;
|
||||
|
||||
const textOffsetX = textStyle.getOffsetX();
|
||||
const textOffsetY = textStyle.getOffsetY();
|
||||
|
||||
Reference in New Issue
Block a user