Code Monkey home page Code Monkey logo

colr-gradients-spec's Introduction

Gradients for COLR/CPAL Fonts

December 2019

Authors:

Table of Contents

Introduction

We propose an extension of the COLR table to support greater graphic capabilities. The current version number of COLR table is 0. We propose this as COLR table format version 1.

The initial design proposal aimed to add support for gradient fills, in addition to the existing solid color fills, and to integrate variation into color formats in variable fonts. As the design work progressed, it seemed appropriate to introduce other capabilities:

  • Different composition modes
  • Affine transformations
  • Flexible means of re-use of components to provide size reduction

While extending the design for these additional capabilities, it also seemed appropriate to change the basic structure for color glyph descriptions from a vector of layered elements to be a directed, acyclic graph.

It is our understanding that this brings the capabilities of COLR/CPAL to nearly match those of SVG Native for vector graphics. SVG Native allows embedding PNG and JPEG images while this proposal does not. We'd like to explore in the future, how COLR/CPAL can be mixed with sbix to address that limitation as well.

The aim is for version 1 of the COLR table to be incorporated into a future version of the OpenType spec and into an update of the ISO/IEC 14496-22 Open Font Format (OFF) standard.

Earlier revisions of this document provided many details regarding the proposed COLR extensions. These have since been moved and elaborated with full details in a separate document for submission to OFF—see the next section.

Extending ISO/IEC 14496-22 Open Font Format

The COLR version 1 enhancements have been proposed for incorporation into the ISO/IEC 14496-22 Open Font Format standard. In ISO process, this would be done as an amendment (Amendment 2) of the current edition of that standard. Content for a working draft of that amendment has been prepared in a separate doc: Proposed changes to ISO/IEC 14496-22 (Amendment 2).

Implementation

This section is NOT meant for ISO submissions

Font Tooling

Cosimo (@anthrotype) and Rod (@rsheeter) have implemented nanoemoji to compile a set of SVGs into color font formats, including COLR v1.

color-fonts has a collection of sample color fonts.

Chromium, Skia, Freetype support

Chrome Canary as of version 90.0.4421.5 and above supports COLRv1 fonts when switching on the COLR v1 flag. Chrome 98+ ship COLRv1 enabled by default, see https://developer.chrome.com/blog/colrv1-fonts/.

To enable the feature and experiment with it, follow these steps:

  1. Download and open Chrome Canary, make sure it's updated to a version equal or newer than 90.0.4421.5.
  2. Go to chrome://flags/#colr-v1-fonts and enable the feature.

Screenshot of Chrome flag settings page.

Skia support is in tip-of-tree Skia, implementation details

FreeType support is in tip-of-tree FreeType, see freetype.h for API details.

C++ Structures

The following provides a C++ implementation of the structures defined above.

// Base template types

template <typename T, typename Length=uint16>
struct ArrayOf
{
  Length count;
  T      array[/*count*/];
};

// Index of the first variation record in the variation index map or
// variation store if no variation index map is present.
// A record with N variable fields finds them at VarIdxBase+0, ...,+N-1
// Use 0xFFFFFFFF to indicate no variation.
typedef uint32 VarIdxBase;

// Color structures

// The (Var)ColorStop alpha is multiplied into the alpha of the CPAL entry
// (converted to float -- divide by 255) looked up using paletteIndex to
// produce a final alpha.
struct ColorStop
{
  F2DOT14    stopOffset;
  uint16     paletteIndex;
  F2DOT14    alpha; // Default 1.0. Values outside [0.,1.] reserved.
};

struct VarColorStop
{
  F2DOT14    stopOffset; // VarIdx varIndexBase + 0
  uint16     paletteIndex;
  F2DOT14    alpha; // Default 1.0. Values outside [0.,1.] reserved.
                    // VarIdx varIndexBase + 1
  VarIdxBase varIndexBase;
};

enum Extend : uint8
{
  EXTEND_PAD     = 0,
  EXTEND_REPEAT  = 1,
  EXTEND_REFLECT = 2,
};

struct ColorLine
{
  Extend             extend;
  ArrayOf<ColorStop> colorStops;
};

struct VarColorLine
{
  Extend             extend;
  ArrayOf<VarColorStop> colorStops;
};


// Composition modes

// Compositing modes are taken from https://www.w3.org/TR/compositing-1/
// NOTE: a brief audit of major implementations suggests most support most
// or all of the specified modes.
enum CompositeMode : uint8
{
  // Porter-Duff modes
  // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators
  COMPOSITE_CLEAR          =  0,  // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_clear
  COMPOSITE_SRC            =  1,  // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_src
  COMPOSITE_DEST           =  2,  // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_dst
  COMPOSITE_SRC_OVER       =  3,  // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_srcover
  COMPOSITE_DEST_OVER      =  4,  // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_dstover
  COMPOSITE_SRC_IN         =  5,  // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_srcin
  COMPOSITE_DEST_IN        =  6,  // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_dstin
  COMPOSITE_SRC_OUT        =  7,  // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_srcout
  COMPOSITE_DEST_OUT       =  8,  // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_dstout
  COMPOSITE_SRC_ATOP       =  9,  // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_srcatop
  COMPOSITE_DEST_ATOP      = 10,  // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_dstatop
  COMPOSITE_XOR            = 11,  // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_xor
  COMPOSITE_PLUS           = 12,  // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_plus

