Skip to content

Commit

Permalink
Add several utilities for handling font-based mark symbolizers
Browse files Browse the repository at this point in the history
This is using regexp a lot since we have to parse string values in
both directions:
* when reading a Geostyler symbolizer `wellKnownName` property
(syntax being `ttf://Font name #char code`)
* when reading an OL style object, where the font name and symbol
size are both stored in the `font` property of the style

Note that when generating an OL text style for a mark symbolizer,
a fake font called `geostyler-mark-symbolizer` will be appended at the
end of the `font` property of the style; this has no impact on the
rendering and is used to determine whether the text style was created
for a Mark symbolizer or a Text symbolizer.
  • Loading branch information
jahow committed Jun 18, 2020
1 parent a1fa4bb commit 0880dc2
Show file tree
Hide file tree
Showing 2 changed files with 213 additions and 4 deletions.
135 changes: 132 additions & 3 deletions src/Util/OlStyleUtil.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import OlStyleUtil from './OlStyleUtil';
import OlStyleUtil, { DUMMY_MARK_SYMBOLIZER_FONT } from './OlStyleUtil';
import OlFeature from 'ol/Feature';
import OlGeomPoint from 'ol/geom/Point';
import { TextSymbolizer } from 'geostyler-style';
import { MarkSymbolizer, TextSymbolizer } from 'geostyler-style';

