Skip to content
Merged
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")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't see those files in this PR or the repo before the PR - apologies if I am missing something obvious.
But given that I cannot understand how the tests are passing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are loading the test files from the typesetting-utils module, so that typesetting remains lightweight.

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