  // Blend modes
  // https://www.w3.org/TR/compositing-1/#blending
  COMPOSITE_SCREEN         = 13,  // https://www.w3.org/TR/compositing-1/#blendingscreen
  COMPOSITE_OVERLAY        = 14,  // https://www.w3.org/TR/compositing-1/#blendingoverlay
  COMPOSITE_DARKEN         = 15,  // https://www.w3.org/TR/compositing-1/#blendingdarken
  COMPOSITE_LIGHTEN        = 16,  // https://www.w3.org/TR/compositing-1/#blendinglighten
  COMPOSITE_COLOR_DODGE    = 17,  // https://www.w3.org/TR/compositing-1/#blendingcolordodge
  COMPOSITE_COLOR_BURN     = 18,  // https://www.w3.org/TR/compositing-1/#blendingcolorburn
  COMPOSITE_HARD_LIGHT     = 19,  // https://www.w3.org/TR/compositing-1/#blendinghardlight
  COMPOSITE_SOFT_LIGHT     = 20,  // https://www.w3.org/TR/compositing-1/#blendingsoftlight
  COMPOSITE_DIFFERENCE     = 21,  // https://www.w3.org/TR/compositing-1/#blendingdifference
  COMPOSITE_EXCLUSION      = 22,  // https://www.w3.org/TR/compositing-1/#blendingexclusion
  COMPOSITE_MULTIPLY       = 23,  // https://www.w3.org/TR/compositing-1/#blendingmultiply

  // Modes that, uniquely, do not operate on components
  // https://www.w3.org/TR/compositing-1/#blendingnonseparable
  COMPOSITE_HSL_HUE        = 24,  // https://www.w3.org/TR/compositing-1/#blendinghue
  COMPOSITE_HSL_SATURATION = 25,  // https://www.w3.org/TR/compositing-1/#blendingsaturation
  COMPOSITE_HSL_COLOR      = 26,  // https://www.w3.org/TR/compositing-1/#blendingcolor
  COMPOSITE_HSL_LUMINOSITY = 27,  // https://www.w3.org/TR/compositing-1/#blendingluminosity
};

// Affine 2D transformations

// This is a standard 2x3 matrix for 2D affine transformation.
struct Affine2x3
{
  Fixed xx;
  Fixed yx;
  Fixed xy;
  Fixed yy;
  Fixed dx;
  Fixed dy;
};

struct VarAffine2x3
{
  Fixed xx;  // VarIdx varIndexBase + 0
  Fixed yx;  // VarIdx varIndexBase + 1
  Fixed xy;  // VarIdx varIndexBase + 2
  Fixed yy;  // VarIdx varIndexBase + 3
  Fixed dx;  // VarIdx varIndexBase + 4
  Fixed dy;  // VarIdx varIndexBase + 5
  VarIdxBase varIndexBase;
};

// Paint tables

// Each layer is composited on top of previous with mode COMPOSITE_SRC_OVER.
// NOTE: uint8 size saves bytes in most cases and does not
// preclude use of large layer counts via PaintComposite or a tree
// of PaintColrLayers.
struct PaintColrLayers
{
  uint8               format; // = 1
  uint8               numLayers;
  uint32              firstLayerIndex;  // index into COLRv1::layerList
}

// The Paint(Var)Solid alpha is multiplied into the alpha of the CPAL entry
// (converted to float -- divide by 255) looked up using paletteIndex to
// produce a final alpha.
struct PaintSolid
{
  uint8      format; // = 2
  uint16     paletteIndex;
  F2DOT14    alpha; // Default 1.0. Values outside [0.,1.] reserved.
};

struct PaintVarSolid
{
  uint8      format; // = 3
  uint16     paletteIndex;
  F2DOT14    alpha; // Default 1.0. Values outside [0.,1.] reserved.
                    // VarIdx varIndexBase + 0
  VarIdxBase varIndexBase;
};

struct PaintLinearGradient
{
  uint8                  format; // = 4
  Offset24<ColorLine>    colorLine;
  FWORD                  x0;
  FWORD                  y0;
  FWORD                  x1;
  FWORD                  y1;
  FWORD                  x2; // Normal; Equal to (x1,y1) in simple cases.
  FWORD                  y2;
};

struct PaintVarLinearGradient
{
  uint8                  format; // = 5
  Offset24<VarColorLine> colorLine;
  FWORD                  x0; // VarIdx varIndexBase + 0
  FWORD                  y0; // VarIdx varIndexBase + 1
  FWORD                  x1; // VarIdx varIndexBase + 2
  FWORD                  y1; // VarIdx varIndexBase + 3
  FWORD                  x2; // VarIdx varIndexBase + 4. Normal; Equal to (x1,y1) in simple cases.
  FWORD                  y2; // VarIdx varIndexBase + 5
  VarIdxBase             varIndexBase;
};

struct PaintRadialGradient
{
  uint8                  format; // = 6
  Offset24<ColorLine>    colorLine;
  FWORD                  x0;
  FWORD                  y0;
  UFWORD                 radius0;
  FWORD                  x1;
  FWORD                  y1;
  UFWORD                 radius1;
};

struct PaintVarRadialGradient
{
  uint8                  format; // = 7
  Offset24<VarColorLine> colorLine;
  FWORD                  x0; // VarIdx varIndexBase + 0
  FWORD                  y0; // VarIdx varIndexBase + 1
  UFWORD                 radius0; // VarIdx varIndexBase + 2
  FWORD                  x1; // VarIdx varIndexBase + 3
  FWORD                  y1; // VarIdx varIndexBase + 4
  UFWORD                 radius1; // VarIdx varIndexBase + 5
  VarIdxBase             varIndexBase;
};

struct PaintSweepGradient
{
  uint8                  format; // = 8
  Offset24<ColorLine>    colorLine;
  FWORD                  centerX;
  FWORD                  centerY;
  F2DOT14                startAngle; // 180° in counter-clockwise degrees per 1.0 of value
  F2DOT14                endAngle;   // 180° in counter-clockwise degrees per 1.0 of value
};

struct PaintVarSweepGradient
{
  uint8                  format; // = 9
  Offset24<VarColorLine> colorLine;
  FWORD                  centerX; // VarIdx varIndexBase + 0
  FWORD                  centerY; // VarIdx varIndexBase + 1
  F2DOT14                startAngle; // VarIdx varIndexBase + 2. 180° in counter-clockwise degrees per 1.0 of value
  F2DOT14                endAngle; // VarIdx varIndexBase + 3. 180° in counter-clockwise degrees per 1.0 of value
  VarIdxBase             varIndexBase;
};

// Paint a non-COLR glyph, filled as indicated by paint.
struct PaintGlyph
{
  uint8                  format; // = 10
  Offset24<Paint>        paint;
  uint16                 glyphID;    // not a COLR-only gid
                                 // shall be less than maxp.numGlyphs
}

