Skip to content

Commit

Permalink
February 2023 Release of the APL 2023.1 compliant APL Viewhost Web
Browse files Browse the repository at this point in the history
For more details on this release refer to CHANGELOG.md

To learn about APL see: https://developer.amazon.com/docs/alexa-presentation-language/understand-apl.html
  • Loading branch information
amzn-admfox committed Feb 7, 2023
1 parent 160ee98 commit 395ca65
Show file tree
Hide file tree
Showing 34 changed files with 989 additions and 210 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Changelog for apl-viewhost-web

## [2023.1]
This release adds support for version 2023.1 of the APL specification. Please also see APL Core Library for changes: [apl-core-library CHANGELOG](https://github.com/alexa/apl-core-library/blob/master/CHANGELOG.md)

### Added
- SRT support for APL Video textTrack
- Support for new Porter-Duff blend modes

### Changed
- Bug fixes

## [2022.2]
This release adds support for version 2022.2 of the APL specification. Please also see APL Core Library for changes: [apl-core-library CHANGELOG](https://github.com/alexa/apl-core-library/blob/master/CHANGELOG.md)

Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Alexa Presentation Language (APL) Viewhost Web

<p>
<a href="https://github.com/alexa/apl-viewhost-web/tree/v2022.2.0" alt="version">
<img src="https://img.shields.io/badge/stable%20version-2022.2.0-brightgreen" /></a>
<a href="https://github.com/alexa/apl-core-library/tree/v2022.2.0" alt="APLCore">
<img src="https://img.shields.io/badge/apl%20core%20library-2022.2.0-navy" /></a>
<a href="https://github.com/alexa/apl-viewhost-web/tree/v2023.1.0" alt="version">
<img src="https://img.shields.io/badge/stable%20version-2023.1.0-brightgreen" /></a>
<a href="https://github.com/alexa/apl-core-library/tree/v2023.1.0" alt="APLCore">
<img src="https://img.shields.io/badge/apl%20core%20library-2023.1.0-navy" /></a>
</p>

## Introduction
Expand All @@ -16,7 +16,7 @@ platform or framework for which the view host was designed by leveraging the fun

### Prerequisites

* [NodeJS](https://nodejs.org/en/) - version 10.x or higher
* [NodeJS](https://nodejs.org/en/) - version 16.x or higher
* [cmake](https://cmake.org/install/) - the easiest way to install on Mac is using `brew install cmake`
* [Yarn](https://yarnpkg.com/getting-started/install)

Expand Down
5 changes: 4 additions & 1 deletion js/apl-html/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "apl-html",
"name": "@amzn/apl-html",
"version": "1.0.0",
"license": "SEE LICENSE IN LICENSE.txt",
"main": "lib/index.js",
Expand Down Expand Up @@ -52,5 +52,8 @@
"webpack-cli": "^3.3.12",
"webpack-merge": "^4.2.1",
"xregexp": "4.2.4"
},
"npm-pretty-much": {
"legacyPackageNameAlias": "apl-html"
}
}
83 changes: 83 additions & 0 deletions js/apl-html/src/CommandFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*!
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

import APLRenderer from './APLRenderer';
import { EventType } from './enums/EventType';
import { DataSourceFetchRequest } from './events/DataSourceFetchRequest';
import { ExtensionEvent } from './events/ExtensionEvent';
import { Finish } from './events/Finish';
import { Focus } from './events/Focus';
import { LineHighlight } from './events/LineHighlight';
import { MediaRequest } from './events/MediaRequest';
import { OpenUrl } from './events/OpenUrl';
import { ReInflate} from './events/ReInflate';
import { RequestLineBounds } from './events/RequestLineBounds';
import { SendEvent } from './events/SendEvent';

/**
* Creates and executes a command
* @param event The core engine event
* @param renderer A reference to the renderer instance
* @internal
*/
export const commandFactory = (event: APL.Event, renderer: APLRenderer) => {
if (factoryMap[event.getType()]) {
return factoryMap[event.getType()](event, renderer);
}
throw new Error(`Cannot create command with type ${event.getType()}`);
};

const factoryMap = {
[EventType.kEventTypeSendEvent]: (event: APL.Event, renderer: APLRenderer) => {
const command = new SendEvent(event, renderer);
command.execute();
return command;
},
[EventType.kEventTypeRequestLineBounds]: (event: APL.Event, renderer: APLRenderer) => {
const command = new RequestLineBounds(event, renderer);
command.execute();
return command;
},
[EventType.kEventTypeLineHighlight]: (event: APL.Event, renderer: APLRenderer) => {
const command = new LineHighlight(event, renderer);
command.execute();
return command;
},
[EventType.kEventTypeReinflate]: (event: APL.Event, renderer: APLRenderer) => {
const command = new ReInflate(event, renderer);
command.execute();
return command;
},
[EventType.kEventTypeFinish]: (event: APL.Event, renderer: APLRenderer) => {
const command = new Finish(event, renderer);
command.execute();
return command;
},
[EventType.kEventTypeFocus]: (event: APL.Event, renderer: APLRenderer) => {
const command = new Focus(event, renderer);
command.execute();
return command;
},
[EventType.kEventTypeOpenURL]: (event: APL.Event, renderer: APLRenderer) => {
const command = new OpenUrl(event, renderer);
command.execute();
return command;
},
[EventType.kEventTypeDataSourceFetchRequest]: (event: APL.Event, renderer: APLRenderer) => {
const command = new DataSourceFetchRequest(event, renderer);
command.execute();
return command;
},
[EventType.kEventTypeExtension]: (event: APL.Event, renderer: APLRenderer) => {
const command = new ExtensionEvent(event, renderer);
command.execute();
return command;
},
[EventType.kEventTypeMediaRequest]: (event: APL.Event, renderer: APLRenderer) => {
const command = new MediaRequest(event, renderer);
command.execute();
return command;
}
};
39 changes: 22 additions & 17 deletions js/apl-html/src/ComponentFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ export const componentFactory = (renderer: APLRenderer, component: APL.Component
parent?: Component, ensureLayout: boolean = false,
insertAt: number = -1 ): Component<IGenericPropType> => {
const id = component.getUniqueId();
let comp;
if (renderer.componentMap[id]) {
const comp = renderer.componentMap[id];
comp = renderer.componentMap[id];
comp.parent = parent;
if (ensureLayout && comp instanceof Text) {
comp.setDimensions();
Expand All @@ -40,25 +41,29 @@ export const componentFactory = (renderer: APLRenderer, component: APL.Component
}
return comp;
} else if (factoryMap[component.getType()]) {
const item = factoryMap[component.getType()](renderer, component, parent);
if (ensureLayout) {
if (item instanceof Text) {
item.init();
}
item.component.ensureLayout();
item.init();
if (parent) {
if (insertAt >= 0 && parent.container.children.length > 0 &&
insertAt < parent.container.children.length) {
parent.container.insertBefore(item.container, parent.container.children.item(insertAt));
} else {
parent.container.appendChild(item.container);
}
comp = factoryMap[component.getType()](renderer, component, parent);
} else {
// Any unknown component is effectively container
comp = new Container(renderer, component, componentFactory, parent);
}

if (ensureLayout) {
if (comp instanceof Text) {
comp.init();
}
comp.component.ensureLayout();
comp.init();
if (parent) {
if (insertAt >= 0 && parent.container.children.length > 0 &&
insertAt < parent.container.children.length) {
parent.container.insertBefore(comp.container, parent.container.children.item(insertAt));
} else {
parent.container.appendChild(comp.container);
}
}
return item;
}
throw new Error(`Cannot create component with type ${component.getType()}`);

return comp;
};

// tslint:disable:max-line-length
Expand Down
2 changes: 1 addition & 1 deletion js/apl-html/src/components/Component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -741,7 +741,7 @@ export abstract class Component<PropsType = IGenericPropType> extends EventEmitt
const isLegacyComponentType: boolean = LEGACY_CLIPPING_COMPONENTS_SET.has(componentType);
const isLegacyAplVersion: boolean = this.renderer && this.renderer.getLegacyClippingEnabled();

if (isLegacyComponentType || isParentLegacy || !isLegacyAplVersion) {
if (!this.parent || isLegacyComponentType || isParentLegacy || !isLegacyAplVersion) {
this.enableClipping();
}
}
Expand Down
56 changes: 36 additions & 20 deletions js/apl-html/src/components/filters/Blend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,21 @@ export interface IBlend extends IBaseFilter {
* https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feBlend
*/

enum BlendType {
Blend = 'feBlend',
Composite = 'feComposite'
}

export function getBlendFilter(filter: Filter, imageSrcArray: string[]): IImageFilterElement | undefined {
const blendId: string = uuidv4().toString();
let filterImageArray: SVGFEImageElement[] = [];
const blend: SVGElement = document.createElementNS(SVG_NS, 'feBlend');
blend.setAttributeNS('', 'mode', getBlendMode((filter as IBlend).mode));
const [elementType, operator] = getBlendMode((filter as IBlend).mode);
const blend: SVGElement = document.createElementNS(SVG_NS, elementType);
if (elementType === BlendType.Composite) {
blend.setAttributeNS('', 'operator', operator);
} else {
blend.setAttributeNS('', 'mode', operator);
}
blend.setAttributeNS('', 'result', blendId);

/*
Expand Down Expand Up @@ -78,41 +88,47 @@ export function getBlendFilter(filter: Filter, imageSrcArray: string[]): IImageF
* Return Blend Mode
* https://codepen.io/yoksel/pen/BiExv
*/
export function getBlendMode(mode: BlendMode): string {
function getBlendMode(mode: BlendMode): [BlendType, string] {
switch (mode) {
case BlendMode.kBlendModeNormal:
return 'normal';
return [BlendType.Blend, 'normal'];
case BlendMode.kBlendModeMultiply:
return 'multiply';
return [BlendType.Blend, 'multiply'];
case BlendMode.kBlendModeScreen:
return 'screen';
return [BlendType.Blend, 'screen'];
case BlendMode.kBlendModeOverlay:
return 'overlay';
return [BlendType.Blend, 'overlay'];
case BlendMode.kBlendModeDarken:
return 'darken';
return [BlendType.Blend, 'darken'];
case BlendMode.kBlendModeLighten:
return 'lighten';
return [BlendType.Blend, 'lighten'];
case BlendMode.kBlendModeColorDodge:
return 'color-dodge';
return [BlendType.Blend, 'color-dodge'];
case BlendMode.kBlendModeColorBurn:
return 'color-burn';
return [BlendType.Blend, 'color-burn'];
case BlendMode.kBlendModeHardLight:
return 'hard-light';
return [BlendType.Blend, 'hard-light'];
case BlendMode.kBlendModeSoftLight:
return 'soft-light';
return [BlendType.Blend, 'soft-light'];
case BlendMode.kBlendModeDifference:
return 'difference';
return [BlendType.Blend, 'difference'];
case BlendMode.kBlendModeExclusion:
return 'exclusion';
return [BlendType.Blend, 'exclusion'];
case BlendMode.kBlendModeHue:
return 'hue';
return [BlendType.Blend, 'hue'];
case BlendMode.kBlendModeSaturation:
return 'saturation';
return [BlendType.Blend, 'saturation'];
case BlendMode.kBlendModeColor:
return 'color';
return [BlendType.Blend, 'color'];
case BlendMode.kBlendModeLuminosity:
return 'luminosity';
return [BlendType.Blend, 'luminosity'];
case BlendMode.kBlendModeSourceIn:
return [BlendType.Composite, 'in'];
case BlendMode.kBlendModeSourceAtop:
return [BlendType.Composite, 'atop'];
case BlendMode.kBlendModeSourceOut:
return [BlendType.Composite, 'out'];
default:
return 'normal';
return [BlendType.Blend, 'normal'];
}
}
52 changes: 52 additions & 0 deletions js/apl-html/src/components/filters/Blur.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict';

import { Filter, generateSVGFeImage, isIndexOutOfBound } from '../../utils/FilterUtils';
import { SVG_NS, uuidv4 } from '../Component';
import { BITMAP_IMAGE_REGEX_CHECK, IBaseFilter, IImageFilterElement } from './ImageFilter';

/**
* @ignore
*/
export interface IBlur extends IBaseFilter {
radius: number;
source: number;
}

/*
* Apply a Gaussian blur with a specified radius. The new image is appended to the end of the array.
* Specs: https://developer.amazon.com/en-US/docs/alexa/alexa-presentation-language/apl-filters.html#blur
* Utilize svg <feGaussianBlur> filter
* https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feGaussianBlur
*/
export function getBlurFilter(filter: Filter, imageSrcArray: string[]): IImageFilterElement | undefined {
const blurId: string = uuidv4().toString();
let filterImageArray: SVGFEImageElement[] = [];
const blur: SVGElement = document.createElementNS(SVG_NS, 'feGaussianBlur');
blur.setAttributeNS('', 'stdDeviation', (filter as IBlur).radius.toString());
blur.setAttributeNS('', 'result', blurId);
/*
* All filters that operate on a single image have a default image source property of -1;
* that is, by default they take as input the last image in the image array.
*/
let index: number = (filter as IBlur).source;

// Negative case : index outside source array bounds. return undefined
if (isIndexOutOfBound(index, imageSrcArray.length)) {
return undefined;
}
if (index < 0) {
index += imageSrcArray.length;
}
const imageId: string = imageSrcArray[index];
if (imageId.match(BITMAP_IMAGE_REGEX_CHECK)) {
filterImageArray = generateSVGFeImage(imageId, blur);
} else {
blur.setAttributeNS('', 'in', imageId);
}
return { filterId: blurId, filterElement: blur, filterImageArray };
}
Loading

0 comments on commit 395ca65

Please sign in to comment.