Skip to content

COLR table #196

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions font/color.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package font

import (
"errors"
"fmt"

"github.com/go-text/typesetting/font/opentype/tables"
)

// Support for COLR and CPAL tables

// CPAL is the 'CPAL' table,
// with [numPalettes]x[numPaletteEntries] colors.
// CPAL[0] is the default palette
type CPAL [][]tables.ColorRecord

func newCPAL(table tables.CPAL) (CPAL, error) {
numPalettes := len(table.ColorRecordIndices)
numColors := len(table.ColorRecordsArray)

// "The first palette, palette index 0, is the default palette.
// A minimum of one palette must be provided in the CPAL table if the table is present.
// Palettes must have a minimum of one color record. An empty CPAL table,
// with no palettes and no color records is not permitted."
if numPalettes == 0 {
return nil, errors.New("empty CPAL table")
}
out := make(CPAL, numPalettes)
for i, startIndex := range table.ColorRecordIndices {
endIndex := int(startIndex) + int(table.NumPaletteEntries)
if endIndex > numColors {
return nil, fmt.Errorf("invalid CPAL table (expected at least %d colors, got %d)", endIndex, numColors)
}
out[i] = table.ColorRecordsArray[startIndex:endIndex]
}
return out, nil
}
15 changes: 15 additions & 0 deletions font/font.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,9 @@ type Font struct {
bitmap bitmap
sbix sbix

COLR *tables.COLR1 // color glyphs, optional
CPAL CPAL // color glyphs, optional

os2 os2
names tables.Name
head tables.Head
Expand Down Expand Up @@ -267,6 +270,18 @@ func NewFont(ld *ot.Loader) (*Font, error) {
svg, _, _ := tables.ParseSVG(raw)
out.svg, _ = newSvg(svg)

raw, _ = ld.RawTable(ot.MustNewTag("COLR"))
if colr, err := tables.ParseCOLR(raw); err == nil {
out.COLR = &colr
// color table without CPAL is broken
raw, _ = ld.RawTable(ot.MustNewTag("CPAL"))
cpal, _, _ := tables.ParseCPAL(raw)
out.CPAL, err = newCPAL(cpal)
if err != nil {
return nil, err
}
}

out.hhea, out.hmtx, _ = loadHmtx(ld, out.nGlyphs)
out.vhea, out.vmtx, _ = loadVmtx(ld, out.nGlyphs)

Expand Down
12 changes: 12 additions & 0 deletions font/font_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,15 @@ func TestCapHeight(t *testing.T) {
tu.Assert(t, face.LineMetric(CapHeight) == 730)
tu.Assert(t, face.LineMetric(XHeight) == 520)
}

func TestLoadColor(t *testing.T) {
ld := readFontFile(t, "color/NotoColorEmoji-Regular.ttf")
ft, err := NewFont(ld)
tu.AssertNoErr(t, err)
tu.Assert(t, ft.COLR != nil && ft.CPAL != nil)

ld = readFontFile(t, "color/CoralPixels-Regular.ttf")
ft, err = NewFont(ld)
tu.AssertNoErr(t, err)
tu.Assert(t, ft.COLR != nil && ft.CPAL != nil)
}
6 changes: 6 additions & 0 deletions font/opentype/tables/glyphs.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,9 @@ type EBLC = CBLC
// Bloc is the bitmap location table
// See - https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6bloc.html
type Bloc = CBLC

// PaintColrLayersResolved is a simili PaintTable, build
// from COLR version 0 table.
type PaintColrLayersResolved []Layer

func (PaintColrLayersResolved) isPaintTable() {}
86 changes: 86 additions & 0 deletions font/opentype/tables/glyphs_color_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package tables

import (
"testing"

tu "github.com/go-text/typesetting/testutils"
)

func TestCOLR(t *testing.T) {
ft := readFontFile(t, "color/NotoColorEmoji-Regular.ttf")
colr, err := ParseCOLR(readTable(t, ft, "COLR"))
tu.AssertNoErr(t, err)
tu.Assert(t, len(colr.baseGlyphRecords) == 0)
tu.Assert(t, len(colr.layerRecords) == 0)
tu.Assert(t, len(colr.baseGlyphList.paintRecords) == 3845)
tu.Assert(t, colr.baseGlyphList.paintRecords[0].Paint == PaintColrLayers{1, 3, 47625})
tu.Assert(t, colr.ClipList.clips[0].ClipBox == ClipBoxFormat1{1, 480, 192, 800, 512})
tu.Assert(t, colr.VarIndexMap == nil && colr.ItemVariationStore == nil)
tu.Assert(t, len(colr.LayerList.paintTables) == 69264)

clipBox, ok := colr.ClipList.Search(87)
tu.Assert(t, ok && clipBox == ClipBoxFormat1{1, 64, -224, 1216, 928})

// reference from fonttools
paint := colr.LayerList.paintTables[6]
transform, ok := paint.(PaintTransform)
tu.Assert(t, ok)
_, innerOK := transform.Paint.(PaintGlyph)
tu.Assert(t, transform.Transform == Affine2x3{1, 0, 0, 1, 4.3119965, 0.375})
tu.Assert(t, innerOK)

_, ok = colr.Search(1)
tu.Assert(t, !ok)
_, ok = colr.Search(0xFFFF)
tu.Assert(t, !ok)

pt, ok := colr.Search(12)
asColrLayers, ok2 := pt.(PaintColrLayers)
tu.Assert(t, ok && ok2)
tu.Assert(t, asColrLayers == PaintColrLayers{1, 9, 2427})

for _, paint := range colr.baseGlyphList.paintRecords {
if layers, ok := paint.Paint.(PaintColrLayers); ok {
l, err := colr.LayerList.Resolve(layers)
tu.AssertNoErr(t, err)
tu.Assert(t, len(l) == int(layers.NumLayers))
}
}

ft = readFontFile(t, "color/CoralPixels-Regular.ttf")
colr, err = ParseCOLR(readTable(t, ft, "COLR"))
tu.AssertNoErr(t, err)
tu.Assert(t, len(colr.baseGlyphRecords) == 335)
tu.Assert(t, len(colr.layerRecords) == 5603)
g1, g2 := colr.baseGlyphRecords[0], colr.baseGlyphRecords[1]
tu.Assert(t, g1 == baseGlyph{0, 0, 11} && g2 == baseGlyph{2, 11, 18})
tu.Assert(t, colr.layerRecords[0].PaletteIndex == 4)

_, ok = colr.Search(1)
tu.Assert(t, !ok)
_, ok = colr.Search(0xFFFF)
tu.Assert(t, !ok)

pt, ok = colr.Search(0)
asLayers, ok2 := pt.(PaintColrLayersResolved)
tu.Assert(t, ok && ok2)
tu.Assert(t, len(asLayers) == 11)
tu.Assert(t, asLayers[0].PaletteIndex == 4)
tu.Assert(t, asLayers[10].PaletteIndex == 11)
}

func TestCPAL(t *testing.T) {
ft := readFontFile(t, "color/NotoColorEmoji-Regular.ttf")
cpal, _, err := ParseCPAL(readTable(t, ft, "CPAL"))
tu.AssertNoErr(t, err)
tu.Assert(t, cpal.Version == 0)
tu.Assert(t, cpal.NumPaletteEntries == 5921)
tu.Assert(t, cpal.numPalettes == 1 && len(cpal.ColorRecordIndices) == 1)

ft = readFontFile(t, "color/CoralPixels-Regular.ttf")
cpal, _, err = ParseCPAL(readTable(t, ft, "CPAL"))
tu.AssertNoErr(t, err)
tu.Assert(t, cpal.Version == 0)
tu.Assert(t, cpal.NumPaletteEntries == 32)
tu.Assert(t, cpal.numPalettes == 2 && len(cpal.ColorRecordIndices) == 2)
}
Loading