struct PaintColrGlyph
{
  uint8                  format; // = 11
  uint16                 glyphID;    // shall be a COLR gid
}

struct PaintTransform
{
  uint8                  format; // = 12
  Offset24<Paint>        paint;
  Offset24<Affine2x3>    transform;
};

struct PaintVarTransform
{
  uint8                  format; // = 13
  Offset24<Paint>        paint;
  Offset24<VarAffine2x3> transform;
};

struct PaintTranslate
{
  uint8                  format; // = 14
  Offset24<Paint>        paint;
  FWORD                  dx;
  FWORD                  dy;
};

struct PaintVarTranslate
{
  uint8                  format; // = 15
  Offset24<Paint>        paint;
  FWORD                  dx; // VarIdx varIndexBase + 0
  FWORD                  dy; // VarIdx varIndexBase + 1
  VarIdxBase             varIndexBase;
};

struct PaintScale
{
  uint8                  format; // = 16
  Offset24<Paint>        paint;
  F2DOT14                scaleX;
  F2DOT14                scaleY;
};

struct PaintVarScale
{
  uint8                  format; // = 17
  Offset24<Paint>        paint;
  F2DOT14                scaleX; // VarIdx varIndexBase + 0
  F2DOT14                scaleY; // VarIdx varIndexBase + 1
  VarIdxBase             varIndexBase;
};

struct PaintScaleAroundCenter
{
  uint8                  format; // = 18
  Offset24<Paint>        paint;
  F2DOT14                scaleX;
  F2DOT14                scaleY;
  FWORD                  centerX;
  FWORD                  centerY;
};

struct PaintVarScaleAroundCenter
{
  uint8                  format; // = 19
  Offset24<Paint>        paint;
  F2DOT14                scaleX; // VarIdx varIndexBase + 0
  F2DOT14                scaleY; // VarIdx varIndexBase + 1
  FWORD                  centerX; // VarIdx varIndexBase + 2
  FWORD                  centerY; // VarIdx varIndexBase + 3
  VarIdxBase             varIndexBase;
};

struct PaintScaleUniform
{
  uint8                  format; // = 20
  Offset24<Paint>        paint;
  F2DOT14                scale;
};

struct PaintVarScaleUniform
{
  uint8                  format; // = 21
  Offset24<Paint>        paint;
  F2DOT14                scale; // VarIdx varIndexBase + 0
  VarIdxBase             varIndexBase;
};

struct PaintScaleUniformAroundCenter
{
  uint8                  format; // = 22
  Offset24<Paint>        paint;
  F2DOT14                scale;
  FWORD                  centerX;
  FWORD                  centerY;
};

struct PaintVarScaleUniformAroundCenter
{
  uint8                  format; // = 23
  Offset24<Paint>        paint;
  F2DOT14                scale; // VarIdx varIndexBase + 0
  FWORD                  centerX; // VarIdx varIndexBase + 1
  FWORD                  centerY; // VarIdx varIndexBase + 2
  VarIdxBase             varIndexBase;
};

struct PaintRotate
{
  uint8                  format; // = 24
  Offset24<Paint>        paint;
  F2DOT14                angle; // 180° in counter-clockwise degrees per 1.0 of value
};

struct PaintVarRotate
{
  uint8                  format; // = 25
  Offset24<Paint>        paint;
  F2DOT14                angle; // VarIdx varIndexBase + 0. 180° in counter-clockwise degrees per 1.0 of value
  VarIdxBase             varIndexBase;
};

struct PaintRotateAroundCenter
{
  uint8                  format; // = 26
  Offset24<Paint>        paint;
  F2DOT14                angle; // 180° in counter-clockwise degrees per 1.0 of value
  FWORD                  centerX;
  FWORD                  centerY;
};

struct PaintVarRotateAroundCenter
{
  uint8                  format; // = 27
  Offset24<Paint>        paint;
  F2DOT14                angle; // VarIdx varIndexBase + 0. 180° in counter-clockwise degrees per 1.0 of value
  FWORD                  centerX; // VarIdx varIndexBase + 1
  FWORD                  centerY; // VarIdx varIndexBase + 2
  VarIdxBase             varIndexBase;
};

struct PaintSkew
{
  uint8                  format; // = 28
  Offset24<Paint>        paint;
  F2DOT14                xSkewAngle; // 180° in counter-clockwise degrees per 1.0 of value
  F2DOT14                ySkewAngle; // 180° in counter-clockwise degrees per 1.0 of value
};

struct PaintVarSkew
{
  uint8                  format; // = 29
  Offset24<Paint>        paint;
  F2DOT14                xSkewAngle; // VarIdx varIndexBase + 0. 180° in counter-clockwise degrees per 1.0 of value
  F2DOT14                ySkewAngle; // VarIdx varIndexBase + 1. 180° in counter-clockwise degrees per 1.0 of value
  VarIdxBase             varIndexBase;
};

struct PaintSkewAroundCenter
{
  uint8                  format; // = 30
  Offset24<Paint>        paint;
  F2DOT14                xSkewAngle; // 180° in counter-clockwise degrees per 1.0 of value
  F2DOT14                ySkewAngle; // 180° in counter-clockwise degrees per 1.0 of value
  FWORD                  centerX;
  FWORD                  centerY;
};

struct PaintVarSkewAroundCenter
{
  uint8                  format; // = 31
  Offset24<Paint>        paint;
  F2DOT14                xSkewAngle; // VarIdx varIndexBase + 0. 180° in counter-clockwise degrees per 1.0 of value
  F2DOT14                ySkewAngle; // VarIdx varIndexBase + 1. 180° in counter-clockwise degrees per 1.0 of value
  FWORD                  centerX; // VarIdx varIndexBase + 2
  FWORD                  centerY; // VarIdx varIndexBase + 3
  VarIdxBase             varIndexBase;
};

