Merge pull request #13571 from rycgar/justify-text
Add `justify` option for text style
This commit is contained in:
12
examples/vector-labels-justify-text.html
Normal file
12
examples/vector-labels-justify-text.html
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
layout: example.html
|
||||
title: Vector Labels - Justify Text
|
||||
shortdesc: Example of text justification within a label.
|
||||
docs: >
|
||||
This example showcases how the text can be justified within the label box.
|
||||
By default, the text is justified according to the `textAlign` option.
|
||||
However, this option justifies also the label itself according to `textAlign` setting.
|
||||
To decouple the label placement from text placement (within the label box) use `justify`.
|
||||
tags: "vector, openstreetmap, label, rich-text"
|
||||
---
|
||||
<div id="map" class="map"></div>
|
||||
114
examples/vector-labels-justify-text.js
Normal file
114
examples/vector-labels-justify-text.js
Normal file
@@ -0,0 +1,114 @@
|
||||
import Collection from '../src/ol/Collection.js';
|
||||
import Feature from '../src/ol/Feature.js';
|
||||
import GeoJSON from '../src/ol/format/GeoJSON.js';
|
||||
import Map from '../src/ol/Map.js';
|
||||
import Point from '../src/ol/geom/Point.js';
|
||||
import View from '../src/ol/View.js';
|
||||
import {
|
||||
Circle as CircleStyle,
|
||||
Fill,
|
||||
Stroke,
|
||||
Style,
|
||||
Text,
|
||||
} from '../src/ol/style.js';
|
||||
import {OSM, Vector as VectorSource} from '../src/ol/source.js';
|
||||
import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js';
|
||||
|
||||
const features = [
|
||||
{
|
||||
geometry: new Point([-8300000, 6095000]),
|
||||
textAlign: 'left',
|
||||
},
|
||||
{
|
||||
geometry: new Point([-8150000, 6095000]),
|
||||
textAlign: 'center',
|
||||
},
|
||||
{
|
||||
geometry: new Point([-8000000, 6095000]),
|
||||
textAlign: 'right',
|
||||
},
|
||||
{
|
||||
geometry: new Point([-8300000, 6025000]),
|
||||
textAlign: 'left',
|
||||
justify: 'center',
|
||||
},
|
||||
{
|
||||
geometry: new Point([-8150000, 6025000]),
|
||||
textAlign: 'center',
|
||||
justify: 'center',
|
||||
},
|
||||
{
|
||||
geometry: new Point([-8000000, 6025000]),
|
||||
textAlign: 'right',
|
||||
justify: 'center',
|
||||
},
|
||||
{
|
||||
geometry: new Point([-8300000, 5955000]),
|
||||
textAlign: 'left',
|
||||
justify: 'left',
|
||||
},
|
||||
{
|
||||
geometry: new Point([-8150000, 5955000]),
|
||||
textAlign: 'center',
|
||||
justify: 'left',
|
||||
},
|
||||
{
|
||||
geometry: new Point([-8000000, 5955000]),
|
||||
textAlign: 'right',
|
||||
justify: 'left',
|
||||
},
|
||||
];
|
||||
|
||||
function createStyle({textAlign, justify}) {
|
||||
return new Style({
|
||||
image: new CircleStyle({
|
||||
radius: 10,
|
||||
fill: new Fill({color: 'rgba(255, 0, 0, 0.1)'}),
|
||||
stroke: new Stroke({color: 'red', width: 1}),
|
||||
}),
|
||||
text: new Text({
|
||||
font: '16px sans-serif',
|
||||
textAlign,
|
||||
justify,
|
||||
text:
|
||||
`Justify text inside box\ntextAlign: ${textAlign}` +
|
||||
(justify ? `\njustify: ${justify}` : ''),
|
||||
fill: new Fill({
|
||||
color: [255, 255, 255, 1],
|
||||
}),
|
||||
backgroundFill: new Fill({
|
||||
color: [168, 50, 153, 0.6],
|
||||
}),
|
||||
padding: [2, 2, 2, 2],
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
const vectorPoints = new VectorLayer({
|
||||
source: new VectorSource({
|
||||
features: new Collection(
|
||||
features.map((featureOptions) => {
|
||||
const feature = new Feature({
|
||||
geometry: featureOptions.geometry,
|
||||
});
|
||||
feature.setStyle(createStyle(featureOptions));
|
||||
return feature;
|
||||
})
|
||||
),
|
||||
format: new GeoJSON(),
|
||||
}),
|
||||
});
|
||||
|
||||
const map = new Map({
|
||||
layers: [
|
||||
new TileLayer({
|
||||
source: new OSM(),
|
||||
}),
|
||||
vectorPoints,
|
||||
],
|
||||
target: 'map',
|
||||
view: new View({
|
||||
center: [-8150000, 6025000],
|
||||
zoom: 8,
|
||||
}),
|
||||
});
|
||||
@@ -56,6 +56,7 @@ import {getFontParameters} from '../css.js';
|
||||
* @typedef {Object} TextState
|
||||
* @property {string} font Font.
|
||||
* @property {string} [textAlign] TextAlign.
|
||||
* @property {string} [justify] Justify.
|
||||
* @property {string} textBaseline TextBaseline.
|
||||
* @property {string} [placement] Placement.
|
||||
* @property {number} [maxAngle] MaxAngle.
|
||||
|
||||
@@ -239,10 +239,12 @@ class Executor {
|
||||
textState.scale[1] * pixelRatio,
|
||||
];
|
||||
const textIsArray = Array.isArray(text);
|
||||
const align = horizontalTextAlign(
|
||||
textIsArray ? text[0] : text,
|
||||
textState.textAlign || defaultTextAlign
|
||||
);
|
||||
const align = textState.justify
|
||||
? TEXT_ALIGN[textState.justify]
|
||||
: horizontalTextAlign(
|
||||
Array.isArray(text) ? text[0] : text,
|
||||
textState.textAlign || defaultTextAlign
|
||||
);
|
||||
const strokeWidth =
|
||||
strokeKey && strokeState.lineWidth ? strokeState.lineWidth : 0;
|
||||
|
||||
|
||||
@@ -211,6 +211,7 @@ class CanvasTextBuilder extends CanvasBuilder {
|
||||
}
|
||||
this.beginGeometry(geometry, feature);
|
||||
const textAlign = textState.textAlign;
|
||||
// No `justify` support for line placement.
|
||||
let flatOffset = 0;
|
||||
let flatEnd;
|
||||
for (let o = 0, oo = ends.length; o < oo; ++o) {
|
||||
@@ -449,6 +450,7 @@ class CanvasTextBuilder extends CanvasBuilder {
|
||||
this.textStates[textKey] = {
|
||||
font: textState.font,
|
||||
textAlign: textState.textAlign || defaultTextAlign,
|
||||
justify: textState.justify,
|
||||
textBaseline: textState.textBaseline || defaultTextBaseline,
|
||||
scale: textState.scale,
|
||||
};
|
||||
@@ -581,6 +583,7 @@ class CanvasTextBuilder extends CanvasBuilder {
|
||||
textState.maxAngle = textStyle.getMaxAngle();
|
||||
textState.placement = textStyle.getPlacement();
|
||||
textState.textAlign = textStyle.getTextAlign();
|
||||
textState.justify = textStyle.getJustify();
|
||||
textState.textBaseline =
|
||||
textStyle.getTextBaseline() || defaultTextBaseline;
|
||||
textState.backgroundFill = textStyle.getBackgroundFill();
|
||||
@@ -617,6 +620,7 @@ class CanvasTextBuilder extends CanvasBuilder {
|
||||
textState.font +
|
||||
textState.scale +
|
||||
(textState.textAlign || '?') +
|
||||
(textState.justify || '?') +
|
||||
(textState.textBaseline || '?');
|
||||
this.fillKey_ = fillState
|
||||
? typeof fillState.fillStyle == 'string'
|
||||
|
||||
@@ -35,6 +35,10 @@ const DEFAULT_FILL_COLOR = '#333';
|
||||
* @property {string} [textAlign] Text alignment. Possible values: 'left', 'right', 'center', 'end' or 'start'.
|
||||
* Default is 'center' for `placement: 'point'`. For `placement: 'line'`, the default is to let the renderer choose a
|
||||
* placement where `maxAngle` is not exceeded.
|
||||
* @property {string} [justify] Text justification within the text box.
|
||||
* If not set, text is justified towards the `textAlign` anchor.
|
||||
* Otherwise, use options `'left'`, `'center'`, or `'right'` to justify the text within the text box.
|
||||
* **Note:** `justify` is ignored for immediate rendering and also for `placement: 'line'`.
|
||||
* @property {string} [textBaseline='middle'] Text base line. Possible values: 'bottom', 'top', 'middle', 'alphabetic',
|
||||
* 'hanging', 'ideographic'.
|
||||
* @property {import("./Fill.js").default} [fill] Fill style. If none is provided, we'll use a dark fill-style (#333).
|
||||
@@ -101,6 +105,12 @@ class Text {
|
||||
*/
|
||||
this.textAlign_ = options.textAlign;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {string|undefined}
|
||||
*/
|
||||
this.justify_ = options.justify;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {string|undefined}
|
||||
@@ -194,6 +204,7 @@ class Text {
|
||||
scale: Array.isArray(scale) ? scale.slice() : scale,
|
||||
text: this.getText(),
|
||||
textAlign: this.getTextAlign(),
|
||||
justify: this.getJustify(),
|
||||
textBaseline: this.getTextBaseline(),
|
||||
fill: this.getFill() ? this.getFill().clone() : undefined,
|
||||
stroke: this.getStroke() ? this.getStroke().clone() : undefined,
|
||||
@@ -334,6 +345,15 @@ class Text {
|
||||
return this.textAlign_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the justification.
|
||||
* @return {string|undefined} Justification.
|
||||
* @api
|
||||
*/
|
||||
getJustify() {
|
||||
return this.justify_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the text baseline.
|
||||
* @return {string|undefined} Text baseline.
|
||||
@@ -501,6 +521,16 @@ class Text {
|
||||
this.textAlign_ = textAlign;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the justification.
|
||||
*
|
||||
* @param {string|undefined} justify Justification.
|
||||
* @api
|
||||
*/
|
||||
setJustify(justify) {
|
||||
this.justify_ = justify;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the text baseline.
|
||||
*
|
||||
|
||||
BIN
test/rendering/cases/rich-text-justify-style/expected.png
Normal file
BIN
test/rendering/cases/rich-text-justify-style/expected.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
133
test/rendering/cases/rich-text-justify-style/main.js
Normal file
133
test/rendering/cases/rich-text-justify-style/main.js
Normal file
@@ -0,0 +1,133 @@
|
||||
import CircleStyle from '../../../../src/ol/style/Circle.js';
|
||||
import Feature from '../../../../src/ol/Feature.js';
|
||||
import Fill from '../../../../src/ol/style/Fill.js';
|
||||
import Map from '../../../../src/ol/Map.js';
|
||||
import Point from '../../../../src/ol/geom/Point.js';
|
||||
import Stroke from '../../../../src/ol/style/Stroke.js';
|
||||
import Style from '../../../../src/ol/style/Style.js';
|
||||
import Text from '../../../../src/ol/style/Text.js';
|
||||
import VectorLayer from '../../../../src/ol/layer/Vector.js';
|
||||
import VectorSource from '../../../../src/ol/source/Vector.js';
|
||||
import View from '../../../../src/ol/View.js';
|
||||
|
||||
const vectorSource = new VectorSource({
|
||||
features: [
|
||||
// inline - justify: undefined - right-bottom
|
||||
new Feature({
|
||||
geometry: new Point([-10, 50]),
|
||||
text: [
|
||||
'just:',
|
||||
'',
|
||||
'undefined',
|
||||
'italic 14px/1.5 Ubuntu',
|
||||
'\n',
|
||||
'',
|
||||
'right-bottom',
|
||||
'20px/1.2 Ubuntu',
|
||||
],
|
||||
textAlign: 'right',
|
||||
textBaseline: 'bottom',
|
||||
}),
|
||||
// multi-line - justify: left - center-middle
|
||||
new Feature({
|
||||
geometry: new Point([0, 0]),
|
||||
text: ['just: left', '', '\n', '', 'center-middle', 'italic 20px Ubuntu'],
|
||||
textAlign: 'center',
|
||||
textBaseline: 'middle',
|
||||
justify: 'left',
|
||||
}),
|
||||
|
||||
// inline - justify: left - right-top
|
||||
new Feature({
|
||||
geometry: new Point([-10, -50]),
|
||||
text: [
|
||||
'just:',
|
||||
'',
|
||||
'left',
|
||||
'italic 14px/1.5 Ubuntu',
|
||||
'\n',
|
||||
'',
|
||||
'right-top',
|
||||
'28px/1 Ubuntu',
|
||||
],
|
||||
textAlign: 'right',
|
||||
textBaseline: 'top',
|
||||
justify: 'left',
|
||||
}),
|
||||
|
||||
// inline - justify: undefined - left-bottom
|
||||
new Feature({
|
||||
geometry: new Point([10, 50]),
|
||||
text: [
|
||||
'just:',
|
||||
'',
|
||||
'undefined',
|
||||
'italic 14px/1.5 Ubuntu',
|
||||
'\n',
|
||||
'',
|
||||
'left-bottom',
|
||||
'20px/1.2 Ubuntu',
|
||||
],
|
||||
textAlign: 'left',
|
||||
textBaseline: 'bottom',
|
||||
}),
|
||||
|
||||
// inline - justify: right - left-top
|
||||
new Feature({
|
||||
geometry: new Point([10, -50]),
|
||||
text: [
|
||||
'just:',
|
||||
'',
|
||||
'right',
|
||||
'italic 14px/1.5 Ubuntu',
|
||||
'\n',
|
||||
'',
|
||||
'left-top',
|
||||
'28px/1 Ubuntu',
|
||||
],
|
||||
textAlign: 'left',
|
||||
textBaseline: 'top',
|
||||
justify: 'right',
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
new Map({
|
||||
pixelRatio: 1,
|
||||
layers: [
|
||||
new VectorLayer({
|
||||
source: vectorSource,
|
||||
style: function (feature) {
|
||||
return new Style({
|
||||
text: new Text({
|
||||
text: feature.get('text'),
|
||||
font: '18px Ubuntu',
|
||||
textAlign: feature.get('textAlign'),
|
||||
justify: feature.get('justify'),
|
||||
textBaseline: feature.get('textBaseline'),
|
||||
fill: new Fill({
|
||||
color: 'black',
|
||||
}),
|
||||
stroke: new Stroke({
|
||||
color: 'white',
|
||||
}),
|
||||
backgroundStroke: new Stroke({width: 1}),
|
||||
}),
|
||||
image: new CircleStyle({
|
||||
radius: 10,
|
||||
fill: new Fill({
|
||||
color: 'cyan',
|
||||
}),
|
||||
}),
|
||||
});
|
||||
},
|
||||
}),
|
||||
],
|
||||
target: 'map',
|
||||
view: new View({
|
||||
center: [0, 0],
|
||||
resolution: 1,
|
||||
}),
|
||||
});
|
||||
|
||||
render({tolerance: 0.01});
|
||||
Reference in New Issue
Block a user