From 5f6d41481597ecb363aa893a99e2a7596503c076 Mon Sep 17 00:00:00 2001 From: Benoit KUGLER Date: Wed, 18 Jun 2025 12:48:24 +0200 Subject: [PATCH 1/8] [opentype/tables] first pass at parsing COLR table --- font/opentype/tables/glyphs_color_test.go | 29 + font/opentype/tables/glyphs_colr_gen.go | 1384 +++++++++++++++++++++ font/opentype/tables/glyphs_colr_src.go | 426 +++++++ font/opentype/tables/tables.go | 8 + font/opentype/tables/xvar_gen.go | 164 --- 5 files changed, 1847 insertions(+), 164 deletions(-) create mode 100644 font/opentype/tables/glyphs_color_test.go create mode 100755 font/opentype/tables/glyphs_colr_gen.go create mode 100644 font/opentype/tables/glyphs_colr_src.go diff --git a/font/opentype/tables/glyphs_color_test.go b/font/opentype/tables/glyphs_color_test.go new file mode 100644 index 00000000..567e0d56 --- /dev/null +++ b/font/opentype/tables/glyphs_color_test.go @@ -0,0 +1,29 @@ +package tables + +import ( + "testing" + + tu "github.com/go-text/typesetting/testutils" +) + +func TestCOLR(t *testing.T) { + ft := readFontFile(t, "common/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, len(colr.LayerList.PaintTables) == 69264) + tu.Assert(t, colr.ClipList.Clips[0].ClipBox == ClipBoxFormat1{1, 480, 192, 800, 512}) + tu.Assert(t, colr.VarIndexMap == nil && colr.ItemVariationStore == nil) + + ft = readFontFile(t, "common/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) +} diff --git a/font/opentype/tables/glyphs_colr_gen.go b/font/opentype/tables/glyphs_colr_gen.go new file mode 100755 index 00000000..8c8bd65c --- /dev/null +++ b/font/opentype/tables/glyphs_colr_gen.go @@ -0,0 +1,1384 @@ +// SPDX-License-Identifier: Unlicense OR BSD-3-Clause + +package tables + +import ( + "encoding/binary" + "fmt" +) + +// Code generated by binarygen from glyphs_colr_src.go. DO NOT EDIT + +func (item *BaseGlyph) mustParse(src []byte) { + _ = src[5] // early bound checking + item.GlyphID = binary.BigEndian.Uint16(src[0:]) + item.FirstLayerIndex = binary.BigEndian.Uint16(src[2:]) + item.NumLayers = binary.BigEndian.Uint16(src[4:]) +} + +func (item *ClipBoxFormat1) mustParse(src []byte) { + _ = src[8] // early bound checking + item.format = src[0] + item.XMin = int16(binary.BigEndian.Uint16(src[1:])) + item.YMin = int16(binary.BigEndian.Uint16(src[3:])) + item.XMax = int16(binary.BigEndian.Uint16(src[5:])) + item.YMax = int16(binary.BigEndian.Uint16(src[7:])) +} + +func (item *ClipBoxFormat2) mustParse(src []byte) { + _ = src[12] // early bound checking + item.format = src[0] + item.XMin = int16(binary.BigEndian.Uint16(src[1:])) + item.YMin = int16(binary.BigEndian.Uint16(src[3:])) + item.XMax = int16(binary.BigEndian.Uint16(src[5:])) + item.YMax = int16(binary.BigEndian.Uint16(src[7:])) + item.VarIndexBase = binary.BigEndian.Uint32(src[9:]) +} + +func (item *Layer) mustParse(src []byte) { + _ = src[3] // early bound checking + item.GlyphID = binary.BigEndian.Uint16(src[0:]) + item.PaletteIndex = binary.BigEndian.Uint16(src[2:]) +} + +func (item *PaintColrGlyph) mustParse(src []byte) { + _ = src[2] // early bound checking + item.format = src[0] + item.GlyphID = binary.BigEndian.Uint16(src[1:]) +} + +func (item *PaintColrLayers) mustParse(src []byte) { + _ = src[5] // early bound checking + item.format = src[0] + item.NumLayers = src[1] + item.FirstLayerIndex = binary.BigEndian.Uint32(src[2:]) +} + +func (item *PaintComposite) mustParse(src []byte) { + _ = src[7] // early bound checking + item.format = src[0] + item.SourcePaintOffset[0] = src[1] + item.SourcePaintOffset[1] = src[2] + item.SourcePaintOffset[2] = src[3] + item.CompositeMode = src[4] + item.BackdropPaintOffset[0] = src[5] + item.BackdropPaintOffset[1] = src[6] + item.BackdropPaintOffset[2] = src[7] +} + +func (item *PaintGlyph) mustParse(src []byte) { + _ = src[5] // early bound checking + item.format = src[0] + item.PaintOffset[0] = src[1] + item.PaintOffset[1] = src[2] + item.PaintOffset[2] = src[3] + item.GlyphID = binary.BigEndian.Uint16(src[4:]) +} + +func (item *PaintLinearGradient) mustParse(src []byte) { + _ = src[15] // early bound checking + item.format = src[0] + item.ColorLineOffset[0] = src[1] + item.ColorLineOffset[1] = src[2] + item.ColorLineOffset[2] = src[3] + item.X0 = int16(binary.BigEndian.Uint16(src[4:])) + item.Y0 = int16(binary.BigEndian.Uint16(src[6:])) + item.X1 = int16(binary.BigEndian.Uint16(src[8:])) + item.Y1 = int16(binary.BigEndian.Uint16(src[10:])) + item.X2 = int16(binary.BigEndian.Uint16(src[12:])) + item.Y2 = int16(binary.BigEndian.Uint16(src[14:])) +} + +func (item *PaintRadialGradient) mustParse(src []byte) { + _ = src[15] // early bound checking + item.format = src[0] + item.ColorLineOffset[0] = src[1] + item.ColorLineOffset[1] = src[2] + item.ColorLineOffset[2] = src[3] + item.X0 = int16(binary.BigEndian.Uint16(src[4:])) + item.Y0 = int16(binary.BigEndian.Uint16(src[6:])) + item.Radius0 = binary.BigEndian.Uint16(src[8:]) + item.X1 = int16(binary.BigEndian.Uint16(src[10:])) + item.Y1 = int16(binary.BigEndian.Uint16(src[12:])) + item.Radius1 = binary.BigEndian.Uint16(src[14:]) +} + +func (item *PaintRotate) mustParse(src []byte) { + _ = src[5] // early bound checking + item.format = src[0] + item.PaintOffset[0] = src[1] + item.PaintOffset[1] = src[2] + item.PaintOffset[2] = src[3] + item.Angle = Fixed214(binary.BigEndian.Uint16(src[4:])) +} + +func (item *PaintRotateAroundCenter) mustParse(src []byte) { + _ = src[9] // early bound checking + item.format = src[0] + item.PaintOffset[0] = src[1] + item.PaintOffset[1] = src[2] + item.PaintOffset[2] = src[3] + item.Angle = Fixed214(binary.BigEndian.Uint16(src[4:])) + item.CenterX = int16(binary.BigEndian.Uint16(src[6:])) + item.CenterY = int16(binary.BigEndian.Uint16(src[8:])) +} + +func (item *PaintScale) mustParse(src []byte) { + _ = src[7] // early bound checking + item.format = src[0] + item.PaintOffset[0] = src[1] + item.PaintOffset[1] = src[2] + item.PaintOffset[2] = src[3] + item.ScaleX = Fixed214(binary.BigEndian.Uint16(src[4:])) + item.ScaleY = Fixed214(binary.BigEndian.Uint16(src[6:])) +} + +func (item *PaintScaleAroundCenter) mustParse(src []byte) { + _ = src[11] // early bound checking + item.format = src[0] + item.PaintOffset[0] = src[1] + item.PaintOffset[1] = src[2] + item.PaintOffset[2] = src[3] + item.ScaleX = Fixed214(binary.BigEndian.Uint16(src[4:])) + item.ScaleY = Fixed214(binary.BigEndian.Uint16(src[6:])) + item.CenterX = int16(binary.BigEndian.Uint16(src[8:])) + item.CenterY = int16(binary.BigEndian.Uint16(src[10:])) +} + +func (item *PaintScaleUniform) mustParse(src []byte) { + _ = src[5] // early bound checking + item.format = src[0] + item.PaintOffset[0] = src[1] + item.PaintOffset[1] = src[2] + item.PaintOffset[2] = src[3] + item.Scale = Fixed214(binary.BigEndian.Uint16(src[4:])) +} + +func (item *PaintScaleUniformAroundCenter) mustParse(src []byte) { + _ = src[9] // early bound checking + item.format = src[0] + item.PaintOffset[0] = src[1] + item.PaintOffset[1] = src[2] + item.PaintOffset[2] = src[3] + item.Scale = Fixed214(binary.BigEndian.Uint16(src[4:])) + item.CenterX = int16(binary.BigEndian.Uint16(src[6:])) + item.CenterY = int16(binary.BigEndian.Uint16(src[8:])) +} + +func (item *PaintSkew) mustParse(src []byte) { + _ = src[7] // early bound checking + item.format = src[0] + item.PaintOffset[0] = src[1] + item.PaintOffset[1] = src[2] + item.PaintOffset[2] = src[3] + item.XSkewAngle = Fixed214(binary.BigEndian.Uint16(src[4:])) + item.YSkewAngle = Fixed214(binary.BigEndian.Uint16(src[6:])) +} + +func (item *PaintSkewAroundCenter) mustParse(src []byte) { + _ = src[11] // early bound checking + item.format = src[0] + item.PaintOffset[0] = src[1] + item.PaintOffset[1] = src[2] + item.PaintOffset[2] = src[3] + item.XSkewAngle = Fixed214(binary.BigEndian.Uint16(src[4:])) + item.YSkewAngle = Fixed214(binary.BigEndian.Uint16(src[6:])) + item.CenterX = int16(binary.BigEndian.Uint16(src[8:])) + item.CenterY = int16(binary.BigEndian.Uint16(src[10:])) +} + +func (item *PaintSolid) mustParse(src []byte) { + _ = src[4] // early bound checking + item.format = src[0] + item.PaletteIndex = binary.BigEndian.Uint16(src[1:]) + item.Alpha = Fixed214(binary.BigEndian.Uint16(src[3:])) +} + +func (item *PaintSweepGradient) mustParse(src []byte) { + _ = src[11] // early bound checking + item.format = src[0] + item.ColorLineOffset[0] = src[1] + item.ColorLineOffset[1] = src[2] + item.ColorLineOffset[2] = src[3] + item.CenterX = int16(binary.BigEndian.Uint16(src[4:])) + item.CenterY = int16(binary.BigEndian.Uint16(src[6:])) + item.StartAngle = Fixed214(binary.BigEndian.Uint16(src[8:])) + item.EndAngle = Fixed214(binary.BigEndian.Uint16(src[10:])) +} + +func (item *PaintTransform) mustParse(src []byte) { + _ = src[6] // early bound checking + item.format = src[0] + item.PaintOffset[0] = src[1] + item.PaintOffset[1] = src[2] + item.PaintOffset[2] = src[3] + item.TransformOffset[0] = src[4] + item.TransformOffset[1] = src[5] + item.TransformOffset[2] = src[6] +} + +func (item *PaintTranslate) mustParse(src []byte) { + _ = src[7] // early bound checking + item.format = src[0] + item.PaintOffset[0] = src[1] + item.PaintOffset[1] = src[2] + item.PaintOffset[2] = src[3] + item.Dx = int16(binary.BigEndian.Uint16(src[4:])) + item.Dy = int16(binary.BigEndian.Uint16(src[6:])) +} + +func (item *PaintVarLinearGradient) mustParse(src []byte) { + _ = src[19] // early bound checking + item.format = src[0] + item.ColorLineOffset[0] = src[1] + item.ColorLineOffset[1] = src[2] + item.ColorLineOffset[2] = src[3] + item.X0 = int16(binary.BigEndian.Uint16(src[4:])) + item.Y0 = int16(binary.BigEndian.Uint16(src[6:])) + item.X1 = int16(binary.BigEndian.Uint16(src[8:])) + item.Y1 = int16(binary.BigEndian.Uint16(src[10:])) + item.X2 = int16(binary.BigEndian.Uint16(src[12:])) + item.Y2 = int16(binary.BigEndian.Uint16(src[14:])) + item.VarIndexBase = binary.BigEndian.Uint32(src[16:]) +} + +func (item *PaintVarRadialGradient) mustParse(src []byte) { + _ = src[19] // early bound checking + item.format = src[0] + item.ColorLineOffset[0] = src[1] + item.ColorLineOffset[1] = src[2] + item.ColorLineOffset[2] = src[3] + item.X0 = int16(binary.BigEndian.Uint16(src[4:])) + item.Y0 = int16(binary.BigEndian.Uint16(src[6:])) + item.Radius0 = binary.BigEndian.Uint16(src[8:]) + item.X1 = int16(binary.BigEndian.Uint16(src[10:])) + item.Y1 = int16(binary.BigEndian.Uint16(src[12:])) + item.Radius1 = binary.BigEndian.Uint16(src[14:]) + item.VarIndexBase = binary.BigEndian.Uint32(src[16:]) +} + +func (item *PaintVarRotate) mustParse(src []byte) { + _ = src[9] // early bound checking + item.format = src[0] + item.PaintOffset[0] = src[1] + item.PaintOffset[1] = src[2] + item.PaintOffset[2] = src[3] + item.Angle = Fixed214(binary.BigEndian.Uint16(src[4:])) + item.VarIndexBase = binary.BigEndian.Uint32(src[6:]) +} + +func (item *PaintVarRotateAroundCenter) mustParse(src []byte) { + _ = src[13] // early bound checking + item.format = src[0] + item.PaintOffset[0] = src[1] + item.PaintOffset[1] = src[2] + item.PaintOffset[2] = src[3] + item.Angle = Fixed214(binary.BigEndian.Uint16(src[4:])) + item.CenterX = int16(binary.BigEndian.Uint16(src[6:])) + item.CenterY = int16(binary.BigEndian.Uint16(src[8:])) + item.VarIndexBase = binary.BigEndian.Uint32(src[10:]) +} + +func (item *PaintVarScale) mustParse(src []byte) { + _ = src[11] // early bound checking + item.format = src[0] + item.PaintOffset[0] = src[1] + item.PaintOffset[1] = src[2] + item.PaintOffset[2] = src[3] + item.ScaleX = Fixed214(binary.BigEndian.Uint16(src[4:])) + item.ScaleY = Fixed214(binary.BigEndian.Uint16(src[6:])) + item.VarIndexBase = binary.BigEndian.Uint32(src[8:]) +} + +func (item *PaintVarScaleAroundCenter) mustParse(src []byte) { + _ = src[15] // early bound checking + item.format = src[0] + item.PaintOffset[0] = src[1] + item.PaintOffset[1] = src[2] + item.PaintOffset[2] = src[3] + item.ScaleX = Fixed214(binary.BigEndian.Uint16(src[4:])) + item.ScaleY = Fixed214(binary.BigEndian.Uint16(src[6:])) + item.CenterX = int16(binary.BigEndian.Uint16(src[8:])) + item.CenterY = int16(binary.BigEndian.Uint16(src[10:])) + item.VarIndexBase = binary.BigEndian.Uint32(src[12:]) +} + +func (item *PaintVarScaleUniform) mustParse(src []byte) { + _ = src[9] // early bound checking + item.format = src[0] + item.PaintOffset[0] = src[1] + item.PaintOffset[1] = src[2] + item.PaintOffset[2] = src[3] + item.Scale = Fixed214(binary.BigEndian.Uint16(src[4:])) + item.VarIndexBase = binary.BigEndian.Uint32(src[6:]) +} + +func (item *PaintVarScaleUniformAroundCenter) mustParse(src []byte) { + _ = src[13] // early bound checking + item.format = src[0] + item.PaintOffset[0] = src[1] + item.PaintOffset[1] = src[2] + item.PaintOffset[2] = src[3] + item.Scale = Fixed214(binary.BigEndian.Uint16(src[4:])) + item.CenterX = int16(binary.BigEndian.Uint16(src[6:])) + item.CenterY = int16(binary.BigEndian.Uint16(src[8:])) + item.VarIndexBase = binary.BigEndian.Uint32(src[10:]) +} + +func (item *PaintVarSkew) mustParse(src []byte) { + _ = src[11] // early bound checking + item.format = src[0] + item.PaintOffset[0] = src[1] + item.PaintOffset[1] = src[2] + item.PaintOffset[2] = src[3] + item.XSkewAngle = Fixed214(binary.BigEndian.Uint16(src[4:])) + item.YSkewAngle = Fixed214(binary.BigEndian.Uint16(src[6:])) + item.VarIndexBase = binary.BigEndian.Uint32(src[8:]) +} + +func (item *PaintVarSkewAroundCenter) mustParse(src []byte) { + _ = src[15] // early bound checking + item.format = src[0] + item.PaintOffset[0] = src[1] + item.PaintOffset[1] = src[2] + item.PaintOffset[2] = src[3] + item.XSkewAngle = Fixed214(binary.BigEndian.Uint16(src[4:])) + item.YSkewAngle = Fixed214(binary.BigEndian.Uint16(src[6:])) + item.CenterX = int16(binary.BigEndian.Uint16(src[8:])) + item.CenterY = int16(binary.BigEndian.Uint16(src[10:])) + item.VarIndexBase = binary.BigEndian.Uint32(src[12:]) +} + +func (item *PaintVarSolid) mustParse(src []byte) { + _ = src[8] // early bound checking + item.format = src[0] + item.PaletteIndex = binary.BigEndian.Uint16(src[1:]) + item.Alpha = Fixed214(binary.BigEndian.Uint16(src[3:])) + item.VarIndexBase = binary.BigEndian.Uint32(src[5:]) +} + +func (item *PaintVarSweepGradient) mustParse(src []byte) { + _ = src[15] // early bound checking + item.format = src[0] + item.ColorLineOffset[0] = src[1] + item.ColorLineOffset[1] = src[2] + item.ColorLineOffset[2] = src[3] + item.CenterX = int16(binary.BigEndian.Uint16(src[4:])) + item.CenterY = int16(binary.BigEndian.Uint16(src[6:])) + item.StartAngle = Fixed214(binary.BigEndian.Uint16(src[8:])) + item.EndAngle = Fixed214(binary.BigEndian.Uint16(src[10:])) + item.VarIndexBase = binary.BigEndian.Uint32(src[12:]) +} + +func (item *PaintVarTransform) mustParse(src []byte) { + _ = src[6] // early bound checking + item.format = src[0] + item.PaintOffset[0] = src[1] + item.PaintOffset[1] = src[2] + item.PaintOffset[2] = src[3] + item.TransformOffset[0] = src[4] + item.TransformOffset[1] = src[5] + item.TransformOffset[2] = src[6] +} + +func (item *PaintVarTranslate) mustParse(src []byte) { + _ = src[11] // early bound checking + item.format = src[0] + item.PaintOffset[0] = src[1] + item.PaintOffset[1] = src[2] + item.PaintOffset[2] = src[3] + item.Dx = int16(binary.BigEndian.Uint16(src[4:])) + item.Dy = int16(binary.BigEndian.Uint16(src[6:])) + item.VarIndexBase = binary.BigEndian.Uint32(src[8:]) +} + +func ParseBaseGlyph(src []byte) (BaseGlyph, int, error) { + var item BaseGlyph + n := 0 + if L := len(src); L < 6 { + return item, 0, fmt.Errorf("reading BaseGlyph: "+"EOF: expected length: 6, got %d", L) + } + item.mustParse(src) + n += 6 + return item, n, nil +} + +func ParseBaseGlyphList(src []byte) (BaseGlyphList, int, error) { + var item BaseGlyphList + n := 0 + if L := len(src); L < 4 { + return item, 0, fmt.Errorf("reading BaseGlyphList: "+"EOF: expected length: 4, got %d", L) + } + arrayLengthPaintRecords := int(binary.BigEndian.Uint32(src[0:])) + n += 4 + + { + + offset := 4 + for i := 0; i < arrayLengthPaintRecords; i++ { + elem, read, err := ParseBaseGlyphPaintRecord(src[offset:], src) + if err != nil { + return item, 0, fmt.Errorf("reading BaseGlyphList: %s", err) + } + item.PaintRecords = append(item.PaintRecords, elem) + offset += read + } + n = offset + } + return item, n, nil +} + +func ParseBaseGlyphPaintRecord(src []byte, parentSrc []byte) (BaseGlyphPaintRecord, int, error) { + var item BaseGlyphPaintRecord + n := 0 + if L := len(src); L < 6 { + return item, 0, fmt.Errorf("reading BaseGlyphPaintRecord: "+"EOF: expected length: 6, got %d", L) + } + _ = src[5] // early bound checking + item.GlyphID = binary.BigEndian.Uint16(src[0:]) + offsetPaint := int(binary.BigEndian.Uint32(src[2:])) + n += 6 + + { + + if offsetPaint != 0 { // ignore null offset + if L := len(parentSrc); L < offsetPaint { + return item, 0, fmt.Errorf("reading BaseGlyphPaintRecord: "+"EOF: expected length: %d, got %d", offsetPaint, L) + } + + var ( + err error + read int + ) + item.Paint, read, err = ParsePaintTable(parentSrc[offsetPaint:]) + if err != nil { + return item, 0, fmt.Errorf("reading BaseGlyphPaintRecord: %s", err) + } + offsetPaint += read + } + } + return item, n, nil +} + +func ParseCOLR1(src []byte) (COLR1, int, error) { + var item COLR1 + n := 0 + { + var ( + err error + read int + ) + item.colr0, read, err = parseColr0(src[0:]) + if err != nil { + return item, 0, fmt.Errorf("reading COLR1: %s", err) + } + n += read + } + if L := len(src); L < n+20 { + return item, 0, fmt.Errorf("reading COLR1: "+"EOF: expected length: n + 20, got %d", L) + } + _ = src[n+19] // early bound checking + offsetBaseGlyphList := int(binary.BigEndian.Uint32(src[n:])) + offsetLayerList := int(binary.BigEndian.Uint32(src[n+4:])) + offsetClipList := int(binary.BigEndian.Uint32(src[n+8:])) + offsetVarIndexMap := int(binary.BigEndian.Uint32(src[n+12:])) + offsetItemVariationStore := int(binary.BigEndian.Uint32(src[n+16:])) + n += 20 + + { + + if offsetBaseGlyphList != 0 { // ignore null offset + if L := len(src); L < offsetBaseGlyphList { + return item, 0, fmt.Errorf("reading COLR1: "+"EOF: expected length: %d, got %d", offsetBaseGlyphList, L) + } + + var err error + item.BaseGlyphList, _, err = ParseBaseGlyphList(src[offsetBaseGlyphList:]) + if err != nil { + return item, 0, fmt.Errorf("reading COLR1: %s", err) + } + + } + } + { + + if offsetLayerList != 0 { // ignore null offset + if L := len(src); L < offsetLayerList { + return item, 0, fmt.Errorf("reading COLR1: "+"EOF: expected length: %d, got %d", offsetLayerList, L) + } + + var err error + item.LayerList, _, err = ParseLayerList(src[offsetLayerList:]) + if err != nil { + return item, 0, fmt.Errorf("reading COLR1: %s", err) + } + + } + } + { + + if offsetClipList != 0 { // ignore null offset + if L := len(src); L < offsetClipList { + return item, 0, fmt.Errorf("reading COLR1: "+"EOF: expected length: %d, got %d", offsetClipList, L) + } + + var err error + item.ClipList, _, err = ParseClipList(src[offsetClipList:]) + if err != nil { + return item, 0, fmt.Errorf("reading COLR1: %s", err) + } + + } + } + { + + if offsetVarIndexMap != 0 { // ignore null offset + if L := len(src); L < offsetVarIndexMap { + return item, 0, fmt.Errorf("reading COLR1: "+"EOF: expected length: %d, got %d", offsetVarIndexMap, L) + } + + var tmpVarIndexMap DeltaSetMapping + var err error + tmpVarIndexMap, _, err = ParseDeltaSetMapping(src[offsetVarIndexMap:]) + if err != nil { + return item, 0, fmt.Errorf("reading COLR1: %s", err) + } + + item.VarIndexMap = &tmpVarIndexMap + } + } + { + + if offsetItemVariationStore != 0 { // ignore null offset + if L := len(src); L < offsetItemVariationStore { + return item, 0, fmt.Errorf("reading COLR1: "+"EOF: expected length: %d, got %d", offsetItemVariationStore, L) + } + + var tmpItemVariationStore ItemVarStore + var err error + tmpItemVariationStore, _, err = ParseItemVarStore(src[offsetItemVariationStore:]) + if err != nil { + return item, 0, fmt.Errorf("reading COLR1: %s", err) + } + + item.ItemVariationStore = &tmpItemVariationStore + } + } + return item, n, nil +} + +func ParseClip(src []byte, parentSrc []byte) (Clip, int, error) { + var item Clip + n := 0 + if L := len(src); L < 7 { + return item, 0, fmt.Errorf("reading Clip: "+"EOF: expected length: 7, got %d", L) + } + _ = src[6] // early bound checking + item.StartGlyphID = binary.BigEndian.Uint16(src[0:]) + item.EndGlyphID = binary.BigEndian.Uint16(src[2:]) + offsetClipBox := int(readUint24(src[4:])) + n += 7 + + { + + if offsetClipBox != 0 { // ignore null offset + if L := len(parentSrc); L < offsetClipBox { + return item, 0, fmt.Errorf("reading Clip: "+"EOF: expected length: %d, got %d", offsetClipBox, L) + } + + var ( + err error + read int + ) + item.ClipBox, read, err = ParseClipBox(parentSrc[offsetClipBox:]) + if err != nil { + return item, 0, fmt.Errorf("reading Clip: %s", err) + } + offsetClipBox += read + } + } + return item, n, nil +} + +func ParseClipBox(src []byte) (ClipBox, int, error) { + var item ClipBox + + if L := len(src); L < 1 { + return item, 0, fmt.Errorf("reading ClipBox: "+"EOF: expected length: 1, got %d", L) + } + format := byte(src[0]) + var ( + read int + err error + ) + switch format { + case 1: + item, read, err = ParseClipBoxFormat1(src[0:]) + case 2: + item, read, err = ParseClipBoxFormat2(src[0:]) + default: + err = fmt.Errorf("unsupported ClipBox format %d", format) + } + if err != nil { + return item, 0, fmt.Errorf("reading ClipBox: %s", err) + } + + return item, read, nil +} + +func ParseClipBoxFormat1(src []byte) (ClipBoxFormat1, int, error) { + var item ClipBoxFormat1 + n := 0 + if L := len(src); L < 9 { + return item, 0, fmt.Errorf("reading ClipBoxFormat1: "+"EOF: expected length: 9, got %d", L) + } + item.mustParse(src) + n += 9 + return item, n, nil +} + +func ParseClipBoxFormat2(src []byte) (ClipBoxFormat2, int, error) { + var item ClipBoxFormat2 + n := 0 + if L := len(src); L < 13 { + return item, 0, fmt.Errorf("reading ClipBoxFormat2: "+"EOF: expected length: 13, got %d", L) + } + item.mustParse(src) + n += 13 + return item, n, nil +} + +func ParseClipList(src []byte) (ClipList, int, error) { + var item ClipList + n := 0 + if L := len(src); L < 5 { + return item, 0, fmt.Errorf("reading ClipList: "+"EOF: expected length: 5, got %d", L) + } + _ = src[4] // early bound checking + item.format = src[0] + arrayLengthClips := int(binary.BigEndian.Uint32(src[1:])) + n += 5 + + { + + offset := 5 + for i := 0; i < arrayLengthClips; i++ { + elem, read, err := ParseClip(src[offset:], src) + if err != nil { + return item, 0, fmt.Errorf("reading ClipList: %s", err) + } + item.Clips = append(item.Clips, elem) + offset += read + } + n = offset + } + return item, n, nil +} + +func ParseDeltaSetMapping(src []byte) (DeltaSetMapping, int, error) { + var item DeltaSetMapping + n := 0 + if L := len(src); L < 2 { + return item, 0, fmt.Errorf("reading DeltaSetMapping: "+"EOF: expected length: 2, got %d", L) + } + _ = src[1] // early bound checking + item.format = src[0] + item.entryFormat = src[1] + n += 2 + + { + + err := item.parseMap(src[2:]) + if err != nil { + return item, 0, fmt.Errorf("reading DeltaSetMapping: %s", err) + } + } + return item, n, nil +} + +func ParseItemVarStore(src []byte) (ItemVarStore, int, error) { + var item ItemVarStore + n := 0 + if L := len(src); L < 8 { + return item, 0, fmt.Errorf("reading ItemVarStore: "+"EOF: expected length: 8, got %d", L) + } + _ = src[7] // early bound checking + item.format = binary.BigEndian.Uint16(src[0:]) + offsetVariationRegionList := int(binary.BigEndian.Uint32(src[2:])) + arrayLengthItemVariationDatas := int(binary.BigEndian.Uint16(src[6:])) + n += 8 + + { + + if offsetVariationRegionList != 0 { // ignore null offset + if L := len(src); L < offsetVariationRegionList { + return item, 0, fmt.Errorf("reading ItemVarStore: "+"EOF: expected length: %d, got %d", offsetVariationRegionList, L) + } + + var err error + item.VariationRegionList, _, err = ParseVariationRegionList(src[offsetVariationRegionList:]) + if err != nil { + return item, 0, fmt.Errorf("reading ItemVarStore: %s", err) + } + + } + } + { + + if L := len(src); L < 8+arrayLengthItemVariationDatas*4 { + return item, 0, fmt.Errorf("reading ItemVarStore: "+"EOF: expected length: %d, got %d", 8+arrayLengthItemVariationDatas*4, L) + } + + item.ItemVariationDatas = make([]ItemVariationData, arrayLengthItemVariationDatas) // allocation guarded by the previous check + for i := range item.ItemVariationDatas { + offset := int(binary.BigEndian.Uint32(src[8+i*4:])) + // ignore null offsets + if offset == 0 { + continue + } + + if L := len(src); L < offset { + return item, 0, fmt.Errorf("reading ItemVarStore: "+"EOF: expected length: %d, got %d", offset, L) + } + + var err error + item.ItemVariationDatas[i], _, err = ParseItemVariationData(src[offset:]) + if err != nil { + return item, 0, fmt.Errorf("reading ItemVarStore: %s", err) + } + } + n += arrayLengthItemVariationDatas * 4 + } + return item, n, nil +} + +func ParseItemVariationData(src []byte) (ItemVariationData, int, error) { + var item ItemVariationData + n := 0 + if L := len(src); L < 6 { + return item, 0, fmt.Errorf("reading ItemVariationData: "+"EOF: expected length: 6, got %d", L) + } + _ = src[5] // early bound checking + item.itemCount = binary.BigEndian.Uint16(src[0:]) + item.wordDeltaCount = binary.BigEndian.Uint16(src[2:]) + item.regionIndexCount = binary.BigEndian.Uint16(src[4:]) + n += 6 + + { + arrayLength := int(item.regionIndexCount) + + if L := len(src); L < 6+arrayLength*2 { + return item, 0, fmt.Errorf("reading ItemVariationData: "+"EOF: expected length: %d, got %d", 6+arrayLength*2, L) + } + + item.RegionIndexes = make([]uint16, arrayLength) // allocation guarded by the previous check + for i := range item.RegionIndexes { + item.RegionIndexes[i] = binary.BigEndian.Uint16(src[6+i*2:]) + } + n += arrayLength * 2 + } + { + + err := item.parseDeltaSets(src[n:]) + if err != nil { + return item, 0, fmt.Errorf("reading ItemVariationData: %s", err) + } + } + return item, n, nil +} + +func ParseLayer(src []byte) (Layer, int, error) { + var item Layer + n := 0 + if L := len(src); L < 4 { + return item, 0, fmt.Errorf("reading Layer: "+"EOF: expected length: 4, got %d", L) + } + item.mustParse(src) + n += 4 + return item, n, nil +} + +func ParseLayerList(src []byte) (LayerList, int, error) { + var item LayerList + n := 0 + if L := len(src); L < 4 { + return item, 0, fmt.Errorf("reading LayerList: "+"EOF: expected length: 4, got %d", L) + } + arrayLengthPaintTables := int(binary.BigEndian.Uint32(src[0:])) + n += 4 + + { + + if L := len(src); L < 4+arrayLengthPaintTables*4 { + return item, 0, fmt.Errorf("reading LayerList: "+"EOF: expected length: %d, got %d", 4+arrayLengthPaintTables*4, L) + } + + item.PaintTables = make([]PaintTable, arrayLengthPaintTables) // allocation guarded by the previous check + for i := range item.PaintTables { + offset := int(binary.BigEndian.Uint32(src[4+i*4:])) + // ignore null offsets + if offset == 0 { + continue + } + + if L := len(src); L < offset { + return item, 0, fmt.Errorf("reading LayerList: "+"EOF: expected length: %d, got %d", offset, L) + } + + var err error + item.PaintTables[i], _, err = ParsePaintTable(src[offset:]) + if err != nil { + return item, 0, fmt.Errorf("reading LayerList: %s", err) + } + } + n += arrayLengthPaintTables * 4 + } + return item, n, nil +} + +func ParsePaintColrGlyph(src []byte) (PaintColrGlyph, int, error) { + var item PaintColrGlyph + n := 0 + if L := len(src); L < 3 { + return item, 0, fmt.Errorf("reading PaintColrGlyph: "+"EOF: expected length: 3, got %d", L) + } + item.mustParse(src) + n += 3 + return item, n, nil +} + +func ParsePaintColrLayers(src []byte) (PaintColrLayers, int, error) { + var item PaintColrLayers + n := 0 + if L := len(src); L < 6 { + return item, 0, fmt.Errorf("reading PaintColrLayers: "+"EOF: expected length: 6, got %d", L) + } + item.mustParse(src) + n += 6 + return item, n, nil +} + +func ParsePaintComposite(src []byte) (PaintComposite, int, error) { + var item PaintComposite + n := 0 + if L := len(src); L < 8 { + return item, 0, fmt.Errorf("reading PaintComposite: "+"EOF: expected length: 8, got %d", L) + } + item.mustParse(src) + n += 8 + return item, n, nil +} + +func ParsePaintGlyph(src []byte) (PaintGlyph, int, error) { + var item PaintGlyph + n := 0 + if L := len(src); L < 6 { + return item, 0, fmt.Errorf("reading PaintGlyph: "+"EOF: expected length: 6, got %d", L) + } + item.mustParse(src) + n += 6 + return item, n, nil +} + +func ParsePaintLinearGradient(src []byte) (PaintLinearGradient, int, error) { + var item PaintLinearGradient + n := 0 + if L := len(src); L < 16 { + return item, 0, fmt.Errorf("reading PaintLinearGradient: "+"EOF: expected length: 16, got %d", L) + } + item.mustParse(src) + n += 16 + return item, n, nil +} + +func ParsePaintRadialGradient(src []byte) (PaintRadialGradient, int, error) { + var item PaintRadialGradient + n := 0 + if L := len(src); L < 16 { + return item, 0, fmt.Errorf("reading PaintRadialGradient: "+"EOF: expected length: 16, got %d", L) + } + item.mustParse(src) + n += 16 + return item, n, nil +} + +func ParsePaintRotate(src []byte) (PaintRotate, int, error) { + var item PaintRotate + n := 0 + if L := len(src); L < 6 { + return item, 0, fmt.Errorf("reading PaintRotate: "+"EOF: expected length: 6, got %d", L) + } + item.mustParse(src) + n += 6 + return item, n, nil +} + +func ParsePaintRotateAroundCenter(src []byte) (PaintRotateAroundCenter, int, error) { + var item PaintRotateAroundCenter + n := 0 + if L := len(src); L < 10 { + return item, 0, fmt.Errorf("reading PaintRotateAroundCenter: "+"EOF: expected length: 10, got %d", L) + } + item.mustParse(src) + n += 10 + return item, n, nil +} + +func ParsePaintScale(src []byte) (PaintScale, int, error) { + var item PaintScale + n := 0 + if L := len(src); L < 8 { + return item, 0, fmt.Errorf("reading PaintScale: "+"EOF: expected length: 8, got %d", L) + } + item.mustParse(src) + n += 8 + return item, n, nil +} + +func ParsePaintScaleAroundCenter(src []byte) (PaintScaleAroundCenter, int, error) { + var item PaintScaleAroundCenter + n := 0 + if L := len(src); L < 12 { + return item, 0, fmt.Errorf("reading PaintScaleAroundCenter: "+"EOF: expected length: 12, got %d", L) + } + item.mustParse(src) + n += 12 + return item, n, nil +} + +func ParsePaintScaleUniform(src []byte) (PaintScaleUniform, int, error) { + var item PaintScaleUniform + n := 0 + if L := len(src); L < 6 { + return item, 0, fmt.Errorf("reading PaintScaleUniform: "+"EOF: expected length: 6, got %d", L) + } + item.mustParse(src) + n += 6 + return item, n, nil +} + +func ParsePaintScaleUniformAroundCenter(src []byte) (PaintScaleUniformAroundCenter, int, error) { + var item PaintScaleUniformAroundCenter + n := 0 + if L := len(src); L < 10 { + return item, 0, fmt.Errorf("reading PaintScaleUniformAroundCenter: "+"EOF: expected length: 10, got %d", L) + } + item.mustParse(src) + n += 10 + return item, n, nil +} + +func ParsePaintSkew(src []byte) (PaintSkew, int, error) { + var item PaintSkew + n := 0 + if L := len(src); L < 8 { + return item, 0, fmt.Errorf("reading PaintSkew: "+"EOF: expected length: 8, got %d", L) + } + item.mustParse(src) + n += 8 + return item, n, nil +} + +func ParsePaintSkewAroundCenter(src []byte) (PaintSkewAroundCenter, int, error) { + var item PaintSkewAroundCenter + n := 0 + if L := len(src); L < 12 { + return item, 0, fmt.Errorf("reading PaintSkewAroundCenter: "+"EOF: expected length: 12, got %d", L) + } + item.mustParse(src) + n += 12 + return item, n, nil +} + +func ParsePaintSolid(src []byte) (PaintSolid, int, error) { + var item PaintSolid + n := 0 + if L := len(src); L < 5 { + return item, 0, fmt.Errorf("reading PaintSolid: "+"EOF: expected length: 5, got %d", L) + } + item.mustParse(src) + n += 5 + return item, n, nil +} + +func ParsePaintSweepGradient(src []byte) (PaintSweepGradient, int, error) { + var item PaintSweepGradient + n := 0 + if L := len(src); L < 12 { + return item, 0, fmt.Errorf("reading PaintSweepGradient: "+"EOF: expected length: 12, got %d", L) + } + item.mustParse(src) + n += 12 + return item, n, nil +} + +func ParsePaintTable(src []byte) (PaintTable, int, error) { + var item PaintTable + + if L := len(src); L < 1 { + return item, 0, fmt.Errorf("reading PaintTable: "+"EOF: expected length: 1, got %d", L) + } + format := byte(src[0]) + var ( + read int + err error + ) + switch format { + case 11: + item, read, err = ParsePaintColrGlyph(src[0:]) + case 1: + item, read, err = ParsePaintColrLayers(src[0:]) + case 32: + item, read, err = ParsePaintComposite(src[0:]) + case 10: + item, read, err = ParsePaintGlyph(src[0:]) + case 4: + item, read, err = ParsePaintLinearGradient(src[0:]) + case 6: + item, read, err = ParsePaintRadialGradient(src[0:]) + case 24: + item, read, err = ParsePaintRotate(src[0:]) + case 26: + item, read, err = ParsePaintRotateAroundCenter(src[0:]) + case 16: + item, read, err = ParsePaintScale(src[0:]) + case 18: + item, read, err = ParsePaintScaleAroundCenter(src[0:]) + case 20: + item, read, err = ParsePaintScaleUniform(src[0:]) + case 22: + item, read, err = ParsePaintScaleUniformAroundCenter(src[0:]) + case 28: + item, read, err = ParsePaintSkew(src[0:]) + case 30: + item, read, err = ParsePaintSkewAroundCenter(src[0:]) + case 2: + item, read, err = ParsePaintSolid(src[0:]) + case 8: + item, read, err = ParsePaintSweepGradient(src[0:]) + case 12: + item, read, err = ParsePaintTransform(src[0:]) + case 14: + item, read, err = ParsePaintTranslate(src[0:]) + case 5: + item, read, err = ParsePaintVarLinearGradient(src[0:]) + case 7: + item, read, err = ParsePaintVarRadialGradient(src[0:]) + case 25: + item, read, err = ParsePaintVarRotate(src[0:]) + case 27: + item, read, err = ParsePaintVarRotateAroundCenter(src[0:]) + case 17: + item, read, err = ParsePaintVarScale(src[0:]) + case 19: + item, read, err = ParsePaintVarScaleAroundCenter(src[0:]) + case 21: + item, read, err = ParsePaintVarScaleUniform(src[0:]) + case 23: + item, read, err = ParsePaintVarScaleUniformAroundCenter(src[0:]) + case 29: + item, read, err = ParsePaintVarSkew(src[0:]) + case 31: + item, read, err = ParsePaintVarSkewAroundCenter(src[0:]) + case 3: + item, read, err = ParsePaintVarSolid(src[0:]) + case 9: + item, read, err = ParsePaintVarSweepGradient(src[0:]) + case 13: + item, read, err = ParsePaintVarTransform(src[0:]) + case 15: + item, read, err = ParsePaintVarTranslate(src[0:]) + default: + err = fmt.Errorf("unsupported PaintTable format %d", format) + } + if err != nil { + return item, 0, fmt.Errorf("reading PaintTable: %s", err) + } + + return item, read, nil +} + +func ParsePaintTransform(src []byte) (PaintTransform, int, error) { + var item PaintTransform + n := 0 + if L := len(src); L < 7 { + return item, 0, fmt.Errorf("reading PaintTransform: "+"EOF: expected length: 7, got %d", L) + } + item.mustParse(src) + n += 7 + return item, n, nil +} + +func ParsePaintTranslate(src []byte) (PaintTranslate, int, error) { + var item PaintTranslate + n := 0 + if L := len(src); L < 8 { + return item, 0, fmt.Errorf("reading PaintTranslate: "+"EOF: expected length: 8, got %d", L) + } + item.mustParse(src) + n += 8 + return item, n, nil +} + +func ParsePaintVarLinearGradient(src []byte) (PaintVarLinearGradient, int, error) { + var item PaintVarLinearGradient + n := 0 + if L := len(src); L < 20 { + return item, 0, fmt.Errorf("reading PaintVarLinearGradient: "+"EOF: expected length: 20, got %d", L) + } + item.mustParse(src) + n += 20 + return item, n, nil +} + +func ParsePaintVarRadialGradient(src []byte) (PaintVarRadialGradient, int, error) { + var item PaintVarRadialGradient + n := 0 + if L := len(src); L < 20 { + return item, 0, fmt.Errorf("reading PaintVarRadialGradient: "+"EOF: expected length: 20, got %d", L) + } + item.mustParse(src) + n += 20 + return item, n, nil +} + +func ParsePaintVarRotate(src []byte) (PaintVarRotate, int, error) { + var item PaintVarRotate + n := 0 + if L := len(src); L < 10 { + return item, 0, fmt.Errorf("reading PaintVarRotate: "+"EOF: expected length: 10, got %d", L) + } + item.mustParse(src) + n += 10 + return item, n, nil +} + +func ParsePaintVarRotateAroundCenter(src []byte) (PaintVarRotateAroundCenter, int, error) { + var item PaintVarRotateAroundCenter + n := 0 + if L := len(src); L < 14 { + return item, 0, fmt.Errorf("reading PaintVarRotateAroundCenter: "+"EOF: expected length: 14, got %d", L) + } + item.mustParse(src) + n += 14 + return item, n, nil +} + +func ParsePaintVarScale(src []byte) (PaintVarScale, int, error) { + var item PaintVarScale + n := 0 + if L := len(src); L < 12 { + return item, 0, fmt.Errorf("reading PaintVarScale: "+"EOF: expected length: 12, got %d", L) + } + item.mustParse(src) + n += 12 + return item, n, nil +} + +func ParsePaintVarScaleAroundCenter(src []byte) (PaintVarScaleAroundCenter, int, error) { + var item PaintVarScaleAroundCenter + n := 0 + if L := len(src); L < 16 { + return item, 0, fmt.Errorf("reading PaintVarScaleAroundCenter: "+"EOF: expected length: 16, got %d", L) + } + item.mustParse(src) + n += 16 + return item, n, nil +} + +func ParsePaintVarScaleUniform(src []byte) (PaintVarScaleUniform, int, error) { + var item PaintVarScaleUniform + n := 0 + if L := len(src); L < 10 { + return item, 0, fmt.Errorf("reading PaintVarScaleUniform: "+"EOF: expected length: 10, got %d", L) + } + item.mustParse(src) + n += 10 + return item, n, nil +} + +func ParsePaintVarScaleUniformAroundCenter(src []byte) (PaintVarScaleUniformAroundCenter, int, error) { + var item PaintVarScaleUniformAroundCenter + n := 0 + if L := len(src); L < 14 { + return item, 0, fmt.Errorf("reading PaintVarScaleUniformAroundCenter: "+"EOF: expected length: 14, got %d", L) + } + item.mustParse(src) + n += 14 + return item, n, nil +} + +func ParsePaintVarSkew(src []byte) (PaintVarSkew, int, error) { + var item PaintVarSkew + n := 0 + if L := len(src); L < 12 { + return item, 0, fmt.Errorf("reading PaintVarSkew: "+"EOF: expected length: 12, got %d", L) + } + item.mustParse(src) + n += 12 + return item, n, nil +} + +func ParsePaintVarSkewAroundCenter(src []byte) (PaintVarSkewAroundCenter, int, error) { + var item PaintVarSkewAroundCenter + n := 0 + if L := len(src); L < 16 { + return item, 0, fmt.Errorf("reading PaintVarSkewAroundCenter: "+"EOF: expected length: 16, got %d", L) + } + item.mustParse(src) + n += 16 + return item, n, nil +} + +func ParsePaintVarSolid(src []byte) (PaintVarSolid, int, error) { + var item PaintVarSolid + n := 0 + if L := len(src); L < 9 { + return item, 0, fmt.Errorf("reading PaintVarSolid: "+"EOF: expected length: 9, got %d", L) + } + item.mustParse(src) + n += 9 + return item, n, nil +} + +func ParsePaintVarSweepGradient(src []byte) (PaintVarSweepGradient, int, error) { + var item PaintVarSweepGradient + n := 0 + if L := len(src); L < 16 { + return item, 0, fmt.Errorf("reading PaintVarSweepGradient: "+"EOF: expected length: 16, got %d", L) + } + item.mustParse(src) + n += 16 + return item, n, nil +} + +func ParsePaintVarTransform(src []byte) (PaintVarTransform, int, error) { + var item PaintVarTransform + n := 0 + if L := len(src); L < 7 { + return item, 0, fmt.Errorf("reading PaintVarTransform: "+"EOF: expected length: 7, got %d", L) + } + item.mustParse(src) + n += 7 + return item, n, nil +} + +func ParsePaintVarTranslate(src []byte) (PaintVarTranslate, int, error) { + var item PaintVarTranslate + n := 0 + if L := len(src); L < 12 { + return item, 0, fmt.Errorf("reading PaintVarTranslate: "+"EOF: expected length: 12, got %d", L) + } + item.mustParse(src) + n += 12 + return item, n, nil +} + +func ParseVariationRegion(src []byte, regionAxesCount int) (VariationRegion, int, error) { + var item VariationRegion + n := 0 + { + + if L := len(src); L < regionAxesCount*6 { + return item, 0, fmt.Errorf("reading VariationRegion: "+"EOF: expected length: %d, got %d", regionAxesCount*6, L) + } + + item.RegionAxes = make([]RegionAxisCoordinates, regionAxesCount) // allocation guarded by the previous check + for i := range item.RegionAxes { + item.RegionAxes[i].mustParse(src[i*6:]) + } + n += regionAxesCount * 6 + } + return item, n, nil +} + +func ParseVariationRegionList(src []byte) (VariationRegionList, int, error) { + var item VariationRegionList + n := 0 + if L := len(src); L < 4 { + return item, 0, fmt.Errorf("reading VariationRegionList: "+"EOF: expected length: 4, got %d", L) + } + _ = src[3] // early bound checking + item.axisCount = binary.BigEndian.Uint16(src[0:]) + arrayLengthVariationRegions := int(binary.BigEndian.Uint16(src[2:])) + n += 4 + + { + + offset := 4 + for i := 0; i < arrayLengthVariationRegions; i++ { + elem, read, err := ParseVariationRegion(src[offset:], int(item.axisCount)) + if err != nil { + return item, 0, fmt.Errorf("reading VariationRegionList: %s", err) + } + item.VariationRegions = append(item.VariationRegions, elem) + offset += read + } + n = offset + } + return item, n, nil +} + +func (item *RegionAxisCoordinates) mustParse(src []byte) { + _ = src[5] // early bound checking + item.StartCoord = Coord(binary.BigEndian.Uint16(src[0:])) + item.PeakCoord = Coord(binary.BigEndian.Uint16(src[2:])) + item.EndCoord = Coord(binary.BigEndian.Uint16(src[4:])) +} + +func parseColr0(src []byte) (colr0, int, error) { + var item colr0 + n := 0 + if L := len(src); L < 14 { + return item, 0, fmt.Errorf("reading colr0: "+"EOF: expected length: 14, got %d", L) + } + _ = src[13] // early bound checking + item.Version = binary.BigEndian.Uint16(src[0:]) + item.numBaseGlyphRecords = binary.BigEndian.Uint16(src[2:]) + offsetBaseGlyphRecords := int(binary.BigEndian.Uint32(src[4:])) + offsetLayerRecords := int(binary.BigEndian.Uint32(src[8:])) + item.numLayerRecords = binary.BigEndian.Uint16(src[12:]) + n += 14 + + { + + if offsetBaseGlyphRecords != 0 { // ignore null offset + if L := len(src); L < offsetBaseGlyphRecords { + return item, 0, fmt.Errorf("reading colr0: "+"EOF: expected length: %d, got %d", offsetBaseGlyphRecords, L) + } + + arrayLength := int(item.numBaseGlyphRecords) + + if L := len(src); L < offsetBaseGlyphRecords+arrayLength*6 { + return item, 0, fmt.Errorf("reading colr0: "+"EOF: expected length: %d, got %d", offsetBaseGlyphRecords+arrayLength*6, L) + } + + item.BaseGlyphRecords = make([]BaseGlyph, arrayLength) // allocation guarded by the previous check + for i := range item.BaseGlyphRecords { + item.BaseGlyphRecords[i].mustParse(src[offsetBaseGlyphRecords+i*6:]) + } + offsetBaseGlyphRecords += arrayLength * 6 + } + } + { + + if offsetLayerRecords != 0 { // ignore null offset + if L := len(src); L < offsetLayerRecords { + return item, 0, fmt.Errorf("reading colr0: "+"EOF: expected length: %d, got %d", offsetLayerRecords, L) + } + + arrayLength := int(item.numLayerRecords) + + if L := len(src); L < offsetLayerRecords+arrayLength*4 { + return item, 0, fmt.Errorf("reading colr0: "+"EOF: expected length: %d, got %d", offsetLayerRecords+arrayLength*4, L) + } + + item.LayerRecords = make([]Layer, arrayLength) // allocation guarded by the previous check + for i := range item.LayerRecords { + item.LayerRecords[i].mustParse(src[offsetLayerRecords+i*4:]) + } + offsetLayerRecords += arrayLength * 4 + } + } + return item, n, nil +} diff --git a/font/opentype/tables/glyphs_colr_src.go b/font/opentype/tables/glyphs_colr_src.go new file mode 100644 index 00000000..bccdd42e --- /dev/null +++ b/font/opentype/tables/glyphs_colr_src.go @@ -0,0 +1,426 @@ +package tables + +import "fmt" + +func ParseCOLR(src []byte) (COLR1, error) { + header, _, err := parseColr0(src) + if err != nil { + return COLR1{}, err + } + switch header.Version { + case 0: + return COLR1{colr0: header}, nil + case 1: + out, _, err := ParseCOLR1(src) + return out, err + default: + return COLR1{}, fmt.Errorf("unsupported version for COLR: %d", header.Version) + } +} + +// https://learn.microsoft.com/en-us/typography/opentype/spec/colr#colr-table-formats +type colr0 struct { + Version uint16 // Table version number + numBaseGlyphRecords uint16 // Number of BaseGlyph records. + BaseGlyphRecords []BaseGlyph `arrayCount:"ComputedField-numBaseGlyphRecords" offsetSize:"Offset32"` // Offset to baseGlyphRecords array, from beginning of COLR table. + LayerRecords []Layer `arrayCount:"ComputedField-numLayerRecords" offsetSize:"Offset32"` // Offset to layerRecords array, from beginning of COLR table. + numLayerRecords uint16 // Number of Layer records. +} + +type COLR1 struct { + colr0 + BaseGlyphList BaseGlyphList `offsetSize:"Offset32"` // Offset to BaseGlyphList table, from beginning of COLR table. + LayerList LayerList `offsetSize:"Offset32"` // Offset to LayerList table, from beginning of COLR table (may be NULL). + ClipList ClipList `offsetSize:"Offset32"` // Offset to ClipList table, from beginning of COLR table (may be NULL). + VarIndexMap *DeltaSetMapping `offsetSize:"Offset32"` // Offset to DeltaSetIndexMap table, from beginning of COLR table (may be NULL). + ItemVariationStore *ItemVarStore `offsetSize:"Offset32"` // Offset to ItemVariationStore, from beginning of COLR table (may be NULL). +} + +type BaseGlyph struct { + GlyphID GlyphID // Glyph ID of the base glyph. + FirstLayerIndex uint16 // Index (base 0) into the layerRecords array. + NumLayers uint16 // Number of color layers associated with this glyph. +} + +type Layer struct { + GlyphID GlyphID // Glyph ID of the glyph used for a given layer. + PaletteIndex uint16 // Index (base 0) for a palette entry in the CPAL table. +} + +type BaseGlyphList struct { + PaintRecords []BaseGlyphPaintRecord `arrayCount:"FirstUint32"` // numBaseGlyphPaintRecords +} + +type BaseGlyphPaintRecord struct { + GlyphID GlyphID // Glyph ID of the base glyph. + Paint PaintTable `offsetSize:"Offset32" offsetRelativeTo:"Parent"` // Offset to a Paint table, from beginning of BaseGlyphList table. +} + +type LayerList struct { + PaintTables []PaintTable `arrayCount:"FirstUint32" offsetsArray:"Offset32"` // Offsets to Paint tables, from beginning of LayerList table. +} + +type ClipList struct { + format uint8 // Set to 1. + Clips []Clip `arrayCount:"FirstUint32"` // Clip records. Sorted by startGlyphID. +} + +type Clip struct { + StartGlyphID GlyphID // First glyph ID in the range. + EndGlyphID GlyphID // Last glyph ID in the range. + ClipBox ClipBox `offsetSize:"Offset24" offsetRelativeTo:"Parent"` // Offset to a ClipBox table, from beginning of ClipList table. +} + +type ClipBox interface { + isClipBox() +} + +func (ClipBoxFormat1) isClipBox() {} +func (ClipBoxFormat2) isClipBox() {} + +// static clip box +type ClipBoxFormat1 struct { + format byte `unionTag:"1"` + XMin int16 // Minimum x of clip box. + YMin int16 // Minimum y of clip box. + XMax int16 // Maximum x of clip box. + YMax int16 // Maximum y of clip box. +} + +// variable clip box +type ClipBoxFormat2 struct { + format byte `unionTag:"2"` + XMin int16 // Minimum x of clip box. For variation, use varIndexBase + 0. + YMin int16 // Minimum y of clip box. For variation, use varIndexBase + 1. + XMax int16 // Maximum x of clip box. For variation, use varIndexBase + 2. + YMax int16 // Maximum y of clip box. For variation, use varIndexBase + 3. + VarIndexBase uint32 // Base index into DeltaSetIndexMap. +} + +type PaintTable interface { + isPaintTable() +} + +func (PaintColrLayers) isPaintTable() {} +func (PaintSolid) isPaintTable() {} +func (PaintVarSolid) isPaintTable() {} +func (PaintLinearGradient) isPaintTable() {} +func (PaintVarLinearGradient) isPaintTable() {} +func (PaintRadialGradient) isPaintTable() {} +func (PaintVarRadialGradient) isPaintTable() {} +func (PaintSweepGradient) isPaintTable() {} +func (PaintVarSweepGradient) isPaintTable() {} +func (PaintGlyph) isPaintTable() {} +func (PaintColrGlyph) isPaintTable() {} +func (PaintTransform) isPaintTable() {} +func (PaintVarTransform) isPaintTable() {} +func (PaintTranslate) isPaintTable() {} +func (PaintVarTranslate) isPaintTable() {} +func (PaintScale) isPaintTable() {} +func (PaintVarScale) isPaintTable() {} +func (PaintScaleAroundCenter) isPaintTable() {} +func (PaintVarScaleAroundCenter) isPaintTable() {} +func (PaintScaleUniform) isPaintTable() {} +func (PaintVarScaleUniform) isPaintTable() {} +func (PaintScaleUniformAroundCenter) isPaintTable() {} +func (PaintVarScaleUniformAroundCenter) isPaintTable() {} +func (PaintRotate) isPaintTable() {} +func (PaintVarRotate) isPaintTable() {} +func (PaintRotateAroundCenter) isPaintTable() {} +func (PaintVarRotateAroundCenter) isPaintTable() {} +func (PaintSkew) isPaintTable() {} +func (PaintVarSkew) isPaintTable() {} +func (PaintSkewAroundCenter) isPaintTable() {} +func (PaintVarSkewAroundCenter) isPaintTable() {} +func (PaintComposite) isPaintTable() {} + +type Offset24 [3]byte // TODO: + +// (format 1) +type PaintColrLayers struct { + format byte `unionTag:"1"` + NumLayers uint8 // Number of offsets to paint tables to read from LayerList. + FirstLayerIndex uint32 // Index (base 0) into the LayerList. +} + +// (format 2) +type PaintSolid struct { + format byte `unionTag:"2"` + PaletteIndex uint16 // Index for a CPAL palette entry. + Alpha Fixed214 // Alpha value. +} + +// (format 3) +type PaintVarSolid struct { + format byte `unionTag:"3"` + PaletteIndex uint16 // Index for a CPAL palette entry. + Alpha Fixed214 // Alpha value. For variation, use varIndexBase + 0. + VarIndexBase uint32 // Base index into DeltaSetIndexMap. +} + +// (format 4) +type PaintLinearGradient struct { + format byte `unionTag:"4"` + ColorLineOffset Offset24 // Offset to ColorLine table, from beginning of PaintLinearGradient table. + X0 int16 // Start point (p₀) x coordinate. + Y0 int16 // Start point (p₀) y coordinate. + X1 int16 // End point (p₁) x coordinate. + Y1 int16 // End point (p₁) y coordinate. + X2 int16 // Rotation point (p₂) x coordinate. + Y2 int16 // Rotation point (p₂) y coordinate. +} + +// (format 5) +type PaintVarLinearGradient struct { + format byte `unionTag:"5"` + ColorLineOffset Offset24 // Offset to VarColorLine table, from beginning of PaintVarLinearGradient table. + X0 int16 // Start point (p₀) x coordinate. For variation, use varIndexBase + 0. + Y0 int16 // Start point (p₀) y coordinate. For variation, use varIndexBase + 1. + X1 int16 // End point (p₁) x coordinate. For variation, use varIndexBase + 2. + Y1 int16 // End point (p₁) y coordinate. For variation, use varIndexBase + 3. + X2 int16 // Rotation point (p₂) x coordinate. For variation, use varIndexBase + 4. + Y2 int16 // Rotation point (p₂) y coordinate. For variation, use varIndexBase + 5. + VarIndexBase uint32 // Base index into DeltaSetIndexMap. +} + +// (format 6) +type PaintRadialGradient struct { + format byte `unionTag:"6"` + ColorLineOffset Offset24 // Offset to ColorLine table, from beginning of PaintRadialGradient table. + X0 int16 // Start circle center x coordinate. + Y0 int16 // Start circle center y coordinate. + Radius0 uint16 // Start circle radius. + X1 int16 // End circle center x coordinate. + Y1 int16 // End circle center y coordinate. + Radius1 uint16 // End circle radius. +} + +// (format 7) +type PaintVarRadialGradient struct { + format byte `unionTag:"7"` + ColorLineOffset Offset24 // Offset to VarColorLine table, from beginning of PaintVarRadialGradient table. + X0 int16 // Start circle center x coordinate. For variation, use varIndexBase + 0. + Y0 int16 // Start circle center y coordinate. For variation, use varIndexBase + 1. + Radius0 uint16 // Start circle radius. For variation, use varIndexBase + 2. + X1 int16 // End circle center x coordinate. For variation, use varIndexBase + 3. + Y1 int16 // End circle center y coordinate. For variation, use varIndexBase + 4. + Radius1 uint16 // End circle radius. For variation, use varIndexBase + 5. + VarIndexBase uint32 // Base index into DeltaSetIndexMap. +} + +// (format 8) +type PaintSweepGradient struct { + format byte `unionTag:"8"` + ColorLineOffset Offset24 // Offset to ColorLine table, from beginning of PaintSweepGradient table. + CenterX int16 // Center x coordinate. + CenterY int16 // Center y coordinate. + StartAngle Fixed214 // Start of the angular range of the gradient: add 1.0 and multiply by 180° to retrieve counter-clockwise degrees. + EndAngle Fixed214 // End of the angular range of the gradient: add 1.0 and multiply by 180° to retrieve counter-clockwise degrees. +} + +// (format 9) +type PaintVarSweepGradient struct { + format byte `unionTag:"9"` + ColorLineOffset Offset24 // Offset to VarColorLine table, from beginning of PaintVarSweepGradient table. + CenterX int16 // Center x coordinate. For variation, use varIndexBase + 0. + CenterY int16 // Center y coordinate. For variation, use varIndexBase + 1. + StartAngle Fixed214 // Start of the angular range of the gradient: add 1.0 and multiply by 180° to retrieve counter-clockwise degrees. For variation, use varIndexBase + 2. + EndAngle Fixed214 // End of the angular range of the gradient: add 1.0 and multiply by 180° to retrieve counter-clockwise degrees. For variation, use varIndexBase + 3. + VarIndexBase uint32 // Base index into DeltaSetIndexMap. +} + +// (format 10) +type PaintGlyph struct { + format byte `unionTag:"10"` + PaintOffset Offset24 // Offset to a Paint table, from beginning of PaintGlyph table. + GlyphID uint16 // Glyph ID for the source outline. +} + +// (format 11) +type PaintColrGlyph struct { + format byte `unionTag:"11"` + GlyphID uint16 // Glyph ID for a BaseGlyphList base glyph. +} + +// (format 12) +type PaintTransform struct { + format byte `unionTag:"12"` + PaintOffset Offset24 // Offset to a Paint subtable, from beginning of PaintTransform table. + TransformOffset Offset24 // Offset to an Affine2x3 table, from beginning of PaintTransform table. +} + +// (format 13) +type PaintVarTransform struct { + format byte `unionTag:"13"` + PaintOffset Offset24 // Offset to a Paint subtable, from beginning of PaintVarTransform table. + TransformOffset Offset24 // Offset to a VarAffine2x3 table, from beginning of PaintVarTransform table. +} + +// (format 14) +type PaintTranslate struct { + format byte `unionTag:"14"` + PaintOffset Offset24 // Offset to a Paint subtable, from beginning of PaintTranslate table. + Dx int16 // Translation in x direction. + Dy int16 // Translation in y direction. +} + +// (format 15) +type PaintVarTranslate struct { + format byte `unionTag:"15"` + PaintOffset Offset24 // Offset to a Paint subtable, from beginning of PaintVarTranslate table. + Dx int16 // Translation in x direction. For variation, use varIndexBase + 0. + Dy int16 // Translation in y direction. For variation, use varIndexBase + 1. + VarIndexBase uint32 // Base index into DeltaSetIndexMap. +} + +// (format 16) +type PaintScale struct { + format byte `unionTag:"16"` + PaintOffset Offset24 // Offset to a Paint subtable, from beginning of PaintScale table. + ScaleX Fixed214 // Scale factor in x direction. + ScaleY Fixed214 // Scale factor in y direction. +} + +// (format 17) +type PaintVarScale struct { + format byte `unionTag:"17"` + PaintOffset Offset24 // Offset to a Paint subtable, from beginning of PaintVarScale table. + ScaleX Fixed214 // Scale factor in x direction. For variation, use varIndexBase + 0. + ScaleY Fixed214 // Scale factor in y direction. For variation, use varIndexBase + 1. + VarIndexBase uint32 // Base index into DeltaSetIndexMap. +} + +// (format 18) +type PaintScaleAroundCenter struct { + format byte `unionTag:"18"` + PaintOffset Offset24 // Offset to a Paint subtable, from beginning of PaintScaleAroundCenter table. + ScaleX Fixed214 // Scale factor in x direction. + ScaleY Fixed214 // Scale factor in y direction. + CenterX int16 // x coordinate for the center of scaling. + CenterY int16 // y coordinate for the center of scaling. +} + +// (format 19) +type PaintVarScaleAroundCenter struct { + format byte `unionTag:"19"` + PaintOffset Offset24 // Offset to a Paint subtable, from beginning of PaintVarScaleAroundCenter table. + ScaleX Fixed214 // Scale factor in x direction. For variation, use varIndexBase + 0. + ScaleY Fixed214 // Scale factor in y direction. For variation, use varIndexBase + 1. + CenterX int16 // x coordinate for the center of scaling. For variation, use varIndexBase + 2. + CenterY int16 // y coordinate for the center of scaling. For variation, use varIndexBase + 3. + VarIndexBase uint32 // Base index into DeltaSetIndexMap. +} + +// (format 20) +type PaintScaleUniform struct { + format byte `unionTag:"20"` + PaintOffset Offset24 // Offset to a Paint subtable, from beginning of PaintScaleUniform table. + Scale Fixed214 // Scale factor in x and y directions. +} + +// (format 21) +type PaintVarScaleUniform struct { + format byte `unionTag:"21"` + PaintOffset Offset24 // Offset to a Paint subtable, from beginning of PaintVarScaleUniform table. + Scale Fixed214 // Scale factor in x and y directions. For variation, use varIndexBase + 0. + VarIndexBase uint32 // Base index into DeltaSetIndexMap. +} + +// (format 22) +type PaintScaleUniformAroundCenter struct { + format byte `unionTag:"22"` + PaintOffset Offset24 // Offset to a Paint subtable, from beginning of PaintScaleUniformAroundCenter table. + Scale Fixed214 // Scale factor in x and y directions. + CenterX int16 // x coordinate for the center of scaling. + CenterY int16 // y coordinate for the center of scaling. +} + +// (format 23) +type PaintVarScaleUniformAroundCenter struct { + format byte `unionTag:"23"` + PaintOffset Offset24 // Offset to a Paint subtable, from beginning of PaintVarScaleUniformAroundCenter table. + Scale Fixed214 // Scale factor in x and y directions. For variation, use varIndexBase + 0. + CenterX int16 // x coordinate for the center of scaling. For variation, use varIndexBase + 1. + CenterY int16 // y coordinate for the center of scaling. For variation, use varIndexBase + 2. + VarIndexBase uint32 // Base index into DeltaSetIndexMap. +} + +// (format 24) +type PaintRotate struct { + format byte `unionTag:"24"` + PaintOffset Offset24 // Offset to a Paint subtable, from beginning of PaintRotate table. + Angle Fixed214 // Rotation angle, 180° in counter-clockwise degrees per 1.0 of value. +} + +// (format 25) +type PaintVarRotate struct { + format byte `unionTag:"25"` + PaintOffset Offset24 // Offset to a Paint subtable, from beginning of PaintVarRotate table. + Angle Fixed214 // Rotation angle, 180° in counter-clockwise degrees per 1.0 of value. For variation, use varIndexBase + 0. + VarIndexBase uint32 // Base index into DeltaSetIndexMap. +} + +// (format 26) +type PaintRotateAroundCenter struct { + format byte `unionTag:"26"` + PaintOffset Offset24 // Offset to a Paint subtable, from beginning of PaintRotateAroundCenter table. + Angle Fixed214 // Rotation angle, 180° in counter-clockwise degrees per 1.0 of value. + CenterX int16 // x coordinate for the center of rotation. + CenterY int16 // y coordinate for the center of rotation. +} + +// (format 27) +type PaintVarRotateAroundCenter struct { + format byte `unionTag:"27"` + PaintOffset Offset24 // Offset to a Paint subtable, from beginning of PaintVarRotateAroundCenter table. + Angle Fixed214 // Rotation angle, 180° in counter-clockwise degrees per 1.0 of value. For variation, use varIndexBase + 0. + CenterX int16 // x coordinate for the center of rotation. For variation, use varIndexBase + 1. + CenterY int16 // y coordinate for the center of rotation. For variation, use varIndexBase + 2. + VarIndexBase uint32 // Base index into DeltaSetIndexMap. +} + +// (format 28) +type PaintSkew struct { + format byte `unionTag:"28"` + PaintOffset Offset24 // Offset to a Paint subtable, from beginning of PaintSkew table. + XSkewAngle Fixed214 // Angle of skew in the direction of the x-axis, 180° in counter-clockwise degrees per 1.0 of value. + YSkewAngle Fixed214 // Angle of skew in the direction of the y-axis, 180° in counter-clockwise degrees per 1.0 of value. +} + +// (format 29) +type PaintVarSkew struct { + format byte `unionTag:"29"` + PaintOffset Offset24 // Offset to a Paint subtable, from beginning of PaintVarSkew table. + XSkewAngle Fixed214 // Angle of skew in the direction of the x-axis, 180° in counter-clockwise degrees per 1.0 of value. For variation, use varIndexBase + 0. + YSkewAngle Fixed214 // Angle of skew in the direction of the y-axis, 180° in counter-clockwise degrees per 1.0 of value. For variation, use varIndexBase + 1. + VarIndexBase uint32 // Base index into DeltaSetIndexMap. +} + +// (format 30) +type PaintSkewAroundCenter struct { + format byte `unionTag:"30"` + PaintOffset Offset24 // Offset to a Paint subtable, from beginning of PaintSkewAroundCenter table. + XSkewAngle Fixed214 // Angle of skew in the direction of the x-axis, 180° in counter-clockwise degrees per 1.0 of value. + YSkewAngle Fixed214 // Angle of skew in the direction of the y-axis, 180° in counter-clockwise degrees per 1.0 of value. + CenterX int16 // x coordinate for the center of rotation. + CenterY int16 // y coordinate for the center of rotation. +} + +// (format 31) +type PaintVarSkewAroundCenter struct { + format byte `unionTag:"31"` + PaintOffset Offset24 // Offset to a Paint subtable, from beginning of PaintVarSkewAroundCenter table. + XSkewAngle Fixed214 // Angle of skew in the direction of the x-axis, 180° in counter-clockwise degrees per 1.0 of value. For variation, use varIndexBase + 0. + YSkewAngle Fixed214 // Angle of skew in the direction of the y-axis, 180° in counter-clockwise degrees per 1.0 of value. For variation, use varIndexBase + 1. + CenterX int16 // x coordinate for the center of rotation. For variation, use varIndexBase + 2. + CenterY int16 // y coordinate for the center of rotation. For variation, use varIndexBase + 3. + VarIndexBase uint32 // Base index into DeltaSetIndexMap. +} + +// (format 32) +type PaintComposite struct { + format byte `unionTag:"32"` + SourcePaintOffset Offset24 // Offset to a source Paint table, from beginning of PaintComposite table. + CompositeMode uint8 // A CompositeMode enumeration value. + BackdropPaintOffset Offset24 // Offset to a backdrop Paint table, from beginning of PaintComposite table. +} diff --git a/font/opentype/tables/tables.go b/font/opentype/tables/tables.go index 1ba47462..9d1d4c69 100644 --- a/font/opentype/tables/tables.go +++ b/font/opentype/tables/tables.go @@ -26,6 +26,9 @@ func Float1616ToUint(f Float1616) uint32 { return uint32(int32(f * (1 << 16))) } +// Fixed214 is a number stored as a fixed 2.14 integer +type Fixed214 = Coord + func Float214FromUint(v uint16) float32 { // value are actually signed integers return float32(int16(v)) / (1 << 14) @@ -38,6 +41,11 @@ func NewCoord(c float64) Coord { return Coord(c * (1 << 14)) } +func readUint24(b []byte) uint32 { + _ = b[2] // bounds check hint to compiler; see golang.org/issue/14808 + return uint32(b[2]) | uint32(b[1])<<8 | uint32(b[0])<<16 +} + // Number of seconds since 12:00 midnight that started January 1st 1904 in GMT/UTC time zone. type longdatetime = uint64 diff --git a/font/opentype/tables/xvar_gen.go b/font/opentype/tables/xvar_gen.go index ea35afaf..80e7b83e 100755 --- a/font/opentype/tables/xvar_gen.go +++ b/font/opentype/tables/xvar_gen.go @@ -44,27 +44,6 @@ func ParseAvar(src []byte) (Avar, int, error) { return item, n, nil } -func ParseDeltaSetMapping(src []byte) (DeltaSetMapping, int, error) { - var item DeltaSetMapping - n := 0 - if L := len(src); L < 2 { - return item, 0, fmt.Errorf("reading DeltaSetMapping: "+"EOF: expected length: 2, got %d", L) - } - _ = src[1] // early bound checking - item.format = src[0] - item.entryFormat = src[1] - n += 2 - - { - - err := item.parseMap(src[2:]) - if err != nil { - return item, 0, fmt.Errorf("reading DeltaSetMapping: %s", err) - } - } - return item, n, nil -} - func ParseFvar(src []byte) (Fvar, int, error) { var item Fvar n := 0 @@ -320,97 +299,6 @@ func ParseInstanceRecord(src []byte, coordinatesCount int) (InstanceRecord, int, return item, n, nil } -func ParseItemVarStore(src []byte) (ItemVarStore, int, error) { - var item ItemVarStore - n := 0 - if L := len(src); L < 8 { - return item, 0, fmt.Errorf("reading ItemVarStore: "+"EOF: expected length: 8, got %d", L) - } - _ = src[7] // early bound checking - item.format = binary.BigEndian.Uint16(src[0:]) - offsetVariationRegionList := int(binary.BigEndian.Uint32(src[2:])) - arrayLengthItemVariationDatas := int(binary.BigEndian.Uint16(src[6:])) - n += 8 - - { - - if offsetVariationRegionList != 0 { // ignore null offset - if L := len(src); L < offsetVariationRegionList { - return item, 0, fmt.Errorf("reading ItemVarStore: "+"EOF: expected length: %d, got %d", offsetVariationRegionList, L) - } - - var err error - item.VariationRegionList, _, err = ParseVariationRegionList(src[offsetVariationRegionList:]) - if err != nil { - return item, 0, fmt.Errorf("reading ItemVarStore: %s", err) - } - - } - } - { - - if L := len(src); L < 8+arrayLengthItemVariationDatas*4 { - return item, 0, fmt.Errorf("reading ItemVarStore: "+"EOF: expected length: %d, got %d", 8+arrayLengthItemVariationDatas*4, L) - } - - item.ItemVariationDatas = make([]ItemVariationData, arrayLengthItemVariationDatas) // allocation guarded by the previous check - for i := range item.ItemVariationDatas { - offset := int(binary.BigEndian.Uint32(src[8+i*4:])) - // ignore null offsets - if offset == 0 { - continue - } - - if L := len(src); L < offset { - return item, 0, fmt.Errorf("reading ItemVarStore: "+"EOF: expected length: %d, got %d", offset, L) - } - - var err error - item.ItemVariationDatas[i], _, err = ParseItemVariationData(src[offset:]) - if err != nil { - return item, 0, fmt.Errorf("reading ItemVarStore: %s", err) - } - } - n += arrayLengthItemVariationDatas * 4 - } - return item, n, nil -} - -func ParseItemVariationData(src []byte) (ItemVariationData, int, error) { - var item ItemVariationData - n := 0 - if L := len(src); L < 6 { - return item, 0, fmt.Errorf("reading ItemVariationData: "+"EOF: expected length: 6, got %d", L) - } - _ = src[5] // early bound checking - item.itemCount = binary.BigEndian.Uint16(src[0:]) - item.wordDeltaCount = binary.BigEndian.Uint16(src[2:]) - item.regionIndexCount = binary.BigEndian.Uint16(src[4:]) - n += 6 - - { - arrayLength := int(item.regionIndexCount) - - if L := len(src); L < 6+arrayLength*2 { - return item, 0, fmt.Errorf("reading ItemVariationData: "+"EOF: expected length: %d, got %d", 6+arrayLength*2, L) - } - - item.RegionIndexes = make([]uint16, arrayLength) // allocation guarded by the previous check - for i := range item.RegionIndexes { - item.RegionIndexes[i] = binary.BigEndian.Uint16(src[6+i*2:]) - } - n += arrayLength * 2 - } - { - - err := item.parseDeltaSets(src[n:]) - if err != nil { - return item, 0, fmt.Errorf("reading ItemVariationData: %s", err) - } - } - return item, n, nil -} - func ParseMVAR(src []byte) (MVAR, int, error) { var item MVAR n := 0 @@ -553,58 +441,6 @@ func ParseVarValueRecord(src []byte) (VarValueRecord, int, error) { return item, n, nil } -func ParseVariationRegion(src []byte, regionAxesCount int) (VariationRegion, int, error) { - var item VariationRegion - n := 0 - { - - if L := len(src); L < regionAxesCount*6 { - return item, 0, fmt.Errorf("reading VariationRegion: "+"EOF: expected length: %d, got %d", regionAxesCount*6, L) - } - - item.RegionAxes = make([]RegionAxisCoordinates, regionAxesCount) // allocation guarded by the previous check - for i := range item.RegionAxes { - item.RegionAxes[i].mustParse(src[i*6:]) - } - n += regionAxesCount * 6 - } - return item, n, nil -} - -func ParseVariationRegionList(src []byte) (VariationRegionList, int, error) { - var item VariationRegionList - n := 0 - if L := len(src); L < 4 { - return item, 0, fmt.Errorf("reading VariationRegionList: "+"EOF: expected length: 4, got %d", L) - } - _ = src[3] // early bound checking - item.axisCount = binary.BigEndian.Uint16(src[0:]) - arrayLengthVariationRegions := int(binary.BigEndian.Uint16(src[2:])) - n += 4 - - { - - offset := 4 - for i := 0; i < arrayLengthVariationRegions; i++ { - elem, read, err := ParseVariationRegion(src[offset:], int(item.axisCount)) - if err != nil { - return item, 0, fmt.Errorf("reading VariationRegionList: %s", err) - } - item.VariationRegions = append(item.VariationRegions, elem) - offset += read - } - n = offset - } - return item, n, nil -} - -func (item *RegionAxisCoordinates) mustParse(src []byte) { - _ = src[5] // early bound checking - item.StartCoord = Coord(binary.BigEndian.Uint16(src[0:])) - item.PeakCoord = Coord(binary.BigEndian.Uint16(src[2:])) - item.EndCoord = Coord(binary.BigEndian.Uint16(src[4:])) -} - func (item *VarValueRecord) mustParse(src []byte) { _ = src[7] // early bound checking item.ValueTag = Tag(binary.BigEndian.Uint32(src[0:])) From bd0029b43cf5344ff8dd81505ff31cc67630ae0a Mon Sep 17 00:00:00 2001 From: Benoit KUGLER Date: Wed, 18 Jun 2025 13:01:14 +0200 Subject: [PATCH 2/8] bump dev deps --- go.mod | 2 +- go.sum | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index fbe50767..e4fb54f2 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/go-text/typesetting go 1.19 require ( - github.com/go-text/typesetting-utils v0.0.0-20250527170436-63e4acdcf075 + github.com/go-text/typesetting-utils v0.0.0-20250618105726-5072bae158e0 golang.org/x/image v0.23.0 golang.org/x/text v0.21.0 ) diff --git a/go.sum b/go.sum index b269cffe..7c6390fd 100644 --- a/go.sum +++ b/go.sum @@ -1,20 +1,37 @@ github.com/go-text/typesetting-utils v0.0.0-20250527170436-63e4acdcf075 h1:zRaPuzKe/+Euzz3WwE0YwjXAX4IwvGPRH6abtmRI/4M= github.com/go-text/typesetting-utils v0.0.0-20250527170436-63e4acdcf075/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o= +github.com/go-text/typesetting-utils v0.0.0-20250618105726-5072bae158e0 h1:kuHSF7hY9bEOjNIbTV6QtfkLOUJFGNkDDZ3FtxHUjGA= +github.com/go-text/typesetting-utils v0.0.0-20250618105726-5072bae158e0/go.mod h1:3/62I4La/HBRX9TcTpBj4eipLiwzf+vhI+7whTc9V7o= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68= golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -22,19 +39,33 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 55334a4f6fb830be1be1305f0239088800e7c309 Mon Sep 17 00:00:00 2001 From: Benoit KUGLER Date: Wed, 18 Jun 2025 13:08:27 +0200 Subject: [PATCH 3/8] fix dev dep --- font/opentype/tables/glyphs_color_test.go | 4 ++-- go.mod | 2 +- go.sum | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/font/opentype/tables/glyphs_color_test.go b/font/opentype/tables/glyphs_color_test.go index 567e0d56..6abd4fde 100644 --- a/font/opentype/tables/glyphs_color_test.go +++ b/font/opentype/tables/glyphs_color_test.go @@ -7,7 +7,7 @@ import ( ) func TestCOLR(t *testing.T) { - ft := readFontFile(t, "common/NotoColorEmoji-Regular.ttf") + 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) @@ -18,7 +18,7 @@ func TestCOLR(t *testing.T) { tu.Assert(t, colr.ClipList.Clips[0].ClipBox == ClipBoxFormat1{1, 480, 192, 800, 512}) tu.Assert(t, colr.VarIndexMap == nil && colr.ItemVariationStore == nil) - ft = readFontFile(t, "common/CoralPixels-Regular.ttf") + 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) diff --git a/go.mod b/go.mod index e4fb54f2..b7bfab1e 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/go-text/typesetting go 1.19 require ( - github.com/go-text/typesetting-utils v0.0.0-20250618105726-5072bae158e0 + github.com/go-text/typesetting-utils v0.0.0-20250618110550-c820a94c77b8 golang.org/x/image v0.23.0 golang.org/x/text v0.21.0 ) diff --git a/go.sum b/go.sum index 7c6390fd..9c75081e 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ github.com/go-text/typesetting-utils v0.0.0-20250527170436-63e4acdcf075 h1:zRaPu github.com/go-text/typesetting-utils v0.0.0-20250527170436-63e4acdcf075/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o= github.com/go-text/typesetting-utils v0.0.0-20250618105726-5072bae158e0 h1:kuHSF7hY9bEOjNIbTV6QtfkLOUJFGNkDDZ3FtxHUjGA= github.com/go-text/typesetting-utils v0.0.0-20250618105726-5072bae158e0/go.mod h1:3/62I4La/HBRX9TcTpBj4eipLiwzf+vhI+7whTc9V7o= +github.com/go-text/typesetting-utils v0.0.0-20250618110550-c820a94c77b8 h1:4KCscI9qYWMGTuz6BpJtbUSRzcBrUSSE0ENMJbNSrFs= +github.com/go-text/typesetting-utils v0.0.0-20250618110550-c820a94c77b8/go.mod h1:3/62I4La/HBRX9TcTpBj4eipLiwzf+vhI+7whTc9V7o= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= From 93d7609a268a2056ab2c0d0d738c2b8b36f8ac53 Mon Sep 17 00:00:00 2001 From: Benoit KUGLER Date: Fri, 20 Jun 2025 11:44:27 +0200 Subject: [PATCH 4/8] [opentype] support for COLRv1 table --- font/opentype/tables/glyphs_color_test.go | 10 +- font/opentype/tables/glyphs_colr_gen.go | 1216 +++++++++++++++------ font/opentype/tables/glyphs_colr_src.go | 398 ++++--- 3 files changed, 1107 insertions(+), 517 deletions(-) diff --git a/font/opentype/tables/glyphs_color_test.go b/font/opentype/tables/glyphs_color_test.go index 6abd4fde..cbd131ee 100644 --- a/font/opentype/tables/glyphs_color_test.go +++ b/font/opentype/tables/glyphs_color_test.go @@ -14,9 +14,17 @@ func TestCOLR(t *testing.T) { 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, len(colr.LayerList.PaintTables) == 69264) 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) + + // 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) ft = readFontFile(t, "color/CoralPixels-Regular.ttf") colr, err = ParseCOLR(readTable(t, ft, "COLR")) diff --git a/font/opentype/tables/glyphs_colr_gen.go b/font/opentype/tables/glyphs_colr_gen.go index 8c8bd65c..6651580b 100755 --- a/font/opentype/tables/glyphs_colr_gen.go +++ b/font/opentype/tables/glyphs_colr_gen.go @@ -9,6 +9,16 @@ import ( // Code generated by binarygen from glyphs_colr_src.go. DO NOT EDIT +func (item *Affine2x3) mustParse(src []byte) { + _ = src[23] // early bound checking + item.Xx = Float1616FromUint(binary.BigEndian.Uint32(src[0:])) + item.Yx = Float1616FromUint(binary.BigEndian.Uint32(src[4:])) + item.Xy = Float1616FromUint(binary.BigEndian.Uint32(src[8:])) + item.Yy = Float1616FromUint(binary.BigEndian.Uint32(src[12:])) + item.Dx = Float1616FromUint(binary.BigEndian.Uint32(src[16:])) + item.Dy = Float1616FromUint(binary.BigEndian.Uint32(src[20:])) +} + func (item *BaseGlyph) mustParse(src []byte) { _ = src[5] // early bound checking item.GlyphID = binary.BigEndian.Uint16(src[0:]) @@ -35,6 +45,13 @@ func (item *ClipBoxFormat2) mustParse(src []byte) { item.VarIndexBase = binary.BigEndian.Uint32(src[9:]) } +func (item *ColorStop) mustParse(src []byte) { + _ = src[5] // early bound checking + item.StopOffset = Coord(binary.BigEndian.Uint16(src[0:])) + item.PaletteIndex = binary.BigEndian.Uint16(src[2:]) + item.Alpha = Coord(binary.BigEndian.Uint16(src[4:])) +} + func (item *Layer) mustParse(src []byte) { _ = src[3] // early bound checking item.GlyphID = binary.BigEndian.Uint16(src[0:]) @@ -54,342 +71,30 @@ func (item *PaintColrLayers) mustParse(src []byte) { item.FirstLayerIndex = binary.BigEndian.Uint32(src[2:]) } -func (item *PaintComposite) mustParse(src []byte) { - _ = src[7] // early bound checking - item.format = src[0] - item.SourcePaintOffset[0] = src[1] - item.SourcePaintOffset[1] = src[2] - item.SourcePaintOffset[2] = src[3] - item.CompositeMode = src[4] - item.BackdropPaintOffset[0] = src[5] - item.BackdropPaintOffset[1] = src[6] - item.BackdropPaintOffset[2] = src[7] -} - -func (item *PaintGlyph) mustParse(src []byte) { - _ = src[5] // early bound checking - item.format = src[0] - item.PaintOffset[0] = src[1] - item.PaintOffset[1] = src[2] - item.PaintOffset[2] = src[3] - item.GlyphID = binary.BigEndian.Uint16(src[4:]) -} - -func (item *PaintLinearGradient) mustParse(src []byte) { - _ = src[15] // early bound checking - item.format = src[0] - item.ColorLineOffset[0] = src[1] - item.ColorLineOffset[1] = src[2] - item.ColorLineOffset[2] = src[3] - item.X0 = int16(binary.BigEndian.Uint16(src[4:])) - item.Y0 = int16(binary.BigEndian.Uint16(src[6:])) - item.X1 = int16(binary.BigEndian.Uint16(src[8:])) - item.Y1 = int16(binary.BigEndian.Uint16(src[10:])) - item.X2 = int16(binary.BigEndian.Uint16(src[12:])) - item.Y2 = int16(binary.BigEndian.Uint16(src[14:])) -} - -func (item *PaintRadialGradient) mustParse(src []byte) { - _ = src[15] // early bound checking - item.format = src[0] - item.ColorLineOffset[0] = src[1] - item.ColorLineOffset[1] = src[2] - item.ColorLineOffset[2] = src[3] - item.X0 = int16(binary.BigEndian.Uint16(src[4:])) - item.Y0 = int16(binary.BigEndian.Uint16(src[6:])) - item.Radius0 = binary.BigEndian.Uint16(src[8:]) - item.X1 = int16(binary.BigEndian.Uint16(src[10:])) - item.Y1 = int16(binary.BigEndian.Uint16(src[12:])) - item.Radius1 = binary.BigEndian.Uint16(src[14:]) -} - -func (item *PaintRotate) mustParse(src []byte) { - _ = src[5] // early bound checking - item.format = src[0] - item.PaintOffset[0] = src[1] - item.PaintOffset[1] = src[2] - item.PaintOffset[2] = src[3] - item.Angle = Fixed214(binary.BigEndian.Uint16(src[4:])) -} - -func (item *PaintRotateAroundCenter) mustParse(src []byte) { - _ = src[9] // early bound checking - item.format = src[0] - item.PaintOffset[0] = src[1] - item.PaintOffset[1] = src[2] - item.PaintOffset[2] = src[3] - item.Angle = Fixed214(binary.BigEndian.Uint16(src[4:])) - item.CenterX = int16(binary.BigEndian.Uint16(src[6:])) - item.CenterY = int16(binary.BigEndian.Uint16(src[8:])) -} - -func (item *PaintScale) mustParse(src []byte) { - _ = src[7] // early bound checking - item.format = src[0] - item.PaintOffset[0] = src[1] - item.PaintOffset[1] = src[2] - item.PaintOffset[2] = src[3] - item.ScaleX = Fixed214(binary.BigEndian.Uint16(src[4:])) - item.ScaleY = Fixed214(binary.BigEndian.Uint16(src[6:])) -} - -func (item *PaintScaleAroundCenter) mustParse(src []byte) { - _ = src[11] // early bound checking - item.format = src[0] - item.PaintOffset[0] = src[1] - item.PaintOffset[1] = src[2] - item.PaintOffset[2] = src[3] - item.ScaleX = Fixed214(binary.BigEndian.Uint16(src[4:])) - item.ScaleY = Fixed214(binary.BigEndian.Uint16(src[6:])) - item.CenterX = int16(binary.BigEndian.Uint16(src[8:])) - item.CenterY = int16(binary.BigEndian.Uint16(src[10:])) -} - -func (item *PaintScaleUniform) mustParse(src []byte) { - _ = src[5] // early bound checking - item.format = src[0] - item.PaintOffset[0] = src[1] - item.PaintOffset[1] = src[2] - item.PaintOffset[2] = src[3] - item.Scale = Fixed214(binary.BigEndian.Uint16(src[4:])) -} - -func (item *PaintScaleUniformAroundCenter) mustParse(src []byte) { - _ = src[9] // early bound checking - item.format = src[0] - item.PaintOffset[0] = src[1] - item.PaintOffset[1] = src[2] - item.PaintOffset[2] = src[3] - item.Scale = Fixed214(binary.BigEndian.Uint16(src[4:])) - item.CenterX = int16(binary.BigEndian.Uint16(src[6:])) - item.CenterY = int16(binary.BigEndian.Uint16(src[8:])) -} - -func (item *PaintSkew) mustParse(src []byte) { - _ = src[7] // early bound checking - item.format = src[0] - item.PaintOffset[0] = src[1] - item.PaintOffset[1] = src[2] - item.PaintOffset[2] = src[3] - item.XSkewAngle = Fixed214(binary.BigEndian.Uint16(src[4:])) - item.YSkewAngle = Fixed214(binary.BigEndian.Uint16(src[6:])) -} - -func (item *PaintSkewAroundCenter) mustParse(src []byte) { - _ = src[11] // early bound checking - item.format = src[0] - item.PaintOffset[0] = src[1] - item.PaintOffset[1] = src[2] - item.PaintOffset[2] = src[3] - item.XSkewAngle = Fixed214(binary.BigEndian.Uint16(src[4:])) - item.YSkewAngle = Fixed214(binary.BigEndian.Uint16(src[6:])) - item.CenterX = int16(binary.BigEndian.Uint16(src[8:])) - item.CenterY = int16(binary.BigEndian.Uint16(src[10:])) -} - func (item *PaintSolid) mustParse(src []byte) { _ = src[4] // early bound checking item.format = src[0] item.PaletteIndex = binary.BigEndian.Uint16(src[1:]) - item.Alpha = Fixed214(binary.BigEndian.Uint16(src[3:])) -} - -func (item *PaintSweepGradient) mustParse(src []byte) { - _ = src[11] // early bound checking - item.format = src[0] - item.ColorLineOffset[0] = src[1] - item.ColorLineOffset[1] = src[2] - item.ColorLineOffset[2] = src[3] - item.CenterX = int16(binary.BigEndian.Uint16(src[4:])) - item.CenterY = int16(binary.BigEndian.Uint16(src[6:])) - item.StartAngle = Fixed214(binary.BigEndian.Uint16(src[8:])) - item.EndAngle = Fixed214(binary.BigEndian.Uint16(src[10:])) -} - -func (item *PaintTransform) mustParse(src []byte) { - _ = src[6] // early bound checking - item.format = src[0] - item.PaintOffset[0] = src[1] - item.PaintOffset[1] = src[2] - item.PaintOffset[2] = src[3] - item.TransformOffset[0] = src[4] - item.TransformOffset[1] = src[5] - item.TransformOffset[2] = src[6] -} - -func (item *PaintTranslate) mustParse(src []byte) { - _ = src[7] // early bound checking - item.format = src[0] - item.PaintOffset[0] = src[1] - item.PaintOffset[1] = src[2] - item.PaintOffset[2] = src[3] - item.Dx = int16(binary.BigEndian.Uint16(src[4:])) - item.Dy = int16(binary.BigEndian.Uint16(src[6:])) -} - -func (item *PaintVarLinearGradient) mustParse(src []byte) { - _ = src[19] // early bound checking - item.format = src[0] - item.ColorLineOffset[0] = src[1] - item.ColorLineOffset[1] = src[2] - item.ColorLineOffset[2] = src[3] - item.X0 = int16(binary.BigEndian.Uint16(src[4:])) - item.Y0 = int16(binary.BigEndian.Uint16(src[6:])) - item.X1 = int16(binary.BigEndian.Uint16(src[8:])) - item.Y1 = int16(binary.BigEndian.Uint16(src[10:])) - item.X2 = int16(binary.BigEndian.Uint16(src[12:])) - item.Y2 = int16(binary.BigEndian.Uint16(src[14:])) - item.VarIndexBase = binary.BigEndian.Uint32(src[16:]) -} - -func (item *PaintVarRadialGradient) mustParse(src []byte) { - _ = src[19] // early bound checking - item.format = src[0] - item.ColorLineOffset[0] = src[1] - item.ColorLineOffset[1] = src[2] - item.ColorLineOffset[2] = src[3] - item.X0 = int16(binary.BigEndian.Uint16(src[4:])) - item.Y0 = int16(binary.BigEndian.Uint16(src[6:])) - item.Radius0 = binary.BigEndian.Uint16(src[8:]) - item.X1 = int16(binary.BigEndian.Uint16(src[10:])) - item.Y1 = int16(binary.BigEndian.Uint16(src[12:])) - item.Radius1 = binary.BigEndian.Uint16(src[14:]) - item.VarIndexBase = binary.BigEndian.Uint32(src[16:]) -} - -func (item *PaintVarRotate) mustParse(src []byte) { - _ = src[9] // early bound checking - item.format = src[0] - item.PaintOffset[0] = src[1] - item.PaintOffset[1] = src[2] - item.PaintOffset[2] = src[3] - item.Angle = Fixed214(binary.BigEndian.Uint16(src[4:])) - item.VarIndexBase = binary.BigEndian.Uint32(src[6:]) -} - -func (item *PaintVarRotateAroundCenter) mustParse(src []byte) { - _ = src[13] // early bound checking - item.format = src[0] - item.PaintOffset[0] = src[1] - item.PaintOffset[1] = src[2] - item.PaintOffset[2] = src[3] - item.Angle = Fixed214(binary.BigEndian.Uint16(src[4:])) - item.CenterX = int16(binary.BigEndian.Uint16(src[6:])) - item.CenterY = int16(binary.BigEndian.Uint16(src[8:])) - item.VarIndexBase = binary.BigEndian.Uint32(src[10:]) -} - -func (item *PaintVarScale) mustParse(src []byte) { - _ = src[11] // early bound checking - item.format = src[0] - item.PaintOffset[0] = src[1] - item.PaintOffset[1] = src[2] - item.PaintOffset[2] = src[3] - item.ScaleX = Fixed214(binary.BigEndian.Uint16(src[4:])) - item.ScaleY = Fixed214(binary.BigEndian.Uint16(src[6:])) - item.VarIndexBase = binary.BigEndian.Uint32(src[8:]) -} - -func (item *PaintVarScaleAroundCenter) mustParse(src []byte) { - _ = src[15] // early bound checking - item.format = src[0] - item.PaintOffset[0] = src[1] - item.PaintOffset[1] = src[2] - item.PaintOffset[2] = src[3] - item.ScaleX = Fixed214(binary.BigEndian.Uint16(src[4:])) - item.ScaleY = Fixed214(binary.BigEndian.Uint16(src[6:])) - item.CenterX = int16(binary.BigEndian.Uint16(src[8:])) - item.CenterY = int16(binary.BigEndian.Uint16(src[10:])) - item.VarIndexBase = binary.BigEndian.Uint32(src[12:]) -} - -func (item *PaintVarScaleUniform) mustParse(src []byte) { - _ = src[9] // early bound checking - item.format = src[0] - item.PaintOffset[0] = src[1] - item.PaintOffset[1] = src[2] - item.PaintOffset[2] = src[3] - item.Scale = Fixed214(binary.BigEndian.Uint16(src[4:])) - item.VarIndexBase = binary.BigEndian.Uint32(src[6:]) -} - -func (item *PaintVarScaleUniformAroundCenter) mustParse(src []byte) { - _ = src[13] // early bound checking - item.format = src[0] - item.PaintOffset[0] = src[1] - item.PaintOffset[1] = src[2] - item.PaintOffset[2] = src[3] - item.Scale = Fixed214(binary.BigEndian.Uint16(src[4:])) - item.CenterX = int16(binary.BigEndian.Uint16(src[6:])) - item.CenterY = int16(binary.BigEndian.Uint16(src[8:])) - item.VarIndexBase = binary.BigEndian.Uint32(src[10:]) -} - -func (item *PaintVarSkew) mustParse(src []byte) { - _ = src[11] // early bound checking - item.format = src[0] - item.PaintOffset[0] = src[1] - item.PaintOffset[1] = src[2] - item.PaintOffset[2] = src[3] - item.XSkewAngle = Fixed214(binary.BigEndian.Uint16(src[4:])) - item.YSkewAngle = Fixed214(binary.BigEndian.Uint16(src[6:])) - item.VarIndexBase = binary.BigEndian.Uint32(src[8:]) -} - -func (item *PaintVarSkewAroundCenter) mustParse(src []byte) { - _ = src[15] // early bound checking - item.format = src[0] - item.PaintOffset[0] = src[1] - item.PaintOffset[1] = src[2] - item.PaintOffset[2] = src[3] - item.XSkewAngle = Fixed214(binary.BigEndian.Uint16(src[4:])) - item.YSkewAngle = Fixed214(binary.BigEndian.Uint16(src[6:])) - item.CenterX = int16(binary.BigEndian.Uint16(src[8:])) - item.CenterY = int16(binary.BigEndian.Uint16(src[10:])) - item.VarIndexBase = binary.BigEndian.Uint32(src[12:]) + item.Alpha = Coord(binary.BigEndian.Uint16(src[3:])) } func (item *PaintVarSolid) mustParse(src []byte) { _ = src[8] // early bound checking item.format = src[0] item.PaletteIndex = binary.BigEndian.Uint16(src[1:]) - item.Alpha = Fixed214(binary.BigEndian.Uint16(src[3:])) + item.Alpha = Coord(binary.BigEndian.Uint16(src[3:])) item.VarIndexBase = binary.BigEndian.Uint32(src[5:]) } -func (item *PaintVarSweepGradient) mustParse(src []byte) { - _ = src[15] // early bound checking - item.format = src[0] - item.ColorLineOffset[0] = src[1] - item.ColorLineOffset[1] = src[2] - item.ColorLineOffset[2] = src[3] - item.CenterX = int16(binary.BigEndian.Uint16(src[4:])) - item.CenterY = int16(binary.BigEndian.Uint16(src[6:])) - item.StartAngle = Fixed214(binary.BigEndian.Uint16(src[8:])) - item.EndAngle = Fixed214(binary.BigEndian.Uint16(src[10:])) - item.VarIndexBase = binary.BigEndian.Uint32(src[12:]) -} - -func (item *PaintVarTransform) mustParse(src []byte) { - _ = src[6] // early bound checking - item.format = src[0] - item.PaintOffset[0] = src[1] - item.PaintOffset[1] = src[2] - item.PaintOffset[2] = src[3] - item.TransformOffset[0] = src[4] - item.TransformOffset[1] = src[5] - item.TransformOffset[2] = src[6] -} - -func (item *PaintVarTranslate) mustParse(src []byte) { - _ = src[11] // early bound checking - item.format = src[0] - item.PaintOffset[0] = src[1] - item.PaintOffset[1] = src[2] - item.PaintOffset[2] = src[3] - item.Dx = int16(binary.BigEndian.Uint16(src[4:])) - item.Dy = int16(binary.BigEndian.Uint16(src[6:])) - item.VarIndexBase = binary.BigEndian.Uint32(src[8:]) +func ParseAffine2x3(src []byte) (Affine2x3, int, error) { + var item Affine2x3 + n := 0 + if L := len(src); L < 24 { + return item, 0, fmt.Errorf("reading Affine2x3: "+"EOF: expected length: 24, got %d", L) + } + item.mustParse(src) + n += 24 + return item, n, nil } func ParseBaseGlyph(src []byte) (BaseGlyph, int, error) { @@ -675,6 +380,32 @@ func ParseClipList(src []byte) (ClipList, int, error) { return item, n, nil } +func ParseColorLine(src []byte) (ColorLine, int, error) { + var item ColorLine + n := 0 + if L := len(src); L < 3 { + return item, 0, fmt.Errorf("reading ColorLine: "+"EOF: expected length: 3, got %d", L) + } + _ = src[2] // early bound checking + item.Extend = Extend(src[0]) + arrayLengthColorStops := int(binary.BigEndian.Uint16(src[1:])) + n += 3 + + { + + if L := len(src); L < 3+arrayLengthColorStops*6 { + return item, 0, fmt.Errorf("reading ColorLine: "+"EOF: expected length: %d, got %d", 3+arrayLengthColorStops*6, L) + } + + item.ColorStops = make([]ColorStop, arrayLengthColorStops) // allocation guarded by the previous check + for i := range item.ColorStops { + item.ColorStops[i].mustParse(src[3+i*6:]) + } + n += arrayLengthColorStops * 6 + } + return item, n, nil +} + func ParseDeltaSetMapping(src []byte) (DeltaSetMapping, int, error) { var item DeltaSetMapping n := 0 @@ -864,8 +595,49 @@ func ParsePaintComposite(src []byte) (PaintComposite, int, error) { if L := len(src); L < 8 { return item, 0, fmt.Errorf("reading PaintComposite: "+"EOF: expected length: 8, got %d", L) } - item.mustParse(src) + _ = src[7] // early bound checking + item.format = src[0] + offsetSourcePaint := int(readUint24(src[1:])) + item.CompositeMode = CompositeMode(src[4]) + offsetBackdropPaint := int(readUint24(src[5:])) n += 8 + + { + + if offsetSourcePaint != 0 { // ignore null offset + if L := len(src); L < offsetSourcePaint { + return item, 0, fmt.Errorf("reading PaintComposite: "+"EOF: expected length: %d, got %d", offsetSourcePaint, L) + } + + var ( + err error + read int + ) + item.SourcePaint, read, err = ParsePaintTable(src[offsetSourcePaint:]) + if err != nil { + return item, 0, fmt.Errorf("reading PaintComposite: %s", err) + } + offsetSourcePaint += read + } + } + { + + if offsetBackdropPaint != 0 { // ignore null offset + if L := len(src); L < offsetBackdropPaint { + return item, 0, fmt.Errorf("reading PaintComposite: "+"EOF: expected length: %d, got %d", offsetBackdropPaint, L) + } + + var ( + err error + read int + ) + item.BackdropPaint, read, err = ParsePaintTable(src[offsetBackdropPaint:]) + if err != nil { + return item, 0, fmt.Errorf("reading PaintComposite: %s", err) + } + offsetBackdropPaint += read + } + } return item, n, nil } @@ -875,8 +647,30 @@ func ParsePaintGlyph(src []byte) (PaintGlyph, int, error) { if L := len(src); L < 6 { return item, 0, fmt.Errorf("reading PaintGlyph: "+"EOF: expected length: 6, got %d", L) } - item.mustParse(src) + _ = src[5] // early bound checking + item.format = src[0] + offsetPaint := int(readUint24(src[1:])) + item.GlyphID = binary.BigEndian.Uint16(src[4:]) n += 6 + + { + + if offsetPaint != 0 { // ignore null offset + if L := len(src); L < offsetPaint { + return item, 0, fmt.Errorf("reading PaintGlyph: "+"EOF: expected length: %d, got %d", offsetPaint, L) + } + + var ( + err error + read int + ) + item.Paint, read, err = ParsePaintTable(src[offsetPaint:]) + if err != nil { + return item, 0, fmt.Errorf("reading PaintGlyph: %s", err) + } + offsetPaint += read + } + } return item, n, nil } @@ -886,19 +680,67 @@ func ParsePaintLinearGradient(src []byte) (PaintLinearGradient, int, error) { if L := len(src); L < 16 { return item, 0, fmt.Errorf("reading PaintLinearGradient: "+"EOF: expected length: 16, got %d", L) } - item.mustParse(src) + _ = src[15] // early bound checking + item.format = src[0] + offsetColorLine := int(readUint24(src[1:])) + item.X0 = int16(binary.BigEndian.Uint16(src[4:])) + item.Y0 = int16(binary.BigEndian.Uint16(src[6:])) + item.X1 = int16(binary.BigEndian.Uint16(src[8:])) + item.Y1 = int16(binary.BigEndian.Uint16(src[10:])) + item.X2 = int16(binary.BigEndian.Uint16(src[12:])) + item.Y2 = int16(binary.BigEndian.Uint16(src[14:])) n += 16 - return item, n, nil -} -func ParsePaintRadialGradient(src []byte) (PaintRadialGradient, int, error) { - var item PaintRadialGradient + { + + if offsetColorLine != 0 { // ignore null offset + if L := len(src); L < offsetColorLine { + return item, 0, fmt.Errorf("reading PaintLinearGradient: "+"EOF: expected length: %d, got %d", offsetColorLine, L) + } + + var err error + item.ColorLine, _, err = ParseColorLine(src[offsetColorLine:]) + if err != nil { + return item, 0, fmt.Errorf("reading PaintLinearGradient: %s", err) + } + + } + } + return item, n, nil +} + +func ParsePaintRadialGradient(src []byte) (PaintRadialGradient, int, error) { + var item PaintRadialGradient n := 0 if L := len(src); L < 16 { return item, 0, fmt.Errorf("reading PaintRadialGradient: "+"EOF: expected length: 16, got %d", L) } - item.mustParse(src) + _ = src[15] // early bound checking + item.format = src[0] + offsetColorLine := int(readUint24(src[1:])) + item.X0 = int16(binary.BigEndian.Uint16(src[4:])) + item.Y0 = int16(binary.BigEndian.Uint16(src[6:])) + item.Radius0 = binary.BigEndian.Uint16(src[8:]) + item.X1 = int16(binary.BigEndian.Uint16(src[10:])) + item.Y1 = int16(binary.BigEndian.Uint16(src[12:])) + item.Radius1 = binary.BigEndian.Uint16(src[14:]) n += 16 + + { + + if offsetColorLine != 0 { // ignore null offset + if L := len(src); L < offsetColorLine { + return item, 0, fmt.Errorf("reading PaintRadialGradient: "+"EOF: expected length: %d, got %d", offsetColorLine, L) + } + + var err error + item.ColorLine, _, err = ParseColorLine(src[offsetColorLine:]) + if err != nil { + return item, 0, fmt.Errorf("reading PaintRadialGradient: %s", err) + } + + } + } return item, n, nil } @@ -908,8 +750,30 @@ func ParsePaintRotate(src []byte) (PaintRotate, int, error) { if L := len(src); L < 6 { return item, 0, fmt.Errorf("reading PaintRotate: "+"EOF: expected length: 6, got %d", L) } - item.mustParse(src) + _ = src[5] // early bound checking + item.format = src[0] + offsetPaint := int(readUint24(src[1:])) + item.Angle = Coord(binary.BigEndian.Uint16(src[4:])) n += 6 + + { + + if offsetPaint != 0 { // ignore null offset + if L := len(src); L < offsetPaint { + return item, 0, fmt.Errorf("reading PaintRotate: "+"EOF: expected length: %d, got %d", offsetPaint, L) + } + + var ( + err error + read int + ) + item.Paint, read, err = ParsePaintTable(src[offsetPaint:]) + if err != nil { + return item, 0, fmt.Errorf("reading PaintRotate: %s", err) + } + offsetPaint += read + } + } return item, n, nil } @@ -919,8 +783,32 @@ func ParsePaintRotateAroundCenter(src []byte) (PaintRotateAroundCenter, int, err if L := len(src); L < 10 { return item, 0, fmt.Errorf("reading PaintRotateAroundCenter: "+"EOF: expected length: 10, got %d", L) } - item.mustParse(src) + _ = src[9] // early bound checking + item.format = src[0] + offsetPaint := int(readUint24(src[1:])) + item.Angle = Coord(binary.BigEndian.Uint16(src[4:])) + item.CenterX = int16(binary.BigEndian.Uint16(src[6:])) + item.CenterY = int16(binary.BigEndian.Uint16(src[8:])) n += 10 + + { + + if offsetPaint != 0 { // ignore null offset + if L := len(src); L < offsetPaint { + return item, 0, fmt.Errorf("reading PaintRotateAroundCenter: "+"EOF: expected length: %d, got %d", offsetPaint, L) + } + + var ( + err error + read int + ) + item.Paint, read, err = ParsePaintTable(src[offsetPaint:]) + if err != nil { + return item, 0, fmt.Errorf("reading PaintRotateAroundCenter: %s", err) + } + offsetPaint += read + } + } return item, n, nil } @@ -930,8 +818,31 @@ func ParsePaintScale(src []byte) (PaintScale, int, error) { if L := len(src); L < 8 { return item, 0, fmt.Errorf("reading PaintScale: "+"EOF: expected length: 8, got %d", L) } - item.mustParse(src) + _ = src[7] // early bound checking + item.format = src[0] + offsetPaint := int(readUint24(src[1:])) + item.ScaleX = Coord(binary.BigEndian.Uint16(src[4:])) + item.ScaleY = Coord(binary.BigEndian.Uint16(src[6:])) n += 8 + + { + + if offsetPaint != 0 { // ignore null offset + if L := len(src); L < offsetPaint { + return item, 0, fmt.Errorf("reading PaintScale: "+"EOF: expected length: %d, got %d", offsetPaint, L) + } + + var ( + err error + read int + ) + item.Paint, read, err = ParsePaintTable(src[offsetPaint:]) + if err != nil { + return item, 0, fmt.Errorf("reading PaintScale: %s", err) + } + offsetPaint += read + } + } return item, n, nil } @@ -941,8 +852,33 @@ func ParsePaintScaleAroundCenter(src []byte) (PaintScaleAroundCenter, int, error if L := len(src); L < 12 { return item, 0, fmt.Errorf("reading PaintScaleAroundCenter: "+"EOF: expected length: 12, got %d", L) } - item.mustParse(src) + _ = src[11] // early bound checking + item.format = src[0] + offsetPaint := int(readUint24(src[1:])) + item.ScaleX = Coord(binary.BigEndian.Uint16(src[4:])) + item.ScaleY = Coord(binary.BigEndian.Uint16(src[6:])) + item.CenterX = int16(binary.BigEndian.Uint16(src[8:])) + item.CenterY = int16(binary.BigEndian.Uint16(src[10:])) n += 12 + + { + + if offsetPaint != 0 { // ignore null offset + if L := len(src); L < offsetPaint { + return item, 0, fmt.Errorf("reading PaintScaleAroundCenter: "+"EOF: expected length: %d, got %d", offsetPaint, L) + } + + var ( + err error + read int + ) + item.Paint, read, err = ParsePaintTable(src[offsetPaint:]) + if err != nil { + return item, 0, fmt.Errorf("reading PaintScaleAroundCenter: %s", err) + } + offsetPaint += read + } + } return item, n, nil } @@ -952,8 +888,30 @@ func ParsePaintScaleUniform(src []byte) (PaintScaleUniform, int, error) { if L := len(src); L < 6 { return item, 0, fmt.Errorf("reading PaintScaleUniform: "+"EOF: expected length: 6, got %d", L) } - item.mustParse(src) + _ = src[5] // early bound checking + item.format = src[0] + offsetPaint := int(readUint24(src[1:])) + item.Scale = Coord(binary.BigEndian.Uint16(src[4:])) n += 6 + + { + + if offsetPaint != 0 { // ignore null offset + if L := len(src); L < offsetPaint { + return item, 0, fmt.Errorf("reading PaintScaleUniform: "+"EOF: expected length: %d, got %d", offsetPaint, L) + } + + var ( + err error + read int + ) + item.Paint, read, err = ParsePaintTable(src[offsetPaint:]) + if err != nil { + return item, 0, fmt.Errorf("reading PaintScaleUniform: %s", err) + } + offsetPaint += read + } + } return item, n, nil } @@ -963,8 +921,32 @@ func ParsePaintScaleUniformAroundCenter(src []byte) (PaintScaleUniformAroundCent if L := len(src); L < 10 { return item, 0, fmt.Errorf("reading PaintScaleUniformAroundCenter: "+"EOF: expected length: 10, got %d", L) } - item.mustParse(src) + _ = src[9] // early bound checking + item.format = src[0] + offsetPaint := int(readUint24(src[1:])) + item.Scale = Coord(binary.BigEndian.Uint16(src[4:])) + item.CenterX = int16(binary.BigEndian.Uint16(src[6:])) + item.CenterY = int16(binary.BigEndian.Uint16(src[8:])) n += 10 + + { + + if offsetPaint != 0 { // ignore null offset + if L := len(src); L < offsetPaint { + return item, 0, fmt.Errorf("reading PaintScaleUniformAroundCenter: "+"EOF: expected length: %d, got %d", offsetPaint, L) + } + + var ( + err error + read int + ) + item.Paint, read, err = ParsePaintTable(src[offsetPaint:]) + if err != nil { + return item, 0, fmt.Errorf("reading PaintScaleUniformAroundCenter: %s", err) + } + offsetPaint += read + } + } return item, n, nil } @@ -974,8 +956,31 @@ func ParsePaintSkew(src []byte) (PaintSkew, int, error) { if L := len(src); L < 8 { return item, 0, fmt.Errorf("reading PaintSkew: "+"EOF: expected length: 8, got %d", L) } - item.mustParse(src) + _ = src[7] // early bound checking + item.format = src[0] + offsetPaint := int(readUint24(src[1:])) + item.XSkewAngle = Coord(binary.BigEndian.Uint16(src[4:])) + item.YSkewAngle = Coord(binary.BigEndian.Uint16(src[6:])) n += 8 + + { + + if offsetPaint != 0 { // ignore null offset + if L := len(src); L < offsetPaint { + return item, 0, fmt.Errorf("reading PaintSkew: "+"EOF: expected length: %d, got %d", offsetPaint, L) + } + + var ( + err error + read int + ) + item.Paint, read, err = ParsePaintTable(src[offsetPaint:]) + if err != nil { + return item, 0, fmt.Errorf("reading PaintSkew: %s", err) + } + offsetPaint += read + } + } return item, n, nil } @@ -985,8 +990,33 @@ func ParsePaintSkewAroundCenter(src []byte) (PaintSkewAroundCenter, int, error) if L := len(src); L < 12 { return item, 0, fmt.Errorf("reading PaintSkewAroundCenter: "+"EOF: expected length: 12, got %d", L) } - item.mustParse(src) + _ = src[11] // early bound checking + item.format = src[0] + offsetPaint := int(readUint24(src[1:])) + item.XSkewAngle = Coord(binary.BigEndian.Uint16(src[4:])) + item.YSkewAngle = Coord(binary.BigEndian.Uint16(src[6:])) + item.CenterX = int16(binary.BigEndian.Uint16(src[8:])) + item.CenterY = int16(binary.BigEndian.Uint16(src[10:])) n += 12 + + { + + if offsetPaint != 0 { // ignore null offset + if L := len(src); L < offsetPaint { + return item, 0, fmt.Errorf("reading PaintSkewAroundCenter: "+"EOF: expected length: %d, got %d", offsetPaint, L) + } + + var ( + err error + read int + ) + item.Paint, read, err = ParsePaintTable(src[offsetPaint:]) + if err != nil { + return item, 0, fmt.Errorf("reading PaintSkewAroundCenter: %s", err) + } + offsetPaint += read + } + } return item, n, nil } @@ -1007,8 +1037,30 @@ func ParsePaintSweepGradient(src []byte) (PaintSweepGradient, int, error) { if L := len(src); L < 12 { return item, 0, fmt.Errorf("reading PaintSweepGradient: "+"EOF: expected length: 12, got %d", L) } - item.mustParse(src) + _ = src[11] // early bound checking + item.format = src[0] + offsetColorLine := int(readUint24(src[1:])) + item.CenterX = int16(binary.BigEndian.Uint16(src[4:])) + item.CenterY = int16(binary.BigEndian.Uint16(src[6:])) + item.StartAngle = Coord(binary.BigEndian.Uint16(src[8:])) + item.EndAngle = Coord(binary.BigEndian.Uint16(src[10:])) n += 12 + + { + + if offsetColorLine != 0 { // ignore null offset + if L := len(src); L < offsetColorLine { + return item, 0, fmt.Errorf("reading PaintSweepGradient: "+"EOF: expected length: %d, got %d", offsetColorLine, L) + } + + var err error + item.ColorLine, _, err = ParseColorLine(src[offsetColorLine:]) + if err != nil { + return item, 0, fmt.Errorf("reading PaintSweepGradient: %s", err) + } + + } + } return item, n, nil } @@ -1104,8 +1156,45 @@ func ParsePaintTransform(src []byte) (PaintTransform, int, error) { if L := len(src); L < 7 { return item, 0, fmt.Errorf("reading PaintTransform: "+"EOF: expected length: 7, got %d", L) } - item.mustParse(src) + _ = src[6] // early bound checking + item.format = src[0] + offsetPaint := int(readUint24(src[1:])) + offsetTransform := int(readUint24(src[4:])) n += 7 + + { + + if offsetPaint != 0 { // ignore null offset + if L := len(src); L < offsetPaint { + return item, 0, fmt.Errorf("reading PaintTransform: "+"EOF: expected length: %d, got %d", offsetPaint, L) + } + + var ( + err error + read int + ) + item.Paint, read, err = ParsePaintTable(src[offsetPaint:]) + if err != nil { + return item, 0, fmt.Errorf("reading PaintTransform: %s", err) + } + offsetPaint += read + } + } + { + + if offsetTransform != 0 { // ignore null offset + if L := len(src); L < offsetTransform { + return item, 0, fmt.Errorf("reading PaintTransform: "+"EOF: expected length: %d, got %d", offsetTransform, L) + } + + var err error + item.Transform, _, err = ParseAffine2x3(src[offsetTransform:]) + if err != nil { + return item, 0, fmt.Errorf("reading PaintTransform: %s", err) + } + + } + } return item, n, nil } @@ -1115,8 +1204,31 @@ func ParsePaintTranslate(src []byte) (PaintTranslate, int, error) { if L := len(src); L < 8 { return item, 0, fmt.Errorf("reading PaintTranslate: "+"EOF: expected length: 8, got %d", L) } - item.mustParse(src) + _ = src[7] // early bound checking + item.format = src[0] + offsetPaint := int(readUint24(src[1:])) + item.Dx = int16(binary.BigEndian.Uint16(src[4:])) + item.Dy = int16(binary.BigEndian.Uint16(src[6:])) n += 8 + + { + + if offsetPaint != 0 { // ignore null offset + if L := len(src); L < offsetPaint { + return item, 0, fmt.Errorf("reading PaintTranslate: "+"EOF: expected length: %d, got %d", offsetPaint, L) + } + + var ( + err error + read int + ) + item.Paint, read, err = ParsePaintTable(src[offsetPaint:]) + if err != nil { + return item, 0, fmt.Errorf("reading PaintTranslate: %s", err) + } + offsetPaint += read + } + } return item, n, nil } @@ -1126,19 +1238,69 @@ func ParsePaintVarLinearGradient(src []byte) (PaintVarLinearGradient, int, error if L := len(src); L < 20 { return item, 0, fmt.Errorf("reading PaintVarLinearGradient: "+"EOF: expected length: 20, got %d", L) } - item.mustParse(src) - n += 20 - return item, n, nil -} - + _ = src[19] // early bound checking + item.format = src[0] + offsetColorLine := int(readUint24(src[1:])) + item.X0 = int16(binary.BigEndian.Uint16(src[4:])) + item.Y0 = int16(binary.BigEndian.Uint16(src[6:])) + item.X1 = int16(binary.BigEndian.Uint16(src[8:])) + item.Y1 = int16(binary.BigEndian.Uint16(src[10:])) + item.X2 = int16(binary.BigEndian.Uint16(src[12:])) + item.Y2 = int16(binary.BigEndian.Uint16(src[14:])) + item.VarIndexBase = binary.BigEndian.Uint32(src[16:]) + n += 20 + + { + + if offsetColorLine != 0 { // ignore null offset + if L := len(src); L < offsetColorLine { + return item, 0, fmt.Errorf("reading PaintVarLinearGradient: "+"EOF: expected length: %d, got %d", offsetColorLine, L) + } + + var err error + item.ColorLine, _, err = ParseVarColorLine(src[offsetColorLine:]) + if err != nil { + return item, 0, fmt.Errorf("reading PaintVarLinearGradient: %s", err) + } + + } + } + return item, n, nil +} + func ParsePaintVarRadialGradient(src []byte) (PaintVarRadialGradient, int, error) { var item PaintVarRadialGradient n := 0 if L := len(src); L < 20 { return item, 0, fmt.Errorf("reading PaintVarRadialGradient: "+"EOF: expected length: 20, got %d", L) } - item.mustParse(src) + _ = src[19] // early bound checking + item.format = src[0] + offsetColorLine := int(readUint24(src[1:])) + item.X0 = int16(binary.BigEndian.Uint16(src[4:])) + item.Y0 = int16(binary.BigEndian.Uint16(src[6:])) + item.Radius0 = binary.BigEndian.Uint16(src[8:]) + item.X1 = int16(binary.BigEndian.Uint16(src[10:])) + item.Y1 = int16(binary.BigEndian.Uint16(src[12:])) + item.Radius1 = binary.BigEndian.Uint16(src[14:]) + item.VarIndexBase = binary.BigEndian.Uint32(src[16:]) n += 20 + + { + + if offsetColorLine != 0 { // ignore null offset + if L := len(src); L < offsetColorLine { + return item, 0, fmt.Errorf("reading PaintVarRadialGradient: "+"EOF: expected length: %d, got %d", offsetColorLine, L) + } + + var err error + item.ColorLine, _, err = ParseVarColorLine(src[offsetColorLine:]) + if err != nil { + return item, 0, fmt.Errorf("reading PaintVarRadialGradient: %s", err) + } + + } + } return item, n, nil } @@ -1148,8 +1310,31 @@ func ParsePaintVarRotate(src []byte) (PaintVarRotate, int, error) { if L := len(src); L < 10 { return item, 0, fmt.Errorf("reading PaintVarRotate: "+"EOF: expected length: 10, got %d", L) } - item.mustParse(src) + _ = src[9] // early bound checking + item.format = src[0] + offsetPaint := int(readUint24(src[1:])) + item.Angle = Coord(binary.BigEndian.Uint16(src[4:])) + item.VarIndexBase = binary.BigEndian.Uint32(src[6:]) n += 10 + + { + + if offsetPaint != 0 { // ignore null offset + if L := len(src); L < offsetPaint { + return item, 0, fmt.Errorf("reading PaintVarRotate: "+"EOF: expected length: %d, got %d", offsetPaint, L) + } + + var ( + err error + read int + ) + item.Paint, read, err = ParsePaintTable(src[offsetPaint:]) + if err != nil { + return item, 0, fmt.Errorf("reading PaintVarRotate: %s", err) + } + offsetPaint += read + } + } return item, n, nil } @@ -1159,8 +1344,33 @@ func ParsePaintVarRotateAroundCenter(src []byte) (PaintVarRotateAroundCenter, in if L := len(src); L < 14 { return item, 0, fmt.Errorf("reading PaintVarRotateAroundCenter: "+"EOF: expected length: 14, got %d", L) } - item.mustParse(src) + _ = src[13] // early bound checking + item.format = src[0] + offsetPaint := int(readUint24(src[1:])) + item.Angle = Coord(binary.BigEndian.Uint16(src[4:])) + item.CenterX = int16(binary.BigEndian.Uint16(src[6:])) + item.CenterY = int16(binary.BigEndian.Uint16(src[8:])) + item.VarIndexBase = binary.BigEndian.Uint32(src[10:]) n += 14 + + { + + if offsetPaint != 0 { // ignore null offset + if L := len(src); L < offsetPaint { + return item, 0, fmt.Errorf("reading PaintVarRotateAroundCenter: "+"EOF: expected length: %d, got %d", offsetPaint, L) + } + + var ( + err error + read int + ) + item.Paint, read, err = ParsePaintTable(src[offsetPaint:]) + if err != nil { + return item, 0, fmt.Errorf("reading PaintVarRotateAroundCenter: %s", err) + } + offsetPaint += read + } + } return item, n, nil } @@ -1170,8 +1380,32 @@ func ParsePaintVarScale(src []byte) (PaintVarScale, int, error) { if L := len(src); L < 12 { return item, 0, fmt.Errorf("reading PaintVarScale: "+"EOF: expected length: 12, got %d", L) } - item.mustParse(src) + _ = src[11] // early bound checking + item.format = src[0] + offsetPaint := int(readUint24(src[1:])) + item.ScaleX = Coord(binary.BigEndian.Uint16(src[4:])) + item.ScaleY = Coord(binary.BigEndian.Uint16(src[6:])) + item.VarIndexBase = binary.BigEndian.Uint32(src[8:]) n += 12 + + { + + if offsetPaint != 0 { // ignore null offset + if L := len(src); L < offsetPaint { + return item, 0, fmt.Errorf("reading PaintVarScale: "+"EOF: expected length: %d, got %d", offsetPaint, L) + } + + var ( + err error + read int + ) + item.Paint, read, err = ParsePaintTable(src[offsetPaint:]) + if err != nil { + return item, 0, fmt.Errorf("reading PaintVarScale: %s", err) + } + offsetPaint += read + } + } return item, n, nil } @@ -1181,8 +1415,34 @@ func ParsePaintVarScaleAroundCenter(src []byte) (PaintVarScaleAroundCenter, int, if L := len(src); L < 16 { return item, 0, fmt.Errorf("reading PaintVarScaleAroundCenter: "+"EOF: expected length: 16, got %d", L) } - item.mustParse(src) + _ = src[15] // early bound checking + item.format = src[0] + offsetPaint := int(readUint24(src[1:])) + item.ScaleX = Coord(binary.BigEndian.Uint16(src[4:])) + item.ScaleY = Coord(binary.BigEndian.Uint16(src[6:])) + item.CenterX = int16(binary.BigEndian.Uint16(src[8:])) + item.CenterY = int16(binary.BigEndian.Uint16(src[10:])) + item.VarIndexBase = binary.BigEndian.Uint32(src[12:]) n += 16 + + { + + if offsetPaint != 0 { // ignore null offset + if L := len(src); L < offsetPaint { + return item, 0, fmt.Errorf("reading PaintVarScaleAroundCenter: "+"EOF: expected length: %d, got %d", offsetPaint, L) + } + + var ( + err error + read int + ) + item.Paint, read, err = ParsePaintTable(src[offsetPaint:]) + if err != nil { + return item, 0, fmt.Errorf("reading PaintVarScaleAroundCenter: %s", err) + } + offsetPaint += read + } + } return item, n, nil } @@ -1192,8 +1452,31 @@ func ParsePaintVarScaleUniform(src []byte) (PaintVarScaleUniform, int, error) { if L := len(src); L < 10 { return item, 0, fmt.Errorf("reading PaintVarScaleUniform: "+"EOF: expected length: 10, got %d", L) } - item.mustParse(src) + _ = src[9] // early bound checking + item.format = src[0] + offsetPaint := int(readUint24(src[1:])) + item.Scale = Coord(binary.BigEndian.Uint16(src[4:])) + item.VarIndexBase = binary.BigEndian.Uint32(src[6:]) n += 10 + + { + + if offsetPaint != 0 { // ignore null offset + if L := len(src); L < offsetPaint { + return item, 0, fmt.Errorf("reading PaintVarScaleUniform: "+"EOF: expected length: %d, got %d", offsetPaint, L) + } + + var ( + err error + read int + ) + item.Paint, read, err = ParsePaintTable(src[offsetPaint:]) + if err != nil { + return item, 0, fmt.Errorf("reading PaintVarScaleUniform: %s", err) + } + offsetPaint += read + } + } return item, n, nil } @@ -1203,8 +1486,33 @@ func ParsePaintVarScaleUniformAroundCenter(src []byte) (PaintVarScaleUniformArou if L := len(src); L < 14 { return item, 0, fmt.Errorf("reading PaintVarScaleUniformAroundCenter: "+"EOF: expected length: 14, got %d", L) } - item.mustParse(src) + _ = src[13] // early bound checking + item.format = src[0] + offsetPaint := int(readUint24(src[1:])) + item.Scale = Coord(binary.BigEndian.Uint16(src[4:])) + item.CenterX = int16(binary.BigEndian.Uint16(src[6:])) + item.CenterY = int16(binary.BigEndian.Uint16(src[8:])) + item.VarIndexBase = binary.BigEndian.Uint32(src[10:]) n += 14 + + { + + if offsetPaint != 0 { // ignore null offset + if L := len(src); L < offsetPaint { + return item, 0, fmt.Errorf("reading PaintVarScaleUniformAroundCenter: "+"EOF: expected length: %d, got %d", offsetPaint, L) + } + + var ( + err error + read int + ) + item.Paint, read, err = ParsePaintTable(src[offsetPaint:]) + if err != nil { + return item, 0, fmt.Errorf("reading PaintVarScaleUniformAroundCenter: %s", err) + } + offsetPaint += read + } + } return item, n, nil } @@ -1214,8 +1522,32 @@ func ParsePaintVarSkew(src []byte) (PaintVarSkew, int, error) { if L := len(src); L < 12 { return item, 0, fmt.Errorf("reading PaintVarSkew: "+"EOF: expected length: 12, got %d", L) } - item.mustParse(src) + _ = src[11] // early bound checking + item.format = src[0] + offsetPaint := int(readUint24(src[1:])) + item.XSkewAngle = Coord(binary.BigEndian.Uint16(src[4:])) + item.YSkewAngle = Coord(binary.BigEndian.Uint16(src[6:])) + item.VarIndexBase = binary.BigEndian.Uint32(src[8:]) n += 12 + + { + + if offsetPaint != 0 { // ignore null offset + if L := len(src); L < offsetPaint { + return item, 0, fmt.Errorf("reading PaintVarSkew: "+"EOF: expected length: %d, got %d", offsetPaint, L) + } + + var ( + err error + read int + ) + item.Paint, read, err = ParsePaintTable(src[offsetPaint:]) + if err != nil { + return item, 0, fmt.Errorf("reading PaintVarSkew: %s", err) + } + offsetPaint += read + } + } return item, n, nil } @@ -1225,8 +1557,34 @@ func ParsePaintVarSkewAroundCenter(src []byte) (PaintVarSkewAroundCenter, int, e if L := len(src); L < 16 { return item, 0, fmt.Errorf("reading PaintVarSkewAroundCenter: "+"EOF: expected length: 16, got %d", L) } - item.mustParse(src) + _ = src[15] // early bound checking + item.format = src[0] + offsetPaint := int(readUint24(src[1:])) + item.XSkewAngle = Coord(binary.BigEndian.Uint16(src[4:])) + item.YSkewAngle = Coord(binary.BigEndian.Uint16(src[6:])) + item.CenterX = int16(binary.BigEndian.Uint16(src[8:])) + item.CenterY = int16(binary.BigEndian.Uint16(src[10:])) + item.VarIndexBase = binary.BigEndian.Uint32(src[12:]) n += 16 + + { + + if offsetPaint != 0 { // ignore null offset + if L := len(src); L < offsetPaint { + return item, 0, fmt.Errorf("reading PaintVarSkewAroundCenter: "+"EOF: expected length: %d, got %d", offsetPaint, L) + } + + var ( + err error + read int + ) + item.Paint, read, err = ParsePaintTable(src[offsetPaint:]) + if err != nil { + return item, 0, fmt.Errorf("reading PaintVarSkewAroundCenter: %s", err) + } + offsetPaint += read + } + } return item, n, nil } @@ -1247,8 +1605,31 @@ func ParsePaintVarSweepGradient(src []byte) (PaintVarSweepGradient, int, error) if L := len(src); L < 16 { return item, 0, fmt.Errorf("reading PaintVarSweepGradient: "+"EOF: expected length: 16, got %d", L) } - item.mustParse(src) + _ = src[15] // early bound checking + item.format = src[0] + offsetColorLine := int(readUint24(src[1:])) + item.CenterX = int16(binary.BigEndian.Uint16(src[4:])) + item.CenterY = int16(binary.BigEndian.Uint16(src[6:])) + item.StartAngle = Coord(binary.BigEndian.Uint16(src[8:])) + item.EndAngle = Coord(binary.BigEndian.Uint16(src[10:])) + item.VarIndexBase = binary.BigEndian.Uint32(src[12:]) n += 16 + + { + + if offsetColorLine != 0 { // ignore null offset + if L := len(src); L < offsetColorLine { + return item, 0, fmt.Errorf("reading PaintVarSweepGradient: "+"EOF: expected length: %d, got %d", offsetColorLine, L) + } + + var err error + item.ColorLine, _, err = ParseVarColorLine(src[offsetColorLine:]) + if err != nil { + return item, 0, fmt.Errorf("reading PaintVarSweepGradient: %s", err) + } + + } + } return item, n, nil } @@ -1258,8 +1639,45 @@ func ParsePaintVarTransform(src []byte) (PaintVarTransform, int, error) { if L := len(src); L < 7 { return item, 0, fmt.Errorf("reading PaintVarTransform: "+"EOF: expected length: 7, got %d", L) } - item.mustParse(src) + _ = src[6] // early bound checking + item.format = src[0] + offsetPaint := int(readUint24(src[1:])) + offsetTransform := int(readUint24(src[4:])) n += 7 + + { + + if offsetPaint != 0 { // ignore null offset + if L := len(src); L < offsetPaint { + return item, 0, fmt.Errorf("reading PaintVarTransform: "+"EOF: expected length: %d, got %d", offsetPaint, L) + } + + var ( + err error + read int + ) + item.Paint, read, err = ParsePaintTable(src[offsetPaint:]) + if err != nil { + return item, 0, fmt.Errorf("reading PaintVarTransform: %s", err) + } + offsetPaint += read + } + } + { + + if offsetTransform != 0 { // ignore null offset + if L := len(src); L < offsetTransform { + return item, 0, fmt.Errorf("reading PaintVarTransform: "+"EOF: expected length: %d, got %d", offsetTransform, L) + } + + var err error + item.Transform, _, err = ParseVarAffine2x3(src[offsetTransform:]) + if err != nil { + return item, 0, fmt.Errorf("reading PaintVarTransform: %s", err) + } + + } + } return item, n, nil } @@ -1269,8 +1687,69 @@ func ParsePaintVarTranslate(src []byte) (PaintVarTranslate, int, error) { if L := len(src); L < 12 { return item, 0, fmt.Errorf("reading PaintVarTranslate: "+"EOF: expected length: 12, got %d", L) } - item.mustParse(src) + _ = src[11] // early bound checking + item.format = src[0] + offsetPaint := int(readUint24(src[1:])) + item.Dx = int16(binary.BigEndian.Uint16(src[4:])) + item.Dy = int16(binary.BigEndian.Uint16(src[6:])) + item.VarIndexBase = binary.BigEndian.Uint32(src[8:]) n += 12 + + { + + if offsetPaint != 0 { // ignore null offset + if L := len(src); L < offsetPaint { + return item, 0, fmt.Errorf("reading PaintVarTranslate: "+"EOF: expected length: %d, got %d", offsetPaint, L) + } + + var ( + err error + read int + ) + item.Paint, read, err = ParsePaintTable(src[offsetPaint:]) + if err != nil { + return item, 0, fmt.Errorf("reading PaintVarTranslate: %s", err) + } + offsetPaint += read + } + } + return item, n, nil +} + +func ParseVarAffine2x3(src []byte) (VarAffine2x3, int, error) { + var item VarAffine2x3 + n := 0 + if L := len(src); L < 28 { + return item, 0, fmt.Errorf("reading VarAffine2x3: "+"EOF: expected length: 28, got %d", L) + } + item.mustParse(src) + n += 28 + return item, n, nil +} + +func ParseVarColorLine(src []byte) (VarColorLine, int, error) { + var item VarColorLine + n := 0 + if L := len(src); L < 3 { + return item, 0, fmt.Errorf("reading VarColorLine: "+"EOF: expected length: 3, got %d", L) + } + _ = src[2] // early bound checking + item.Extend = Extend(src[0]) + arrayLengthColorStops := int(binary.BigEndian.Uint16(src[1:])) + n += 3 + + { + + if L := len(src); L < 3+arrayLengthColorStops*10 { + return item, 0, fmt.Errorf("reading VarColorLine: "+"EOF: expected length: %d, got %d", 3+arrayLengthColorStops*10, L) + } + + item.ColorStops = make([]VarColorStop, arrayLengthColorStops) // allocation guarded by the previous check + for i := range item.ColorStops { + item.ColorStops[i].mustParse(src[3+i*10:]) + } + n += arrayLengthColorStops * 10 + } return item, n, nil } @@ -1326,6 +1805,25 @@ func (item *RegionAxisCoordinates) mustParse(src []byte) { item.EndCoord = Coord(binary.BigEndian.Uint16(src[4:])) } +func (item *VarAffine2x3) mustParse(src []byte) { + _ = src[27] // early bound checking + item.Xx = Float1616FromUint(binary.BigEndian.Uint32(src[0:])) + item.Yx = Float1616FromUint(binary.BigEndian.Uint32(src[4:])) + item.Xy = Float1616FromUint(binary.BigEndian.Uint32(src[8:])) + item.Yy = Float1616FromUint(binary.BigEndian.Uint32(src[12:])) + item.Dx = Float1616FromUint(binary.BigEndian.Uint32(src[16:])) + item.Dy = Float1616FromUint(binary.BigEndian.Uint32(src[20:])) + item.VarIndexBase = binary.BigEndian.Uint32(src[24:]) +} + +func (item *VarColorStop) mustParse(src []byte) { + _ = src[9] // early bound checking + item.StopOffset = Coord(binary.BigEndian.Uint16(src[0:])) + item.PaletteIndex = binary.BigEndian.Uint16(src[2:]) + item.Alpha = Coord(binary.BigEndian.Uint16(src[4:])) + item.VarIndexBase = binary.BigEndian.Uint32(src[6:]) +} + func parseColr0(src []byte) (colr0, int, error) { var item colr0 n := 0 diff --git a/font/opentype/tables/glyphs_colr_src.go b/font/opentype/tables/glyphs_colr_src.go index bccdd42e..567f345b 100644 --- a/font/opentype/tables/glyphs_colr_src.go +++ b/font/opentype/tables/glyphs_colr_src.go @@ -90,11 +90,61 @@ type ClipBoxFormat1 struct { // variable clip box type ClipBoxFormat2 struct { format byte `unionTag:"2"` - XMin int16 // Minimum x of clip box. For variation, use varIndexBase + 0. - YMin int16 // Minimum y of clip box. For variation, use varIndexBase + 1. - XMax int16 // Maximum x of clip box. For variation, use varIndexBase + 2. - YMax int16 // Maximum y of clip box. For variation, use varIndexBase + 3. - VarIndexBase uint32 // Base index into DeltaSetIndexMap. + XMin int16 // Minimum x of clip box. For variation, use varIndexBase + 0. + YMin int16 // Minimum y of clip box. For variation, use varIndexBase + 1. + XMax int16 // Maximum x of clip box. For variation, use varIndexBase + 2. + YMax int16 // Maximum y of clip box. For variation, use varIndexBase + 3. + VarIndexBase uint32 // Base index into DeltaSetIndexMap. +} + +type ColorStop struct { + StopOffset Fixed214 // Position on a color line. + PaletteIndex uint16 // Index for a CPAL palette entry. + Alpha Fixed214 // Alpha value. +} + +type VarColorStop struct { + StopOffset Fixed214 // Position on a color line. For variation, use varIndexBase + 0. + PaletteIndex uint16 // Index for a CPAL palette entry. + Alpha Fixed214 // Alpha value. For variation, use varIndexBase + 1. + VarIndexBase uint32 // Base index into DeltaSetIndexMap +} + +type Extend uint8 + +const ( + ExtendPad Extend = iota // Use nearest color stop. + ExtendRepeat // Repeat from farthest color stop. + ExtendReflect // Mirror color line from nearest end. +) + +type ColorLine struct { + Extend Extend // An Extend enum value. + ColorStops []ColorStop `arrayCount:"FirstUint16"` // [numStops] +} + +type VarColorLine struct { + Extend Extend // An Extend enum value. + ColorStops []VarColorStop `arrayCount:"FirstUint16"` // [numStops] Allows for variations. +} + +type Affine2x3 struct { + Xx Float1616 // x-component of transformed x-basis vector. + Yx Float1616 // y-component of transformed x-basis vector. + Xy Float1616 // x-component of transformed y-basis vector. + Yy Float1616 // y-component of transformed y-basis vector. + Dx Float1616 // Translation in x direction. + Dy Float1616 // Translation in y direction. +} + +type VarAffine2x3 struct { + Xx Float1616 // x-component of transformed x-basis vector. For variation, use varIndexBase + 0. + Yx Float1616 // y-component of transformed x-basis vector. For variation, use varIndexBase + 1. + Xy Float1616 // x-component of transformed y-basis vector. For variation, use varIndexBase + 2. + Yy Float1616 // y-component of transformed y-basis vector. For variation, use varIndexBase + 3. + Dx Float1616 // Translation in x direction. For variation, use varIndexBase + 4. + Dy Float1616 // Translation in y direction. For variation, use varIndexBase + 5. + VarIndexBase uint32 // Base index into DeltaSetIndexMap. } type PaintTable interface { @@ -134,8 +184,6 @@ func (PaintSkewAroundCenter) isPaintTable() {} func (PaintVarSkewAroundCenter) isPaintTable() {} func (PaintComposite) isPaintTable() {} -type Offset24 [3]byte // TODO: - // (format 1) type PaintColrLayers struct { format byte `unionTag:"1"` @@ -160,80 +208,80 @@ type PaintVarSolid struct { // (format 4) type PaintLinearGradient struct { - format byte `unionTag:"4"` - ColorLineOffset Offset24 // Offset to ColorLine table, from beginning of PaintLinearGradient table. - X0 int16 // Start point (p₀) x coordinate. - Y0 int16 // Start point (p₀) y coordinate. - X1 int16 // End point (p₁) x coordinate. - Y1 int16 // End point (p₁) y coordinate. - X2 int16 // Rotation point (p₂) x coordinate. - Y2 int16 // Rotation point (p₂) y coordinate. + format byte `unionTag:"4"` + ColorLine ColorLine `offsetSize:"Offset24"` // Offset to ColorLine table, from beginning of PaintLinearGradient table. + X0 int16 // Start point (p₀) x coordinate. + Y0 int16 // Start point (p₀) y coordinate. + X1 int16 // End point (p₁) x coordinate. + Y1 int16 // End point (p₁) y coordinate. + X2 int16 // Rotation point (p₂) x coordinate. + Y2 int16 // Rotation point (p₂) y coordinate. } // (format 5) type PaintVarLinearGradient struct { - format byte `unionTag:"5"` - ColorLineOffset Offset24 // Offset to VarColorLine table, from beginning of PaintVarLinearGradient table. - X0 int16 // Start point (p₀) x coordinate. For variation, use varIndexBase + 0. - Y0 int16 // Start point (p₀) y coordinate. For variation, use varIndexBase + 1. - X1 int16 // End point (p₁) x coordinate. For variation, use varIndexBase + 2. - Y1 int16 // End point (p₁) y coordinate. For variation, use varIndexBase + 3. - X2 int16 // Rotation point (p₂) x coordinate. For variation, use varIndexBase + 4. - Y2 int16 // Rotation point (p₂) y coordinate. For variation, use varIndexBase + 5. - VarIndexBase uint32 // Base index into DeltaSetIndexMap. + format byte `unionTag:"5"` + ColorLine VarColorLine `offsetSize:"Offset24"` // Offset to VarColorLine table, from beginning of PaintVarLinearGradient table. + X0 int16 // Start point (p₀) x coordinate. For variation, use varIndexBase + 0. + Y0 int16 // Start point (p₀) y coordinate. For variation, use varIndexBase + 1. + X1 int16 // End point (p₁) x coordinate. For variation, use varIndexBase + 2. + Y1 int16 // End point (p₁) y coordinate. For variation, use varIndexBase + 3. + X2 int16 // Rotation point (p₂) x coordinate. For variation, use varIndexBase + 4. + Y2 int16 // Rotation point (p₂) y coordinate. For variation, use varIndexBase + 5. + VarIndexBase uint32 // Base index into DeltaSetIndexMap. } // (format 6) type PaintRadialGradient struct { - format byte `unionTag:"6"` - ColorLineOffset Offset24 // Offset to ColorLine table, from beginning of PaintRadialGradient table. - X0 int16 // Start circle center x coordinate. - Y0 int16 // Start circle center y coordinate. - Radius0 uint16 // Start circle radius. - X1 int16 // End circle center x coordinate. - Y1 int16 // End circle center y coordinate. - Radius1 uint16 // End circle radius. + format byte `unionTag:"6"` + ColorLine ColorLine `offsetSize:"Offset24"` // Offset to ColorLine table, from beginning of PaintRadialGradient table. + X0 int16 // Start circle center x coordinate. + Y0 int16 // Start circle center y coordinate. + Radius0 uint16 // Start circle radius. + X1 int16 // End circle center x coordinate. + Y1 int16 // End circle center y coordinate. + Radius1 uint16 // End circle radius. } // (format 7) type PaintVarRadialGradient struct { - format byte `unionTag:"7"` - ColorLineOffset Offset24 // Offset to VarColorLine table, from beginning of PaintVarRadialGradient table. - X0 int16 // Start circle center x coordinate. For variation, use varIndexBase + 0. - Y0 int16 // Start circle center y coordinate. For variation, use varIndexBase + 1. - Radius0 uint16 // Start circle radius. For variation, use varIndexBase + 2. - X1 int16 // End circle center x coordinate. For variation, use varIndexBase + 3. - Y1 int16 // End circle center y coordinate. For variation, use varIndexBase + 4. - Radius1 uint16 // End circle radius. For variation, use varIndexBase + 5. - VarIndexBase uint32 // Base index into DeltaSetIndexMap. + format byte `unionTag:"7"` + ColorLine VarColorLine `offsetSize:"Offset24"` // Offset to VarColorLine table, from beginning of PaintVarRadialGradient table. + X0 int16 // Start circle center x coordinate. For variation, use varIndexBase + 0. + Y0 int16 // Start circle center y coordinate. For variation, use varIndexBase + 1. + Radius0 uint16 // Start circle radius. For variation, use varIndexBase + 2. + X1 int16 // End circle center x coordinate. For variation, use varIndexBase + 3. + Y1 int16 // End circle center y coordinate. For variation, use varIndexBase + 4. + Radius1 uint16 // End circle radius. For variation, use varIndexBase + 5. + VarIndexBase uint32 // Base index into DeltaSetIndexMap. } // (format 8) type PaintSweepGradient struct { - format byte `unionTag:"8"` - ColorLineOffset Offset24 // Offset to ColorLine table, from beginning of PaintSweepGradient table. - CenterX int16 // Center x coordinate. - CenterY int16 // Center y coordinate. - StartAngle Fixed214 // Start of the angular range of the gradient: add 1.0 and multiply by 180° to retrieve counter-clockwise degrees. - EndAngle Fixed214 // End of the angular range of the gradient: add 1.0 and multiply by 180° to retrieve counter-clockwise degrees. + format byte `unionTag:"8"` + ColorLine ColorLine `offsetSize:"Offset24"` // Offset to ColorLine table, from beginning of PaintSweepGradient table. + CenterX int16 // Center x coordinate. + CenterY int16 // Center y coordinate. + StartAngle Fixed214 // Start of the angular range of the gradient: add 1.0 and multiply by 180° to retrieve counter-clockwise degrees. + EndAngle Fixed214 // End of the angular range of the gradient: add 1.0 and multiply by 180° to retrieve counter-clockwise degrees. } // (format 9) type PaintVarSweepGradient struct { - format byte `unionTag:"9"` - ColorLineOffset Offset24 // Offset to VarColorLine table, from beginning of PaintVarSweepGradient table. - CenterX int16 // Center x coordinate. For variation, use varIndexBase + 0. - CenterY int16 // Center y coordinate. For variation, use varIndexBase + 1. - StartAngle Fixed214 // Start of the angular range of the gradient: add 1.0 and multiply by 180° to retrieve counter-clockwise degrees. For variation, use varIndexBase + 2. - EndAngle Fixed214 // End of the angular range of the gradient: add 1.0 and multiply by 180° to retrieve counter-clockwise degrees. For variation, use varIndexBase + 3. - VarIndexBase uint32 // Base index into DeltaSetIndexMap. + format byte `unionTag:"9"` + ColorLine VarColorLine `offsetSize:"Offset24"` // Offset to VarColorLine table, from beginning of PaintVarSweepGradient table. + CenterX int16 // Center x coordinate. For variation, use varIndexBase + 0. + CenterY int16 // Center y coordinate. For variation, use varIndexBase + 1. + StartAngle Fixed214 // Start of the angular range of the gradient: add 1.0 and multiply by 180° to retrieve counter-clockwise degrees. For variation, use varIndexBase + 2. + EndAngle Fixed214 // End of the angular range of the gradient: add 1.0 and multiply by 180° to retrieve counter-clockwise degrees. For variation, use varIndexBase + 3. + VarIndexBase uint32 // Base index into DeltaSetIndexMap. } // (format 10) type PaintGlyph struct { - format byte `unionTag:"10"` - PaintOffset Offset24 // Offset to a Paint table, from beginning of PaintGlyph table. - GlyphID uint16 // Glyph ID for the source outline. + format byte `unionTag:"10"` + Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint table, from beginning of PaintGlyph table. + GlyphID uint16 // Glyph ID for the source outline. } // (format 11) @@ -244,183 +292,219 @@ type PaintColrGlyph struct { // (format 12) type PaintTransform struct { - format byte `unionTag:"12"` - PaintOffset Offset24 // Offset to a Paint subtable, from beginning of PaintTransform table. - TransformOffset Offset24 // Offset to an Affine2x3 table, from beginning of PaintTransform table. + format byte `unionTag:"12"` + Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint subtable, from beginning of PaintTransform table. + Transform Affine2x3 `offsetSize:"Offset24"` // Offset to an Affine2x3 table, from beginning of PaintTransform table. } // (format 13) type PaintVarTransform struct { - format byte `unionTag:"13"` - PaintOffset Offset24 // Offset to a Paint subtable, from beginning of PaintVarTransform table. - TransformOffset Offset24 // Offset to a VarAffine2x3 table, from beginning of PaintVarTransform table. + format byte `unionTag:"13"` + Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint subtable, from beginning of PaintVarTransform table. + Transform VarAffine2x3 `offsetSize:"Offset24"` // Offset to a VarAffine2x3 table, from beginning of PaintVarTransform table. } // (format 14) type PaintTranslate struct { - format byte `unionTag:"14"` - PaintOffset Offset24 // Offset to a Paint subtable, from beginning of PaintTranslate table. - Dx int16 // Translation in x direction. - Dy int16 // Translation in y direction. + format byte `unionTag:"14"` + Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint subtable, from beginning of PaintTranslate table. + Dx int16 // Translation in x direction. + Dy int16 // Translation in y direction. } // (format 15) type PaintVarTranslate struct { - format byte `unionTag:"15"` - PaintOffset Offset24 // Offset to a Paint subtable, from beginning of PaintVarTranslate table. - Dx int16 // Translation in x direction. For variation, use varIndexBase + 0. - Dy int16 // Translation in y direction. For variation, use varIndexBase + 1. - VarIndexBase uint32 // Base index into DeltaSetIndexMap. + format byte `unionTag:"15"` + Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint subtable, from beginning of PaintVarTranslate table. + Dx int16 // Translation in x direction. For variation, use varIndexBase + 0. + Dy int16 // Translation in y direction. For variation, use varIndexBase + 1. + VarIndexBase uint32 // Base index into DeltaSetIndexMap. } // (format 16) type PaintScale struct { - format byte `unionTag:"16"` - PaintOffset Offset24 // Offset to a Paint subtable, from beginning of PaintScale table. - ScaleX Fixed214 // Scale factor in x direction. - ScaleY Fixed214 // Scale factor in y direction. + format byte `unionTag:"16"` + Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint subtable, from beginning of PaintScale table. + ScaleX Fixed214 // Scale factor in x direction. + ScaleY Fixed214 // Scale factor in y direction. } // (format 17) type PaintVarScale struct { - format byte `unionTag:"17"` - PaintOffset Offset24 // Offset to a Paint subtable, from beginning of PaintVarScale table. - ScaleX Fixed214 // Scale factor in x direction. For variation, use varIndexBase + 0. - ScaleY Fixed214 // Scale factor in y direction. For variation, use varIndexBase + 1. - VarIndexBase uint32 // Base index into DeltaSetIndexMap. + format byte `unionTag:"17"` + Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint subtable, from beginning of PaintVarScale table. + ScaleX Fixed214 // Scale factor in x direction. For variation, use varIndexBase + 0. + ScaleY Fixed214 // Scale factor in y direction. For variation, use varIndexBase + 1. + VarIndexBase uint32 // Base index into DeltaSetIndexMap. } // (format 18) type PaintScaleAroundCenter struct { - format byte `unionTag:"18"` - PaintOffset Offset24 // Offset to a Paint subtable, from beginning of PaintScaleAroundCenter table. - ScaleX Fixed214 // Scale factor in x direction. - ScaleY Fixed214 // Scale factor in y direction. - CenterX int16 // x coordinate for the center of scaling. - CenterY int16 // y coordinate for the center of scaling. + format byte `unionTag:"18"` + Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint subtable, from beginning of PaintScaleAroundCenter table. + ScaleX Fixed214 // Scale factor in x direction. + ScaleY Fixed214 // Scale factor in y direction. + CenterX int16 // x coordinate for the center of scaling. + CenterY int16 // y coordinate for the center of scaling. } // (format 19) type PaintVarScaleAroundCenter struct { - format byte `unionTag:"19"` - PaintOffset Offset24 // Offset to a Paint subtable, from beginning of PaintVarScaleAroundCenter table. - ScaleX Fixed214 // Scale factor in x direction. For variation, use varIndexBase + 0. - ScaleY Fixed214 // Scale factor in y direction. For variation, use varIndexBase + 1. - CenterX int16 // x coordinate for the center of scaling. For variation, use varIndexBase + 2. - CenterY int16 // y coordinate for the center of scaling. For variation, use varIndexBase + 3. - VarIndexBase uint32 // Base index into DeltaSetIndexMap. + format byte `unionTag:"19"` + Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint subtable, from beginning of PaintVarScaleAroundCenter table. + ScaleX Fixed214 // Scale factor in x direction. For variation, use varIndexBase + 0. + ScaleY Fixed214 // Scale factor in y direction. For variation, use varIndexBase + 1. + CenterX int16 // x coordinate for the center of scaling. For variation, use varIndexBase + 2. + CenterY int16 // y coordinate for the center of scaling. For variation, use varIndexBase + 3. + VarIndexBase uint32 // Base index into DeltaSetIndexMap. } // (format 20) type PaintScaleUniform struct { - format byte `unionTag:"20"` - PaintOffset Offset24 // Offset to a Paint subtable, from beginning of PaintScaleUniform table. - Scale Fixed214 // Scale factor in x and y directions. + format byte `unionTag:"20"` + Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint subtable, from beginning of PaintScaleUniform table. + Scale Fixed214 // Scale factor in x and y directions. } // (format 21) type PaintVarScaleUniform struct { - format byte `unionTag:"21"` - PaintOffset Offset24 // Offset to a Paint subtable, from beginning of PaintVarScaleUniform table. - Scale Fixed214 // Scale factor in x and y directions. For variation, use varIndexBase + 0. - VarIndexBase uint32 // Base index into DeltaSetIndexMap. + format byte `unionTag:"21"` + Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint subtable, from beginning of PaintVarScaleUniform table. + Scale Fixed214 // Scale factor in x and y directions. For variation, use varIndexBase + 0. + VarIndexBase uint32 // Base index into DeltaSetIndexMap. } // (format 22) type PaintScaleUniformAroundCenter struct { - format byte `unionTag:"22"` - PaintOffset Offset24 // Offset to a Paint subtable, from beginning of PaintScaleUniformAroundCenter table. - Scale Fixed214 // Scale factor in x and y directions. - CenterX int16 // x coordinate for the center of scaling. - CenterY int16 // y coordinate for the center of scaling. + format byte `unionTag:"22"` + Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint subtable, from beginning of PaintScaleUniformAroundCenter table. + Scale Fixed214 // Scale factor in x and y directions. + CenterX int16 // x coordinate for the center of scaling. + CenterY int16 // y coordinate for the center of scaling. } // (format 23) type PaintVarScaleUniformAroundCenter struct { - format byte `unionTag:"23"` - PaintOffset Offset24 // Offset to a Paint subtable, from beginning of PaintVarScaleUniformAroundCenter table. - Scale Fixed214 // Scale factor in x and y directions. For variation, use varIndexBase + 0. - CenterX int16 // x coordinate for the center of scaling. For variation, use varIndexBase + 1. - CenterY int16 // y coordinate for the center of scaling. For variation, use varIndexBase + 2. - VarIndexBase uint32 // Base index into DeltaSetIndexMap. + format byte `unionTag:"23"` + Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint subtable, from beginning of PaintVarScaleUniformAroundCenter table. + Scale Fixed214 // Scale factor in x and y directions. For variation, use varIndexBase + 0. + CenterX int16 // x coordinate for the center of scaling. For variation, use varIndexBase + 1. + CenterY int16 // y coordinate for the center of scaling. For variation, use varIndexBase + 2. + VarIndexBase uint32 // Base index into DeltaSetIndexMap. } // (format 24) type PaintRotate struct { - format byte `unionTag:"24"` - PaintOffset Offset24 // Offset to a Paint subtable, from beginning of PaintRotate table. - Angle Fixed214 // Rotation angle, 180° in counter-clockwise degrees per 1.0 of value. + format byte `unionTag:"24"` + Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint subtable, from beginning of PaintRotate table. + Angle Fixed214 // Rotation angle, 180° in counter-clockwise degrees per 1.0 of value. } // (format 25) type PaintVarRotate struct { - format byte `unionTag:"25"` - PaintOffset Offset24 // Offset to a Paint subtable, from beginning of PaintVarRotate table. - Angle Fixed214 // Rotation angle, 180° in counter-clockwise degrees per 1.0 of value. For variation, use varIndexBase + 0. - VarIndexBase uint32 // Base index into DeltaSetIndexMap. + format byte `unionTag:"25"` + Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint subtable, from beginning of PaintVarRotate table. + Angle Fixed214 // Rotation angle, 180° in counter-clockwise degrees per 1.0 of value. For variation, use varIndexBase + 0. + VarIndexBase uint32 // Base index into DeltaSetIndexMap. } // (format 26) type PaintRotateAroundCenter struct { - format byte `unionTag:"26"` - PaintOffset Offset24 // Offset to a Paint subtable, from beginning of PaintRotateAroundCenter table. - Angle Fixed214 // Rotation angle, 180° in counter-clockwise degrees per 1.0 of value. - CenterX int16 // x coordinate for the center of rotation. - CenterY int16 // y coordinate for the center of rotation. + format byte `unionTag:"26"` + Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint subtable, from beginning of PaintRotateAroundCenter table. + Angle Fixed214 // Rotation angle, 180° in counter-clockwise degrees per 1.0 of value. + CenterX int16 // x coordinate for the center of rotation. + CenterY int16 // y coordinate for the center of rotation. } // (format 27) type PaintVarRotateAroundCenter struct { - format byte `unionTag:"27"` - PaintOffset Offset24 // Offset to a Paint subtable, from beginning of PaintVarRotateAroundCenter table. - Angle Fixed214 // Rotation angle, 180° in counter-clockwise degrees per 1.0 of value. For variation, use varIndexBase + 0. - CenterX int16 // x coordinate for the center of rotation. For variation, use varIndexBase + 1. - CenterY int16 // y coordinate for the center of rotation. For variation, use varIndexBase + 2. - VarIndexBase uint32 // Base index into DeltaSetIndexMap. + format byte `unionTag:"27"` + Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint subtable, from beginning of PaintVarRotateAroundCenter table. + Angle Fixed214 // Rotation angle, 180° in counter-clockwise degrees per 1.0 of value. For variation, use varIndexBase + 0. + CenterX int16 // x coordinate for the center of rotation. For variation, use varIndexBase + 1. + CenterY int16 // y coordinate for the center of rotation. For variation, use varIndexBase + 2. + VarIndexBase uint32 // Base index into DeltaSetIndexMap. } // (format 28) type PaintSkew struct { - format byte `unionTag:"28"` - PaintOffset Offset24 // Offset to a Paint subtable, from beginning of PaintSkew table. - XSkewAngle Fixed214 // Angle of skew in the direction of the x-axis, 180° in counter-clockwise degrees per 1.0 of value. - YSkewAngle Fixed214 // Angle of skew in the direction of the y-axis, 180° in counter-clockwise degrees per 1.0 of value. + format byte `unionTag:"28"` + Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint subtable, from beginning of PaintSkew table. + XSkewAngle Fixed214 // Angle of skew in the direction of the x-axis, 180° in counter-clockwise degrees per 1.0 of value. + YSkewAngle Fixed214 // Angle of skew in the direction of the y-axis, 180° in counter-clockwise degrees per 1.0 of value. } // (format 29) type PaintVarSkew struct { - format byte `unionTag:"29"` - PaintOffset Offset24 // Offset to a Paint subtable, from beginning of PaintVarSkew table. - XSkewAngle Fixed214 // Angle of skew in the direction of the x-axis, 180° in counter-clockwise degrees per 1.0 of value. For variation, use varIndexBase + 0. - YSkewAngle Fixed214 // Angle of skew in the direction of the y-axis, 180° in counter-clockwise degrees per 1.0 of value. For variation, use varIndexBase + 1. - VarIndexBase uint32 // Base index into DeltaSetIndexMap. + format byte `unionTag:"29"` + Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint subtable, from beginning of PaintVarSkew table. + XSkewAngle Fixed214 // Angle of skew in the direction of the x-axis, 180° in counter-clockwise degrees per 1.0 of value. For variation, use varIndexBase + 0. + YSkewAngle Fixed214 // Angle of skew in the direction of the y-axis, 180° in counter-clockwise degrees per 1.0 of value. For variation, use varIndexBase + 1. + VarIndexBase uint32 // Base index into DeltaSetIndexMap. } // (format 30) type PaintSkewAroundCenter struct { - format byte `unionTag:"30"` - PaintOffset Offset24 // Offset to a Paint subtable, from beginning of PaintSkewAroundCenter table. - XSkewAngle Fixed214 // Angle of skew in the direction of the x-axis, 180° in counter-clockwise degrees per 1.0 of value. - YSkewAngle Fixed214 // Angle of skew in the direction of the y-axis, 180° in counter-clockwise degrees per 1.0 of value. - CenterX int16 // x coordinate for the center of rotation. - CenterY int16 // y coordinate for the center of rotation. + format byte `unionTag:"30"` + Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint subtable, from beginning of PaintSkewAroundCenter table. + XSkewAngle Fixed214 // Angle of skew in the direction of the x-axis, 180° in counter-clockwise degrees per 1.0 of value. + YSkewAngle Fixed214 // Angle of skew in the direction of the y-axis, 180° in counter-clockwise degrees per 1.0 of value. + CenterX int16 // x coordinate for the center of rotation. + CenterY int16 // y coordinate for the center of rotation. } // (format 31) type PaintVarSkewAroundCenter struct { - format byte `unionTag:"31"` - PaintOffset Offset24 // Offset to a Paint subtable, from beginning of PaintVarSkewAroundCenter table. - XSkewAngle Fixed214 // Angle of skew in the direction of the x-axis, 180° in counter-clockwise degrees per 1.0 of value. For variation, use varIndexBase + 0. - YSkewAngle Fixed214 // Angle of skew in the direction of the y-axis, 180° in counter-clockwise degrees per 1.0 of value. For variation, use varIndexBase + 1. - CenterX int16 // x coordinate for the center of rotation. For variation, use varIndexBase + 2. - CenterY int16 // y coordinate for the center of rotation. For variation, use varIndexBase + 3. - VarIndexBase uint32 // Base index into DeltaSetIndexMap. + format byte `unionTag:"31"` + Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint subtable, from beginning of PaintVarSkewAroundCenter table. + XSkewAngle Fixed214 // Angle of skew in the direction of the x-axis, 180° in counter-clockwise degrees per 1.0 of value. For variation, use varIndexBase + 0. + YSkewAngle Fixed214 // Angle of skew in the direction of the y-axis, 180° in counter-clockwise degrees per 1.0 of value. For variation, use varIndexBase + 1. + CenterX int16 // x coordinate for the center of rotation. For variation, use varIndexBase + 2. + CenterY int16 // y coordinate for the center of rotation. For variation, use varIndexBase + 3. + VarIndexBase uint32 // Base index into DeltaSetIndexMap. } // (format 32) type PaintComposite struct { - format byte `unionTag:"32"` - SourcePaintOffset Offset24 // Offset to a source Paint table, from beginning of PaintComposite table. - CompositeMode uint8 // A CompositeMode enumeration value. - BackdropPaintOffset Offset24 // Offset to a backdrop Paint table, from beginning of PaintComposite table. -} + format byte `unionTag:"32"` + SourcePaint PaintTable `offsetSize:"Offset24"` // Offset to a source Paint table, from beginning of PaintComposite table. + CompositeMode CompositeMode // A CompositeMode enumeration value. + BackdropPaint PaintTable `offsetSize:"Offset24"` // Offset to a backdrop Paint table, from beginning of PaintComposite table. +} + +type CompositeMode uint8 + +const ( + // Porter-Duff modes + CompositeClear CompositeMode = iota // Clear + CompositeSrc // Source (“Copy” in Composition & Blending Level 1) + CompositeDest // Destination + CompositeSrcOver // Source Over + CompositeDestOver // Destination Over + CompositeSrcIn // Source In + CompositeDestIn // Destination In + CompositeSrcOut // Source Out + CompositeDestOut // Destination Out + CompositeSrcAtop // Source Atop + CompositeDestAtop // Destination Atop + CompositeXor // XOR + CompositePlus // Plus (“Lighter” in Composition & Blending Level 1) + // Separable color blend modes: + CompositeScreen // screen + CompositeOverlay // overlay + CompositeDarken // darken + CompositeLighten // lighten + CompositeColorDodge // color-dodge + CompositeColorBurn // color-burn + CompositeHardLight // hard-light + CompositeSoftLight // soft-light + CompositeDifference // difference + CompositeExclusion // exclusion + CompositeMultiply // multiply + // Non-separable color blend modes: + CompositeHslHue // hue + CompositeHslSaturation // saturation + CompositeHslColor // color + CompositeHslLuminosity // luminosity +) From 1a4dc328453d5aec3f4f4954ef353c2980bc82b6 Mon Sep 17 00:00:00 2001 From: benoitkugler Date: Mon, 23 Jun 2025 12:10:45 +0200 Subject: [PATCH 5/8] [opentype] initial CPAL support --- font/opentype/tables/glyphs_cpal_gen.go | 94 +++++++++++++++++++++++++ font/opentype/tables/glyphs_cpal_src.go | 43 +++++++++++ 2 files changed, 137 insertions(+) create mode 100755 font/opentype/tables/glyphs_cpal_gen.go create mode 100644 font/opentype/tables/glyphs_cpal_src.go diff --git a/font/opentype/tables/glyphs_cpal_gen.go b/font/opentype/tables/glyphs_cpal_gen.go new file mode 100755 index 00000000..8fb20e85 --- /dev/null +++ b/font/opentype/tables/glyphs_cpal_gen.go @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: Unlicense OR BSD-3-Clause + +package tables + +import ( + "encoding/binary" + "fmt" +) + +// Code generated by binarygen from glyphs_cpal_src.go. DO NOT EDIT + +func (item *ColorRecord) mustParse(src []byte) { + _ = src[3] // early bound checking + item.Blue = src[0] + item.Green = src[1] + item.Red = src[2] + item.Alpha = src[3] +} + +func ParseCPAL1(src []byte) (CPAL1, int, error) { + var item CPAL1 + n := 0 + { + var ( + err error + read int + ) + item.cpal0, read, err = parseCpal0(src[0:]) + if err != nil { + return item, 0, fmt.Errorf("reading CPAL1: %s", err) + } + n += read + } + if L := len(src); L < n+12 { + return item, 0, fmt.Errorf("reading CPAL1: "+"EOF: expected length: n + 12, got %d", L) + } + _ = src[n+11] // early bound checking + item.paletteTypesArrayOffset = Offset32(binary.BigEndian.Uint32(src[n:])) + item.paletteLabelsArrayOffset = Offset32(binary.BigEndian.Uint32(src[n+4:])) + item.paletteEntryLabelsArrayOffset = Offset32(binary.BigEndian.Uint32(src[n+8:])) + n += 12 + + return item, n, nil +} + +func parseCpal0(src []byte) (cpal0, int, error) { + var item cpal0 + n := 0 + if L := len(src); L < 12 { + return item, 0, fmt.Errorf("reading cpal0: "+"EOF: expected length: 12, got %d", L) + } + _ = src[11] // early bound checking + item.Version = binary.BigEndian.Uint16(src[0:]) + item.numPaletteEntries = binary.BigEndian.Uint16(src[2:]) + item.numPalettes = binary.BigEndian.Uint16(src[4:]) + item.numColorRecords = binary.BigEndian.Uint16(src[6:]) + offsetColorRecordsArray := int(binary.BigEndian.Uint32(src[8:])) + n += 12 + + { + + if offsetColorRecordsArray != 0 { // ignore null offset + if L := len(src); L < offsetColorRecordsArray { + return item, 0, fmt.Errorf("reading cpal0: "+"EOF: expected length: %d, got %d", offsetColorRecordsArray, L) + } + + arrayLength := int(item.numColorRecords) + + if L := len(src); L < offsetColorRecordsArray+arrayLength*4 { + return item, 0, fmt.Errorf("reading cpal0: "+"EOF: expected length: %d, got %d", offsetColorRecordsArray+arrayLength*4, L) + } + + item.ColorRecordsArray = make([]ColorRecord, arrayLength) // allocation guarded by the previous check + for i := range item.ColorRecordsArray { + item.ColorRecordsArray[i].mustParse(src[offsetColorRecordsArray+i*4:]) + } + offsetColorRecordsArray += arrayLength * 4 + } + } + { + arrayLength := int(item.numPalettes) + + if L := len(src); L < 12+arrayLength*2 { + return item, 0, fmt.Errorf("reading cpal0: "+"EOF: expected length: %d, got %d", 12+arrayLength*2, L) + } + + item.ColorRecordIndices = make([]uint16, arrayLength) // allocation guarded by the previous check + for i := range item.ColorRecordIndices { + item.ColorRecordIndices[i] = binary.BigEndian.Uint16(src[12+i*2:]) + } + n += arrayLength * 2 + } + return item, n, nil +} diff --git a/font/opentype/tables/glyphs_cpal_src.go b/font/opentype/tables/glyphs_cpal_src.go new file mode 100644 index 00000000..c6a9c7f8 --- /dev/null +++ b/font/opentype/tables/glyphs_cpal_src.go @@ -0,0 +1,43 @@ +package tables + +import "fmt" + +func ParseCPAL(src []byte) (CPAL1, error) { + header, _, err := parseCpal0(src) + if err != nil { + return CPAL1{}, err + } + switch header.Version { + case 0: + return CPAL1{cpal0: header}, nil + case 1: + out, _, err := ParseCPAL1(src) + return out, err + default: + return CPAL1{}, fmt.Errorf("unsupported version for CPAL: %d", header.Version) + } +} + +// https://learn.microsoft.com/en-us/typography/opentype/spec/cpal +type cpal0 struct { + Version uint16 // Table version number + numPaletteEntries uint16 // Number of palette entries in each palette. + numPalettes uint16 // Number of palettes in the table. + numColorRecords uint16 // Total number of color records, combined for all palettes. + ColorRecordsArray []ColorRecord `arrayCount:"ComputedField-numColorRecords" offsetSize:"Offset32"` // Offset from the beginning of CPAL table to the first ColorRecord. + ColorRecordIndices []uint16 `arrayCount:"ComputedField-numPalettes"` //[numPalettes] Index of each palette’s first color record in the combined color record array. +} + +type CPAL1 struct { + cpal0 + paletteTypesArrayOffset Offset32 // Offset from the beginning of CPAL table to the Palette Types Array. Set to 0 if no array is provided. + paletteLabelsArrayOffset Offset32 // Offset from the beginning of CPAL table to the Palette Labels Array. Set to 0 if no array is provided. + paletteEntryLabelsArrayOffset Offset32 // Offset from the beginning of CPAL table to the Palette Entry Labels Array. Set to 0 if no array is provided. +} + +type ColorRecord struct { + Blue uint8 // Blue value (B0). + Green uint8 // Green value (B1). + Red uint8 // Red value (B2). + Alpha uint8 // Alpha value (B3). +} From 623522404c8137e0cc28f084292ed274fe5ce482 Mon Sep 17 00:00:00 2001 From: Benoit KUGLER Date: Mon, 23 Jun 2025 12:33:07 +0200 Subject: [PATCH 6/8] [opentype] CPAL support --- font/opentype/tables/glyphs_color_test.go | 16 ++++++++++ font/opentype/tables/glyphs_cpal_gen.go | 38 ++++------------------- font/opentype/tables/glyphs_cpal_src.go | 33 +++----------------- 3 files changed, 27 insertions(+), 60 deletions(-) diff --git a/font/opentype/tables/glyphs_color_test.go b/font/opentype/tables/glyphs_color_test.go index cbd131ee..e36a7ec1 100644 --- a/font/opentype/tables/glyphs_color_test.go +++ b/font/opentype/tables/glyphs_color_test.go @@ -35,3 +35,19 @@ func TestCOLR(t *testing.T) { tu.Assert(t, g1 == BaseGlyph{0, 0, 11} && g2 == BaseGlyph{2, 11, 18}) tu.Assert(t, colr.LayerRecords[0].PaletteIndex == 4) } + +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) +} diff --git a/font/opentype/tables/glyphs_cpal_gen.go b/font/opentype/tables/glyphs_cpal_gen.go index 8fb20e85..6f6d4d5f 100755 --- a/font/opentype/tables/glyphs_cpal_gen.go +++ b/font/opentype/tables/glyphs_cpal_gen.go @@ -17,37 +17,11 @@ func (item *ColorRecord) mustParse(src []byte) { item.Alpha = src[3] } -func ParseCPAL1(src []byte) (CPAL1, int, error) { - var item CPAL1 - n := 0 - { - var ( - err error - read int - ) - item.cpal0, read, err = parseCpal0(src[0:]) - if err != nil { - return item, 0, fmt.Errorf("reading CPAL1: %s", err) - } - n += read - } - if L := len(src); L < n+12 { - return item, 0, fmt.Errorf("reading CPAL1: "+"EOF: expected length: n + 12, got %d", L) - } - _ = src[n+11] // early bound checking - item.paletteTypesArrayOffset = Offset32(binary.BigEndian.Uint32(src[n:])) - item.paletteLabelsArrayOffset = Offset32(binary.BigEndian.Uint32(src[n+4:])) - item.paletteEntryLabelsArrayOffset = Offset32(binary.BigEndian.Uint32(src[n+8:])) - n += 12 - - return item, n, nil -} - -func parseCpal0(src []byte) (cpal0, int, error) { - var item cpal0 +func ParseCPAL(src []byte) (CPAL, int, error) { + var item CPAL n := 0 if L := len(src); L < 12 { - return item, 0, fmt.Errorf("reading cpal0: "+"EOF: expected length: 12, got %d", L) + return item, 0, fmt.Errorf("reading CPAL: "+"EOF: expected length: 12, got %d", L) } _ = src[11] // early bound checking item.Version = binary.BigEndian.Uint16(src[0:]) @@ -61,13 +35,13 @@ func parseCpal0(src []byte) (cpal0, int, error) { if offsetColorRecordsArray != 0 { // ignore null offset if L := len(src); L < offsetColorRecordsArray { - return item, 0, fmt.Errorf("reading cpal0: "+"EOF: expected length: %d, got %d", offsetColorRecordsArray, L) + return item, 0, fmt.Errorf("reading CPAL: "+"EOF: expected length: %d, got %d", offsetColorRecordsArray, L) } arrayLength := int(item.numColorRecords) if L := len(src); L < offsetColorRecordsArray+arrayLength*4 { - return item, 0, fmt.Errorf("reading cpal0: "+"EOF: expected length: %d, got %d", offsetColorRecordsArray+arrayLength*4, L) + return item, 0, fmt.Errorf("reading CPAL: "+"EOF: expected length: %d, got %d", offsetColorRecordsArray+arrayLength*4, L) } item.ColorRecordsArray = make([]ColorRecord, arrayLength) // allocation guarded by the previous check @@ -81,7 +55,7 @@ func parseCpal0(src []byte) (cpal0, int, error) { arrayLength := int(item.numPalettes) if L := len(src); L < 12+arrayLength*2 { - return item, 0, fmt.Errorf("reading cpal0: "+"EOF: expected length: %d, got %d", 12+arrayLength*2, L) + return item, 0, fmt.Errorf("reading CPAL: "+"EOF: expected length: %d, got %d", 12+arrayLength*2, L) } item.ColorRecordIndices = make([]uint16, arrayLength) // allocation guarded by the previous check diff --git a/font/opentype/tables/glyphs_cpal_src.go b/font/opentype/tables/glyphs_cpal_src.go index c6a9c7f8..fe369028 100644 --- a/font/opentype/tables/glyphs_cpal_src.go +++ b/font/opentype/tables/glyphs_cpal_src.go @@ -1,38 +1,15 @@ package tables -import "fmt" - -func ParseCPAL(src []byte) (CPAL1, error) { - header, _, err := parseCpal0(src) - if err != nil { - return CPAL1{}, err - } - switch header.Version { - case 0: - return CPAL1{cpal0: header}, nil - case 1: - out, _, err := ParseCPAL1(src) - return out, err - default: - return CPAL1{}, fmt.Errorf("unsupported version for CPAL: %d", header.Version) - } -} - // https://learn.microsoft.com/en-us/typography/opentype/spec/cpal -type cpal0 struct { +// +// For now, only the CPAL version 0 is supported. +type CPAL struct { Version uint16 // Table version number numPaletteEntries uint16 // Number of palette entries in each palette. numPalettes uint16 // Number of palettes in the table. numColorRecords uint16 // Total number of color records, combined for all palettes. - ColorRecordsArray []ColorRecord `arrayCount:"ComputedField-numColorRecords" offsetSize:"Offset32"` // Offset from the beginning of CPAL table to the first ColorRecord. - ColorRecordIndices []uint16 `arrayCount:"ComputedField-numPalettes"` //[numPalettes] Index of each palette’s first color record in the combined color record array. -} - -type CPAL1 struct { - cpal0 - paletteTypesArrayOffset Offset32 // Offset from the beginning of CPAL table to the Palette Types Array. Set to 0 if no array is provided. - paletteLabelsArrayOffset Offset32 // Offset from the beginning of CPAL table to the Palette Labels Array. Set to 0 if no array is provided. - paletteEntryLabelsArrayOffset Offset32 // Offset from the beginning of CPAL table to the Palette Entry Labels Array. Set to 0 if no array is provided. + ColorRecordsArray []ColorRecord `arrayCount:"ComputedField-numColorRecords" offsetSize:"Offset32"` // Offset from the beginning of CPAL table to the first ColorRecord. + ColorRecordIndices []uint16 `arrayCount:"ComputedField-numPalettes"` // [numPalettes] Index of each palette’s first color record in the combined color record array. } type ColorRecord struct { From f2aace76e9f80645cebf1ef61f180da741048454 Mon Sep 17 00:00:00 2001 From: Benoit KUGLER Date: Mon, 23 Jun 2025 16:20:41 +0200 Subject: [PATCH 7/8] [font] expose COLR API --- font/color.go | 37 ++++++++++ font/font.go | 15 ++++ font/font_test.go | 12 ++++ font/opentype/tables/glyphs.go | 6 ++ font/opentype/tables/glyphs_color_test.go | 51 +++++++++---- font/opentype/tables/glyphs_colr_gen.go | 79 +++++--------------- font/opentype/tables/glyphs_colr_src.go | 87 ++++++++++++++++++++--- font/opentype/tables/glyphs_cpal_gen.go | 3 +- font/opentype/tables/glyphs_cpal_src.go | 2 +- font/renderer.go | 27 +++++-- font/renderer_test.go | 16 +++++ 11 files changed, 243 insertions(+), 92 deletions(-) create mode 100644 font/color.go diff --git a/font/color.go b/font/color.go new file mode 100644 index 00000000..e96b0a37 --- /dev/null +++ b/font/color.go @@ -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 +} diff --git a/font/font.go b/font/font.go index 93614e9f..8d595afe 100644 --- a/font/font.go +++ b/font/font.go @@ -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 @@ -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) diff --git a/font/font_test.go b/font/font_test.go index d0d5d090..678d5e88 100644 --- a/font/font_test.go +++ b/font/font_test.go @@ -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) +} diff --git a/font/opentype/tables/glyphs.go b/font/opentype/tables/glyphs.go index 9e4366e3..ef445306 100644 --- a/font/opentype/tables/glyphs.go +++ b/font/opentype/tables/glyphs.go @@ -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() {} diff --git a/font/opentype/tables/glyphs_color_test.go b/font/opentype/tables/glyphs_color_test.go index e36a7ec1..add57596 100644 --- a/font/opentype/tables/glyphs_color_test.go +++ b/font/opentype/tables/glyphs_color_test.go @@ -10,30 +10,53 @@ 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, 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) + 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] + 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) + 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) + 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) + + 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) { @@ -41,13 +64,13 @@ func TestCPAL(t *testing.T) { 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.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.NumPaletteEntries == 32) tu.Assert(t, cpal.numPalettes == 2 && len(cpal.ColorRecordIndices) == 2) } diff --git a/font/opentype/tables/glyphs_colr_gen.go b/font/opentype/tables/glyphs_colr_gen.go index 6651580b..1d767737 100755 --- a/font/opentype/tables/glyphs_colr_gen.go +++ b/font/opentype/tables/glyphs_colr_gen.go @@ -19,7 +19,7 @@ func (item *Affine2x3) mustParse(src []byte) { item.Dy = Float1616FromUint(binary.BigEndian.Uint32(src[20:])) } -func (item *BaseGlyph) mustParse(src []byte) { +func (item *baseGlyph) mustParse(src []byte) { _ = src[5] // early bound checking item.GlyphID = binary.BigEndian.Uint16(src[0:]) item.FirstLayerIndex = binary.BigEndian.Uint16(src[2:]) @@ -97,8 +97,8 @@ func ParseAffine2x3(src []byte) (Affine2x3, int, error) { return item, n, nil } -func ParseBaseGlyph(src []byte) (BaseGlyph, int, error) { - var item BaseGlyph +func ParseBaseGlyph(src []byte) (baseGlyph, int, error) { + var item baseGlyph n := 0 if L := len(src); L < 6 { return item, 0, fmt.Errorf("reading BaseGlyph: "+"EOF: expected length: 6, got %d", L) @@ -108,8 +108,8 @@ func ParseBaseGlyph(src []byte) (BaseGlyph, int, error) { return item, n, nil } -func ParseBaseGlyphList(src []byte) (BaseGlyphList, int, error) { - var item BaseGlyphList +func ParseBaseGlyphList(src []byte) (baseGlyphList, int, error) { + var item baseGlyphList n := 0 if L := len(src); L < 4 { return item, 0, fmt.Errorf("reading BaseGlyphList: "+"EOF: expected length: 4, got %d", L) @@ -125,7 +125,7 @@ func ParseBaseGlyphList(src []byte) (BaseGlyphList, int, error) { if err != nil { return item, 0, fmt.Errorf("reading BaseGlyphList: %s", err) } - item.PaintRecords = append(item.PaintRecords, elem) + item.paintRecords = append(item.paintRecords, elem) offset += read } n = offset @@ -133,8 +133,8 @@ func ParseBaseGlyphList(src []byte) (BaseGlyphList, int, error) { return item, n, nil } -func ParseBaseGlyphPaintRecord(src []byte, parentSrc []byte) (BaseGlyphPaintRecord, int, error) { - var item BaseGlyphPaintRecord +func ParseBaseGlyphPaintRecord(src []byte, parentSrc []byte) (baseGlyphPaintRecord, int, error) { + var item baseGlyphPaintRecord n := 0 if L := len(src); L < 6 { return item, 0, fmt.Errorf("reading BaseGlyphPaintRecord: "+"EOF: expected length: 6, got %d", L) @@ -145,7 +145,6 @@ func ParseBaseGlyphPaintRecord(src []byte, parentSrc []byte) (BaseGlyphPaintReco n += 6 { - if offsetPaint != 0 { // ignore null offset if L := len(parentSrc); L < offsetPaint { return item, 0, fmt.Errorf("reading BaseGlyphPaintRecord: "+"EOF: expected length: %d, got %d", offsetPaint, L) @@ -191,14 +190,13 @@ func ParseCOLR1(src []byte) (COLR1, int, error) { n += 20 { - if offsetBaseGlyphList != 0 { // ignore null offset if L := len(src); L < offsetBaseGlyphList { return item, 0, fmt.Errorf("reading COLR1: "+"EOF: expected length: %d, got %d", offsetBaseGlyphList, L) } var err error - item.BaseGlyphList, _, err = ParseBaseGlyphList(src[offsetBaseGlyphList:]) + item.baseGlyphList, _, err = ParseBaseGlyphList(src[offsetBaseGlyphList:]) if err != nil { return item, 0, fmt.Errorf("reading COLR1: %s", err) } @@ -206,7 +204,6 @@ func ParseCOLR1(src []byte) (COLR1, int, error) { } } { - if offsetLayerList != 0 { // ignore null offset if L := len(src); L < offsetLayerList { return item, 0, fmt.Errorf("reading COLR1: "+"EOF: expected length: %d, got %d", offsetLayerList, L) @@ -221,7 +218,6 @@ func ParseCOLR1(src []byte) (COLR1, int, error) { } } { - if offsetClipList != 0 { // ignore null offset if L := len(src); L < offsetClipList { return item, 0, fmt.Errorf("reading COLR1: "+"EOF: expected length: %d, got %d", offsetClipList, L) @@ -236,7 +232,6 @@ func ParseCOLR1(src []byte) (COLR1, int, error) { } } { - if offsetVarIndexMap != 0 { // ignore null offset if L := len(src); L < offsetVarIndexMap { return item, 0, fmt.Errorf("reading COLR1: "+"EOF: expected length: %d, got %d", offsetVarIndexMap, L) @@ -253,7 +248,6 @@ func ParseCOLR1(src []byte) (COLR1, int, error) { } } { - if offsetItemVariationStore != 0 { // ignore null offset if L := len(src); L < offsetItemVariationStore { return item, 0, fmt.Errorf("reading COLR1: "+"EOF: expected length: %d, got %d", offsetItemVariationStore, L) @@ -285,7 +279,6 @@ func ParseClip(src []byte, parentSrc []byte) (Clip, int, error) { n += 7 { - if offsetClipBox != 0 { // ignore null offset if L := len(parentSrc); L < offsetClipBox { return item, 0, fmt.Errorf("reading Clip: "+"EOF: expected length: %d, got %d", offsetClipBox, L) @@ -372,7 +365,7 @@ func ParseClipList(src []byte) (ClipList, int, error) { if err != nil { return item, 0, fmt.Errorf("reading ClipList: %s", err) } - item.Clips = append(item.Clips, elem) + item.clips = append(item.clips, elem) offset += read } n = offset @@ -440,7 +433,6 @@ func ParseItemVarStore(src []byte) (ItemVarStore, int, error) { n += 8 { - if offsetVariationRegionList != 0 { // ignore null offset if L := len(src); L < offsetVariationRegionList { return item, 0, fmt.Errorf("reading ItemVarStore: "+"EOF: expected length: %d, got %d", offsetVariationRegionList, L) @@ -544,8 +536,8 @@ func ParseLayerList(src []byte) (LayerList, int, error) { return item, 0, fmt.Errorf("reading LayerList: "+"EOF: expected length: %d, got %d", 4+arrayLengthPaintTables*4, L) } - item.PaintTables = make([]PaintTable, arrayLengthPaintTables) // allocation guarded by the previous check - for i := range item.PaintTables { + item.paintTables = make([]PaintTable, arrayLengthPaintTables) // allocation guarded by the previous check + for i := range item.paintTables { offset := int(binary.BigEndian.Uint32(src[4+i*4:])) // ignore null offsets if offset == 0 { @@ -557,7 +549,7 @@ func ParseLayerList(src []byte) (LayerList, int, error) { } var err error - item.PaintTables[i], _, err = ParsePaintTable(src[offset:]) + item.paintTables[i], _, err = ParsePaintTable(src[offset:]) if err != nil { return item, 0, fmt.Errorf("reading LayerList: %s", err) } @@ -603,7 +595,6 @@ func ParsePaintComposite(src []byte) (PaintComposite, int, error) { n += 8 { - if offsetSourcePaint != 0 { // ignore null offset if L := len(src); L < offsetSourcePaint { return item, 0, fmt.Errorf("reading PaintComposite: "+"EOF: expected length: %d, got %d", offsetSourcePaint, L) @@ -621,7 +612,6 @@ func ParsePaintComposite(src []byte) (PaintComposite, int, error) { } } { - if offsetBackdropPaint != 0 { // ignore null offset if L := len(src); L < offsetBackdropPaint { return item, 0, fmt.Errorf("reading PaintComposite: "+"EOF: expected length: %d, got %d", offsetBackdropPaint, L) @@ -654,7 +644,6 @@ func ParsePaintGlyph(src []byte) (PaintGlyph, int, error) { n += 6 { - if offsetPaint != 0 { // ignore null offset if L := len(src); L < offsetPaint { return item, 0, fmt.Errorf("reading PaintGlyph: "+"EOF: expected length: %d, got %d", offsetPaint, L) @@ -692,7 +681,6 @@ func ParsePaintLinearGradient(src []byte) (PaintLinearGradient, int, error) { n += 16 { - if offsetColorLine != 0 { // ignore null offset if L := len(src); L < offsetColorLine { return item, 0, fmt.Errorf("reading PaintLinearGradient: "+"EOF: expected length: %d, got %d", offsetColorLine, L) @@ -727,7 +715,6 @@ func ParsePaintRadialGradient(src []byte) (PaintRadialGradient, int, error) { n += 16 { - if offsetColorLine != 0 { // ignore null offset if L := len(src); L < offsetColorLine { return item, 0, fmt.Errorf("reading PaintRadialGradient: "+"EOF: expected length: %d, got %d", offsetColorLine, L) @@ -757,7 +744,6 @@ func ParsePaintRotate(src []byte) (PaintRotate, int, error) { n += 6 { - if offsetPaint != 0 { // ignore null offset if L := len(src); L < offsetPaint { return item, 0, fmt.Errorf("reading PaintRotate: "+"EOF: expected length: %d, got %d", offsetPaint, L) @@ -792,7 +778,6 @@ func ParsePaintRotateAroundCenter(src []byte) (PaintRotateAroundCenter, int, err n += 10 { - if offsetPaint != 0 { // ignore null offset if L := len(src); L < offsetPaint { return item, 0, fmt.Errorf("reading PaintRotateAroundCenter: "+"EOF: expected length: %d, got %d", offsetPaint, L) @@ -826,7 +811,6 @@ func ParsePaintScale(src []byte) (PaintScale, int, error) { n += 8 { - if offsetPaint != 0 { // ignore null offset if L := len(src); L < offsetPaint { return item, 0, fmt.Errorf("reading PaintScale: "+"EOF: expected length: %d, got %d", offsetPaint, L) @@ -862,7 +846,6 @@ func ParsePaintScaleAroundCenter(src []byte) (PaintScaleAroundCenter, int, error n += 12 { - if offsetPaint != 0 { // ignore null offset if L := len(src); L < offsetPaint { return item, 0, fmt.Errorf("reading PaintScaleAroundCenter: "+"EOF: expected length: %d, got %d", offsetPaint, L) @@ -895,7 +878,6 @@ func ParsePaintScaleUniform(src []byte) (PaintScaleUniform, int, error) { n += 6 { - if offsetPaint != 0 { // ignore null offset if L := len(src); L < offsetPaint { return item, 0, fmt.Errorf("reading PaintScaleUniform: "+"EOF: expected length: %d, got %d", offsetPaint, L) @@ -930,7 +912,6 @@ func ParsePaintScaleUniformAroundCenter(src []byte) (PaintScaleUniformAroundCent n += 10 { - if offsetPaint != 0 { // ignore null offset if L := len(src); L < offsetPaint { return item, 0, fmt.Errorf("reading PaintScaleUniformAroundCenter: "+"EOF: expected length: %d, got %d", offsetPaint, L) @@ -964,7 +945,6 @@ func ParsePaintSkew(src []byte) (PaintSkew, int, error) { n += 8 { - if offsetPaint != 0 { // ignore null offset if L := len(src); L < offsetPaint { return item, 0, fmt.Errorf("reading PaintSkew: "+"EOF: expected length: %d, got %d", offsetPaint, L) @@ -1000,7 +980,6 @@ func ParsePaintSkewAroundCenter(src []byte) (PaintSkewAroundCenter, int, error) n += 12 { - if offsetPaint != 0 { // ignore null offset if L := len(src); L < offsetPaint { return item, 0, fmt.Errorf("reading PaintSkewAroundCenter: "+"EOF: expected length: %d, got %d", offsetPaint, L) @@ -1047,7 +1026,6 @@ func ParsePaintSweepGradient(src []byte) (PaintSweepGradient, int, error) { n += 12 { - if offsetColorLine != 0 { // ignore null offset if L := len(src); L < offsetColorLine { return item, 0, fmt.Errorf("reading PaintSweepGradient: "+"EOF: expected length: %d, got %d", offsetColorLine, L) @@ -1163,7 +1141,6 @@ func ParsePaintTransform(src []byte) (PaintTransform, int, error) { n += 7 { - if offsetPaint != 0 { // ignore null offset if L := len(src); L < offsetPaint { return item, 0, fmt.Errorf("reading PaintTransform: "+"EOF: expected length: %d, got %d", offsetPaint, L) @@ -1181,7 +1158,6 @@ func ParsePaintTransform(src []byte) (PaintTransform, int, error) { } } { - if offsetTransform != 0 { // ignore null offset if L := len(src); L < offsetTransform { return item, 0, fmt.Errorf("reading PaintTransform: "+"EOF: expected length: %d, got %d", offsetTransform, L) @@ -1212,7 +1188,6 @@ func ParsePaintTranslate(src []byte) (PaintTranslate, int, error) { n += 8 { - if offsetPaint != 0 { // ignore null offset if L := len(src); L < offsetPaint { return item, 0, fmt.Errorf("reading PaintTranslate: "+"EOF: expected length: %d, got %d", offsetPaint, L) @@ -1251,7 +1226,6 @@ func ParsePaintVarLinearGradient(src []byte) (PaintVarLinearGradient, int, error n += 20 { - if offsetColorLine != 0 { // ignore null offset if L := len(src); L < offsetColorLine { return item, 0, fmt.Errorf("reading PaintVarLinearGradient: "+"EOF: expected length: %d, got %d", offsetColorLine, L) @@ -1287,7 +1261,6 @@ func ParsePaintVarRadialGradient(src []byte) (PaintVarRadialGradient, int, error n += 20 { - if offsetColorLine != 0 { // ignore null offset if L := len(src); L < offsetColorLine { return item, 0, fmt.Errorf("reading PaintVarRadialGradient: "+"EOF: expected length: %d, got %d", offsetColorLine, L) @@ -1318,7 +1291,6 @@ func ParsePaintVarRotate(src []byte) (PaintVarRotate, int, error) { n += 10 { - if offsetPaint != 0 { // ignore null offset if L := len(src); L < offsetPaint { return item, 0, fmt.Errorf("reading PaintVarRotate: "+"EOF: expected length: %d, got %d", offsetPaint, L) @@ -1354,7 +1326,6 @@ func ParsePaintVarRotateAroundCenter(src []byte) (PaintVarRotateAroundCenter, in n += 14 { - if offsetPaint != 0 { // ignore null offset if L := len(src); L < offsetPaint { return item, 0, fmt.Errorf("reading PaintVarRotateAroundCenter: "+"EOF: expected length: %d, got %d", offsetPaint, L) @@ -1389,7 +1360,6 @@ func ParsePaintVarScale(src []byte) (PaintVarScale, int, error) { n += 12 { - if offsetPaint != 0 { // ignore null offset if L := len(src); L < offsetPaint { return item, 0, fmt.Errorf("reading PaintVarScale: "+"EOF: expected length: %d, got %d", offsetPaint, L) @@ -1426,7 +1396,6 @@ func ParsePaintVarScaleAroundCenter(src []byte) (PaintVarScaleAroundCenter, int, n += 16 { - if offsetPaint != 0 { // ignore null offset if L := len(src); L < offsetPaint { return item, 0, fmt.Errorf("reading PaintVarScaleAroundCenter: "+"EOF: expected length: %d, got %d", offsetPaint, L) @@ -1460,7 +1429,6 @@ func ParsePaintVarScaleUniform(src []byte) (PaintVarScaleUniform, int, error) { n += 10 { - if offsetPaint != 0 { // ignore null offset if L := len(src); L < offsetPaint { return item, 0, fmt.Errorf("reading PaintVarScaleUniform: "+"EOF: expected length: %d, got %d", offsetPaint, L) @@ -1496,7 +1464,6 @@ func ParsePaintVarScaleUniformAroundCenter(src []byte) (PaintVarScaleUniformArou n += 14 { - if offsetPaint != 0 { // ignore null offset if L := len(src); L < offsetPaint { return item, 0, fmt.Errorf("reading PaintVarScaleUniformAroundCenter: "+"EOF: expected length: %d, got %d", offsetPaint, L) @@ -1531,7 +1498,6 @@ func ParsePaintVarSkew(src []byte) (PaintVarSkew, int, error) { n += 12 { - if offsetPaint != 0 { // ignore null offset if L := len(src); L < offsetPaint { return item, 0, fmt.Errorf("reading PaintVarSkew: "+"EOF: expected length: %d, got %d", offsetPaint, L) @@ -1568,7 +1534,6 @@ func ParsePaintVarSkewAroundCenter(src []byte) (PaintVarSkewAroundCenter, int, e n += 16 { - if offsetPaint != 0 { // ignore null offset if L := len(src); L < offsetPaint { return item, 0, fmt.Errorf("reading PaintVarSkewAroundCenter: "+"EOF: expected length: %d, got %d", offsetPaint, L) @@ -1616,7 +1581,6 @@ func ParsePaintVarSweepGradient(src []byte) (PaintVarSweepGradient, int, error) n += 16 { - if offsetColorLine != 0 { // ignore null offset if L := len(src); L < offsetColorLine { return item, 0, fmt.Errorf("reading PaintVarSweepGradient: "+"EOF: expected length: %d, got %d", offsetColorLine, L) @@ -1646,7 +1610,6 @@ func ParsePaintVarTransform(src []byte) (PaintVarTransform, int, error) { n += 7 { - if offsetPaint != 0 { // ignore null offset if L := len(src); L < offsetPaint { return item, 0, fmt.Errorf("reading PaintVarTransform: "+"EOF: expected length: %d, got %d", offsetPaint, L) @@ -1664,7 +1627,6 @@ func ParsePaintVarTransform(src []byte) (PaintVarTransform, int, error) { } } { - if offsetTransform != 0 { // ignore null offset if L := len(src); L < offsetTransform { return item, 0, fmt.Errorf("reading PaintVarTransform: "+"EOF: expected length: %d, got %d", offsetTransform, L) @@ -1696,7 +1658,6 @@ func ParsePaintVarTranslate(src []byte) (PaintVarTranslate, int, error) { n += 12 { - if offsetPaint != 0 { // ignore null offset if L := len(src); L < offsetPaint { return item, 0, fmt.Errorf("reading PaintVarTranslate: "+"EOF: expected length: %d, got %d", offsetPaint, L) @@ -1839,7 +1800,6 @@ func parseColr0(src []byte) (colr0, int, error) { n += 14 { - if offsetBaseGlyphRecords != 0 { // ignore null offset if L := len(src); L < offsetBaseGlyphRecords { return item, 0, fmt.Errorf("reading colr0: "+"EOF: expected length: %d, got %d", offsetBaseGlyphRecords, L) @@ -1851,15 +1811,14 @@ func parseColr0(src []byte) (colr0, int, error) { return item, 0, fmt.Errorf("reading colr0: "+"EOF: expected length: %d, got %d", offsetBaseGlyphRecords+arrayLength*6, L) } - item.BaseGlyphRecords = make([]BaseGlyph, arrayLength) // allocation guarded by the previous check - for i := range item.BaseGlyphRecords { - item.BaseGlyphRecords[i].mustParse(src[offsetBaseGlyphRecords+i*6:]) + item.baseGlyphRecords = make([]baseGlyph, arrayLength) // allocation guarded by the previous check + for i := range item.baseGlyphRecords { + item.baseGlyphRecords[i].mustParse(src[offsetBaseGlyphRecords+i*6:]) } offsetBaseGlyphRecords += arrayLength * 6 } } { - if offsetLayerRecords != 0 { // ignore null offset if L := len(src); L < offsetLayerRecords { return item, 0, fmt.Errorf("reading colr0: "+"EOF: expected length: %d, got %d", offsetLayerRecords, L) @@ -1871,9 +1830,9 @@ func parseColr0(src []byte) (colr0, int, error) { return item, 0, fmt.Errorf("reading colr0: "+"EOF: expected length: %d, got %d", offsetLayerRecords+arrayLength*4, L) } - item.LayerRecords = make([]Layer, arrayLength) // allocation guarded by the previous check - for i := range item.LayerRecords { - item.LayerRecords[i].mustParse(src[offsetLayerRecords+i*4:]) + item.layerRecords = make([]Layer, arrayLength) // allocation guarded by the previous check + for i := range item.layerRecords { + item.layerRecords[i].mustParse(src[offsetLayerRecords+i*4:]) } offsetLayerRecords += arrayLength * 4 } diff --git a/font/opentype/tables/glyphs_colr_src.go b/font/opentype/tables/glyphs_colr_src.go index 567f345b..eb9b42d6 100644 --- a/font/opentype/tables/glyphs_colr_src.go +++ b/font/opentype/tables/glyphs_colr_src.go @@ -22,21 +22,49 @@ func ParseCOLR(src []byte) (COLR1, error) { type colr0 struct { Version uint16 // Table version number numBaseGlyphRecords uint16 // Number of BaseGlyph records. - BaseGlyphRecords []BaseGlyph `arrayCount:"ComputedField-numBaseGlyphRecords" offsetSize:"Offset32"` // Offset to baseGlyphRecords array, from beginning of COLR table. - LayerRecords []Layer `arrayCount:"ComputedField-numLayerRecords" offsetSize:"Offset32"` // Offset to layerRecords array, from beginning of COLR table. + baseGlyphRecords []baseGlyph `arrayCount:"ComputedField-numBaseGlyphRecords" offsetSize:"Offset32"` // Offset to baseGlyphRecords array, from beginning of COLR table. + layerRecords []Layer `arrayCount:"ComputedField-numLayerRecords" offsetSize:"Offset32"` // Offset to layerRecords array, from beginning of COLR table. numLayerRecords uint16 // Number of Layer records. } +func (cl colr0) paintForGlyph(g GlyphID) (PaintColrLayersResolved, bool) { + for i, j := 0, len(cl.baseGlyphRecords); i < j; { + h := i + (j-i)/2 + entry := cl.baseGlyphRecords[h] + if g < entry.GlyphID { + j = h + } else if entry.GlyphID < g { + i = h + 1 + } else { + return cl.layerRecords[entry.FirstLayerIndex : entry.FirstLayerIndex+entry.NumLayers], true + } + } + return nil, false +} + type COLR1 struct { colr0 - BaseGlyphList BaseGlyphList `offsetSize:"Offset32"` // Offset to BaseGlyphList table, from beginning of COLR table. + baseGlyphList baseGlyphList `offsetSize:"Offset32"` // Offset to BaseGlyphList table, from beginning of COLR table. LayerList LayerList `offsetSize:"Offset32"` // Offset to LayerList table, from beginning of COLR table (may be NULL). ClipList ClipList `offsetSize:"Offset32"` // Offset to ClipList table, from beginning of COLR table (may be NULL). VarIndexMap *DeltaSetMapping `offsetSize:"Offset32"` // Offset to DeltaSetIndexMap table, from beginning of COLR table (may be NULL). ItemVariationStore *ItemVarStore `offsetSize:"Offset32"` // Offset to ItemVariationStore, from beginning of COLR table (may be NULL). } -type BaseGlyph struct { +func (cl *COLR1) Search(gid GlyphID) (PaintTable, bool) { + if cl == nil { + return nil, false + } + // "Applications that support COLR version 1 should give preference to the version 1 color glyph. + // For applications that support COLR version 1, the application should search for a base glyph ID first in the BaseGlyphList. + // Then, if not found, search in the baseGlyphRecords array, if present." + if paint, ok := cl.baseGlyphList.paintForGlyph(gid); ok { + return paint, true + } + return cl.colr0.paintForGlyph(gid) +} + +type baseGlyph struct { GlyphID GlyphID // Glyph ID of the base glyph. FirstLayerIndex uint16 // Index (base 0) into the layerRecords array. NumLayers uint16 // Number of color layers associated with this glyph. @@ -47,22 +75,63 @@ type Layer struct { PaletteIndex uint16 // Index (base 0) for a palette entry in the CPAL table. } -type BaseGlyphList struct { - PaintRecords []BaseGlyphPaintRecord `arrayCount:"FirstUint32"` // numBaseGlyphPaintRecords +type baseGlyphList struct { + paintRecords []baseGlyphPaintRecord `arrayCount:"FirstUint32"` // numBaseGlyphPaintRecords } -type BaseGlyphPaintRecord struct { +func (bl baseGlyphList) paintForGlyph(g GlyphID) (PaintTable, bool) { + // binary search + for i, j := 0, len(bl.paintRecords); i < j; { + h := i + (j-i)/2 + entry := bl.paintRecords[h] + if g < entry.GlyphID { + j = h + } else if entry.GlyphID < g { + i = h + 1 + } else { + return entry.Paint, true + } + } + return nil, false +} + +type baseGlyphPaintRecord struct { GlyphID GlyphID // Glyph ID of the base glyph. Paint PaintTable `offsetSize:"Offset32" offsetRelativeTo:"Parent"` // Offset to a Paint table, from beginning of BaseGlyphList table. } type LayerList struct { - PaintTables []PaintTable `arrayCount:"FirstUint32" offsetsArray:"Offset32"` // Offsets to Paint tables, from beginning of LayerList table. + paintTables []PaintTable `arrayCount:"FirstUint32" offsetsArray:"Offset32"` // Offsets to Paint tables, from beginning of LayerList table. +} + +// Resolve returns an error for invalid (out of bounds) indices +func (ll LayerList) Resolve(paint PaintColrLayers) ([]PaintTable, error) { + last := paint.FirstLayerIndex + uint32(paint.NumLayers) + if L := len(ll.paintTables); int(last) > L { + return nil, fmt.Errorf("out of bounds PaintColrLayers: expected %d, got %d", last, L) + } + return ll.paintTables[paint.FirstLayerIndex:last], nil } type ClipList struct { format uint8 // Set to 1. - Clips []Clip `arrayCount:"FirstUint32"` // Clip records. Sorted by startGlyphID. + clips []Clip `arrayCount:"FirstUint32"` // Clip records. Sorted by startGlyphID. +} + +func (cl ClipList) Search(g GlyphID) (ClipBox, bool) { + // binary search + for i, j := 0, len(cl.clips); i < j; { + h := i + (j-i)/2 + entry := cl.clips[h] + if g < entry.StartGlyphID { + j = h + } else if entry.EndGlyphID < g { + i = h + 1 + } else { + return entry.ClipBox, true + } + } + return nil, false } type Clip struct { diff --git a/font/opentype/tables/glyphs_cpal_gen.go b/font/opentype/tables/glyphs_cpal_gen.go index 6f6d4d5f..f408afd6 100755 --- a/font/opentype/tables/glyphs_cpal_gen.go +++ b/font/opentype/tables/glyphs_cpal_gen.go @@ -25,14 +25,13 @@ func ParseCPAL(src []byte) (CPAL, int, error) { } _ = src[11] // early bound checking item.Version = binary.BigEndian.Uint16(src[0:]) - item.numPaletteEntries = binary.BigEndian.Uint16(src[2:]) + item.NumPaletteEntries = binary.BigEndian.Uint16(src[2:]) item.numPalettes = binary.BigEndian.Uint16(src[4:]) item.numColorRecords = binary.BigEndian.Uint16(src[6:]) offsetColorRecordsArray := int(binary.BigEndian.Uint32(src[8:])) n += 12 { - if offsetColorRecordsArray != 0 { // ignore null offset if L := len(src); L < offsetColorRecordsArray { return item, 0, fmt.Errorf("reading CPAL: "+"EOF: expected length: %d, got %d", offsetColorRecordsArray, L) diff --git a/font/opentype/tables/glyphs_cpal_src.go b/font/opentype/tables/glyphs_cpal_src.go index fe369028..a34d0ae4 100644 --- a/font/opentype/tables/glyphs_cpal_src.go +++ b/font/opentype/tables/glyphs_cpal_src.go @@ -5,7 +5,7 @@ package tables // For now, only the CPAL version 0 is supported. type CPAL struct { Version uint16 // Table version number - numPaletteEntries uint16 // Number of palette entries in each palette. + NumPaletteEntries uint16 // Number of palette entries in each palette. numPalettes uint16 // Number of palettes in the table. numColorRecords uint16 // Total number of color records, combined for all palettes. ColorRecordsArray []ColorRecord `arrayCount:"ComputedField-numColorRecords" offsetSize:"Offset32"` // Offset from the beginning of CPAL table to the first ColorRecord. diff --git a/font/renderer.go b/font/renderer.go index 171bae46..3782e6b3 100644 --- a/font/renderer.go +++ b/font/renderer.go @@ -10,6 +10,7 @@ import ( "io" ot "github.com/go-text/typesetting/font/opentype" + "github.com/go-text/typesetting/font/opentype/tables" ) var ( @@ -31,6 +32,7 @@ type GlyphData interface { func (GlyphOutline) isGlyphData() {} func (GlyphSVG) isGlyphData() {} func (GlyphBitmap) isGlyphData() {} +func (GlyphColor) isGlyphData() {} // GlyphOutline exposes the path to draw for // vector glyph. @@ -105,13 +107,23 @@ type BitmapSize struct { XPpem, YPpem uint16 } +// GlyphColor describe a colored glyph, as found in +// COLR tables +type GlyphColor struct { + Paint tables.PaintTable +} + // GlyphData returns the glyph content for [gid], or nil if // not found. func (f *Face) GlyphData(gid GID) GlyphData { + if out, ok := f.COLR.Search(gID(gid)); ok { + return GlyphColor{out} + } + // since outline may be specified for SVG and bitmaps, check it at the end outB, err := f.sbix.glyphData(gID(gid), f.xPpem, f.yPpem) if err == nil { - outline, ok := f.outlineGlyphData(gID(gid)) + outline, ok := f.GlyphDataOutline(gID(gid)) if ok { outB.Outline = &outline } @@ -120,7 +132,7 @@ func (f *Face) GlyphData(gid GID) GlyphData { outB, err = f.bitmap.glyphData(gID(gid), f.xPpem, f.yPpem) if err == nil { - outline, ok := f.outlineGlyphData(gID(gid)) + outline, ok := f.GlyphDataOutline(gID(gid)) if ok { outB.Outline = &outline } @@ -132,11 +144,11 @@ func (f *Face) GlyphData(gid GID) GlyphData { // Spec : // For every SVG glyph description, there must be a corresponding TrueType, // CFF or CFF2 glyph description in the font. - outS.Outline, _ = f.outlineGlyphData(gID(gid)) + outS.Outline, _ = f.GlyphDataOutline(gID(gid)) return outS } - if out, ok := f.outlineGlyphData(gID(gid)); ok { + if out, ok := f.GlyphDataOutline(gID(gid)); ok { return out } @@ -199,8 +211,11 @@ func (bt bitmap) glyphData(gid gID, xPpem, yPpem uint16) (GlyphBitmap, error) { return out, nil } -// look for data in 'glyf', 'CFF ' and 'CFF2' tables -func (f *Face) outlineGlyphData(gid gID) (GlyphOutline, bool) { +// GlyphDataOutline looks for data in 'glyf', 'CFF ' and 'CFF2' tables. +// +// It is a bit faster than calling [Face.GlyphData] and may be used for instance +// when rendering colored glyphs (from the 'COLR' table). +func (f *Face) GlyphDataOutline(gid gID) (GlyphOutline, bool) { out, err := f.glyphDataFromCFF1(gid) if err == nil { return out, true diff --git a/font/renderer_test.go b/font/renderer_test.go index 0e8063ec..b21cdb77 100644 --- a/font/renderer_test.go +++ b/font/renderer_test.go @@ -607,3 +607,19 @@ func TestMixedGlyphs(t *testing.T) { tu.Assert(t, gd != nil) } } + +func TestColorGlyphs(t *testing.T) { + ld := readFontFile(t, "color/NotoColorEmoji-Regular.ttf") + ft, err := NewFont(ld) + tu.AssertNoErr(t, err) + face := NewFace(ft) + _, ok := face.GlyphData(12).(GlyphColor) + tu.Assert(t, ok) + + ld = readFontFile(t, "color/CoralPixels-Regular.ttf") + ft, err = NewFont(ld) + tu.AssertNoErr(t, err) + face = NewFace(ft) + _, ok = face.GlyphData(0).(GlyphColor) + tu.Assert(t, ok) +} From edf19c3183f0119b45be66ef9654654f4eb282c0 Mon Sep 17 00:00:00 2001 From: Benoit KUGLER Date: Tue, 8 Jul 2025 10:28:43 +0200 Subject: [PATCH 8/8] [opentype/tables] use sort.Search --- font/opentype/tables/glyphs_color_test.go | 10 +++++ font/opentype/tables/glyphs_colr_src.go | 50 +++++++++++------------ 2 files changed, 34 insertions(+), 26 deletions(-) diff --git a/font/opentype/tables/glyphs_color_test.go b/font/opentype/tables/glyphs_color_test.go index add57596..c369c202 100644 --- a/font/opentype/tables/glyphs_color_test.go +++ b/font/opentype/tables/glyphs_color_test.go @@ -29,6 +29,11 @@ func TestCOLR(t *testing.T) { 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) @@ -51,6 +56,11 @@ func TestCOLR(t *testing.T) { 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) diff --git a/font/opentype/tables/glyphs_colr_src.go b/font/opentype/tables/glyphs_colr_src.go index eb9b42d6..c7520f25 100644 --- a/font/opentype/tables/glyphs_colr_src.go +++ b/font/opentype/tables/glyphs_colr_src.go @@ -1,6 +1,9 @@ package tables -import "fmt" +import ( + "fmt" + "sort" +) func ParseCOLR(src []byte) (COLR1, error) { header, _, err := parseColr0(src) @@ -27,19 +30,17 @@ type colr0 struct { numLayerRecords uint16 // Number of Layer records. } -func (cl colr0) paintForGlyph(g GlyphID) (PaintColrLayersResolved, bool) { - for i, j := 0, len(cl.baseGlyphRecords); i < j; { - h := i + (j-i)/2 - entry := cl.baseGlyphRecords[h] - if g < entry.GlyphID { - j = h - } else if entry.GlyphID < g { - i = h + 1 - } else { - return cl.layerRecords[entry.FirstLayerIndex : entry.FirstLayerIndex+entry.NumLayers], true - } +func (cl colr0) paintForGlyph(gi GlyphID) (PaintColrLayersResolved, bool) { + num := len(cl.baseGlyphRecords) + idx := sort.Search(num, func(i int) bool { return gi <= cl.baseGlyphRecords[i].GlyphID }) + if idx >= num { + return nil, false } - return nil, false + entry := cl.baseGlyphRecords[idx] + if gi != entry.GlyphID { + return nil, false + } + return cl.layerRecords[entry.FirstLayerIndex : entry.FirstLayerIndex+entry.NumLayers], true } type COLR1 struct { @@ -79,20 +80,17 @@ type baseGlyphList struct { paintRecords []baseGlyphPaintRecord `arrayCount:"FirstUint32"` // numBaseGlyphPaintRecords } -func (bl baseGlyphList) paintForGlyph(g GlyphID) (PaintTable, bool) { - // binary search - for i, j := 0, len(bl.paintRecords); i < j; { - h := i + (j-i)/2 - entry := bl.paintRecords[h] - if g < entry.GlyphID { - j = h - } else if entry.GlyphID < g { - i = h + 1 - } else { - return entry.Paint, true - } +func (bl baseGlyphList) paintForGlyph(gi GlyphID) (PaintTable, bool) { + num := len(bl.paintRecords) + idx := sort.Search(num, func(i int) bool { return gi <= bl.paintRecords[i].GlyphID }) + if idx >= num { + return nil, false } - return nil, false + entry := bl.paintRecords[idx] + if gi != entry.GlyphID { + return nil, false + } + return entry.Paint, true } type baseGlyphPaintRecord struct {