struct PaintComposite
{
  uint8                  format; // = 32
  Offset24<Paint>        sourcePaint;
  CompositeMode          compositeMode;   // If mode is unrecognized use COMPOSITE_CLEAR
  Offset24<Paint>        backdropPaint;
};

struct ClipBox {
  uint8                 format; // = 1
  FWORD                 xMin;
  FWORD                 yMin;
  FWORD                 xMax;
  FWORD                 yMax;
}

struct VarClipBox {
  uint8                 format; // = 2
  FWORD                 xMin; // VarIdx varIndexBase + 0.
  FWORD                 yMin; // VarIdx varIndexBase + 1.
  FWORD                 xMax; // VarIdx varIndexBase + 2.
  FWORD                 yMax; // VarIdx varIndexBase + 3.
  VarIdxBase            varIndexBase; // Set to 0 in non-variable fonts.
}

struct Clip {
  uint16                startGlyphID;  // first gid clip applies to
  uint16                endGlyphID;    // last gid clip applies to, inclusive
  Offset24<ClipBox>     clipBox;   // Box or VarBox
}

struct BaseGlyphPaintRecord
{
  uint16                glyphID;
  Offset32<Paint>       paint;  // Typically PaintColrLayers
};

// Entries shall be sorted in ascending order of the `glyphID` field of the `BaseGlyphPaintRecord`s.
typedef ArrayOf<BaseGlyphPaintRecord, uint32> BaseGlyphList;

// Only layers accessed via PaintColrLayers (format 1) need be encoded here.
typedef ArrayOf<Offset32<Paint>, uint32> LayerList;

struct ClipList
{
  uint8                 format;  // Set to 1.
  ArrayOf<Clip, uint32> clips;  // Clip records, sorted by startGlyphID
}

struct COLRv1
{
  // Version-0 fields
  uint16                                            version;
  uint16                                            numBaseGlyphRecords;
  Offset32<SortedUnsizedArrayOf<BaseGlyphRecord>>   baseGlyphRecords;
  Offset32<UnsizedArrayOf<LayerRecord>>             layerRecords;
  uint16                                            numLayerRecords;
  // Version-1 additions
  Offset32<BaseGlyphList>                           baseGlyphList;
  Offset32<LayerList>                               layerList;
  Offset32<ClipList>                                clipList;   // May be NULL
  Offset32<VarIdxMap>                               varIdxMap;  // May be NULL
  Offset32<ItemVariationStore>                      varStore;
};

Rendering

Pseudocode

Allocate a bitmap for the glyph, using the ClipBox for the glyph (if present) or
by traversing the paint graph to determine bounds.
0) Start at base glyph paint.
 a) Paint a paint, switch:
    1) PaintColrLayers
         Paint each referenced layer by performing a)
    2) PaintSolid
          SkCanvas::drawColor with color configured
    3) PaintLinearGradient
          SkCanvas::drawPaint with liner gradient configured
          (expected to be bounded by parent composite mode or clipped by current clip, check bounds?)
    4) PaintRadialGradient
          SkCanvas::drawPaint with radial gradient configured
          (expected to be bounded by parent composite mode or clipped by current clip, check bounds?)
    5) PaintGlyph
         gid must not COLRv1
         saveLayer
         setClipPath to gid path
           recurse to a)
         restore
    6) PaintColrGlyph
         gid must be from
         if gid on recursion blacklist, do nothing
         recurse to 0) with different gid
    7) PaintTransform
          saveLayer()
          apply transform
          call a) for paint
          restore
    8) PaintTranslate
          saveLayer()
          apply transform
          call a) for paint
          restore
    9) PaintScale
          saveLayer()
          apply transform
          call a) for paint
          restore
    10) PaintRotate
          saveLayer()
          apply transform
          call a) for paint
          restore
    11) PaintSkew
          saveLayer()
          apply transform
          call a) for paint
          restore
    12) PaintComposite
          saveLayer()
          paint Paint for backdrop, call a)
          saveLayer() with setting composite mode on SkPaint
          paint Paint for src, call a)
          restore with saved composite mode
          restore the outer layer

HarfBuzz

HarfBuzz implementation will follow later. No major client relies on HarfBuzz for color fonts currently, but we certainly want to implement later as there are clients who like to remove FreeType dependency completely.

References

Acknowledgements

Thanks to Benjamin Wagner (@bungeman), Dave Crossland (@davelab6), and Roderick Sheeter (@rsheeter) for review and detailed feedback on earlier proposal.

colr-gradients-spec's People

Contributors

anthrotype avatar davelab6 avatar drott avatar peterconstable avatar rsheeter avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

colr-gradients-spec's Issues

Clarify how things combine

@PeterConstable noted on #45:

But what about PaintGlyph > PaintTransformed > PaintComposite.src > PaintColrGlyph > ...?

I would like us to bias toward being permissive. I think the underlying issue is we need to add a bit more discussion around how things combine. My hope is we can define some simple guidelines.

Prepare initial ISO submission

  • Add ISO OFF references to the MD
    • Intent of this is to become able to create the docx for ISO as a direct copy of what is in MD
    • Refer to OFF (ISO/IEC 14496-22:2019 “OFF”)
    • COLR: refer to subclause 5.7.11 “COLR – Color Table”
    • CPAL: refer to subclause 5.7.12 “CPAL – Palette Table”
  • List all references for addition to the Bibliography section of the std
    • Probably most notably W3 Compositing
  • Explicitly mark any content that should NOT be included in OFF
  • Create said docx and upload to GH (work in the open!)
  • Submit to ISO

Improve note on bounding box

To simplify implementation allocation of a drawing surface the bounding box of the glyph corresponding to the BaseGlyphV1Record should be taken to describe the drawing area for the COLR v1 glyph.

Somehow incorporate that the "non-color / outside-of-this-table bounding box of the glyph..." is used.

Note: A glyf entry with two points at the diagonal extrema would suffice.

Unlike Apple's sbix, with this format, the non-color glyph is NOT an empty. It conveys the same concept of the glyph in simple bicolor outline.

So the note should specify that if the color drawing's bounds are beyond the non-color, then adding "a degenerate outline with two points placed at two diagonal corners of the desired bounding box would suffice".

