Skip to content

Commit

Permalink
"concat"/"formatted" -> "format"
Browse files Browse the repository at this point in the history
  • Loading branch information
ChrisLoer committed Jul 27, 2018
1 parent 899d393 commit 9a2c511
Show file tree
Hide file tree
Showing 29 changed files with 248 additions and 168 deletions.
2 changes: 2 additions & 0 deletions build/generate-flow-typed-style-spec.js
Expand Up @@ -114,6 +114,8 @@ fs.writeFileSync('flow-typed/style-spec.js', `// Generated code; do not edit. Ed
declare type ColorSpecification = string;
declare type FormattedSpecification = string;
declare type FilterSpecification =
| ['has', string]
| ['!has', string]
Expand Down
6 changes: 5 additions & 1 deletion build/generate-style-code.js
Expand Up @@ -5,6 +5,7 @@ const fs = require('fs');
const ejs = require('ejs');
const spec = require('../src/style-spec/reference/v8');
const Color = require('../src/style-spec/util/color');
const {Formatted} = require('../src/style-spec/expression/definitions/formatted');

global.camelize = function (str) {
return str.replace(/(?:^|-)(.)/g, function (_, x) {
Expand All @@ -24,6 +25,8 @@ global.flowType = function (property) {
return Object.keys(property.values).map(JSON.stringify).join(' | ');
case 'color':
return `Color`;
case 'formatted':
return `string | Formatted`;
case 'array':
if (property.length) {
return `[${new Array(property.length).fill(flowType({type: property.value})).join(', ')}]`;
Expand Down Expand Up @@ -61,6 +64,8 @@ global.runtimeType = function (property) {
return 'StringType';
case 'color':
return `ColorType`;
case 'formatted':
return `FormattedType`;
case 'array':
if (property.length) {
return `array(${runtimeType({type: property.value})}, ${property.length})`;
Expand Down Expand Up @@ -131,4 +136,3 @@ const layers = Object.keys(spec.layer.type.values).map((type) => {
for (const layer of layers) {
fs.writeFileSync(`src/style/style_layer/${layer.type.replace('-', '_')}_style_layer_properties.js`, propertiesJs(layer))
}

32 changes: 16 additions & 16 deletions debug/streets-v10-viewport.json
Expand Up @@ -2853,10 +2853,10 @@
"text-padding": 1,
"text-rotation-alignment": "map",
"text-pitch-alignment": "viewport",
"text-field": ["concat",
["formatted", ["get", "name"], { "font-scale": 1.2 }],
["formatted", " - ", {}],
["formatted", ["get", "name_en"], { "font-scale": 0.8, "text-font": ["literal", [ "DIN Offc Pro Italic", "Arial Unicode MS Regular" ]] }]
"text-field": ["format",
["get", "name"], { "font-scale": 1.2 },
" - ", {},
["get", "name_en"], { "font-scale": 0.8, "text-font": ["literal", [ "DIN Offc Pro Italic", "Arial Unicode MS Regular" ]] }
],
"text-letter-spacing": 0.01
},
Expand Down Expand Up @@ -2888,10 +2888,10 @@
"text-padding": 1,
"text-rotation-alignment": "map",
"text-pitch-alignment": "viewport",
"text-field": ["concat",
["formatted", ["get", "name"], { "font-scale": 1.2 }],
["formatted", " - ", {}],
["formatted", ["get", "name_en"], { "font-scale": 0.8, "text-font": ["literal", [ "DIN Offc Pro Italic", "Arial Unicode MS Regular" ]] }]
"text-field": ["format",
["get", "name"], { "font-scale": 1.2 },
" - ", {},
["get", "name_en"], { "font-scale": 0.8, "text-font": ["literal", [ "DIN Offc Pro Italic", "Arial Unicode MS Regular" ]] }
],
"text-letter-spacing": 0.01
},
Expand All @@ -2917,10 +2917,10 @@
"text-padding": 1,
"text-rotation-alignment": "map",
"text-pitch-alignment": "viewport",
"text-field": ["concat",
["formatted", ["get", "name"], { "font-scale": 1.2 }],
["formatted", " - ", {}],
["formatted", ["get", "name_en"], { "font-scale": 0.8, "text-font": ["literal", [ "DIN Offc Pro Italic", "Arial Unicode MS Regular" ]] }]
"text-field": ["format",
["get", "name"], { "font-scale": 1.2 },
" - ", {},
["get", "name_en"], { "font-scale": 0.8, "text-font": ["literal", [ "DIN Offc Pro Italic", "Arial Unicode MS Regular" ]] }
],
"text-letter-spacing": 0.01
},
Expand Down Expand Up @@ -3699,10 +3699,10 @@
},
"text-offset": { "base": 1, "stops": [ [ 7.99, [ 0, 0.15 ] ], [ 8, [ 0, 0 ] ] ] },
"text-anchor": { "base": 1, "stops": [ [ 7, "top" ], [ 8, "center" ] ] },
"text-field": ["concat",
["formatted", ["get", "name_en"], { "font-scale": 1.2 }],
["formatted", "\n", {}],
["formatted", ["get", "name"], { "font-scale": 0.8, "text-font": ["literal", [ "DIN Offc Pro Italic", "Arial Unicode MS Regular" ]] }]
"text-field": ["format",
["get", "name"], { "font-scale": 1.2 },
"\n", {},
["get", "name_en"], { "font-scale": 0.8, "text-font": ["literal", [ "DIN Offc Pro Italic", "Arial Unicode MS Regular" ]] }
],
"text-max-width": 7,
"text-size": { "base": 0.9, "stops": [ [ 4, 12 ], [ 10, 32 ] ] }
Expand Down
7 changes: 4 additions & 3 deletions docs/components/expression-metadata.js
Expand Up @@ -135,11 +135,12 @@ const types = {
type: 'collator',
parameters: [ '{ "case-sensitive": boolean, "diacritic-sensitive": boolean, "locale": string }' ]
}],
formatted: [{
format: [{
type: 'formatted',
parameters: [
'string',
'{ "font-scale": number, "text-font": array<string> }'
'input_1: string, options_1: { "font-scale": number, "text-font": array<string> }',
'...',
'input_n: string, options_n: { "font-scale": number, "text-font": array<string> }'
]
}]
};
Expand Down
16 changes: 16 additions & 0 deletions docs/pages/style-spec.js
Expand Up @@ -159,6 +159,9 @@ const navigation = [
{
"title": "String"
},
{
"title": "Formatted"
},
{
"title": "Boolean"
},
Expand Down Expand Up @@ -948,6 +951,19 @@ export default class extends React.Component {
<p>Especially of note is the support for hsl, which can be <a href='http://mothereffinghsl.com/'>easier to reason about than rgb()</a>.</p>
</div>

<div className='pad2 keyline-bottom'>
<a id='types-formatted' className='anchor'/>
<h3 className='space-bottom1'><a href='#types-formatted' title='link to formatted'>Formatted</a></h3>
<p>The <code>formatted</code> type represents a string broken into sections annotated with separate formatting options.</p>
{highlightJSON(`
{
"text-field": ["format",
"foo", { "font-scale": 1.2 },
"bar", { "font-scale": 0.8 }
]
}`)}
</div>

<div className='pad2 keyline-bottom'>
<a id='types-string' className='anchor'/>
<h3 className='space-bottom1'><a href='#types-string' title='link to string'>String</a></h3>
Expand Down
4 changes: 3 additions & 1 deletion flow-typed/style-spec.js
Expand Up @@ -2,6 +2,8 @@

declare type ColorSpecification = string;

declare type FormattedSpecification = string;

declare type FilterSpecification =
| ['has', string]
| ['!has', string]
Expand Down Expand Up @@ -223,7 +225,7 @@ declare type SymbolLayerSpecification = {|
"icon-pitch-alignment"?: PropertyValueSpecification<"map" | "viewport" | "auto">,
"text-pitch-alignment"?: PropertyValueSpecification<"map" | "viewport" | "auto">,
"text-rotation-alignment"?: PropertyValueSpecification<"map" | "viewport" | "auto">,
"text-field"?: DataDrivenPropertyValueSpecification<string>,
"text-field"?: DataDrivenPropertyValueSpecification<FormattedSpecification>,
"text-font"?: DataDrivenPropertyValueSpecification<Array<string>>,
"text-size"?: DataDrivenPropertyValueSpecification<number>,
"text-max-width"?: DataDrivenPropertyValueSpecification<number>,
Expand Down
2 changes: 1 addition & 1 deletion src/data/bucket/symbol_bucket.js
Expand Up @@ -349,7 +349,7 @@ class SymbolBucket implements Bucket {
const textField = layout.get('text-field');
const iconImage = layout.get('icon-image');
const hasText =
(textField.value.kind !== 'constant' || textField.value.value.length > 0) &&
(textField.value.kind !== 'constant' || textField.value.value.toString().length > 0) &&
(textFont.value.kind !== 'constant' || textFont.value.value.length > 0);
const hasIcon = iconImage.value.kind !== 'constant' || iconImage.value.value && iconImage.value.value.length > 0;

Expand Down
137 changes: 77 additions & 60 deletions src/style-spec/expression/definitions/formatted.js
Expand Up @@ -18,13 +18,6 @@ export class FormattedSection {
this.scale = scale;
this.fontStack = fontStack;
}

serialize() {
const fontStack = this.fontStack ?
["literal", this.fontStack.split(',')] :
null;
return ["formatted", this.text, { "text-font": fontStack, "font-scale": this.scale }];
}
}

export class Formatted {
Expand All @@ -35,77 +28,96 @@ export class Formatted {
}

toString(): string {
return this.sections.map(section => section.text).join();
return this.sections.map(section => section.text).join('');
}

serialize() {
if (this.sections.length === 1) {
return this.sections[0].serialize();
} else {
return ["concat"].concat(this.sections.map(section => section.serialize()));
const serialized = ["format"];
for (const section of this.sections) {
serialized.push(section.text);
const fontStack = section.fontStack ?
["literal", section.fontStack.split(',')] :
null;
serialized.push({ "text-font": fontStack, "font-scale": section.scale });
}
return serialized;
}
}

export class FormattedExpression implements Expression {
type: Type;
text: Expression;
type FormattedSectionExpression = {
text: Expression,
scale: Expression | null;
font: Expression | null;
}

export class FormattedExpression implements Expression {
type: Type;
sections: Array<FormattedSectionExpression>;

constructor(text: Expression, scale: Expression | null, font: Expression | null) {
constructor(sections: Array<FormattedSectionExpression>) {
this.type = FormattedType;
this.text = text;
this.scale = scale;
this.font = font;
this.sections = sections;
}

static parse(args: Array<mixed>, context: ParsingContext): ?Expression {
if (args.length !== 3)
return context.error(`Expected two arguments.`);

const text = context.parse(args[1], 1, ValueType);
if (!text) return null;
const kind = text.type.kind;
if (kind !== 'string' && kind !== 'value' && kind !== 'null')
return context.error(`Formatted text type must be 'string', 'value', or 'null'.`);

const options = (args[2]: any);
if (typeof options !== "object" || Array.isArray(options))
return context.error(`Format options argument must be an object.`);

let scale = null;
if (options['font-scale']) {
scale = context.parse(options['font-scale'], 1, NumberType);
if (!scale) return null;
if (args.length < 3) {
return context.error(`Expected at least two arguments.`);
}

let font = null;
if (options['text-font']) {
font = context.parse(options['text-font'], 1, ValueType); // Require array of strings?
if (!font) return null;
if ((args.length - 1) % 2 !== 0) {
return context.error(`Expected an even number of arguments.`);
}

return new FormattedExpression(text, scale, font);
const sections: Array<FormattedSectionExpression> = [];
for (let i = 1; i < args.length - 1; i += 2) {
const text = context.parse(args[i], 1, ValueType);
if (!text) return null;
const kind = text.type.kind;
if (kind !== 'string' && kind !== 'value' && kind !== 'null')
return context.error(`Formatted text type must be 'string', 'value', or 'null'.`);

const options = (args[i + 1]: any);
if (typeof options !== "object" || Array.isArray(options))
return context.error(`Format options argument must be an object.`);

let scale = null;
if (options['font-scale']) {
scale = context.parse(options['font-scale'], 1, NumberType);
if (!scale) return null;
}

let font = null;
if (options['text-font']) {
font = context.parse(options['text-font'], 1, ValueType); // Require array of strings?
if (!font) return null;
}
sections.push({text, scale, font});
}

return new FormattedExpression(sections);
}

evaluate(ctx: EvaluationContext) {
return new Formatted([
new FormattedSection(
this.text.evaluate(ctx) || "",
this.scale ? this.scale.evaluate(ctx) : null,
this.font ? this.font.evaluate(ctx).join(',') : null
return new Formatted(
this.sections.map(section =>
new FormattedSection(
section.text.evaluate(ctx) || "",
section.scale ? section.scale.evaluate(ctx) : null,
section.font ? section.font.evaluate(ctx).join(',') : null
)
)
]);
);
}

eachChild(fn: (Expression) => void) {
fn(this.text);
if (this.scale) {
fn(this.scale);
}
if (this.font) {
fn(this.font);
for (const section of this.sections) {
fn(section.text);
if (section.scale) {
fn(section.scale);
}
if (section.font) {
fn(section.font);
}
}
}

Expand All @@ -116,13 +128,18 @@ export class FormattedExpression implements Expression {
}

serialize() {
const options = {};
if (this.scale) {
options['font-scale'] = this.scale.serialize();
}
if (this.font) {
options['text-font'] = this.font.serialize();
const serialized = ["format"];
for (const section of this.sections) {
serialized.push(section.text.serialize());
const options = {};
if (section.scale) {
options['font-scale'] = section.scale.serialize();
}
if (section.font) {
options['text-font'] = section.font.serialize();
}
serialized.push(options);
}
return ["formatted", this.text.serialize(), options];
return serialized;
}
}

0 comments on commit 9a2c511

Please sign in to comment.