describe('OlStyleUtil', () => {

Expand Down Expand Up @@ -81,7 +81,7 @@ describe('OlStyleUtil', () => {
expect(OlStyleUtil.getTextFont).toBeDefined();
});

it('returns correct opacity', () => {
it('returns correct font', () => {
const symb: TextSymbolizer = {
kind: 'Text',
color: '#000000',
Expand All @@ -95,6 +95,135 @@ describe('OlStyleUtil', () => {
});
});

describe('#getIsFontGlyphBased', () => {
it('is defined', () => {
expect(OlStyleUtil.getIsFontGlyphBased).toBeDefined();
});

it('returns false if wellknownname is not based on a font glyph', () => {
const symb: MarkSymbolizer = {
kind: 'Mark',
color: '#000000',
wellKnownName: 'shape://backslash'
};
const isGlyph = OlStyleUtil.getIsFontGlyphBased(symb);
expect(isGlyph).toEqual(false);
});

it('returns true if wellknownname is based on a font glyph', () => {
const symb: MarkSymbolizer = {
kind: 'Mark',
color: '#000000',
wellKnownName: 'ttf://Webdings#0x0064'
};
const isGlyph = OlStyleUtil.getIsFontGlyphBased(symb);
expect(isGlyph).toEqual(true);
});
});

describe('#getIsMarkSymbolizerFont', () => {
it('is defined', () => {
expect(OlStyleUtil.getIsMarkSymbolizerFont).toBeDefined();
});

it('returns false if the font was not intended for a mark symbolizer', () => {
const font = '12px \'Arial Black\'';
const isMark = OlStyleUtil.getIsMarkSymbolizerFont(font);
expect(isMark).toEqual(false);
});

it('returns true if the font was intended for a mark symbolizer', () => {
const font = `Normal 12px 'Arial Black', ${DUMMY_MARK_SYMBOLIZER_FONT}`;
const isMark = OlStyleUtil.getIsMarkSymbolizerFont(font);
expect(isMark).toEqual(true);
});
});

describe('#getTextFontForMarkSymbolizer', () => {
it('is defined', () => {
expect(OlStyleUtil.getTextFontForMarkSymbolizer).toBeDefined();
});

it('returns correct font', () => {
const symb: MarkSymbolizer = {
kind: 'Mark',
color: '#000000',
radius: 10,
wellKnownName: 'ttf://Arial Black#0x0064'
};
const font = OlStyleUtil.getTextFontForMarkSymbolizer(symb);
expect(font).toEqual(`Normal 10px 'Arial Black', ${DUMMY_MARK_SYMBOLIZER_FONT}`);
});

it('returns correct font (with default size)', () => {
const symb: MarkSymbolizer = {
kind: 'Mark',
color: '#000000',
wellKnownName: 'ttf://Arial Black#0x0064'
};
const font = OlStyleUtil.getTextFontForMarkSymbolizer(symb);
expect(font).toEqual(`Normal 5px 'Arial Black', ${DUMMY_MARK_SYMBOLIZER_FONT}`);
});
});

describe('#getCharacterForMarkSymbolizer', () => {
it('is defined', () => {
expect(OlStyleUtil.getCharacterForMarkSymbolizer).toBeDefined();
});

it('returns correct character', () => {
const symb: MarkSymbolizer = {
kind: 'Mark',
color: '#000000',
wellKnownName: 'ttf://Arial Black#0x0064'
};
const char = OlStyleUtil.getCharacterForMarkSymbolizer(symb);
expect(char).toEqual('d');
});
});

describe('#getFontNameFromOlFont', () => {
it('is defined', () => {
expect(OlStyleUtil.getFontNameFromOlFont).toBeDefined();
});

it('returns correct font name (1)', () => {
const name = OlStyleUtil.getFontNameFromOlFont(`Normal 13px 'Arial Sans', sans-serif`);
expect(name).toEqual('Arial Sans');
});

it('returns correct font name (2)', () => {
const name = OlStyleUtil.getFontNameFromOlFont(`10px Arial`);
expect(name).toEqual('Arial');
});

it('returns correct font name (3)', () => {
const name = OlStyleUtil.getFontNameFromOlFont(`italic 1.2em "Fira Sans", serif`);
expect(name).toEqual('Fira Sans');
});
});

describe('#getSizeFromOlFont', () => {
it('is defined', () => {
expect(OlStyleUtil.getSizeFromOlFont).toBeDefined();
});

it('returns correct size in pixels (1)', () => {
const size = OlStyleUtil.getSizeFromOlFont(`Normal 13px 'Arial Sans', sans-serif`);
expect(size).toEqual(13);
});

it('returns correct size in pixels (2)', () => {
const size = OlStyleUtil.getSizeFromOlFont(`10px Arial`);
expect(size).toEqual(10);
});

it('returns 0 if no available size in pixels', () => {
const size = OlStyleUtil.getSizeFromOlFont(`italic 1.2em "Fira Sans", serif`);
expect(size).toEqual(0);
});
});

describe('#resolveAttributeTemplate', () => {
let coords: [number, number];
let geom;
Expand Down
82 changes: 81 additions & 1 deletion src/Util/OlStyleUtil.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { TextSymbolizer } from 'geostyler-style';
import { MarkSymbolizer, TextSymbolizer } from 'geostyler-style';

const WELLKNOWNNAME_TTF_REGEXP = /^ttf:\/\/(.+)#(.+)$/;
export const DUMMY_MARK_SYMBOLIZER_FONT = 'geostyler-mark-symbolizer';

/**
* Offers some utility functions to work with OpenLayers Styles.
Expand Down Expand Up @@ -112,6 +115,83 @@ class OlStyleUtil {
return weight + ' ' + size + 'px ' + font;
}

/**
* Returns true if the given mark symbolizer is based on a font glyph
* (i.e. has a well known name property starting with 'ttf://').
*
* @param symbolizer The TextSymbolizer to derive the font string from
*/
public static getIsFontGlyphBased(symbolizer: MarkSymbolizer) {
return WELLKNOWNNAME_TTF_REGEXP.test(symbolizer.wellKnownName);
}

/**
* Returns whether the given font (as used in the OpenLayers Text Style `font` property)
* is intended for a mark symbolizer or not.
* This is done by checking whether the dummy DUMMY_MARK_SYMBOLIZER_FONT font name is present.
*
* @param font The text font to analyze
*/
public static getIsMarkSymbolizerFont(font: string) {
const search = DUMMY_MARK_SYMBOLIZER_FONT;
return font.substring(font.length - search.length, font.length) === search;
}

/**
* Returns an OL compliant font string, to be used for mark symbolizers
* using a font glyph.
* This also includes a dummy DUMMY_MARK_SYMBOLIZER_FONT font name at the end of the
* string to allow determining that this font was intended for a mark symbolizer
* later on.
*
* @param symbolizer The TextSymbolizer to derive the font string from
*/
public static getTextFontForMarkSymbolizer(symbolizer: MarkSymbolizer) {
const parts = symbolizer.wellKnownName.match(WELLKNOWNNAME_TTF_REGEXP);
if (!parts) {
throw new Error(`Could not parse font-based well known name: ${symbolizer.wellKnownName}`);
}
const fontFamily = parts[1];
return `Normal ${symbolizer.radius || 5}px '${fontFamily}', ${DUMMY_MARK_SYMBOLIZER_FONT}`;
}

/**
* Returns a 1-char string to be used as text for mark symbolizers using a font glyph.
*
* @param symbolizer The MarkSymbolizer to derive the character string from
*/
public static getCharacterForMarkSymbolizer(symbolizer: MarkSymbolizer) {
const parts = symbolizer.wellKnownName.match(WELLKNOWNNAME_TTF_REGEXP);
if (!parts) {
throw new Error(`Could not parse font-based well known name: ${symbolizer.wellKnownName}`);
}
return String.fromCharCode(parseInt(parts[2], 16));
}

/**
* Returns the font name used in the OpenLayers text style `font` property.
*
* @param olFont the `font` property of an OpenLayers text style
*/
public static getFontNameFromOlFont(olFont: string) {
const parts = olFont.match(/(?:\d+\S+) '?"?([^,'"]+)/);
if (!parts) {
throw new Error(`Could not find font family name in the following string: ${olFont}`);
}
return parts[1];
}

/**
* Returns the size in pixels specified in the OpenLayers text style `font` property,
* or 0 if not found.
*
* @param olFont the `font` property of an OpenLayers text style
*/
public static getSizeFromOlFont(olFont: string) {
const parts = olFont.match(/(?:(\d+)px)/);
return parts ? parseInt(parts[1], 10) : 0;
}

/**
* Resolves the given template string with the given feature attributes, e.g.
* the template "Size of area is {{AREA_SIZE}} km²" would be to resolved
Expand Down

0 comments on commit 0880dc2

Please sign in to comment.