Some systems might filter out outlines with just one point on them. That's why is better to have both points be part of the same outline than each on its own.

Multi-layer opacity

Compiling vector graphic sources, such as svgs, to COLR fonts will be significant to icon and emoji use cases (twemoji, noto emoji, and others come to mind). SVG allows application of opacity to multiple layers at once (by setting opacity on a group), meaning effectively render the group then apply opacity to the end result, as I understand it.

The current COLR spec doesn't allow for this. Building equivalent stacks of individually layers with opacity to match a group of layers with opacity appears tiresome. Also, when I tried to construct equivalent individual layers to a simple group opacity setup (courtesy of @anthrotype) by hand I immediately ran into halo effects (from anti-aliasing?).

Off the top of my head there are three main options

  1. Use some 2D graphics technique I'm unaware of to build equivalent single layers to match the group w/o introducing artifacts. Hooray, there is no actual problem :)
  2. Add the ability to designate groups of layers with opacity to COLR
  3. Accept that this is not supported by COLR; the set of things we can represent in COLR just got smaller

On extend mode

There are multiple issues and wrong wording and wrong images and inconsistency about how we wrote this. Dominik and I thought about it extensively (at least half a day at the whiteboard) and we did reach a good solution. It just seems like we failed writing that correctly.

I'm a bit busy right now but within a few days I will fully describe all the issues and make recommendation.

https://github.com/googlefonts/colr-gradients-spec#extend-mode

image

image

provide example of radial gradient with degenerate transform

Radial gradients can contain a 2x2 affine matrix to transform the two circles into ellipses, but without affecting the position of their centres -- unlike SVG's gradientTransform, or "graphics systems like Skia or Cairo that apply their (often called user-) transformation matrix to everything".
When rendering CORLv1 radial gradients using the latter graphics systems, one should thus apply the inverse matrix to the centres' coordinates to undo the effect of applying the matrix to everything.
But of course some matrices are singular or degenerate (with 0 determinant) and cannot be inverted.

The draft spec notes that even these have a "well defined shape", however it is not clear to me what this shape should look like. A visual example could help a lot here.

This is the relevant paragraph:

Note: Implementations must be careful to properly render radial gradient even if the provided affine matrix is degenerate or near-degenerate. Such radial gradients do have a well-defined shape, which is a strip or a cone filled with a linear gradient. Implementations using the inverse-transform approach noted above can fall back to a linear-gradient combined with a clipping path to achieve proper rendering of problematic affine transforms.

Offset16 => Offset32

It's going to cause all sorts of fun if our offsets cannot "reach" things. Make more offset16's into offset32's, as @behdad pointed out in #48 (comment).

I suggest upgrading all Offset16's to Offset32. I have changed my position on this since it was designed (last week?)

This is particularly concerning:

// Glyph root
// NOTE: uint8 size saves bytes in most cases and does not
// preclude use of large layer counts via PaintComposite.
typedef ArrayOf<Offset16<Paint>, uint8> LayerV1List;

"friendly names"

+1 to the friendly names.

The name PaintGlyph for format 6 doesn't clearly indicate what is distinctive about it vs. PaintColrGlyph.

The latter provides a color composition (an entire tree) for re-use. It's functionality has a lot of overlap with Transform (format 4); it's just that ColrGlyph uses a GID while the Transform uses a paint offset, and Transform includes a 2x3 while ColrGlyph doesn't. These are more similar than ColorGlyph is to Glyph.

PaintGlyph provides a geometry and fill, and it's the only way to put a fill into a geometry. But "PaintGlyph" doesn't really indicate that.

PaintColrGlyph reasonably indicates its functionality since it references a composition by the base glyph ID.

But perhaps "PaintGlyph" could be replaced with something like "PaintGlyphWithFill" or PaintOutlineWithFill?

Affine2x2 at offset or in sequence?

The Affine2x2 is record-like in having a fixed size (though there's not an array of them). This spec makes it table-like in being referenced at an offset from its parent. Any particular reason why it wasn't just embedded in sequence within its parent?

Base of offsets needs to be specified

Currently it's not. In GSUB/GPOS most offsets are from the beginning of whatever parent struct was referred to by an offset. I'll add followup comment with details of the problem. Filing for now so we don't forget.

Provide an annotated example of COLRv1 data with corresponding test font

I'm looking at the noto-handwriting-colr_1.ttf file here, and not making sense of the data I see in the COLR table.

offset field (type) data
Header
2d60 version (uint16) 0x0001
2d62 numBaseGlyphRecords (uint16) 0x0000
2d64 baseGlyphRecordsOffset (Offset32 0x0000_0000
2d68 layerRecordsOffset (Offset32) 0x0000_0000
2d6c numLayerRecords (uint16) 0x0000
2d6e baseGlyphsV1Offset (Offset32) 0x0000_0016
2d72 varStoreOffset (Offset32) 0x0000_0000
-- -- --
baseGlyphsV1Array
2d76 numBaseGlyphV1Records (uint32) 0x0000_0006
baseGlyphsV1Array[0]
2d7a gID (uint32) 0x0007_0000

That's not a valid glyph ID.

So, either I'm mis-interpreting the spec, or the data, or both; or else the data and the the spec don't match.

(I suspect what's happening is that the font was written assuming that GIDs are always uint16, including in BaseGlyphV1 record -- the pitfalls of changing from past conventions!)

Examples should show extended/shortened intervals, P2=P0

@behdad says in #57 "One thing not shown is color-lines where defined internal is NOT (0,1). Showing both intervals that are a subrange of that, as well as broader (eg. (-3,.5)) etc would be great as well."

&

@rsheeter suggested to show the simple case of P2=P0. I'll track that as an issue to update the examples further.

Add examples

Add examples for a full glyph, consisting of several layers, using a variety of gradients.

default composition mode

If a font as a format 5 paint with an invalid / unrecognized compositeMode value, what should the rendering engine do?

https://www.w3.org/TR/compositing-1/ defines a "normal" mode in which the source is overlaid on the destination with full opacity (B(Cb, Cs) = Cs). The CompositeMode enum doesn't include normal (and normally it wouldn't be needed).

In the unrecognized mode case, should the "normal" mode behaviour be used? Or multiply? Other?

Why format 6?

In the current proposal, format 6 can be used to reference a glyf outline and map it to a Paint table:

// Paint a non-COLR glyph, filled as indicated by paint.
struct PaintGlyph
{
uint8 format; // = 6
uint16 gid; // shall not be a COLR gid
Offset16 paint;
}

What does this enable that can't otherwise be implemented by creating a BaseGlyphV1 record using a virtual glyph ID, then having an associated LayerV1List with a single Paint? Is format 6 just a short hand that saves a glyph ID and a few bytes?

add flag field to layer record?

In #3 LP mentions the "white on black" issue. He notes a mechanism that's already defined in the current COLR spec:

"A palette entry index value of 0xFFFF is a special case indicating that the text foreground color (defined by a higher-level client) should be used and shall not be treated as actual index into CPAL ColorRecord array."

And so suggests that an additional special case be defined in COLR, 0xFFFE, to mean "punch a hole in all previous layers".

Another approach that could be considered would be to add a separate flags field in the layer record. The flags could be used to indicate different blending options with previous layers, including "punch all previous layers", or "punch previous layer". (When punching, the separate paletteIndex could indicate a fill for the punched area?)

Note: I haven't thought through in detail what potential benefits are, or what problem would remain if not provided. (E.g., wouldn't it work to just add another non-punch layer on top?) The original issue that brought this up had to do with winding interactions of overlapping contours within a single glyph, not any interaction between different glyphs. So, I'm just throwing this out for discussion.

PaintFormat4 transform: record or table offset?

In PaintTransformed, the transform is currently shown as an inline record, in contrast to format PaintRadialGradient in which the transform is a table at an offset.

I just want to confirm that's the intended design.

would Point-less be better?

In every 2D graphics spec I've looked at today (HTML canvas, PDF, Skia, Direct2D), I've noticed that linear, radial and conical gradients are specified using separate coordinates, "x0, y0, x1, y1" rather than combining xy pairs into structs. (GDI was different in that regard.)

Would it be better if, instead of defining a Point struct we just used a pair of VarFWords instead? (One less struct to document, and more similar to current 2D graphics APIs.)

Updates per WG3 breakout group

High level: it wasn't quite clear how the introductory content folds into OFF. Make that clearer. I think that means much of it needs to be landed in the COLR table.

The color table currently exists in 5.7 Optional tables. I think we should propose it moving up (5.x COLR) similar to how SVG is 5.5.

I believe the suggested order of content for COLR was:

  • intro section describing primitives
  • table structures / formats
  • examples 

We might want to emphasize the variable capabilities more.

How aligned with OFF do we want the proposal to be?

In #74 there was some discussion of where the Constraints section should live: under COLR table or at a higher level. Currently it is at OFF Changes > COLR table > Constraints, which is roughly where it appears it would land in OFF.

I think if we think about how closely we want to align with OFF the answer to where Constraints and some of the content in Introduction (Back compat, Graphical primitives) should land will become more clear. The advantage of trying to align very closely with OFF is it minimizes the need for an editor to restructure the content. The disadvantage is it may put the content into a layout we wouldn't otherwise choose.

I'm happy to move things around based on what we decide.

@drott wdyt?

should Affine2x3.dx/dy be integers instead of Fixed 16.16?

@Lorp noted on MPEGGroup/OpenFontFormat#20 (comment)

The table at line 170 defines an Affine2x3 matrix with two translation elements, dx and dy. It it intentional that these are VarFixed (16.16) rather than VarFWord (16-bit integer)? It seems to imply that a renderer needs to keep track of fractional point locations.

In glyf table the component x and y offsets are integers, just like the simple glyph point coordinates are. Also in this draft spec, the gradients' end points are encoded as integers (VarFWORD).
Allowing fractional point locations as result of PaintTransform would be inconsistent with that.

Clarify PaintFilledGlyph

It's clearly misleading; it clips not fills. PaintClipGlyph?

Explain nesting of clips == intersection

a radial gradient definition could be degenerate

I think normally the center of one circle (the gradient origin, stop position 0) would be within the circumference of the other circle (stop position 1). But the possibility exists that it is not: you could have two overlapping or even non-overlapping circles.

So, what should be expected if that were the case? Do we need to treat that as invalid in OT, or just let different graphics libraries handle that however they will? (That could mean a font displays differently on different platforms.)

Should a maximum depth for the DAG be specified?

Is there some limit to avoid circular definitions? Or does the layout engine just check to see if a Paint at a given offset occurs more than once between any root/leaf path?

The proposal has this:

Implementations should track the COLR gids they have seen in processing and fail if a gid is reached repeatedly.

But formats 4 and 5 reference paint tables without using a gid.

v1 BaseGlyph / Layer records introduce a pattern for structures that is new to OT

The draft introduces a pattern for structures that doesn't have existing precedent in OpenType. It's certainly implementable. But would it be preferable to stick with existing models already used?

I noticed this while trying to implement the draft in a old parsing implementation I have in VB: For an array of base glyph records each of which has an offset to a (different) array of layer records, I was going to end up creating a collection of collections. I had previously only ever done that for an interpretation of structures (e.g. glyph IDs in a glyph class).

A different approach that would follow an existing precedent would be to have v1 BaseGlyph be a table (not record) that includes an array of layer records, each of which has an offset to a Fill table. That would match the model currently used for ScriptList and FeatureList.

[bikeshed] Rename LayerV1List to PaintList?

The LayerV1List no longer contains an array of LayerV1Records. It now contains an array of Paints. What used to be called a "layer" record is now subsumed as another Paint format 4 (PaintGlyph).
I find it a bit confusing that we subtly change terminology while we define the structs from talking about "layers" (e.g. in the name "LayerV1List") to talking about "paints" (e.g. where we say "LayerV1List" contains list of "paints").
I believe I would find it clearer if we only talked about Paints instead of layers.

I understand that multiple paints are "layered" (as if composited with previous paint using SOURCE_OVER operator) following the z-order of LayerV1List. And that a single "layer" is understood as one (bounded) DAG of paints; so the LayerV1List is a sequence of paint DAGs layered over each other.

What do you think of renaming LayerV1List to PaintList? We'd also ditch the "V1" from the name, since this new structure is quite unlike the V0 one.

Feel free to dismiss/ignore, I don't mean to open a can of worms.

Elaborate on color stop range

Explain in more detail what happens when the range is defined on an interval other than 0 to 1. Smaller, larger, partially overlapping, etc.

Necessarity of PaintColrGlyph (paint format 7)

From the draft I read, it looks like that, this format is created only for sharing data across glyphs. However, since paints are referenced with offsets, it is simple to reuse the same paint for multiple glyphs / upper-layer paints, by pointing the offsets to the same target.

So should we either remove this format, or specify that using a PaintColrGlyph instead of just stretching the offsets do carry semantics, and they are semantically different, even if they are visually the same?

Should format 3 support two transforms?

Currently, there's one Affine2x2 for both circles. That allows the cylinder defined by the two circles to be uniformly elliptical. But conceptually it would be possible to have a cylinder defined by two separately-transformed circles.

Adding a second offset field could allow separate transforms for start and end circles. If one offset is null, the same transform could be applied to both.

Thoughts?

Use (new) Offset24

I'm of two views in general, both of which have not been followed in the spec consistently before:

  • Use types that are wide-enough that won't run out of space, even for (reasonable but) unanticipated uses (eg. 60k axes!). Avoiding Offset16 in most cases is part of this design philosophy.

  • Don't waste bytes if not necessary. Before we've been using a lot of uint16 version numbers, even separate uint16 major/minor ones. That's a waste. Currently proposal use of uint8_t for format / Extend / CompositeMode is in line with that.

Now. The very issue of alignment that Peter seems to be obsessed with in other parts of the spec, is fully ignored here.

We should introduce an Offset24 type, and use it next to uint8_t types when possible, to save a byte without limiting what's reachable, and making it faster as well because fetching one (aligned) int is faster in modern memory systems than fetching one unaligned int, which is implemented as two aligned int fetches.

names of the basic structures

I'm wondering generally about the smaller, more basic structures: to what extent to define and name based on usage in this context versus naming more generally based on data types (and perhaps internal validation constraints).

Also, I'm thinking for structs that combine a basic type with a delta-set index the name should be prefixed with "Var" to reflect that.

  • "Scalar": In this context Fixed is used to scale / as a coefficient. Other things could be scalars (e.g. F2Dot14), and Fixed could be used in other ways. An alternative name "VarFixed" would reflect the type without assuming how it's used.

  • "NormalizedScalar": alternative possibility: "VarF2Dot14"

    • There is a variant used in Color that limits the range to [0, 1]. That's a different validation constraint. So, something like "VarF2Dot14Magnitude" (magnitudes are non-negative) could perhaps also be defined.
  • "Position": since the type used in the template is FWORD, that does limit the context to design-grid values. Otherwise we could generalize to int16 and call it something like "VarInt16".

    • "Position" can suggest (x, y). I think "VarCoordinate" or "VarCoord" would be clearer.
  • "Distance": similar to the previous item, use of UFWORD does limit the context to design-grid values. Otherwise, we could generalize to uint16 and call it something like "VarUint16".

  • "Point": alternate suggestion "VarPoint"

  • "Color": Incorporating "Var" here is trickier since there are two primary members, paletteIndex and transparency, and only the latter is variable. "ColorIndexVarAlpha" is a bit awkward but clear.

All the other structs in the proposal (ColorStop on up) are more complex. I wouldn't propose prefixing "Var" on any of those names.

struct COLRv1 is missing numLayers

V0 fields:

Type Name Description
uint16 version Table version number (starts at 0).
uint16 numBaseGlyphRecords Number of Base Glyph Records.
Offset32 baseGlyphRecordsOffset Offset (from beginning of COLR table) to Base Glyph records.
Offset32 layerRecordsOffset Offset (from beginning of COLR table) to Layer Records.
uint16 numLayerRecords

Circle center to circle center for radial needs illustration

"Color stop position 1 maps to the end point of a linear gradient or the center of the second circle of a radial gradient."

This is non-intuitive. (More intuitive would be going from the center of one circle to a point on the circumference of a second circle.) The figure for linear gradients showing P2 being different from P1 is informative and helps make clear what was non-obvious: why a linear gradient would be defined using two vectors. It would be helpful to have a figure for radial gradients that similarly illustrates the non-obvious aspect of how the second circle is used in defining a radial gradient.

array length at the start of an array at an offset is a departure from elsewhere in OT

The proposed spec defines

template <typename T, typename Len=uint16>
struct ArrayOf
{
  Length count;
  T      array[/*count*/];
};

A consequence is that, if an array of records is referenced by offset, then the count is located at that offset.

There are several other places in the OT spec where there is an array of records at an offset. E.g.

CPAL: offsetFirstColorRecord, offsetPaletteTypeArray, offsetPaletteLabelArray, offsetPaletteEntryLabelArray
EBLC: indexSubTableArrayOffset
MERG: offsetToClassDefOffsets
STAT: offsetToAxisValueOffsets

In no case is the count for the array stored at the start of the array.

Moreover, storing the count at the start of the array can create redundancy and potential for inconsistency. For example, the arrays in CPAL all have the same count. If the count were stored separately for each array, that would create a possibility for inconsistency that doesn't exist by having a single occurrence of the count outside of the arrays.

Also, by changing this convention, we increase potential for confusion and for buggy implementations. (I stumbled over this myself.)

So, count for the BaseGlyphV1Records array should be in the CPAL header.

(Similarly, the definition for BaseGlyphV1Record needs to have numLayerV1Records as a field, and ColorLine needs to have numStops or numColorStops as a field. Those cases are structurally the same, so not quite the problem that exists for the BGRV1 array at an offset.)

handle degenerate linear gradient by using last color stop?

the CORLv1 draft says

If the dot-product (P₁ - P₀) . (P₂ - P₀) is zero (or near-zero for an implementation-defined definition) then gradient is ill-formed and nothing must be rendered

However, the SVG spec says that when the end points overlap, one should render as a solid paint with the last stop's color and opacity:

https://www.w3.org/TR/SVG/pservers.html#LinearGradientNotes

If ‘x1’ = ‘x2’ and ‘y1’ = ‘y2’, then the area to be painted will be painted as a single color using the color and opacity of the last gradient stop.

Can we not do the same when P2-P0 vector is perpendicular to the P1-P0 vector?

Define how to interpolate colors

https://github.com/googlefonts/colr-gradients-spec/blob/master/colr-gradients-spec.md#color-palette-variation currently reads "Colors expressed in sRGB r/g/b channels cannot be easily interpolated. Another solution is needed, perhaps involving transforming to a linear color space and back."

Courtesy of Romain Guy I have a concrete suggestion on how to interpolate in linear space: "apply the color space's EOTF before interpolating, then apply the OETF." I hope I'm not the only one who had to look up the terms :D

Android implements this for animated colors and users seem satisfied. https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/animation/ArgbEvaluator.java. Related, https://developer.android.com/reference/android/graphics/ColorSpace.Named gives names and equations [in source] for a bunch more color spaces.

I suggest we might consider directly adopting this approach; it's been used in real world animation and has a well defined implementation :)

https://www.colour-science.org/posts/the-importance-of-terminology-and-srgb-uncertainty/

32-bit glyph IDs

It's unclear what the thinking was behind using 32-bit glyph IDs in the base glyph and layer records. (Note that v0 structures use uint16.)

  • building in forward compatibility to a future in which fonts can have > 64K glyphs?
  • keeping 4-byte-integral structures?
  • other?

Note that this adds at least 6 bytes of padding to every colour glyph. For an emoji font, that will be an additional 12K to 20K (or more?) of padding.

(I'm not opposed, just want to understand the thinking behind proposing it, and to make sure the size implications are understood.)

"transparency" vs. "opacity" vs. "alpha"

The CPAL table uses "alpha", not "transparency".

CSS uses "alpha" in a context of specifying color values, e.g., rgba(). In other places, it uses "opacity".

  • "transparency" would suggest that a value of 1.0 is fully transparent.
  • "opacity" would suggest that a value of 1.0 is fully opaque.

I believe the latter is intended. So, I suggest that field in the Color struct be renamed "opacity" or "alpha". I think "opacity" is clearer.

Consider CPAL/COLR for solving the white-on-black-with-overlaps issue

I would be pleased if another issue can be considered in this round of CPAL/COLR updates.

The “white on black” issue concerns colour-inverted b/w glyphs as seen in this image, from @adobe-fonts’s white-on-black-vf repo, which contains a test font with this glyph.

67e45d00-818a-11e9-8166-bcc146b20892

Top: actual behaviour. Bottom: desired behaviour

As can be seen, the “white” or transparent parts of the glyph cannot use the overlapping contours that are useful in the “black“ or opaque parts of variable fonts.

In May 2019 @readroberts started a discussion on the [email protected] list (subject line Need new path fill rule for variable fonts with white on black glyphs), in which I suggested a COLR/CPAL approach. Later that month, @kenlunde posted the Adobe test font mentioned above to GitHub. In August 2019, Read summarized three possible approaches in an issue posted to the opentype-variations repo: “White on Black” VF Rasterization Issue:

  1. a new rendering rule treating negative and positive winding differently
  2. a COLR/CPAL solution
  3. support for more complex graphics operations proposed by Renzhi Li

Read’s summary is well worth reading in full. He comes out firmly in favour of (1), a new rendering rule.

To me the rendering rule solution seems lined up for years of incompatibilities while rasterizers catch up, elegant only if we had a blank sheet. As Read acknowledges, it requires methods to export normal text glyphs as composited graphic objects for the vast number of system components that use OpenType fonts.

Back to COLR/CPAL

It seems to me that COLR/CPAL is a pretty good solution. It is explicitly about layers. And it has a built-in fallback mechanism (to a b/w simple or composite glyph) for rasterizers that don’t support it (or that version of the tables). It lacks the ability to define masks, but we can spec that easily. An updated COLR table would define certain layers as "mask". We already have one special value in COLR, 0xFFFF:

A palette entry index value of 0xFFFF is a special case indicating that the text foreground color (defined by a higher-level client) should be used and shall not be treated as actual index into CPAL ColorRecord array. — COLR table specification

We can define 0xFFFE as the “mask” paletteIndex. Any layer “coloured” with paletteIndex 0xFFFE would punch holes in whatever had been built up so far.

Typical use would be to have a single mask layer at the top of the stack, but it may be useful to have multiple mask layers at various positions in the stack.

COLR glyphs already piggy-back on top of the existing rasterizers, and this extra step seems to me less onerous than updating the core rasterizer. The SVG <clipPath> element makes this technique easily translatable to SVG.

Read had three objections to the COLR solution, which I list here for consideration:

The problem is really one of rasterization for normal text fonts. Since the ‘COLR’ table conveys the meaning of being a color font, it doesn’t seem to be the right place to be solving this problem, which is fundamentally a non-color one. Also, this could lead to problems with heuristics, such as font classification.

There may also be some loss of effectiveness of anti-aliasing and hinting. Text renderers have been heavily optimized over the years for different environments. We suspect that some of this optimization could be lost when the ‘white’ paths are applied as a mask.

Use of the ‘COLR’ table currently requires explicit opt-in on the part of the user, which is passed down through the API. This new use of the ‘COLR’ table would need to differentiate between the current use case, which is opt-in only, and the new use case, which involves the ‘COLR’ table being applied to the glyphs in question.

In respect of the anti-aliasing and hinting issue, it may be a worthwhile optimization for renderers, when processing COLR glyphs that only use paletteIndices 0xFFFF (opaque) and 0xFFFE (transparent), to use a rasterizer path tuned for font rendering.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.